{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Automorphisms of Graphs\n",
    "\n",
    "\n",
    "\n",
    "## First Example: The graph of a square\n",
    "\n",
    "Let's look at the graph of a square. Since a square is a 2-cube, we can create the polytope and look at its graph:\n",
    "\n",
    "    \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<!--\n",
       "polymake for knusper\n",
       "Thu Aug 27 11:31:44 2020\n",
       "unnamed\n",
       "-->\n",
       "\n",
       "\n",
       "<html>\n",
       "   <head>\n",
       "      <meta charset=utf-8>\n",
       "      <title>unnamed</title>\n",
       "      <style>\n",
       "/*\n",
       "// COMMON_CODE_BLOCK_BEGIN\n",
       "*/\n",
       "         html {overflow: scroll;}\n",
       "         strong{font-size: 18px;}\n",
       "         canvas { z-index: 8; }\n",
       "         input[type='radio'] {margin-left:0;}\n",
       "         input[type='checkbox'] {margin-right:7px; margin-left: 0px; padding-left:0px;}\n",
       "         .group{padding-bottom: 15px;}\n",
       "         .settings * {z-index: 11; }\n",
       "         .settings{z-index: 10; font-family: Arial, Helvetica, sans-serif; margin-left: 30px; visibility: hidden; width: 14em; height: 96%; border: solid 1px silver; padding: 2px; overflow-y: scroll; box-sizing: border-box; background-color: white; position: absolute;}\n",
       "         .indented{margin-left: 20px; margin-top: 10px; padding-bottom: 0px;} \n",
       "         .shownObjectsList{overflow: auto; max-width: 150px; max-height: 150px;}\n",
       "         .showSettingsButton{visibility: visible; z-index: 12; position: absolute }\n",
       "         .hideSettingsButton{visibility: hidden; z-index: 12; position: absolute; opacity: 0.5}\n",
       "         button{margin-left: 0; margin-top: 10px}\n",
       "         img{cursor: pointer;}\n",
       "         .suboption{padding-top: 15px;}\n",
       "         #model58691562044 { width: 100%; height: 100%; }\n",
       "         .threejs_container { width: 100%; height: 75vh;}\n",
       "         .settings{max-height: 74vh} \n",
       "         input[type=range] {\n",
       "           -webkit-appearance: none;\n",
       "           padding:0; \n",
       "           width:90%; \n",
       "           margin-left: auto;\n",
       "           margin-right: auto;\n",
       "           margin-top: 15px;\n",
       "           margin-bottom: 15px;\n",
       "           display: block;\t\n",
       "         }\n",
       "         input[type=range]:focus {\n",
       "           outline: none;\n",
       "         }\n",
       "         input[type=range]::-webkit-slider-runnable-track {\n",
       "           height: 4px;\n",
       "           cursor: pointer;\n",
       "           animate: 0.2s;\n",
       "           box-shadow: 0px 0px 0px #000000;\n",
       "           background: #E3E3E3;\n",
       "           border-radius: 0px;\n",
       "           border: 0px solid #000000;\n",
       "         }\n",
       "         input[type=range]::-webkit-slider-thumb {\n",
       "           box-shadow: 1px 1px 2px #B8B8B8;\n",
       "           border: 1px solid #ABABAB;\n",
       "           height: 13px;\n",
       "           width: 25px;\n",
       "           border-radius: 20px;\n",
       "           background: #E0E0E0;\n",
       "           cursor: pointer;\n",
       "           -webkit-appearance: none;\n",
       "           margin-top: -5px;\n",
       "         }\n",
       "         input[type=range]:focus::-webkit-slider-runnable-track {\n",
       "           background: #E3E3E3;\n",
       "         }\n",
       "         input[type=range]::-moz-range-track {\n",
       "           height: 4px;\n",
       "           cursor: pointer;\n",
       "           animate: 0.2s;\n",
       "           box-shadow: 0px 0px 0px #000000;\n",
       "           background: #E3E3E3;\n",
       "           border-radius: 0px;\n",
       "           border: 0px solid #000000;\n",
       "         }\n",
       "         input[type=range]::-moz-range-thumb {\n",
       "           box-shadow: 1px 1px 2px #B8B8B8;\n",
       "           border: 1px solid #ABABAB;\n",
       "           height: 13px;\n",
       "           width: 25px;\n",
       "           border-radius: 20px;\n",
       "           background: #E0E0E0;\n",
       "           cursor: pointer;\n",
       "         }\n",
       "         input[type=range]::-ms-track {\n",
       "           height: 4px;\n",
       "           cursor: pointer;\n",
       "           animate: 0.2s;\n",
       "           background: transparent;\n",
       "           border-color: transparent;\n",
       "           color: transparent;\n",
       "         }\n",
       "         input[type=range]::-ms-fill-lower {\n",
       "           background: #E3E3E3;\n",
       "           border: 0px solid #000000;\n",
       "           border-radius: 0px;\n",
       "           box-shadow: 0px 0px 0px #000000;\n",
       "         }\n",
       "         input[type=range]::-ms-fill-upper {\n",
       "           background: #E3E3E3;\n",
       "           border: 0px solid #000000;\n",
       "           border-radius: 0px;\n",
       "           box-shadow: 0px 0px 0px #000000;\n",
       "         }\n",
       "         input[type=range]::-ms-thumb {\n",
       "           box-shadow: 1px 1px 2px #B8B8B8;\n",
       "           border: 1px solid #ABABAB;\n",
       "           height: 13px;\n",
       "           width: 25px;\n",
       "           border-radius: 20px;\n",
       "           background: #E0E0E0;\n",
       "           cursor: pointer;\n",
       "         }\n",
       "         input[type=range]:focus::-ms-fill-lower {\n",
       "           background: #E3E3E3;\n",
       "         }\n",
       "         input[type=range]:focus::-ms-fill-upper {\n",
       "           background: #E3E3E3;\n",
       "         }\n",
       "/*\n",
       "// COMMON_CODE_BLOCK_END\n",
       "*/\n",
       "\t\t</style>\n",
       "   </head>\n",
       "<body>\n",
       "   <div class='threejs_container'>\n",
       "\t\t<div id='settings_0' class='settings'>\n",
       "\t\t\t<div class=group id='transparency_0' class='transparency'>\n",
       "\t\t\t\t<strong>Transparency</strong>\n",
       "\t\t\t\t<input id='transparencyRange_0' type='range' min=0 max=1 step=0.01 value=0>\n",
       "\t\t\t</div>\n",
       "\t\t\t\n",
       "\t\t\t<div class=group id='rotation_0'>\n",
       "\t\t\t\t<strong>Rotation</strong>\n",
       "\t\t\t\t<div class=indented>\n",
       "\t\t\t\t\t<div><input type='checkbox' id='changeRotationX_0'> x-axis</div>\n",
       "\t\t\t\t\t<div><input type='checkbox' id='changeRotationY_0'> y-axis</div>\n",
       "\t\t\t\t\t<div><input type='checkbox' id='changeRotationZ_0'> z-axis</div>\n",
       "\t\t\t\t\t<button id='resetButton_0'>Reset</button>\n",
       "\t\t\t\t</div>\n",
       "\n",
       "\t\t\t\t<div class=suboption>Rotation speed</div>\n",
       "\t\t\t\t<input id='rotationSpeedRange_0' type='range' min=0 max=5 step=0.01 value=2>\n",
       "\t\t\t</div>\n",
       "\n",
       "\n",
       "\t\t\t<div class=group id='display_0'>\n",
       "\t\t\t\t<strong>Display</strong>\n",
       "\t\t\t\t<div class=indented>\n",
       "\t\t\t\t\t<div id='shownObjectTypesList_0' class='shownObjectsList'></div>\n",
       "\t\t\t\t</div>\n",
       "\t\t\t\t<div class=suboption>Objects</div>\n",
       "\t\t\t\t<div class=indented>\n",
       "\t\t\t\t   <div id='shownObjectsList_0' class='shownObjectsList'></div>\n",
       "\t\t\t\t</div>\n",
       "\t\t\t</div>\n",
       "         \n",
       "         <div class=group id='camera_0'>\n",
       "            <strong>Camera</strong>\n",
       "            <div class=indented>\n",
       "               <form>\n",
       "                  <select id=\"cameraType_0\">\n",
       "                     <option value='perspective' selected> Perspective<br></option>\n",
       "                     <option value='orthographic' > Orthographic<br></option>\n",
       "                  </select>\n",
       "               </form>\n",
       "            </div>\n",
       "         </div>\n",
       "\n",
       "\t\t\t<div class=group id='svg_0'>\n",
       "\t\t\t\t<strong>SVG</strong>\n",
       "\t\t\t\t<div class=indented>\n",
       "\t\t\t\t\t<form>\n",
       "\t\t\t\t\t\t<input type=\"radio\" name='screenshotMode' value='download' id='download_0' checked> Download<br>\n",
       "\t\t\t\t\t\t<input type=\"radio\" name='screenshotMode' value='tab' id='tab_0' > New tab<br>\n",
       "\t\t\t\t\t</form>\n",
       "\t\t\t\t\t<button id='takeScreenshot_0'>Screenshot</button>\n",
       "\t\t\t\t</div>\n",
       "\t\t\t</div>\n",
       "\n",
       "\t\t</div>\t<!-- end of settings -->\n",
       "\t\t<img id='hideSettingsButton_0' class='hideSettingsButton' src='/kernelspecs/polymake/close.svg' width=20px\">\n",
       "\t\t<img id='showSettingsButton_0' class='showSettingsButton' src='/kernelspecs/polymake/menu.svg' width=20px\">\n",
       "<div id=\"model58691562044\"></div>\n",
       "</div>\n",
       "   <script>\n",
       "    requirejs.config({\n",
       "      paths: {\n",
       "        three: '/kernelspecs/polymake/three',\n",
       "        TrackballControls: '/kernelspecs/polymake/TrackballControls',\n",
       "        OrbitControls: '/kernelspecs/polymake/OrbitControls',\n",
       "        Projector: '/kernelspecs/polymake/Projector',\n",
       "        SVGRenderer: '/kernelspecs/polymake/SVGRenderer',\n",
       "        WEBGL: '/kernelspecs/polymake/WebGL',\n",
       "      },\n",
       "      shim: {\n",
       "        'three': { exports: 'THREE'},\n",
       "        'SVGRenderer': { deps: [ 'three' ], exports: 'THREE.SVGRenderer' },\n",
       "        'WEBGL': { deps: [ 'three' ], exports: 'THREE.WEBGL' },\n",
       "        'Projector': { deps: [ 'three' ], exports: 'THREE.Projector' },\n",
       "        'TrackballControls': { deps: [ 'three' ], exports: 'THREE.TrackballControls' },\n",
       "        'OrbitControls': { deps: [ 'three' ], exports: 'THREE.OrbitControls' },\n",
       "      }\n",
       "    });\n",
       "    \n",
       "    require(['three'],function(THREE){\n",
       "        window.THREE = THREE;\n",
       "      require(['TrackballControls', 'OrbitControls', 'Projector', 'SVGRenderer', 'WEBGL'],\n",
       "               function(TrackballControls, OrbitControls, Projector, SVGRenderer, WEBGL) {\n",
       "    THREE.TrackballControls = TrackballControls;\n",
       "    THREE.OrbitControls = OrbitControls;\n",
       "    THREE.Projector = Projector;\n",
       "    THREE.SVGRenderer = SVGRenderer;\n",
       "    THREE.WEBGL = WEBGL;\n",
       "\n",
       "// COMMON_CODE_BLOCK_BEGIN\n",
       "\n",
       "const intervalLength = 25; // for automatic animations\n",
       "const explodableModel = false; \n",
       "const modelContains = { points: false, pointlabels: false, lines: false, edgelabels: false, faces: false, arrowheads: false };\n",
       "const foldables = [];\n",
       "\n",
       "var three = document.getElementById(\"model58691562044\");\n",
       "var scene = new THREE.Scene();\n",
       "var renderer = new THREE.WebGLRenderer( { antialias: true } );\n",
       "var svgRenderer = new THREE.SVGRenderer( { antialias: true } );\n",
       "renderer.setPixelRatio( window.devicePixelRatio );\n",
       "renderer.setClearColor(0xFFFFFF, 1);\n",
       "svgRenderer.setClearColor(0xFFFFFF, 1);\n",
       "three.appendChild(renderer.domElement);\n",
       "\n",
       "var frustumSize = 4;\n",
       "var cameras = [new THREE.PerspectiveCamera(75, 1, 0.1, 1000), new THREE.OrthographicCamera()];\n",
       "cameras.forEach(function(cam) {\n",
       "    cam.position.set(0, 0, 5);\n",
       "    cam.lookAt(0, 0, 0);  \n",
       "    cam.up.set(0, 1, 0);         \n",
       "});\n",
       "var controls = [new THREE.TrackballControls(cameras[0], three), new THREE.OrbitControls(cameras[1], three)];\n",
       "var camera, control;\n",
       "\n",
       "controls[0].zoomSpeed = 0.2;\n",
       "controls[0].rotateSpeed = 4;\n",
       "\n",
       "\n",
       "// class to allow move points together with labels and spheres\n",
       "var PMPoint = function (x,y,z) {\n",
       "   this.vector = new THREE.Vector3(x,y,z);\n",
       "   this.sprite = null;\n",
       "   this.sphere = null;\n",
       "}\n",
       "PMPoint.prototype.addLabel = function(labelsprite) {\n",
       "   this.sprite = labelsprite;\n",
       "   this.sprite.position.copy(this.vector);\n",
       "}\n",
       "PMPoint.prototype.addSphere = function(spheremesh) {\n",
       "   this.sphere = spheremesh;\n",
       "   this.sphere.position.copy(this.vector);\n",
       "}\n",
       "PMPoint.prototype.set = function(x,y,z) {\n",
       "   this.vector.set(x,y,z);\n",
       "   if (this.sprite) {\n",
       "      this.sprite.position.copy(this.vector);\n",
       "   }\n",
       "   if (this.sphere) {\n",
       "      this.sphere.position.copy(this.vector);\n",
       "   }\n",
       "}\n",
       "PMPoint.prototype.radius = function() {\n",
       "   if (this.sphere) {\n",
       "      return this.sphere.geometry.parameters.radius;\n",
       "   } else {\n",
       "      return 0;\n",
       "   }\n",
       "};\n",
       "// select the target node\n",
       "var target = document.querySelector('#model58691562044');\n",
       "\n",
       "// create an observer instance\n",
       "var observer = new MutationObserver(function(mutations) {\n",
       "   mutations.forEach(function(mutation) {\n",
       "      if (mutation.removedNodes && mutation.removedNodes.length > 0) {\n",
       "         cancelAnimationFrame(renderId);\n",
       "         observer.disconnect();\n",
       "         console.log(\"cancelled frame \"+renderId);\n",
       "      }\n",
       "   });\n",
       "});\n",
       "\n",
       "// configuration of the observer:\n",
       "var config = { childList: true, characterData: true }\n",
       "\n",
       "// pass in the target node, as well as the observer options\n",
       "while (target) {\n",
       "   if (target.className==\"output\") {\n",
       "      observer.observe(target, config);\n",
       "      break;\n",
       "   }\n",
       "   target = target.parentNode;\n",
       "}\n",
       "\n",
       "// COMMON_CODE_BLOCK_END\n",
       "\n",
       "var obj0 = new THREE.Object3D();\n",
       "obj0.name = \"unnamed__1\";\n",
       "obj0.userData.explodable = 1;\n",
       "obj0.userData.points = [];\n",
       "obj0.userData.points.push(new PMPoint(-0.0534367, 0.597031, -1.23366));\n",
       "obj0.userData.points.push(new PMPoint(1.36405, -0.09649, -0.106124));\n",
       "obj0.userData.points.push(new PMPoint(-1.36405, 0.09649, 0.106124));\n",
       "obj0.userData.points.push(new PMPoint(0.0534367, -0.597031, 1.23366));\n",
       "\n",
       "obj0.userData.pointradii = 0.02;\n",
       "   <!-- Vertex style -->\n",
       "obj0.userData.pointmaterial = new THREE.MeshBasicMaterial( { color: 0xFF0000, side: THREE.DoubleSide, transparent: false } );\n",
       "obj0.userData.pointlabels = [\"0\", \"1\", \"2\", \"3\"];\n",
       "obj0.userData.edgeindices = [1, 0, 2, 0, 3, 1, 3, 2];\n",
       "   <!-- Edge style -->\n",
       "obj0.userData.edgematerial = new THREE.LineBasicMaterial( { color: 0x000000, depthTest: true, linewidth: 1.5, transparent: false } );\n",
       "init_object(obj0);\n",
       "scene.add(obj0);\n",
       "\n",
       "// COMMON_CODE_BLOCK_BEGIN\n",
       "function textSpriteMaterial(message, parameters) {\n",
       "    if ( parameters === undefined ) parameters = {};\n",
       "    var fontface = \"Helvetica\";\n",
       "    var fontsize = parameters.hasOwnProperty(\"fontsize\") ? parameters[\"fontsize\"] : 15;\n",
       "    fontsize = fontsize*10;\n",
       "    var lines = message.split('\\\\n');\n",
       "    var size = 512;\n",
       "    for(var i = 0; i<lines.length; i++){\n",
       "        var tmp = lines[i].length;\n",
       "        while(tmp*fontsize > size){\n",
       "           fontsize--;\n",
       "        }\n",
       "    }\n",
       "    \n",
       "    var canvas = document.createElement('canvas');\n",
       "    canvas.width = size;\n",
       "    canvas.height = size;\n",
       "    var context = canvas.getContext('2d');\n",
       "    context.fillStyle = \"rgba(255, 255, 255, 0)\";\n",
       "    context.fill();\n",
       "    context.font = fontsize + \"px \" + fontface;\n",
       "    \n",
       "    // text color\n",
       "    context.fillStyle = \"rgba(0, 0, 0, 1.0)\";\n",
       "     for(var i = 0; i<lines.length; i++){\n",
       "        context.fillText(lines[i], size/2, size/2+i*fontsize);\n",
       "     }\n",
       "    \n",
       "    // canvas contents will be used for a texture\n",
       "    var texture = new THREE.Texture(canvas);\n",
       "    texture.needsUpdate = true;\n",
       "    \n",
       "    var spriteMaterial = new THREE.SpriteMaterial({map: texture, depthTest: true, depthWrite: false, polygonOffset: true, polygonOffsetFactor: -1, polygonOffsetUnits: 1 });\n",
       "    return spriteMaterial;\n",
       "}\n",
       "\n",
       "\n",
       "// ---------------------- INITIALIZING OBJECTS--------------------------------------\n",
       "// ---------------------------------------------------------------------------------\n",
       "\n",
       "function init_object(obj) {\n",
       "    if (obj.userData.hasOwnProperty(\"pointmaterial\")) {\n",
       "        init_points(obj);\n",
       "        modelContains.points = true;\n",
       "    }\n",
       "    if (obj.userData.hasOwnProperty(\"pointlabels\")) {\n",
       "        init_pointlabels(obj);\n",
       "        modelContains.pointlabels = true;\n",
       "    }\n",
       "    if (obj.userData.hasOwnProperty(\"edgematerial\")) {\n",
       "        init_lines(obj);\n",
       "        modelContains.lines = true;\n",
       "    }\n",
       "    if (obj.userData.hasOwnProperty(\"edgelabels\")) {\n",
       "        init_edgelabels(obj);\n",
       "        modelContains.edgelabels = true;\n",
       "    }\n",
       "    if (obj.userData.hasOwnProperty(\"arrowstyle\")) {\n",
       "        init_arrowheads(obj);\n",
       "        modelContains.arrowheads = true;\n",
       "    }\n",
       "    if (obj.userData.hasOwnProperty(\"facetmaterial\")) {\n",
       "        init_faces(obj);\n",
       "        modelContains.faces = true;\n",
       "    }\n",
       "}\n",
       "\n",
       "function init_points(obj) {\n",
       "    var pointgroup = new THREE.Group();\n",
       "    pointgroup.name = \"points\";\n",
       "    var points = obj.userData.points;\n",
       "    var radii = obj.userData.pointradii;\n",
       "    var materials = obj.userData.pointmaterial;\n",
       "    var geometry,material;\n",
       "    if (!Array.isArray(radii)) {\n",
       "        geometry = new THREE.SphereBufferGeometry(radii);  \n",
       "    }\n",
       "    if (!Array.isArray(materials)) {\n",
       "        material = materials;\n",
       "    }\n",
       "    for (var i=0; i<points.length; i++) {\n",
       "        var point = points[i];\n",
       "        if (Array.isArray(radii)) {\n",
       "            if (radii[i] == 0) {\n",
       "                continue;\n",
       "            }\n",
       "            geometry = new THREE.SphereBufferGeometry(radii[i]);  \n",
       "        } \n",
       "        if (Array.isArray(materials)) {\n",
       "            material = materials[i];     \n",
       "        } \n",
       "        var sphere = new THREE.Mesh(geometry, material);\n",
       "        point.addSphere(sphere);\n",
       "        pointgroup.add(sphere);\n",
       "    }\n",
       "    obj.add(pointgroup);\n",
       "}\n",
       "\n",
       "function init_pointlabels(obj) {\n",
       "    var points = obj.userData.points;\n",
       "    var labels = obj.userData.pointlabels;\n",
       "    var pointlabels = new THREE.Group();\n",
       "    pointlabels.name = \"pointlabels\";\n",
       "    if (Array.isArray(labels)) {\n",
       "        for (var i=0; i<points.length; i++) {\n",
       "            var point = points[i];\n",
       "            var spriteMaterial = textSpriteMaterial( labels[i] );\n",
       "\t        var sprite = new THREE.Sprite(spriteMaterial);\n",
       "            point.addLabel(sprite);\n",
       "            pointlabels.add(sprite);\n",
       "        }\n",
       "    } else {\n",
       "        var spriteMaterial = textSpriteMaterial( labels );\n",
       "        for (var i=0; i<points.length; i++) {\n",
       "            var point = points[i];\n",
       "\t        var sprite = new THREE.Sprite(spriteMaterial);\n",
       "            point.addLabel(sprite);\n",
       "            pointlabels.add(sprite);\n",
       "        }\n",
       "    }\n",
       "    obj.add(pointlabels);\n",
       "}\n",
       "\n",
       "function init_lines(obj) {\n",
       "    var edgeindices = obj.userData.edgeindices;\n",
       "    var points = obj.userData.points;\n",
       "    var materials = obj.userData.edgematerial;\n",
       "    var geometry = new THREE.BufferGeometry();\n",
       "    var bufarr = new Float32Array( obj.userData.edgeindices.length * 3 );\n",
       "    var bufattr = new THREE.Float32BufferAttribute( bufarr, 3 );\n",
       "    var geometry = new THREE.BufferGeometry();\n",
       "    geometry.setAttribute('position', bufattr);\n",
       "    if (Array.isArray(materials)) {     \n",
       "        for (var i=0; i<materials.length; i++) {\n",
       "            geometry.addGroup(2*i,2,i);\n",
       "        }\n",
       "    }\n",
       "    var lines = new THREE.LineSegments(geometry, materials);\n",
       "    lines.name = \"lines\";\n",
       "    obj.add(lines);\n",
       "    updateEdgesPosition(obj);\n",
       "}\n",
       "\n",
       "function init_edgelabels(obj) {\n",
       "    var points = obj.userData.points;\n",
       "    var edgeindices = obj.userData.edgeindices;\n",
       "    var labels = obj.userData.edgelabels;\n",
       "    var edgelabels = new THREE.Group();\n",
       "    edgelabels.name = \"edgelabels\";\n",
       "    if (Array.isArray(labels)) {\n",
       "        for (var i=0; i<edgeindices.length; i=i+2) {\n",
       "            var point = points[i];\n",
       "            var spriteMaterial = textSpriteMaterial( labels[i] );\n",
       "            var sprite = new THREE.Sprite(spriteMaterial);\n",
       "            sprite.position.copy(new THREE.Vector3().addVectors(points[edgeindices[i]].vector,points[edgeindices[i+1]].vector).multiplyScalar(0.5));\n",
       "            edgelabels.add(sprite);\n",
       "        }\n",
       "    } else {\n",
       "        var spriteMaterial = textSpriteMaterial( labels );\n",
       "        for (var i=0; i<points.length; i++) {\n",
       "            var point = points[i];\n",
       "            var sprite = new THREE.Sprite(spriteMaterial);\n",
       "            sprite.position.copy(new THREE.Vector3().addVectors(points[edgeindices[i]].vector,points[edgeindices[i+1]].vector).multiplyScalar(0.5));\n",
       "            pointlabels.add(sprite);\n",
       "        }\n",
       "    }\n",
       "    obj.add(edgelabels);\n",
       "}\n",
       "\n",
       "function init_arrowheads(obj) {\n",
       "    var arrowheads = new THREE.Group();\n",
       "    arrowheads.name = \"arrowheads\";\n",
       "    var arrowstyle = obj.userData.arrowstyle;\n",
       "    var edgeindices = obj.userData.edgeindices;\n",
       "    var edgematerials = obj.userData.edgematerial;\n",
       "    var points = obj.userData.points;\n",
       "    var material;\n",
       "    if (!Array.isArray(edgematerials)) {\n",
       "        material = new THREE.MeshBasicMaterial( {color: edgematerials.color} );\n",
       "    }\n",
       "\n",
       "    for (var i=0; i<edgeindices.length; i=i+2) {\n",
       "        var start = points[edgeindices[i]];\n",
       "        var end = points[edgeindices[i+1]];\n",
       "        var dist = start.vector.distanceTo( end.vector ) - start.radius() - end.radius();\n",
       "        if (dist <= 0) {\n",
       "            continue;\n",
       "        }\n",
       "        var dir = new THREE.Vector3().subVectors(end.vector,start.vector);\n",
       "        dir.normalize();\n",
       "        var axis = new THREE.Vector3().set(dir.z,0,-dir.x);\n",
       "        axis.normalize();\n",
       "        var radians = Math.acos( dir.y );\n",
       "        var radius = dist/25;\n",
       "        var height = dist/5;\n",
       "        var geometry = new THREE.ConeBufferGeometry(radius,height);\n",
       "        var position = new THREE.Vector3().addVectors(start.vector,dir.clone().multiplyScalar(start.radius()+dist-height/2));\n",
       "        if (Array.isArray(edgematerials)) {\n",
       "            material = new THREE.MeshBasicMaterial( {color: edgematerials[i].color} );\n",
       "        }\n",
       "        var cone = new THREE.Mesh( geometry, material );\n",
       "        cone.quaternion.setFromAxisAngle(axis,radians);;\n",
       "        cone.position.copy(position);;\n",
       "        arrowheads.add(cone);\n",
       "    }\n",
       "    obj.add(arrowheads);\n",
       "}\n",
       "\n",
       "function init_faces(obj) {\n",
       "    var points = obj.userData.points;\n",
       "    var facets = obj.userData.facets;\n",
       "    obj.userData.triangleindices = [];\n",
       "    for (var i=0; i<facets.length; i++) {\n",
       "        facet = facets[i];\n",
       "        for (var t=0; t<facet.length-2; t++) {\n",
       "            obj.userData.triangleindices.push(facet[0],facet[t+1],facet[t+2]);  \n",
       "        }\n",
       "    }\n",
       "    var bufarr = new Float32Array( obj.userData.triangleindices.length * 3 );\n",
       "    var bufattr = new THREE.Float32BufferAttribute(bufarr,3);\n",
       "    \n",
       "    var materials = obj.userData.facetmaterial;\n",
       "    var geometry = new THREE.BufferGeometry();\n",
       "    geometry.setAttribute('position',bufattr);\n",
       "    if (Array.isArray(materials)) {\n",
       "        var tricount = 0;\n",
       "        var facet;\n",
       "        for (var i=0; i<facets.length; i++) {\n",
       "            facet = facets[i];\n",
       "            geometry.addGroup(tricount,(facet.length-2)*3,i);\n",
       "            tricount += (facet.length-2)*3;\n",
       "        }\n",
       "    }\n",
       "    var mesh = new THREE.Mesh(geometry, materials);\n",
       "    mesh.name = \"faces\";\n",
       "    obj.add(mesh); \n",
       "    updateFacesPosition(obj);\n",
       "}\n",
       "// //INITIALIZING\n",
       "\n",
       "\n",
       "function updateFacesPosition(obj) {\n",
       "    var points = obj.userData.points;\n",
       "    var indices = obj.userData.triangleindices;\n",
       "    var faces = obj.getObjectByName(\"faces\");\n",
       "    var ba = faces.geometry.getAttribute(\"position\");\n",
       "    for (var i=0; i<indices.length; i++) {\n",
       "        ba.setXYZ(i, points[indices[i]].vector.x, points[indices[i]].vector.y ,points[indices[i]].vector.z); \n",
       "    }\n",
       "    faces.geometry.attributes.position.needsUpdate = true;\n",
       "    \n",
       "}\n",
       "\n",
       "function updateEdgesPosition(obj) {\n",
       "    var points = obj.userData.points;\n",
       "    var indices = obj.userData.edgeindices;\n",
       "    var lines = obj.getObjectByName(\"lines\");\n",
       "    var ba = lines.geometry.getAttribute(\"position\"); \n",
       "    for (var i=0; i<indices.length; i++) {\n",
       "        ba.setXYZ(i, points[indices[i]].vector.x, points[indices[i]].vector.y ,points[indices[i]].vector.z); \n",
       "    }\n",
       "    lines.geometry.attributes.position.needsUpdate = true;\n",
       "}\n",
       "\n",
       "function onWindowResize() {\n",
       "    renderer.setSize( three.clientWidth, three.clientHeight );\n",
       "    svgRenderer.setSize( three.clientWidth, three.clientHeight );\n",
       "    updateCamera();\n",
       "}\n",
       "\n",
       "function updateCamera() {\n",
       "    var width = three.clientWidth;\n",
       "    var height = three.clientHeight;\n",
       "    var aspect = width / height;\n",
       "    if (camera.type == \"OrthographicCamera\") {\n",
       "        camera.left = frustumSize * aspect / - 2;\n",
       "        camera.right = frustumSize * aspect / 2;\n",
       "        camera.top = frustumSize / 2;\n",
       "        camera.bottom = - frustumSize / 2;\n",
       "    } else if (camera.type == \"PerspectiveCamera\") {\n",
       "        camera.aspect = aspect;\n",
       "    }\n",
       "    camera.updateProjectionMatrix();\n",
       "}\n",
       "\n",
       "function changeCamera(event) {\n",
       "    var selindex = event.currentTarget.selectedIndex;\n",
       "    camera = cameras[selindex];\n",
       "    control = controls[selindex];\n",
       "    control.enabled = true; \n",
       "    for (var i=0; i<controls.length; i++) {\n",
       "        if (i!=selindex) {\n",
       "            controls[i].enabled = false;\n",
       "        }\n",
       "    }\n",
       "    updateCamera();\n",
       "}\n",
       "\n",
       "var camtypenode = document.getElementById('cameraType_0');\n",
       "camtypenode.onchange = changeCamera;\n",
       "camtypenode.dispatchEvent(new Event('change'));\n",
       "\n",
       "onWindowResize();\n",
       "window.addEventListener('resize', onWindowResize);\t\n",
       "\n",
       "\n",
       "var xRotationEnabled = false;\n",
       "var yRotationEnabled = false;\n",
       "var zRotationEnabled = false;\n",
       "var rotationSpeedFactor = 1;\n",
       "var settingsShown = false;\n",
       "var labelsShown = true;\n",
       "var intervals = [];\n",
       "var timeouts = [];\n",
       "var explodingSpeed = 0.05;\n",
       "var explodeScale = 0;\n",
       "var XMLS = new XMLSerializer();\n",
       "var svgElement;\n",
       "var renderId;\n",
       "\n",
       "var render = function () {\n",
       "\n",
       "\trenderId = requestAnimationFrame(render);\n",
       "\n",
       "//\tcomment in for automatic explosion\n",
       "//\texplode(updateFactor());\n",
       "\n",
       "    var phi = 0.02 * rotationSpeedFactor;\n",
       "\n",
       "    if (xRotationEnabled) {\n",
       "        scene.rotation.x += phi;\n",
       "    }\n",
       "    if (yRotationEnabled) {\n",
       "        scene.rotation.y += phi;\n",
       "    }\n",
       "    if (zRotationEnabled) {\n",
       "        scene.rotation.z += phi;\n",
       "    }\n",
       "\n",
       "    control.update();\n",
       "    renderer.render(scene, camera);\n",
       "};\n",
       "\n",
       "if ( THREE.WEBGL.isWebGLAvailable() ) {\n",
       "\trender();\n",
       "} else {\n",
       "\tvar warning = WEBGL.getWebGLErrorMessage();\n",
       "\tthree.appendChild( warning );\n",
       "}\n",
       "    \n",
       "function changeTransparency() {\n",
       "    var opacity = 1-Number(event.currentTarget.value);\n",
       "    for (var i=0; i<scene.children.length; i++) {\n",
       "        child = scene.children[i];\n",
       "        if ( child.userData.hasOwnProperty(\"facetmaterial\") ) {\n",
       "            if (Array.isArray(child.userData.facetmaterial)) {\n",
       "                for (var j=0; j<child.userData.facetmaterial.length; j++) {\n",
       "                    child.userData.facetmaterial[j].opacity = opacity;\n",
       "                }\n",
       "            } else {\n",
       "                child.userData.facetmaterial.opacity = opacity;\n",
       "            }    \n",
       "        }\n",
       "    }\n",
       "}\n",
       "\n",
       "function changeRotationX(event){\n",
       "    xRotationEnabled = event.currentTarget.checked;\n",
       "}\t\n",
       "\n",
       "function changeRotationY(event){\n",
       "    yRotationEnabled = event.currentTarget.checked;\n",
       "}\t\n",
       "\n",
       "function changeRotationZ(event){\n",
       "    zRotationEnabled = event.currentTarget.checked;\n",
       "}\t\n",
       "\n",
       "\n",
       "function changeRotationSpeedFactor(event){\n",
       "    rotationSpeedFactor = Number(event.currentTarget.value);\n",
       "}\n",
       "\n",
       "function resetScene(){\n",
       "    scene.rotation.set(0,0,0);\n",
       "    camera.position.set(0,0,5);\n",
       "    camera.up.set(0,1,0);\n",
       "}\n",
       "\n",
       "function showSettings(event){\n",
       "    document.getElementById('settings_0').style.visibility = 'visible';\n",
       "    document.getElementById('showSettingsButton_0').style.visibility = 'hidden';\n",
       "    document.getElementById('hideSettingsButton_0').style.visibility = 'visible';\n",
       "    settingsShown = true;\n",
       "}\n",
       "\n",
       "function hideSettings(event){\n",
       "    document.getElementById('settings_0').style.visibility = 'hidden';\n",
       "    document.getElementById('showSettingsButton_0').style.visibility = 'visible';\n",
       "    document.getElementById('hideSettingsButton_0').style.visibility = 'hidden';\n",
       "    settingsShown = false;\n",
       "}\n",
       "\n",
       "\n",
       "\n",
       "var pos = 150* Math.PI;\n",
       "\n",
       "function updateFactor() {\n",
       "    pos++;\n",
       "    return Math.sin(.01*pos)+1;\n",
       "}\n",
       "\n",
       "// ------------------------ FOLDING ------------------------------------------------\n",
       "// ---------------------------------------------------------------------------------\n",
       "// rotate point p around axis defined by points p1 and p2 by given angle\n",
       "function rotate(p, p1, p2, angle ){   \n",
       "    angle = -angle;\n",
       "    var x = p.x, y = p.y, z = p.z, \n",
       "    a = p1.x, b = p1.y, c = p1.z, \n",
       "    u = p2.x-p1.x, v = p2.y-p1.y, w = p2.z-p1.z;\n",
       "    var result = [];\n",
       "    var L = u*u + v*v + w*w;\n",
       "    var sqrt = Math.sqrt;\n",
       "    var cos = Math.cos;\n",
       "    var sin = Math.sin;\n",
       "\n",
       "    result[0] = ((a*(v*v+w*w)-u*(b*v+c*w-u*x-v*y-w*z))*(1-cos(angle))+L*x*cos(angle)+sqrt(L)*(-c*v+b*w-w*y+v*z)*sin(angle))/L;\n",
       "    result[1] = ((b*(u*u+w*w)-v*(a*u+c*w-u*x-v*y-w*z))*(1-cos(angle))+L*y*cos(angle)+sqrt(L)*(c*u-a*w+w*x-u*z)*sin(angle))/L;\n",
       "    result[2] = ((c*(u*u+v*v)-w*(a*u+b*v-u*x-v*y-w*z))*(1-cos(angle))+L*z*cos(angle)+sqrt(L)*(-b*u+a*v-v*x+u*y)*sin(angle))/L;\n",
       "\n",
       "    return result;\n",
       "}\n",
       "\n",
       "var fold = function(event){\n",
       "    var obj = foldables[Number(event.currentTarget.name)];\n",
       "    var foldvalue = Number(event.currentTarget.value);\n",
       "    var scale = foldvalue - obj.userData.oldscale;\n",
       "\n",
       "    for (var j=0; j<obj.userData.axes.length; j++) {\n",
       "        rotateVertices(obj, j, scale);\n",
       "    }\n",
       "    update(obj);\n",
       "    obj.userData.oldscale += scale;\n",
       "    lookAtBarycenter(obj);\n",
       "}\n",
       "\n",
       "function lookAtBarycenter(obj){\n",
       "    control.target = barycenter(obj);\n",
       "}\n",
       "\n",
       "function barycenter(obj) {\n",
       "    var center = new THREE.Vector3(0,0,0);\n",
       "    var points = obj.userData.points;\n",
       "    for (var i=0; i<points.length; i++){\n",
       "        center.add(points[i].vector);\n",
       "    }\n",
       "    center.divideScalar(points.length);\n",
       "    return center;\n",
       "}\n",
       "\n",
       "function rotateVertices(obj, edge, scale) {\n",
       "    var axes = obj.userData.axes;\n",
       "    var subtrees = obj.userData.subtrees;\n",
       "    var points = obj.userData.points;\n",
       "    var angles = obj.userData.angles;\n",
       "    if (edge < axes.length){\n",
       "        for (var j=0; j<subtrees[edge].length; j++){\n",
       "            var rotP = rotate(points[subtrees[edge][j]].vector, points[axes[edge][0]].vector,points[axes[edge][1]].vector, scale * (Math.PI - angles[edge]));\n",
       "            points[subtrees[edge][j]].set(rotP[0],rotP[1],rotP[2]);\n",
       "        }\n",
       "    }\n",
       "}\n",
       "\n",
       "function update(obj) {\n",
       "   updateFacesPosition(obj);\n",
       "   updateEdgesPosition(obj);\n",
       "}\n",
       "\n",
       "if (foldables.length) {\n",
       "    var settings = document.getElementById('settings_0');\n",
       "    var foldDiv = document.createElement('div');\n",
       "    foldDiv.id = 'fold_0';\n",
       "    var title = document.createElement('strong');\n",
       "    title.innerHTML = 'Fold';\n",
       "    foldDiv.appendChild(title);\n",
       "    foldDiv.className = 'group';\n",
       "    for (var i=0; i<foldables.length; i++) {\n",
       "        var range = document.createElement('input');\n",
       "        range.type = 'range';\n",
       "        range.min = 0;\n",
       "        range.max = 1;\n",
       "        range.value = 0;\n",
       "        range.step = 0.001;\n",
       "        range.name = String(i);\n",
       "        range.oninput = fold;\n",
       "        foldDiv.appendChild(range);\n",
       "    }\n",
       "    lookAtBarycenter(foldables[0]);\n",
       "    settings.insertBefore(foldDiv,settings.childNodes[0]);\n",
       "}\n",
       "\n",
       "    \n",
       "// ---------------------- EXPLOSION ------------------------------------------------\n",
       "// ---------------------------------------------------------------------------------\n",
       "\n",
       "if (explodableModel) {\n",
       "    for (var i=0; i<scene.children.length; i++) {\n",
       "        obj = scene.children[i];\n",
       "        if ( obj.userData.explodable ) {\n",
       "            computeCentroid(obj);\n",
       "        }\n",
       "    }\n",
       "    document.getElementById('explodeRange_0').oninput = triggerExplode;\n",
       "    document.getElementById('explodeCheckbox_0').onchange = triggerAutomaticExplode;\n",
       "    document.getElementById('explodingSpeedRange_0').oninput = setExplodingSpeed;\n",
       "}\n",
       "\n",
       "function computeCentroid(obj) {\n",
       "    centroid = new THREE.Vector3();\n",
       "    obj.userData.points.forEach(function(pmpoint) {\n",
       "        centroid.add(pmpoint.vector);\t\t\t\n",
       "    });\n",
       "    centroid.divideScalar(obj.userData.points.length);\n",
       "    obj.userData.centroid = centroid;\n",
       "}\n",
       "\n",
       "function explode(factor) {\n",
       "    for (var i=0; i<scene.children.length; i++) {\n",
       "        var obj = scene.children[i];\n",
       "        if (obj.userData.hasOwnProperty(\"centroid\")) { \n",
       "            var c = obj.userData.centroid;\n",
       "            obj.position.set(c.x*factor, c.y*factor, c.z*factor);\n",
       "        }\n",
       "    }\t\n",
       "}\n",
       "\n",
       "function triggerExplode(event){\n",
       "    explodeScale = Number(event.currentTarget.value);\n",
       "    explode(explodeScale);\n",
       "}\n",
       "\n",
       "function setExplodingSpeed(event){\n",
       "    explodingSpeed = Number(event.currentTarget.value);\n",
       "}\n",
       "\n",
       "function triggerAutomaticExplode(event){\n",
       "    if (event.currentTarget.checked){\n",
       "        startExploding();\n",
       "    } else {\n",
       "        clearIntervals();\n",
       "    }\t\n",
       "}\n",
       "\n",
       "function startExploding(){\n",
       "    intervals.push(setInterval(explodingInterval, 25));\n",
       "}\n",
       "\n",
       "\n",
       "function explodingInterval(){\n",
       "    explodeScale += explodingSpeed;\n",
       "    if (explodeScale <= 6){ \n",
       "        explode(explodeScale);\n",
       "    }\n",
       "    else{\n",
       "        explode(6);\n",
       "        explodeScale = 6;\n",
       "        clearIntervals();\n",
       "        timeouts.push(setTimeout(startUnexploding, 3000));\n",
       "    }\n",
       "    document.getElementById('explodeRange_0').value = explodeScale;\n",
       "}\n",
       "\n",
       "\n",
       "function startUnexploding(){\n",
       "    intervals.push(setInterval(unexplodingInterval, 25));\n",
       "}\n",
       "\n",
       "function unexplodingInterval(){\n",
       "    explodeScale -= explodingSpeed;\n",
       "    if (explodeScale >= 0){\t\n",
       "        explode(explodeScale);\n",
       "    }\n",
       "    else {\n",
       "        explode(0);\n",
       "        explodeScale = 0;\n",
       "        clearIntervals();\n",
       "        timeouts.push(setTimeout(startExploding, 3000));\n",
       "    }\n",
       "    document.getElementById('explodeRange_0').value = explodeScale;\n",
       "}\n",
       "\n",
       "function clearIntervals(){\n",
       "    intervals.forEach(function(interval){\n",
       "        clearInterval(interval);\n",
       "    });\n",
       "    intervals = [];\n",
       "    timeouts.forEach(function(timeout){\n",
       "        clearTimeout(timeout);\n",
       "    });\n",
       "    timeouts = [];\n",
       "}\n",
       "\n",
       "// ---------------------- DISPLAY --------------------------------------------------\n",
       "// ---------------------------------------------------------------------------------\n",
       "\n",
       "const objectTypeInnerHTMLs = { points: \"Points\", pointlabels: \"Point labels\", lines: \"Edges\", edgelabels: \"Edge labels\", faces: \"Faces\", arrowheads: \"Arrow heads\" };\n",
       "const objectTypeVisible = {};\n",
       "Object.assign(objectTypeVisible,modelContains);\n",
       "const sortedObjectTypeKeys = Object.keys(objectTypeInnerHTMLs).sort();\n",
       "const shownObjectTypesList = document.getElementById('shownObjectTypesList_0');\n",
       "\n",
       "function setVisibility(bool,objname) {\n",
       "    for (var i=0; i<scene.children.length; i++){\n",
       "        var obj = scene.children[i].getObjectByName(objname);\n",
       "        if (obj) {\n",
       "            obj.visible = bool;\n",
       "        }\n",
       "    }\n",
       "}\n",
       "\n",
       "function toggleObjectTypeVisibility(event){\n",
       "    var name = event.currentTarget.name;\n",
       "    var checked = event.currentTarget.checked;\n",
       "    objectTypeVisible[name] = checked;\n",
       "    setVisibility(checked,name);\n",
       "}\n",
       "\n",
       "for (var i=0; i<sortedObjectTypeKeys.length; i++){\n",
       "    var key = sortedObjectTypeKeys[i];\n",
       "    if (modelContains[key]) {\n",
       "        var objTypeNode = document.createElement('span');\n",
       "        objTypeNode.innerHTML = objectTypeInnerHTMLs[key] + '<br>';\n",
       "        var checkbox = document.createElement('input');\n",
       "        checkbox.type = 'checkbox';\n",
       "        checkbox.checked = true;\n",
       "        checkbox.name = key;\n",
       "        checkbox.onchange = toggleObjectTypeVisibility;\n",
       "        shownObjectTypesList.appendChild(checkbox);\n",
       "        shownObjectTypesList.appendChild(objTypeNode);\n",
       "    }\n",
       "}\n",
       "\n",
       "// ------------------------------------------------------\n",
       "\n",
       "function toggleObjectVisibility(event){\n",
       "    var nr = Number(event.currentTarget.name);\n",
       "    scene.children[nr].visible = event.currentTarget.checked;\n",
       "}\n",
       "\n",
       "// append checkboxes for displaying or hiding objects\n",
       "var shownObjectsList = document.getElementById('shownObjectsList_0');\n",
       "for (var i=0; i<scene.children.length; i++){\n",
       "    obj = scene.children[i];\n",
       "    var objNode = document.createElement('span');\n",
       "    objNode.innerHTML = obj.name + '<br>';\n",
       "    var checkbox = document.createElement('input');\n",
       "    checkbox.type = 'checkbox';\n",
       "    checkbox.checked = true;\n",
       "    checkbox.name = String(i);\n",
       "    checkbox.onchange = toggleObjectVisibility;\n",
       "    shownObjectsList.appendChild(checkbox);\n",
       "    shownObjectsList.appendChild(objNode);\n",
       "}\n",
       "\n",
       "// ---------------------- SVG ------------------------------------------------------\n",
       "// ---------------------------------------------------------------------------------\n",
       "\n",
       "function takeSvgScreenshot() {\n",
       "    if (objectTypeVisible[\"pointlabels\"]) {\n",
       "        setVisibility(false,\"pointlabels\");\n",
       "    }\n",
       "    if (objectTypeVisible[\"edgelabels\"]) {\n",
       "        setVisibility(false,\"edgelabels\");\n",
       "    }\n",
       "    svgRenderer.render(scene,camera);\n",
       "    svgElement = XMLS.serializeToString(svgRenderer.domElement);\n",
       "    \n",
       "    if (objectTypeVisible[\"pointlabels\"]) {\n",
       "        setVisibility(true,\"pointlabels\");\n",
       "    }\n",
       "    if (objectTypeVisible[\"edgelabels\"]) {\n",
       "        setVisibility(true,\"edgelabels\");\n",
       "    }\n",
       "\n",
       "    if (document.getElementById('tab_0').checked){\n",
       "        //show in new tab\n",
       "        var myWindow = window.open(\"\",\"\");\n",
       "        myWindow.document.body.innerHTML = svgElement;\n",
       "    } else{\n",
       "        // download svg file \n",
       "        download(\"screenshot.svg\", svgElement);\n",
       "    }\n",
       "}\n",
       "\n",
       "function download(filename, text) {\n",
       "    var element = document.createElement('a');\n",
       "    element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));\n",
       "    element.setAttribute('download', filename);\n",
       "\n",
       "    element.style.display = 'none';\n",
       "    document.body.appendChild(element);\n",
       "\n",
       "    element.click();\n",
       "\n",
       "    document.body.removeChild(element);\n",
       "}\n",
       "\n",
       "\n",
       "document.getElementById('transparencyRange_0').oninput = changeTransparency;\n",
       "document.getElementById('changeRotationX_0').onchange = changeRotationX;\n",
       "document.getElementById('changeRotationY_0').onchange = changeRotationY;\n",
       "document.getElementById('changeRotationZ_0').onchange = changeRotationZ;\n",
       "document.getElementById('resetButton_0').onclick = resetScene;\n",
       "document.getElementById('rotationSpeedRange_0').oninput = changeRotationSpeedFactor;\n",
       "document.getElementById('takeScreenshot_0').onclick = takeSvgScreenshot;\n",
       "document.getElementById('showSettingsButton_0').onclick = showSettings;\n",
       "document.getElementById('hideSettingsButton_0').onclick = hideSettings;\n",
       "\n",
       "\n",
       "// ------------------ SHORTCUTS --------------------------------------------\n",
       "// -------------------------------------------------------------------------\n",
       "\n",
       "/**\n",
       " * http://www.openjs.com/scripts/events/keyboard_shortcuts/\n",
       " * Version : 2.01.B\n",
       " * By Binny V A\n",
       " * License : BSD\n",
       " */\n",
       "shortcut = {\n",
       "\t'all_shortcuts':{},//All the shortcuts are stored in this array\n",
       "\t'add': function(shortcut_combination,callback,opt) {\n",
       "\t\t//Provide a set of default options\n",
       "\t\tvar default_options = {\n",
       "\t\t\t'type':'keydown',\n",
       "\t\t\t'propagate':false,\n",
       "\t\t\t'disable_in_input':false,\n",
       "\t\t\t'target':document,\n",
       "\t\t\t'keycode':false\n",
       "\t\t}\n",
       "\t\tif(!opt) opt = default_options;\n",
       "\t\telse {\n",
       "\t\t\tfor(var dfo in default_options) {\n",
       "\t\t\t\tif(typeof opt[dfo] == 'undefined') opt[dfo] = default_options[dfo];\n",
       "\t\t\t}\n",
       "\t\t}\n",
       "\n",
       "\t\tvar ele = opt.target;\n",
       "\t\tif(typeof opt.target == 'string') ele = document.getElementById(opt.target);\n",
       "\t\tvar ths = this;\n",
       "\t\tshortcut_combination = shortcut_combination.toLowerCase();\n",
       "\n",
       "\t\t//The function to be called at keypress\n",
       "\t\tvar func = function(e) {\n",
       "\t\t\te = e || window.event;\n",
       "\t\t\t\n",
       "\t\t\tif(opt['disable_in_input']) { //Don't enable shortcut keys in Input, Textarea fields\n",
       "\t\t\t\tvar element;\n",
       "\t\t\t\tif(e.target) element=e.target;\n",
       "\t\t\t\telse if(e.srcElement) element=e.srcElement;\n",
       "\t\t\t\tif(element.nodeType==3) element=element.parentNode;\n",
       "\n",
       "\t\t\t\tif(element.tagName == 'INPUT' || element.tagName == 'TEXTAREA') return;\n",
       "\t\t\t}\n",
       "\t\n",
       "\t\t\t//Find Which key is pressed\n",
       "\t\t\tif (e.keyCode) code = e.keyCode;\n",
       "\t\t\telse if (e.which) code = e.which;\n",
       "\t\t\tvar character = String.fromCharCode(code).toLowerCase();\n",
       "\t\t\t\n",
       "\t\t\tif(code == 188) character=\",\"; //If the user presses , when the type is onkeydown\n",
       "\t\t\tif(code == 190) character=\".\"; //If the user presses , when the type is onkeydown\n",
       "\n",
       "\t\t\tvar keys = shortcut_combination.split(\"+\");\n",
       "\t\t\t//Key Pressed - counts the number of valid keypresses - if it is same as the number of keys, the shortcut function is invoked\n",
       "\t\t\tvar kp = 0;\n",
       "\t\t\t\n",
       "\t\t\t//Work around for stupid Shift key bug created by using lowercase - as a result the shift+num combination was broken\n",
       "\t\t\tvar shift_nums = {\n",
       "\t\t\t\t\"`\":\"~\",\n",
       "\t\t\t\t\"1\":\"!\",\n",
       "\t\t\t\t\"2\":\"@\",\n",
       "\t\t\t\t\"3\":\"#\",\n",
       "\t\t\t\t\"4\":\"$\",\n",
       "\t\t\t\t\"5\":\"%\",\n",
       "\t\t\t\t\"6\":\"^\",\n",
       "\t\t\t\t\"7\":\"&\",\n",
       "\t\t\t\t\"8\":\"*\",\n",
       "\t\t\t\t\"9\":\"(\",\n",
       "\t\t\t\t\"0\":\")\",\n",
       "\t\t\t\t\"-\":\"_\",\n",
       "\t\t\t\t\"=\":\"+\",\n",
       "\t\t\t\t\";\":\":\",\n",
       "\t\t\t\t\"'\":\"\\\"\",\n",
       "\t\t\t\t\",\":\"<\",\n",
       "\t\t\t\t\".\":\">\",\n",
       "\t\t\t\t\"/\":\"?\",\n",
       "\t\t\t\t\"\\\\\":\"|\"\n",
       "\t\t\t}\n",
       "\t\t\t//Special Keys - and their codes\n",
       "\t\t\tvar special_keys = {\n",
       "\t\t\t\t'esc':27,\n",
       "\t\t\t\t'escape':27,\n",
       "\t\t\t\t'tab':9,\n",
       "\t\t\t\t'space':32,\n",
       "\t\t\t\t'return':13,\n",
       "\t\t\t\t'enter':13,\n",
       "\t\t\t\t'backspace':8,\n",
       "\t\n",
       "\t\t\t\t'scrolllock':145,\n",
       "\t\t\t\t'scroll_lock':145,\n",
       "\t\t\t\t'scroll':145,\n",
       "\t\t\t\t'capslock':20,\n",
       "\t\t\t\t'caps_lock':20,\n",
       "\t\t\t\t'caps':20,\n",
       "\t\t\t\t'numlock':144,\n",
       "\t\t\t\t'num_lock':144,\n",
       "\t\t\t\t'num':144,\n",
       "\t\t\t\t\n",
       "\t\t\t\t'pause':19,\n",
       "\t\t\t\t'break':19,\n",
       "\t\t\t\t\n",
       "\t\t\t\t'insert':45,\n",
       "\t\t\t\t'home':36,\n",
       "\t\t\t\t'delete':46,\n",
       "\t\t\t\t'end':35,\n",
       "\t\t\t\t\n",
       "\t\t\t\t'pageup':33,\n",
       "\t\t\t\t'page_up':33,\n",
       "\t\t\t\t'pu':33,\n",
       "\t\n",
       "\t\t\t\t'pagedown':34,\n",
       "\t\t\t\t'page_down':34,\n",
       "\t\t\t\t'pd':34,\n",
       "\t\n",
       "\t\t\t\t'left':37,\n",
       "\t\t\t\t'up':38,\n",
       "\t\t\t\t'right':39,\n",
       "\t\t\t\t'down':40,\n",
       "\t\n",
       "\t\t\t\t'f1':112,\n",
       "\t\t\t\t'f2':113,\n",
       "\t\t\t\t'f3':114,\n",
       "\t\t\t\t'f4':115,\n",
       "\t\t\t\t'f5':116,\n",
       "\t\t\t\t'f6':117,\n",
       "\t\t\t\t'f7':118,\n",
       "\t\t\t\t'f8':119,\n",
       "\t\t\t\t'f9':120,\n",
       "\t\t\t\t'f10':121,\n",
       "\t\t\t\t'f11':122,\n",
       "\t\t\t\t'f12':123\n",
       "\t\t\t}\n",
       "\t\n",
       "\t\t\tvar modifiers = { \n",
       "\t\t\t\tshift: { wanted:false, pressed:false},\n",
       "\t\t\t\tctrl : { wanted:false, pressed:false},\n",
       "\t\t\t\talt  : { wanted:false, pressed:false},\n",
       "\t\t\t\tmeta : { wanted:false, pressed:false}\t//Meta is Mac specific\n",
       "\t\t\t};\n",
       "                        \n",
       "\t\t\tif(e.ctrlKey)\tmodifiers.ctrl.pressed = true;\n",
       "\t\t\tif(e.shiftKey)\tmodifiers.shift.pressed = true;\n",
       "\t\t\tif(e.altKey)\tmodifiers.alt.pressed = true;\n",
       "\t\t\tif(e.metaKey)   modifiers.meta.pressed = true;\n",
       "                        \n",
       "\t\t\tfor(var i=0; k=keys[i],i<keys.length; i++) {\n",
       "\t\t\t\t//Modifiers\n",
       "\t\t\t\tif(k == 'ctrl' || k == 'control') {\n",
       "\t\t\t\t\tkp++;\n",
       "\t\t\t\t\tmodifiers.ctrl.wanted = true;\n",
       "\n",
       "\t\t\t\t} else if(k == 'shift') {\n",
       "\t\t\t\t\tkp++;\n",
       "\t\t\t\t\tmodifiers.shift.wanted = true;\n",
       "\n",
       "\t\t\t\t} else if(k == 'alt') {\n",
       "\t\t\t\t\tkp++;\n",
       "\t\t\t\t\tmodifiers.alt.wanted = true;\n",
       "\t\t\t\t} else if(k == 'meta') {\n",
       "\t\t\t\t\tkp++;\n",
       "\t\t\t\t\tmodifiers.meta.wanted = true;\n",
       "\t\t\t\t} else if(k.length > 1) { //If it is a special key\n",
       "\t\t\t\t\tif(special_keys[k] == code) kp++;\n",
       "\t\t\t\t\t\n",
       "\t\t\t\t} else if(opt['keycode']) {\n",
       "\t\t\t\t\tif(opt['keycode'] == code) kp++;\n",
       "\n",
       "\t\t\t\t} else { //The special keys did not match\n",
       "\t\t\t\t\tif(character == k) kp++;\n",
       "\t\t\t\t\telse {\n",
       "\t\t\t\t\t\tif(shift_nums[character] && e.shiftKey) { //Stupid Shift key bug created by using lowercase\n",
       "\t\t\t\t\t\t\tcharacter = shift_nums[character]; \n",
       "\t\t\t\t\t\t\tif(character == k) kp++;\n",
       "\t\t\t\t\t\t}\n",
       "\t\t\t\t\t}\n",
       "\t\t\t\t}\n",
       "\t\t\t}\n",
       "\t\t\t\n",
       "\t\t\tif(kp == keys.length && \n",
       "\t\t\t\t\t\tmodifiers.ctrl.pressed == modifiers.ctrl.wanted &&\n",
       "\t\t\t\t\t\tmodifiers.shift.pressed == modifiers.shift.wanted &&\n",
       "\t\t\t\t\t\tmodifiers.alt.pressed == modifiers.alt.wanted &&\n",
       "\t\t\t\t\t\tmodifiers.meta.pressed == modifiers.meta.wanted) {\n",
       "\t\t\t\tcallback(e);\n",
       "\t\n",
       "\t\t\t\tif(!opt['propagate']) { //Stop the event\n",
       "\t\t\t\t\t//e.cancelBubble is supported by IE - this will kill the bubbling process.\n",
       "\t\t\t\t\te.cancelBubble = true;\n",
       "\t\t\t\t\te.returnValue = false;\n",
       "\t\n",
       "\t\t\t\t\t//e.stopPropagation works in Firefox.\n",
       "\t\t\t\t\tif (e.stopPropagation) {\n",
       "\t\t\t\t\t\te.stopPropagation();\n",
       "\t\t\t\t\t\te.preventDefault();\n",
       "\t\t\t\t\t}\n",
       "\t\t\t\t\treturn false;\n",
       "\t\t\t\t}\n",
       "\t\t\t}\n",
       "\t\t}\n",
       "\t\tthis.all_shortcuts[shortcut_combination] = {\n",
       "\t\t\t'callback':func, \n",
       "\t\t\t'target':ele, \n",
       "\t\t\t'event': opt['type']\n",
       "\t\t};\n",
       "\t\t//Attach the function with the event\n",
       "\t\tif(ele.addEventListener) ele.addEventListener(opt['type'], func, false);\n",
       "\t\telse if(ele.attachEvent) ele.attachEvent('on'+opt['type'], func);\n",
       "\t\telse ele['on'+opt['type']] = func;\n",
       "\t},\n",
       "\n",
       "\t//Remove the shortcut - just specify the shortcut and I will remove the binding\n",
       "\t'remove':function(shortcut_combination) {\n",
       "\t\tshortcut_combination = shortcut_combination.toLowerCase();\n",
       "\t\tvar binding = this.all_shortcuts[shortcut_combination];\n",
       "\t\tdelete(this.all_shortcuts[shortcut_combination])\n",
       "\t\tif(!binding) return;\n",
       "\t\tvar type = binding['event'];\n",
       "\t\tvar ele = binding['target'];\n",
       "\t\tvar callback = binding['callback'];\n",
       "\n",
       "\t\tif(ele.detachEvent) ele.detachEvent('on'+type, callback);\n",
       "\t\telse if(ele.removeEventListener) ele.removeEventListener(type, callback, false);\n",
       "\t\telse ele['on'+type] = false;\n",
       "\t}\n",
       "}\n",
       "\n",
       "shortcut.add(\"Alt+Left\",function() {\n",
       "\tvar event = new Event('click');\n",
       "\tif (settingsShown){\n",
       "\t\tdocument.getElementById('hideSettingsButton_0').dispatchEvent(event);\n",
       "\t} else {\n",
       "\t\tdocument.getElementById('showSettingsButton_0').dispatchEvent(event);\n",
       "\t}\n",
       "});\n",
       "\n",
       "\n",
       "// COMMON_CODE_BLOCK_END\n",
       "\n",
       "});});\n",
       "      </script>\n",
       "   </body>\n",
       "</html>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<details><summary><pre style=\"display:inline\"><small>Click here for additional output</small></pre></summary>\n",
       "<pre>\n",
       "polymake: used package threejs\n",
       "   Three.js is a lightweight cross-browser JavaScript library/API used to create and display animated 3D computer graphics on a Web browser.\n",
       "   See http://github.com/mrdoob for the source code.\n",
       "\n",
       "</pre>\n",
       "</details>\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "$c=cube(2);\n",
    "$c->GRAPH->VISUAL;"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "To study the automorphisms of this graph, we create a `GraphAdjacency<Dir>` object refering to the `C++` class named `Graph` (see the [tutorial on graphs](apps_graph.ipynb) for more details):\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "$g=new GraphAdjacency($c->GRAPH->ADJACENCY);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "The picture of the graph shows that the node with label 0 is adjacent to the nodes 1 and 2, Node 1 is adjacent to 0 and 3, and so on. For the complete adjacency information you can print `$c->GRAPH->ADJACENCY` or just the GraphAdjacency object `$g`:\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0:1 2\n",
       "1:0 3\n",
       "2:0 3\n",
       "3:1 2\n"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "print rows_labeled($g);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, we compute the generators of the automorphism group of this graph (see the [tutorial on groups](apps_group.ipynb) for more info):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<details><summary><pre style=\"display:inline\"><small>Click here for additional output</small></pre></summary>\n",
       "<pre>\n",
       "polymake: used package nauty\n",
       "  Computation of automorphism groups of graphs.\n",
       "  Copyright by Brendan McKay and Adolfo Piperno.\n",
       "  http://pallini.di.uniroma1.it/\n",
       "</pre>\n",
       "</details>\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "$aut=automorphisms($g);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "In this case, the automorphism group has two generators:\n",
    "\n",
    "    \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0 2 1 3\n",
       "1 0 3 2\n"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "print $aut;"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Each generator is a permutation on the nodes. The first generator fixes the nodes 0 and 3, and exchanges the nodes 1 and 2, i.e., it describes the reflection along the diagonal through 0 and 3. The second generator is the reflection along the horizontal line.\n",
    "\n",
    "In order to be able to work with the group, we create a new Group object, which lives in the application `group`:\n",
    "\n",
    "    \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "$action = new group::PermutationAction(GENERATORS => $aut);\n",
    "$autgroup = new group::Group(PERMUTATION_ACTION => $action);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "Now we can ask for basic properties of the group, e.g., the number of elements:\n",
    "\n",
    "    \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "8"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "text/html": [
       "<details><summary><pre style=\"display:inline\"><small>Click here for additional output</small></pre></summary>\n",
       "<pre>\n",
       "polymake: used package permlib\n",
       "  A callable C++ library for permutation computations. \n",
       "  Written by Thomas Rehn.\n",
       "  http://www.math.uni-rostock.de/~rehn/software/permlib.html \n",
       "\n",
       "</pre>\n",
       "</details>\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "print $autgroup->ORDER;"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Sometimes, it is useful to know which elements of the group fix a specific set of indices, that is, we are interested in the subgroup which is the stabilizer of the given set. In the first case, we just fix the index 0:\n",
    "\n",
    "    \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "$s0=new Set<Int>(0);\n",
    "$stab0=group::stabilizer_of_set($action,$s0);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "We learn that the node 0 is only fixed by the permutation `0 2 1 3`:\n",
    "\n",
    "    \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "2"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "print $stab0->ORDER;"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0 2 1 3\n"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "print $stab0->PERMUTATION_ACTION->GENERATORS;"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the second case, we look at the subgroup which leaves the set `{1,2}` invariant: \n",
    "\n",
    "    \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "$s12=new Set<Int>(1,2);\n",
    "$stab12=group::stabilizer_of_set($action,$s12);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "Now, we obtain a group of order 4:\n",
    "\n",
    "    \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "4"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "print $stab12->ORDER;"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3 1 2 0\n",
       "0 2 1 3\n"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "print $stab12->PERMUTATION_ACTION->GENERATORS;"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, we compute the orbits of the indices under the three different groups:\n",
    "\n",
    "    \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{0}\n",
       "{1 2}\n",
       "{3}\n"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "print $stab0->PERMUTATION_ACTION->ORBITS;"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{0 3}\n",
       "{1 2}\n"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "print $stab12->PERMUTATION_ACTION->ORBITS;"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{0 1 2 3}\n"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "print $autgroup->PERMUTATION_ACTION->ORBITS;"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "polymake",
   "language": "polymake",
   "name": "polymake"
  },
  "language_info": {
   "codemirror_mode": "perl",
   "file_extension": ".pl",
   "mimetype": "text/x-polymake",
   "name": "polymake"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
