diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..375542a --- /dev/null +++ b/.babelrc @@ -0,0 +1,5 @@ +{ + "plugins": [ + "transform-es2015-modules-amd" + ] +} diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..6b00f86 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,3 @@ +node_modules +vendor +build \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..3c88712 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,31 @@ +{ + "env": { + "browser": true, + "commonjs": true, + "es6": true, + "node": true + }, + "extends": "eslint:recommended", + "parserOptions": { + "sourceType": "module" + }, + "rules": { + "indent": [ + "error", + 4, + {"SwitchCase": 1} + ], + "linebreak-style": [ + "error", + "unix" + ], + "quotes": [ + "error", + "double" + ], + "semi": [ + "error", + "always" + ] + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index b95924a..35cf6df 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,9 @@ logs *.log +# IDE files +.idea + # Runtime data pids *.pid @@ -26,4 +29,10 @@ node_modules # Storage store/* -!store/README.md \ No newline at end of file +!store/README.md + +# Generated files +dev/js/**/*.amd.js +dev/css/**/*.css +vendor/* +!vendor/README.md \ No newline at end of file diff --git a/build/css/main.css b/build/css/main.css new file mode 100644 index 0000000..c2985d2 --- /dev/null +++ b/build/css/main.css @@ -0,0 +1 @@ +/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */button,hr,input{overflow:visible}audio,canvas,progress,video{display:inline-block}progress,sub,sup{vertical-align:baseline}html{font-family:sans-serif;line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}article,aside,details,figcaption,figure,footer,header,main,menu,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects;color:#0c0}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;margin:0}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:ButtonText dotted 1px}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}.hidden,[hidden],template{display:none}body{margin:0;font:400 87.5%/1.57142857 Arial,sans-serif}a:hover{text-decoration:none}button,input,optgroup,select,textarea{line-height:1.57142857}.oe-description{color:grey;font-size:.85em;line-height:1.2}#oe-view{font-size:0;height:calc(100vh - 22px - 3px * 2 - 1px);line-height:0;margin-right:10px;width:100%}#oe-view.oe-droppable{outline:#000 dashed 1px;position:relative}#oe-view.oe-droppable:after{background-image:repeating-linear-gradient(45deg,transparent,transparent 10px,rgba(255,255,255,.5) 10px,rgba(255,255,255,.5) 20px);content:"";height:100%;left:0;position:absolute;top:0;width:100%}#oe-view.oe-droppable *,#oe-view.oe-droppable:after{pointer-events:none}#oe-view,#oe-view canvas{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}#oe-view canvas{height:100%!important;width:100%!important}.oe-menu{background:#f0f0f0;border-bottom:1px solid #dadada;margin:0;padding:0}.app-busy .oe-menu:after{content:url(../img/loader.gif);cursor:wait;float:right;margin:6px 8px 0 0}.oe-menu>li{display:inline-block;list-style:none;margin:0;padding:0;position:relative}.oe-menu button[menu]{cursor:pointer;background:0 0;border:none;padding:3px 10px}.oe-menu button[menu]:hover{background:#e6e6e6}.oe-menu button[menu]:before{content:attr(value)}.oe-menu menu[type=popup]{background:#f0f0f0;border:1px solid #dadada;margin:0;padding:0;position:absolute;visibility:hidden;z-index:2}.oe-menu menu[type=popup].expanded{visibility:visible}.oe-menu menuitem{cursor:pointer;display:block;max-width:250px;overflow:hidden;padding:5px 7px;text-overflow:ellipsis;white-space:nowrap}.oe-potentials,.oe-store-list{list-style:none;overflow:auto}.oe-menu menuitem:hover{background:#e6e6e6}.oe-menu menuitem:before{content:attr(label)}.oe-menu menuitem[disabled]{color:#a0a0a0}.oe-menu menuitem[disabled]:hover{background:0 0}.oe-menu menuitem[disabled]:before{text-shadow:0 1px 0 #fff}.oe-menu hr{border:1px inset #fff;color:#fff}#oe-file{cursor:pointer;display:none;height:32px;left:0;max-width:175px;opacity:.01;position:absolute;top:28px;z-index:2}menu.expanded+#oe-file{display:inline}.oe-dialog{background:#fff;border:1px solid #0e0e0e;border-radius:5px;left:50%;margin-left:-250px;padding:0 15px;position:absolute;top:50px;width:500px;z-index:20}.oe-dialog:before{background:rgba(255,255,255,.8);content:"";height:100%;left:0;position:fixed;top:0;width:100%;z-index:-1}.oe-dialog-btns input{min-width:85px}.oe-dialog fieldset{border-color:#dadada;margin:1em 0}.oe-dialog-btns{text-align:right;border:none;padding:0}.oe-store-list{max-height:250px;min-height:20px;padding:0}.oe-store-list-loading{background:url(../../img/loader.gif) 50% 50% no-repeat}.oe-store-list>li{cursor:pointer;padding:.2em .5em}.oe-store-list h3{display:inline;font-size:1em;margin:0}.oe-store-list h3:after{color:#0c0;content:" …"}.oe-graph-form:before,.oe-store-list>.active h3:after{content:none}.oe-store-list p{color:transparent;display:inline;font-size:0;margin:0}.oe-store-list>.active{background:#e6e6e6}.oe-store-list>.active p{color:#999;font-size:.85em}.oe-save-form .oe-apply{margin-right:20px}.oe-graph-form{border-color:#dadada;border-radius:0 0 0 5px;border-width:0 0 1px 1px;left:auto;margin-left:0;right:0;top:29px;width:350px;z-index:1}.oe-cutoffs{border:1px solid #dadada;padding:0}.oe-cutoff{background:0 0;border:none;border-top:1px solid #dadada;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;display:block;padding:0;text-align:left;width:100%}.oe-cutoff:first-child{border-top:none}.oe-cutoff.active,.oe-cutoff:focus{background:#f5f5f5;outline:0}.oe-cutoff:before,.oe-potentials li:first-child>span{background:#f0f0f0}.oe-cutoff:before{border-right:1px solid #dadada;content:attr(data-pair);display:inline-block;margin-right:5px;min-width:75px;padding-left:5px}.oe-cutoff-slider{padding:0;width:100%}.oe-cutoff-exact,.oe-cutoff-max,.oe-cutoff-min{display:block;width:32%}.oe-cutoff-exact input,.oe-cutoff-max input,.oe-cutoff-min input{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;width:100%}.oe-cutoff-min{float:left}.oe-cutoff-max{float:right}.oe-cutoff-exact{margin-left:auto;margin-right:auto}.oe-potentials{border:1px solid #dadada;border-collapse:collapse;display:table;margin:0;max-height:300px;padding:0;table-layout:fixed;width:100%}.oe-potentials li{display:table-row;margin:0;padding:0}.oe-potentials li>label,.oe-potentials li>span{border:1px solid #dadada;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;display:table-cell;padding:2px 5px;vertical-align:middle;width:28%}.oe-potentials li>label:first-child,.oe-potentials li>span:first-child{background:#f0f0f0;width:16%}.oe-potentials input{background:0 0;border:none;display:block;font-size:1em;height:1.5em;margin:0;padding:0;width:100%}.oe-appearance-colors input,.oe-appearance-colors label,.oe-appearance-colors select,.oe-translate label{display:inline-block;vertical-align:top}.oe-potentials .missed label{background:url(data:image/gif;base64,R0lGODlhBQAFAIAAAOPj4////yH5BAAAAAAALAAAAAAFAAUAAAIHRH6GodhZAAA7)}.oe-potentials sub{font-style:normal}.potential-filing label{color:#0c0;cursor:pointer;overflow:hidden;position:relative;text-decoration:underline}.potential-filing label:hover{text-decoration:none}.potential-filing label input{cursor:pointer;height:100%;left:0;opacity:.01;position:absolute;top:0;width:100%}.oe-transform-form{border-color:#dadada;border-radius:0 0 0 5px;border-width:0 0 1px 1px;left:auto;margin-left:0;right:0;top:29px;width:350px;z-index:1}.oe-transform-form:before{content:none}.oe-translate>legend:after{content:"\21A6"}.oe-translate input[type=text]{width:75px}.oe-translate input[type=button]{color:#c00;display:inline-block;font:400 22px/20px monospace;vertical-align:top}.oe-rotate>legend:after{content:"\21BB"}.oe-rotate input[data-axis="x"]{color:#a00}.oe-rotate input[data-axis="y"]{color:#0a0}.oe-rotate input[data-axis="z"]{color:#00a}.oe-rotate>legend:after,.oe-translate>legend:after{font:400 20px/1px monospace;margin:0 5px}.oe-evolve-params{background:url(data:image/gif;base64,R0lGODlhAQABAIAAANra2gAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==) 50% 0 repeat-y;float:left;margin-bottom:1em;width:100%}.oe-evolve-params fieldset{border:none;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;float:right;margin:0;padding:0 0 0 25px;width:49.5%}.oe-evolve-params fieldset:first-child{float:left;padding:0 25px 0 0}.oe-evolve-params fieldset>p:first-child{margin-top:0}.oe-evolve-form .oe-dialog-btns{clear:both}.oe-appearance-form{margin-left:-150px;width:300px}.oe-appearance-colors label{margin-right:20px}.oe-report{background:#fff;border-color:#dadada;border-radius:0 0 0 5px;border-width:0 0 1px 1px;padding:0 15px;position:absolute;right:0;top:29px;z-index:1}#oe-report-progress{position:relative}#oe-report-progress:after{content:attr(value) "%";font-size:.8em;left:0;margin-left:5px;position:absolute;text-align:center;width:100%}#oe-report-data>dt{font-weight:700}#oe-report-data>dd{margin:0 0 .5em} \ No newline at end of file diff --git a/build/src/img/dot.gif b/build/img/dot.gif similarity index 100% rename from build/src/img/dot.gif rename to build/img/dot.gif diff --git a/build/src/img/loader.gif b/build/img/loader.gif similarity index 100% rename from build/src/img/loader.gif rename to build/img/loader.gif diff --git a/build/index.html b/build/index.html index 3bf27e9..f4fe771 100644 --- a/build/index.html +++ b/build/index.html @@ -4,7 +4,7 @@ Open evolver - + @@ -18,7 +18,7 @@
- +
  • @@ -224,18 +224,29 @@

    Save evolution log

    -

    Powered by three.js

    -
    +
    - - - - - + + \ No newline at end of file diff --git a/build/js/calc.js b/build/js/calc.js new file mode 100644 index 0000000..1363f3c --- /dev/null +++ b/build/js/calc.js @@ -0,0 +1,439 @@ +"use strict"; + +let structure = null, + tightBondCount = 0; + +let atomicMasses = {}; + +let xhr = new XMLHttpRequest(); +xhr.open("GET", "../lib.json", true); +xhr.addEventListener("load", () => { + if (xhr.status === 200) { + let lib = JSON.parse(xhr.responseText); + atomicMasses = lib.atomicMasses; + self.postMessage({method: "ready"}); + } +}); +xhr.send(null); + + +let api = { + setStructure(data) { + let j = 0; + // Move all existing extra-bonds to the end of a bond array. + // This allows to speed up iterations through bonds of extra-graph + for (let i = 0, bonds = data.bonds, bondCount = bonds.length; i < bondCount; i++) { + if (bonds[j].type === "x") { + bonds.push(bonds.splice(j, 1)[0]); + } else { + j++; + } + } + for (let [pair, params] of data.potentials) { + params.b = core.stiffness(params.w0, params.D0, core.reducedMass(pair)); + } + tightBondCount = j; + structure = data; + return structure; + }, + + updateStructure() { + self.postMessage({method: "updateStructure", data: structure}); + }, + + totalEnergy() { + return core.totalEnergy(); + }, + + gradient() { + grad.alloc(); + core.gradient(); + grad.dispose(); + return core.norm; + }, + + evolve(data) { + core.evolveParams = data; + core.evolve(); + this.updateStructure(); + return {energy: core.totalEnergy(), norm: core.norm}; + }, + + reconnectPairs({pair, cutoff}) { + let [el1, el2] = pair.match(/[A-Z][^A-Z]*/g), + cutoff2 = cutoff * cutoff, + {atoms, bonds} = structure; + for (let i = 0, aLen = atoms.length; i < aLen; i++) { + let jEl; + if (atoms[i].el === el1) { + jEl = el2; + } else if (atoms[i].el === el2) { + jEl = el1; + } else { + continue; + } + for (let j = i + 1; j < aLen; j++) { + if (atoms[j].el === jEl) { + let k, bond, bLen; + for (k = tightBondCount, bond = bonds[k], bLen = bonds.length; k < bLen; bond = bonds[++k]) { + if ((bond.iAtm === i && bond.jAtm === j) || (bond.iAtm === j && bond.jAtm === i)) { + break; + } + } + if (core.sqrDistance(i, j) > cutoff2) { + if (bond) { + bonds.splice(k, 1); // break x-bond, as the distance is greater than cutoff + } + } else if (!bond) { + bonds.push({iAtm: i, jAtm: j, type: "x"}); // create x-bond, as one doesn't exist yet + } + } + } + } + this.updateStructure(); + }, + + collectStats() { + let {atoms, bonds} = structure, + data = {}; + + data.name = structure.name; + data.atomCount = atoms.length; + data.atoms = new Map(); + for (let {el} of atoms) { + let count = data.atoms.get(el); + data.atoms.set(el, count ? count + 1 : 1); + } + + data.bondCount = bonds.length; + data.bonds = new Map(); + for (let {type, iAtm, jAtm, potential} of bonds) { + let prefix = (type === "x") ? "x-" : ""; + let pair = prefix + atoms[jAtm].el + atoms[iAtm].el; + if (!data.bonds.has(pair)) { + pair = prefix + atoms[iAtm].el + atoms[jAtm].el; + if (!data.bonds.has(pair)) { + data.bonds.set(pair, {count: 0, avgLen: 0, avgEnergy: 0, totEnergy: 0}); + } + } + let distance = core.distance(iAtm, jAtm); + let bondData = data.bonds.get(pair); + bondData.count++; + bondData.avgLen += distance; + bondData.totEnergy += core.morse(potential, distance); + } + for (let [, bondData] of data.bonds) { + bondData.avgLen /= bondData.count; + bondData.avgEnergy = bondData.totEnergy / bondData.count; + } + + data.potentials = structure.potentials; + data.totalEnergy = core.totalEnergy(); + return data; + } +}; + +self.onmessage = function ({data: {method, data} = {}}) { + if (typeof api[method] === "function") { + self.postMessage({ + method, + data: api[method](data) + }); + } +}; + + +let grad = { + alloc() { + let atomCount = structure.atoms.length; + this.x = new Float32Array(atomCount); + this.y = new Float32Array(atomCount); + this.z = new Float32Array(atomCount); + }, + + dispose() { + this.x = this.y = this.z = null; + } +}; +let rndGrad = Object.assign({}, grad); + + +let log = { + alloc(size) { + this.data = { + E: new Float32Array(size), + grad: new Float32Array(size), + dt: new Float32Array(size) + }; + }, + + dispose() { + this.data = null; + }, + + write(index) { + let data = this.data; + data.E[index] = core.totalEnergy(); + data.grad[index] = core.norm; + data.dt[index] = core.timeStep(); + } +}; + + +let core = { + /** + * Calculate reduced mass for a given pair of atoms + * @param {String} pair A pair of element labels, e.g. "ZnO". + * Extra-graph pairs ("x-" prefixed) are also acceptable. + * @returns {Number} + */ + reducedMass(pair) { + let elements = pair.match(/[A-Z][^A-Z]*/g); + if (!elements || elements.length < 2) { + throw new Error(`Cannot extract element labels from string ${pair}`); + } + const mass1 = atomicMasses[elements[0]]; + const mass2 = atomicMasses[elements[1]]; + return mass1 * mass2 / (mass1 + mass2); + }, + + stiffness(w0, D0, m) { + // b{1/Å} = w0{1/cm} * 2*pi*c{cm/s} * sqrt[µ{a.m.u.}*1.6605655E-27 / (2*D0{eV}*1.6021892E-19)] / 1E+10 + // i.e. + // b{1/Å} = w0{1/cm} * sqrt[µ{a.m.u.} / D0{eV}] * 1.3559906E-3 + return w0 * Math.sqrt(m / D0) * 1.3559906E-3; + }, + + // In intensive calculations use this method for comparisons rather than `core.distance` + sqrDistance(atom1, atom2) { + let at1 = structure.atoms[atom1], + at2 = structure.atoms[atom2], + dx = at1.x - at2.x, + dy = at1.y - at2.y, + dz = at1.z - at2.z; + return dx * dx + dy * dy + dz * dz; + }, + + distance(atom1, atom2) { + return Math.sqrt(this.sqrDistance(atom1, atom2)); + }, + + morse({D0, R0, b}, distance) { + let exponent = Math.exp(b * (R0 - distance)); + return D0 * exponent * (exponent - 2); + }, + + derivative({D0, R0, b}, distance) { + let cA = D0 * Math.exp(2 * b * R0), + cB = -2 * b, + cC = -2 * Math.sqrt(D0 * cA), + cD = Math.exp(-b * distance); + return cB * cD * (cA * cD + 0.5 * cC); + }, + + gradComponent(atom1, atom2, bond) { + let distance = this.distance(atom1, atom2), + factor = this.derivative(structure.bonds[bond].potential, distance) / distance, + at1 = structure.atoms[atom1], + at2 = structure.atoms[atom2]; + return { + x: factor * (at1.x - at2.x), + y: factor * (at1.y - at2.y), + z: factor * (at1.z - at2.z) + }; + }, + + totalEnergy() { + let energy = 0; + for (let {potential, iAtm, jAtm} of structure.bonds) { + energy += this.morse(potential, this.distance(iAtm, jAtm)); + } + return energy; + }, + + gradient() { + let {atoms, bonds} = structure, + atomCount = atoms.length, + bondCount = bonds.length; + + this.norm = this.sumSqr = this.rootSumSqr = 0; + + for (let i = 0; i < atomCount; i++) { + grad.x[i] = grad.y[i] = grad.z[i] = 0; + for (let b = 0; b < bondCount; b++) { + let j; + if (bonds[b].iAtm === i) { + j = bonds[b].jAtm; + } else if (bonds[b].jAtm === i) { + j = bonds[b].iAtm; + } else { + continue; + } + let gradComponent = this.gradComponent(i, j, b); + grad.x[i] += gradComponent.x; + grad.y[i] += gradComponent.y; + grad.z[i] += gradComponent.z; + } + + let sqrForce = grad.x[i] * grad.x[i] + grad.y[i] * grad.y[i] + grad.z[i] * grad.z[i]; + let mass = atomicMasses[atoms[i].el]; + this.sumSqr += sqrForce / mass; + this.rootSumSqr += sqrForce / (mass * mass); + this.norm += sqrForce; + } + + this.rootSumSqr = Math.sqrt(this.rootSumSqr); + this.norm = Math.sqrt(this.norm); + + // Calc unit vector of internal gradient + let invNorm = 1 / this.norm; + for (let i = 0; i < atomCount; i++) { + grad.x[i] *= invNorm; + grad.y[i] *= invNorm; + grad.z[i] *= invNorm; + } + + return this.norm; + }, + + stochGradient() { + let {atoms, bonds} = structure, + atomCount = atoms.length, + bondCount = bonds.length; + + let rndNorm = this.norm = this.sumSqr = this.rootSumSqr = 0; + + for (let i = 0; i < atomCount; i++) { + grad.x[i] = grad.y[i] = grad.z[i] = 0; + for (let b = 0; b < bondCount; b++) { + let j; + if (bonds[b].iAtm === i) { + j = bonds[b].jAtm; + } else if (bonds[b].jAtm === i) { + j = bonds[b].iAtm; + } else { + continue; + } + let gradComponent = this.gradComponent(i, j, b); + grad.x[i] += gradComponent.x; + grad.y[i] += gradComponent.y; + grad.z[i] += gradComponent.z; + } + + let sqrForce = grad.x[i] * grad.x[i] + grad.y[i] * grad.y[i] + grad.z[i] * grad.z[i]; + let mass = atomicMasses[atoms[i].el]; + this.sumSqr += sqrForce / mass; + this.rootSumSqr += sqrForce / (mass * mass); + this.norm += sqrForce; + + rndGrad.x[i] = 50 - Math.random() * 100; + rndGrad.y[i] = 50 - Math.random() * 100; + rndGrad.z[i] = 50 - Math.random() * 100; + rndNorm += rndGrad.x[i] * rndGrad.x[i] + rndGrad.y[i] * rndGrad.y[i] + rndGrad.z[i] * rndGrad.z[i]; + } + + this.rootSumSqr = Math.sqrt(this.rootSumSqr); + this.norm = Math.sqrt(this.norm); + rndNorm = Math.sqrt(rndNorm); + let rsltNorm = 0; + + // Calc unit vectors of internal and external gradient as well as resulting gradient + let invNorm = 1 / this.norm; + let invRndNorm = 1 / rndNorm; + for (let i = 0; i < atomCount; i++) { + grad.x[i] *= invNorm; + grad.y[i] *= invNorm; + grad.z[i] *= invNorm; + rndGrad.x[i] *= invRndNorm; + rndGrad.y[i] *= invRndNorm; + rndGrad.z[i] *= invRndNorm; + grad.x[i] += rndGrad.x[i]; + grad.y[i] += rndGrad.y[i]; + grad.z[i] += rndGrad.z[i]; + rsltNorm += grad.x[i] * grad.x[i] + grad.y[i] * grad.y[i] + grad.z[i] * grad.z[i]; + } + + rsltNorm = Math.sqrt(rsltNorm); + + // Calc unit vector of resulting gradient + invNorm = 1 / rsltNorm; + for (let i = 0; i < atomCount; i++) { + grad.x[i] *= invNorm; + grad.y[i] *= invNorm; + grad.z[i] *= invNorm; + } + + return this.norm; + }, + + timeStep() { + // dt=sqrt(3NkT/sumSqr); [sumSqr]=eV^2/(angst^2*amu) + return 1.636886e-16 * Math.sqrt(structure.atoms.length * this.evolveParams.temperature / this.sumSqr); + }, + + tuneEvolver() { + let params = this.evolveParams, + initFns = [], // functions to be called before the evolution procedure + stepFns = [], // functions to be called at every evolution step + finFns = []; // functions to be called after the evolution procedure is finished + initFns.push(grad.alloc.bind(grad)); + stepFns.push(params.stoch ? this.stochGradient.bind(this) : this.gradient.bind(this)); + finFns.push(grad.dispose.bind(grad)); + if (params.stoch) { + initFns.push(rndGrad.alloc.bind(rndGrad)); + finFns.push(rndGrad.dispose.bind(rndGrad)); + } + let logInterval = params.logInterval; + if (logInterval) { + initFns.push(log.alloc.bind(log, Math.floor(params.stepCount / logInterval))); + stepFns.push(stepNo => { + let index = stepNo / logInterval; + if (Number.isInteger(index)) { + log.write(index); + } + }); + finFns.push(() => { + self.postMessage({method: "evolve:log", data: log.data}); + log.dispose(); + }); + } + return { + initialize() { + initFns.forEach(fn => fn()); + }, + step(stepNo) { + stepFns.forEach(fn => fn(stepNo)); + }, + finalize() { + finFns.forEach(fn => fn()); + } + }; + }, + + evolve() { + let params = this.evolveParams, + functor = this.tuneEvolver(), + atoms = structure.atoms, + atomCount = atoms.length, + factor = 1.2926E-4 * atomCount * params.temperature, // 1.5NkT [eV] + interval = Math.ceil(params.stepCount / 100), + progressFactor = 100 / params.stepCount, + progressMsg = {method: "evolve:progress", data: 0}; + functor.initialize(); + functor.step(); // pre-calculate current value of gradient before the 1st step + for (let stepNo = 0, stepCount = params.stepCount; stepNo < stepCount; stepNo++) { + let step = factor * this.rootSumSqr / this.sumSqr; + for (let i = 0; i < atomCount; i++) { + atoms[i].x -= step * grad.x[i]; + atoms[i].y -= step * grad.y[i]; + atoms[i].z -= step * grad.z[i]; + } + if (stepNo % interval === 0) { + progressMsg.data = stepNo * progressFactor; + self.postMessage(progressMsg); + } + functor.step(stepNo); + } + functor.finalize(); + } +}; \ No newline at end of file diff --git a/build/js/interface.amd.js b/build/js/interface.amd.js new file mode 100644 index 0000000..9b2f7f1 --- /dev/null +++ b/build/js/interface.amd.js @@ -0,0 +1,1959 @@ +"bundle"; +(function() { +var define = System.amdDefine; +define("components/store.amd.js", ["exports", "jquery", "./abstract-dialog.amd.js", "../app.amd.js", "../templates.amd.js"], function(exports, _jquery, _abstractDialogAmd, _appAmd, _templatesAmd) { + "use strict"; + Object.defineProperty(exports, "__esModule", {value: true}); + var _jquery2 = _interopRequireDefault(_jquery); + var _abstractDialogAmd2 = _interopRequireDefault(_abstractDialogAmd); + var _appAmd2 = _interopRequireDefault(_appAmd); + var _templatesAmd2 = _interopRequireDefault(_templatesAmd); + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : {default: obj}; + } + let store = Object.assign(new _abstractDialogAmd2.default(".oe-store-form"), { + handleSelect(e) { + (0, _jquery2.default)(e.delegateTarget).children(".active").removeClass("active"); + (0, _jquery2.default)(e.currentTarget).addClass("active"); + }, + show() { + if (!this.loaded) { + let $list = this.$el.find(".oe-store-list"); + $list.addClass("oe-store-list-loading"); + _jquery2.default.getJSON("../store/info.json").done((data) => this.resetHTML(data)).fail(() => this.resetHTML()).always(() => $list.removeClass("oe-store-list-loading")); + this.loaded = true; + } + return Object.getPrototypeOf(this).show.apply(this, arguments); + }, + apply() { + let path = this.$el.find(".active[data-path]").data("path"); + if (path) { + _appAmd2.default.execAction("load", `../store/${path}`); + } + }, + resetHTML(data) { + let hasData = data && data.length, + html = hasData ? _templatesAmd2.default.get("store")({records: data}) : "
  • Data is empty or couldn't be loaded
  • "; + this.$el.find(".oe-store-list").html(html); + this.$el.find(".oe-apply").prop("disabled", !hasData); + } + }); + store.listen([{ + type: "click", + owner: ".oe-store-list", + filter: "li[data-path]", + handler: "handleSelect" + }, { + type: "dblclick", + owner: ".oe-store-list", + filter: "li[data-path]", + handler: "handleApply" + }]); + exports.default = store; + _appAmd2.default.addAction("openStore", { + get enabled() { + return true; + }, + exec() { + store.show(); + } + }); +}); + +})(); +(function() { +var define = System.amdDefine; +define("components/save.amd.js", ["exports", "jquery", "./abstract-dialog.amd.js", "../file-processing.amd.js", "../app.amd.js", "../structure.amd.js", "../utils.amd.js"], function(exports, _jquery, _abstractDialogAmd, _fileProcessingAmd, _appAmd, _structureAmd, _utilsAmd) { + "use strict"; + Object.defineProperty(exports, "__esModule", {value: true}); + var _jquery2 = _interopRequireDefault(_jquery); + var _abstractDialogAmd2 = _interopRequireDefault(_abstractDialogAmd); + var _fileProcessingAmd2 = _interopRequireDefault(_fileProcessingAmd); + var _appAmd2 = _interopRequireDefault(_appAmd); + var _structureAmd2 = _interopRequireDefault(_structureAmd); + var _utilsAmd2 = _interopRequireDefault(_utilsAmd); + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : {default: obj}; + } + let save = Object.assign(new _abstractDialogAmd2.default(".oe-save-form"), { + handleTypeChange(e) { + this.$el.find(".type-description").addClass("hidden").filter(`[data-type="${e.target.value}"]`).removeClass("hidden"); + }, + handleSave(e) { + let selected = (0, _jquery2.default)("#oe-file-type").find("option:selected"), + type = selected.closest("optgroup").data("type"), + graphType = selected.data("graph"), + file = _fileProcessingAmd2.default.makeFile(type, graphType); + if (file) { + e.target.setAttribute("download", `untitled.${type}`); + e.target.href = _utilsAmd2.default.getBlobURL(file); + } + } + }); + save.listen([{ + type: "change", + owner: "#oe-file-type", + handler: "handleTypeChange" + }, { + type: "click", + filter: ".oe-apply", + handler: "handleSave" + }]); + exports.default = save; + _appAmd2.default.addAction("save", { + get enabled() { + return _structureAmd2.default.structure.atoms.length > 0; + }, + exec() { + save.show(); + } + }); +}); + +})(); +(function() { +var define = System.amdDefine; +define("components/save-summary.amd.js", ["exports", "./abstract-dialog.amd.js", "../worker.amd.js", "../utils.amd.js", "../app.amd.js", "../structure.amd.js", "../templates.amd.js"], function(exports, _abstractDialogAmd, _workerAmd, _utilsAmd, _appAmd, _structureAmd, _templatesAmd) { + "use strict"; + Object.defineProperty(exports, "__esModule", {value: true}); + var _abstractDialogAmd2 = _interopRequireDefault(_abstractDialogAmd); + var _workerAmd2 = _interopRequireDefault(_workerAmd); + var _utilsAmd2 = _interopRequireDefault(_utilsAmd); + var _appAmd2 = _interopRequireDefault(_appAmd); + var _structureAmd2 = _interopRequireDefault(_structureAmd); + var _templatesAmd2 = _interopRequireDefault(_templatesAmd); + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : {default: obj}; + } + function nodeVisitor(name, node) { + if (node instanceof Map) { + let result = Object.create(null); + for (let [key, value] of node) { + result[key] = value; + } + return result; + } + return node; + } + let saveSummary = Object.assign(new _abstractDialogAmd2.default(".oe-save-summary-form"), { + data: null, + handleCollectStats(data) { + this.data = data; + this.show(); + }, + handleSave(e) { + if (this.data) { + let type = e.target.getAttribute("data-type"), + text; + switch (type) { + case "text/html": + text = _templatesAmd2.default.get("summary")(this.data); + break; + case "application/json": + text = JSON.stringify(this.data, nodeVisitor, 2); + break; + default: + text = "TBD"; + break; + } + e.target.href = _utilsAmd2.default.getBlobURL(text, type); + this.hide(); + } + } + }); + saveSummary.listen([{ + type: "collectStats", + owner: _workerAmd2.default, + handler: "handleCollectStats" + }, { + type: "click", + filter: "a[download]", + handler: "handleSave" + }]); + exports.default = saveSummary; + _appAmd2.default.addAction("saveSummary", { + get enabled() { + return _structureAmd2.default.structure.potentials.size > 0; + }, + exec() { + _workerAmd2.default.invoke("collectStats"); + } + }); +}); + +})(); +(function() { +var define = System.amdDefine; +define("components/graph.amd.js", ["exports", "jquery", "./abstract-dialog.amd.js", "../structure.amd.js", "../worker.amd.js", "../app.amd.js", "../templates.amd.js"], function(exports, _jquery, _abstractDialogAmd, _structureAmd, _workerAmd, _appAmd, _templatesAmd) { + "use strict"; + Object.defineProperty(exports, "__esModule", {value: true}); + var _jquery2 = _interopRequireDefault(_jquery); + var _abstractDialogAmd2 = _interopRequireDefault(_abstractDialogAmd); + var _structureAmd2 = _interopRequireDefault(_structureAmd); + var _workerAmd2 = _interopRequireDefault(_workerAmd); + var _appAmd2 = _interopRequireDefault(_appAmd); + var _templatesAmd2 = _interopRequireDefault(_templatesAmd); + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : {default: obj}; + } + let graph = Object.assign(new _abstractDialogAmd2.default(".oe-graph-form"), { + handleUpdateStructure(pairsUpdated) { + if (pairsUpdated) { + this.resetHTML(); + } + }, + handlePairSelect(e) { + let $target = (0, _jquery2.default)(e.target); + (0, _jquery2.default)(e.delegateTarget).find(".oe-cutoff").not($target).removeClass("active"); + $target.addClass("active"); + let cutoff = $target.text().trim(); + (0, _jquery2.default)("#oe-cutoff-exact").val(cutoff).get(0).select(); + (0, _jquery2.default)(".oe-cutoff-slider").val(this.cutoff2Slider(+cutoff).toFixed(2)); + }, + handleSliderChange(e) { + let cutoff = this.slider2Cutoff(+e.target.value); + (0, _jquery2.default)("#oe-cutoff-exact").val(cutoff.toFixed(4)).trigger("input"); + this.updateGraph((0, _jquery2.default)(".oe-cutoff.active").data("pair"), cutoff); + }, + handleCutoffInput(e) { + (0, _jquery2.default)(".oe-cutoff.active").text(e.target.value); + }, + handleCutoffChange(e) { + if (e.target.checkValidity()) { + (0, _jquery2.default)(".oe-cutoff-slider").val(this.cutoff2Slider(+e.target.value).toFixed(2)); + this.updateGraph((0, _jquery2.default)(".oe-cutoff.active").data("pair"), +e.target.value); + } + }, + cutoff2Slider(cutoff) { + let slider = (0, _jquery2.default)(".oe-cutoff-slider")[0], + minBound = +(0, _jquery2.default)("#oe-cutoff-min").val(), + maxBound = +(0, _jquery2.default)("#oe-cutoff-max").val(), + min = +slider.min, + max = +slider.max; + return min + (cutoff - minBound) * (max - min) / (maxBound - minBound); + }, + slider2Cutoff(value) { + let slider = (0, _jquery2.default)(".oe-cutoff-slider")[0], + minBound = +(0, _jquery2.default)("#oe-cutoff-min").val(), + maxBound = +(0, _jquery2.default)("#oe-cutoff-max").val(), + min = +slider.min, + max = +slider.max; + return minBound + (value - min) * (maxBound - minBound) / (max - min); + }, + resetHTML() { + this.$el.find(".oe-cutoffs").html(_templatesAmd2.default.get("cutoffs")({pairs: _structureAmd2.default.getPairList("basic")})).find(".oe-cutoff").eq(0).addClass("active"); + }, + updateGraph(pair, cutoff) { + _workerAmd2.default.invoke("reconnectPairs", { + pair, + cutoff + }); + } + }); + graph.listen([{ + type: "updateStructure", + owner: _structureAmd2.default, + handler: "handleUpdateStructure" + }, { + type: "click", + filter: ".oe-cutoffs .oe-cutoff", + handler: "handlePairSelect" + }, { + type: "change", + filter: ".oe-cutoff-slider", + handler: "handleSliderChange" + }, { + type: "input", + owner: "#oe-cutoff-exact", + handler: "handleCutoffInput" + }, { + type: "change", + owner: "#oe-cutoff-exact", + handler: "handleCutoffChange" + }]); + exports.default = graph; + _appAmd2.default.addAction("alterGraph", { + get enabled() { + return _structureAmd2.default.structure.atoms.length > 0; + }, + exec() { + graph.show(); + } + }); +}); + +})(); +(function() { +var define = System.amdDefine; +define("components/potentials.amd.js", ["exports", "jquery", "./abstract-dialog.amd.js", "../structure.amd.js", "../utils.amd.js", "../app.amd.js", "../templates.amd.js"], function(exports, _jquery, _abstractDialogAmd, _structureAmd, _utilsAmd, _appAmd, _templatesAmd) { + "use strict"; + Object.defineProperty(exports, "__esModule", {value: true}); + var _jquery2 = _interopRequireDefault(_jquery); + var _abstractDialogAmd2 = _interopRequireDefault(_abstractDialogAmd); + var _structureAmd2 = _interopRequireDefault(_structureAmd); + var _utilsAmd2 = _interopRequireDefault(_utilsAmd); + var _appAmd2 = _interopRequireDefault(_appAmd); + var _templatesAmd2 = _interopRequireDefault(_templatesAmd); + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : {default: obj}; + } + let potentials = Object.assign(new _abstractDialogAmd2.default(".oe-potential-form"), { + handleUpdateStructure(pairsUpdated) { + if (pairsUpdated) { + this.resetHTML(); + } + }, + handleLoad(e) { + _utilsAmd2.default.readFile(e.target.files[0]).then((contents) => { + let rows = contents.split(/\r?\n/); + for (let row of rows) { + let params = row.split("\t"); + this.$el.find(`li[data-pair="${params[0]}"] input`).val((idx) => params[idx + 1] || ""); + } + }); + }, + handleSave(e) { + let text = this.$el.find("li[data-pair]").map((idx, row) => { + let $row = (0, _jquery2.default)(row); + return $row.data("pair") + "\t" + $row.find("input").map((idx, input) => input.value).get().join("\t"); + }).get().join("\n"); + e.target.href = _utilsAmd2.default.getBlobURL(text); + }, + handleApply() { + if (this.$el[0].checkValidity()) { + return Object.getPrototypeOf(this).handleApply.apply(this, arguments); + } else { + window.alert("Please, fix invalid input first"); + } + }, + resetHTML() { + this.$el.find("ul.oe-potentials").html(_templatesAmd2.default.get("potentials")({pairs: _structureAmd2.default.getPairList()})); + }, + apply() { + let potentials = new Map(); + this.$el.find("li[data-pair]").each((idx, row) => { + let params = {}; + row = (0, _jquery2.default)(row); + row.find("input[data-param]").each((idx, el) => { + if (el.value) { + params[(0, _jquery2.default)(el).data("param")] = +el.value; + } else { + return params = false; + } + }); + if (params) { + potentials.set(row.data("pair"), params); + } + }); + _structureAmd2.default.setPotentials(potentials); + this.fix(); + }, + discard() { + this.reset(); + }, + show() { + let atoms = _structureAmd2.default.structure.atoms, + pairs = new Set(); + for (let {type, + iAtm, + jAtm} of _structureAmd2.default.structure.bonds) { + let prefix = type === "x" ? "x-" : ""; + let el1 = atoms[iAtm].el; + let el2 = atoms[jAtm].el; + pairs.add(prefix + el1 + el2).add(prefix + el2 + el1); + } + this.$el.find("li[data-pair]").each((idx, row) => { + row = (0, _jquery2.default)(row); + row.toggleClass("missed", !pairs.has(row.data("pair"))); + }); + return Object.getPrototypeOf(this).show.apply(this, arguments); + } + }); + potentials.listen([{ + type: "updateStructure", + owner: _structureAmd2.default, + handler: "handleUpdateStructure" + }, { + type: "change", + filter: ".load-potentials", + handler: "handleLoad" + }, { + type: "click", + filter: ".save-potentials", + handler: "handleSave" + }]); + exports.default = potentials; + _appAmd2.default.addAction("setup", { + get enabled() { + return _structureAmd2.default.structure.atoms.length > 0; + }, + exec() { + potentials.show(); + } + }); +}); + +})(); +(function() { +var define = System.amdDefine; +define("components/transform.amd.js", ["exports", "jquery", "./abstract-dialog.amd.js", "../app.amd.js", "../structure.amd.js", "../draw.amd.js"], function(exports, _jquery, _abstractDialogAmd, _appAmd, _structureAmd, _drawAmd) { + "use strict"; + Object.defineProperty(exports, "__esModule", {value: true}); + var _jquery2 = _interopRequireDefault(_jquery); + var _abstractDialogAmd2 = _interopRequireDefault(_abstractDialogAmd); + var _appAmd2 = _interopRequireDefault(_appAmd); + var _structureAmd2 = _interopRequireDefault(_structureAmd); + var _drawAmd2 = _interopRequireDefault(_drawAmd); + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : {default: obj}; + } + let transform = Object.assign(new _abstractDialogAmd2.default(".oe-transform-form"), { + handleAppStateChange(busy) { + this.$el.find("fieldset").prop("disabled", busy); + }, + handleTranslate() { + let $fieldSet = this.$el.find(".oe-translate"), + x = +$fieldSet.find("[data-axis='x']").val(), + y = +$fieldSet.find("[data-axis='y']").val(), + z = +$fieldSet.find("[data-axis='z']").val(); + _structureAmd2.default.translate(x, y, z); + }, + handleRotate(e) { + let angle = (0, _jquery2.default)("#oe-rotate-angle").val() * Math.PI / 180, + axis = e.target.getAttribute("data-axis"); + _structureAmd2.default.rotate(angle, axis); + }, + show() { + let center = _structureAmd2.default.getCenterOfMass(); + let $fields = this.$el.find(".oe-translate input[data-axis]"); + $fields.val((idx) => center[$fields.eq(idx).data("axis")].toFixed(5)); + _drawAmd2.default.addAxes(); + return Object.getPrototypeOf(this).show.apply(this, arguments); + }, + hide() { + _drawAmd2.default.removeAxes(); + return Object.getPrototypeOf(this).hide.apply(this, arguments); + } + }); + transform.listen([{ + type: "app:stateChange", + owner: _appAmd2.default, + handler: "handleAppStateChange" + }, { + type: "click", + owner: "#oe-translate-apply", + handler: "handleTranslate" + }, { + type: "click", + filter: ".oe-rotate [data-axis]", + handler: "handleRotate" + }]); + exports.default = transform; + _appAmd2.default.addAction("transform", { + get enabled() { + return _structureAmd2.default.structure.atoms.length > 0; + }, + exec() { + transform.show(); + } + }); +}); + +})(); +(function() { +var define = System.amdDefine; +define("components/report.amd.js", ["exports", "jquery", "./abstract-dialog.amd.js", "../app.amd.js", "../worker.amd.js", "../templates.amd.js"], function(exports, _jquery, _abstractDialogAmd, _appAmd, _workerAmd, _templatesAmd) { + "use strict"; + Object.defineProperty(exports, "__esModule", {value: true}); + var _jquery2 = _interopRequireDefault(_jquery); + var _abstractDialogAmd2 = _interopRequireDefault(_abstractDialogAmd); + var _appAmd2 = _interopRequireDefault(_appAmd); + var _workerAmd2 = _interopRequireDefault(_workerAmd); + var _templatesAmd2 = _interopRequireDefault(_templatesAmd); + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : {default: obj}; + } + let report = Object.assign(new _abstractDialogAmd2.default(".oe-report"), { + handleGlobalKeyUp: _jquery2.default.noop, + print(data) { + this.updateProgress(100); + (0, _jquery2.default)("#oe-report-data").html(_templatesAmd2.default.get("report")({ + energy: data.energy, + grad: data.norm + })); + }, + updateProgress(value) { + (0, _jquery2.default)("#oe-report-progress").attr("value", value); + } + }); + report.listen([{ + type: "app:structure:loaded", + owner: _appAmd2.default, + handler: "hide" + }, { + type: "evolve:progress", + owner: _workerAmd2.default, + handler: "updateProgress" + }]); + exports.default = report; +}); + +})(); +(function() { +var define = System.amdDefine; +define("components/evolve.amd.js", ["exports", "jquery", "./abstract-dialog.amd.js", "../worker.amd.js", "./report.amd.js", "../app.amd.js", "../structure.amd.js"], function(exports, _jquery, _abstractDialogAmd, _workerAmd, _reportAmd, _appAmd, _structureAmd) { + "use strict"; + Object.defineProperty(exports, "__esModule", {value: true}); + var _jquery2 = _interopRequireDefault(_jquery); + var _abstractDialogAmd2 = _interopRequireDefault(_abstractDialogAmd); + var _workerAmd2 = _interopRequireDefault(_workerAmd); + var _reportAmd2 = _interopRequireDefault(_reportAmd); + var _appAmd2 = _interopRequireDefault(_appAmd); + var _structureAmd2 = _interopRequireDefault(_structureAmd); + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : {default: obj}; + } + let evolve = Object.assign(new _abstractDialogAmd2.default(".oe-evolve-form"), { + handleEvolveStop(data) { + _reportAmd2.default.print(data); + }, + handleApply() { + if (this.$el[0].checkValidity()) { + return Object.getPrototypeOf(this).handleApply.apply(this, arguments); + } else { + window.alert("Please, fix invalid input first"); + } + }, + handleKeepLogChange(e) { + (0, _jquery2.default)("#oe-log-interval").prop("disabled", !e.target.checked).val("0"); + }, + apply() { + this.fix(); + _workerAmd2.default.invoke("evolve", { + stepCount: +(0, _jquery2.default)("#oe-step-count").val(), + temperature: +(0, _jquery2.default)("#oe-temperature").val(), + stoch: (0, _jquery2.default)("#oe-stoch").prop("checked"), + logInterval: +(0, _jquery2.default)("#oe-log-interval").val() + }); + _reportAmd2.default.show(); + }, + discard() { + this.reset(); + } + }); + evolve.listen([{ + type: "evolve", + owner: _workerAmd2.default, + handler: "handleEvolveStop" + }, { + type: "change", + owner: "#oe-keep-log", + handler: "handleKeepLogChange" + }]); + exports.default = evolve; + _appAmd2.default.addAction("evolve", { + get enabled() { + return _structureAmd2.default.structure.potentials.size > 0; + }, + exec() { + evolve.show(); + } + }); +}); + +})(); +(function() { +var define = System.amdDefine; +define("components/save-log.amd.js", ["exports", "./abstract-dialog.amd.js", "../worker.amd.js", "../utils.amd.js"], function(exports, _abstractDialogAmd, _workerAmd, _utilsAmd) { + "use strict"; + Object.defineProperty(exports, "__esModule", {value: true}); + var _abstractDialogAmd2 = _interopRequireDefault(_abstractDialogAmd); + var _workerAmd2 = _interopRequireDefault(_workerAmd); + var _utilsAmd2 = _interopRequireDefault(_utilsAmd); + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : {default: obj}; + } + let saveLog = Object.assign(new _abstractDialogAmd2.default(".oe-save-log-dialog"), { + handleEvolveLog(data) { + this.data = data; + this.show(); + }, + handleSave(e) { + let data = this.data, + E = data.E, + grad = data.grad, + dt = data.dt, + t = 0, + dl = e.target.getAttribute("data-delimiter"), + log = `t, ps${dl}E, eV${dl}||grad E||, eV/Å${dl}dt, fs`; + for (let i = 0, + len = E.length; i < len; i++) { + log += "\n" + t.toExponential(4) + dl + E[i].toExponential(4) + dl + grad[i].toExponential(4) + dl + (dt[i] * 1E15).toExponential(4); + t += dt[i] * 1E12; + } + e.target.href = _utilsAmd2.default.getBlobURL(log); + this.hide(); + this.data = null; + } + }); + saveLog.listen([{ + type: "evolve:log", + owner: _workerAmd2.default, + handler: "handleEvolveLog" + }, { + type: "click", + filter: "a[download]", + handler: "handleSave" + }]); + exports.default = saveLog; +}); + +})(); +(function() { +var define = System.amdDefine; +define("components/appearance.amd.js", ["exports", "jquery", "./abstract-dialog.amd.js", "../structure.amd.js", "../draw.amd.js", "../app.amd.js"], function(exports, _jquery, _abstractDialogAmd, _structureAmd, _drawAmd, _appAmd) { + "use strict"; + Object.defineProperty(exports, "__esModule", {value: true}); + var _jquery2 = _interopRequireDefault(_jquery); + var _abstractDialogAmd2 = _interopRequireDefault(_abstractDialogAmd); + var _structureAmd2 = _interopRequireDefault(_structureAmd); + var _drawAmd2 = _interopRequireDefault(_drawAmd); + var _appAmd2 = _interopRequireDefault(_appAmd); + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : {default: obj}; + } + let appearance = Object.assign(new _abstractDialogAmd2.default(".oe-appearance-form"), { + handleUpdateStructure(rescanAtoms) { + if (rescanAtoms) { + (0, _jquery2.default)("#oe-appearance-element").html(""); + this.setCurrElementColor(); + } + }, + handleColorChange(e) { + let color = parseInt(e.target.value.slice(1), 16); + if (isNaN(color)) { + return; + } + if (!this.tmpClrPresets) { + this.tmpClrPresets = new Map(); + } + this.tmpClrPresets.set((0, _jquery2.default)("#oe-appearance-element").val(), color); + }, + apply() { + _drawAmd2.default.appearance = this.$el.find("input[name='appearance']:checked").data("appearance"); + _drawAmd2.default.setBgColor((0, _jquery2.default)("#oe-bg-color").val()); + if (this.tmpClrPresets) { + _drawAmd2.default.setAtomColors(this.tmpClrPresets); + this.tmpClrPresets = undefined; + } + _drawAmd2.default.render(); + this.fix(); + }, + discard() { + this.reset(); + this.tmpClrPresets = undefined; + this.setCurrElementColor(); + }, + setCurrElementColor() { + let el = (0, _jquery2.default)("#oe-appearance-element").val(), + color; + if (this.tmpClrPresets && this.tmpClrPresets.has(el)) { + color = this.tmpClrPresets.get(el); + } else { + color = _drawAmd2.default.getAtomColor(el); + } + (0, _jquery2.default)("#oe-appearance-color").val("#" + ("000000" + color.toString(16)).slice(-6)); + } + }); + appearance.listen([{ + type: "updateStructure", + owner: _structureAmd2.default, + handler: "handleUpdateStructure" + }, { + type: "change", + owner: "#oe-appearance-element", + handler: "setCurrElementColor" + }, { + type: "change", + owner: "#oe-appearance-color", + handler: "handleColorChange" + }]); + exports.default = appearance; + _appAmd2.default.addAction("alterView", { + get enabled() { + return _structureAmd2.default.structure.atoms.length > 0; + }, + exec() { + appearance.show(); + } + }); +}); + +})(); +(function() { +var define = System.amdDefine; +define("components/abstract-dialog.amd.js", ["exports", "../eventful.amd.js"], function(exports, _eventfulAmd) { + "use strict"; + Object.defineProperty(exports, "__esModule", {value: true}); + var _eventfulAmd2 = _interopRequireDefault(_eventfulAmd); + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : {default: obj}; + } + let events = [{ + type: "click", + filter: ".oe-apply", + handler(...params) { + this.handleApply(...params); + } + }, { + type: "click", + filter: ".oe-discard", + handler(...params) { + this.handleDiscard(...params); + } + }, { + type: "keyup", + owner: document, + handler(...params) { + this.handleGlobalKeyUp(...params); + } + }]; + exports.default = class extends _eventfulAmd2.default { + constructor($el) { + super($el); + this.listen(events); + } + handleApply() { + this.apply(); + this.hide(); + } + handleDiscard() { + this.discard(); + this.hide(); + } + handleGlobalKeyUp(e) { + if (e.which === 27) { + this.discard(); + this.hide(); + } + } + apply() {} + discard() {} + show() { + this.$el.removeClass("hidden"); + } + hide() { + this.$el.addClass("hidden"); + } + fix(fields) { + if (!fields) { + fields = this.$el[0].elements; + } + for (let field of Array.from(fields)) { + if (field.type === "checkbox" || field.type === "radio") { + field.defaultChecked = field.checked; + } else if (field.nodeName.toUpperCase() === "OPTION") { + field.defaultSelected = field.selected; + } else if ("defaultValue" in field) { + field.defaultValue = field.value; + } else if (field.options) { + this.fix(field.options); + } + } + } + reset() { + this.$el[0].reset(); + } + }; +}); + +})(); +(function() { +var define = System.amdDefine; +define("templates.amd.js", ["exports", "./app.amd.js", "./utils.amd.js", "_"], function(exports, _appAmd, _utilsAmd, _2) { + "use strict"; + Object.defineProperty(exports, "__esModule", {value: true}); + var _appAmd2 = _interopRequireDefault(_appAmd); + var _utilsAmd2 = _interopRequireDefault(_utilsAmd); + var _3 = _interopRequireDefault(_2); + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : {default: obj}; + } + let templates = new Map(); + _appAmd2.default.busy = true; + _utilsAmd2.default.readFile("tpl/tpl.json").then((json) => { + let tpls = JSON.parse(json); + let tplSettings = {variable: "data"}; + for (let name of Object.keys(tpls)) { + templates.set(name, _3.default.template(tpls[name], tplSettings)); + } + _appAmd2.default.busy = false; + }); + exports.default = {get: templates.get.bind(templates)}; +}); + +})(); +(function() { +var define = System.amdDefine; +define("components/info.amd.js", ["exports", "./abstract-dialog.amd.js", "../worker.amd.js", "../structure.amd.js", "../app.amd.js", "../templates.amd.js"], function(exports, _abstractDialogAmd, _workerAmd, _structureAmd, _appAmd, _templatesAmd) { + "use strict"; + Object.defineProperty(exports, "__esModule", {value: true}); + var _abstractDialogAmd2 = _interopRequireDefault(_abstractDialogAmd); + var _workerAmd2 = _interopRequireDefault(_workerAmd); + var _structureAmd2 = _interopRequireDefault(_structureAmd); + var _appAmd2 = _interopRequireDefault(_appAmd); + var _templatesAmd2 = _interopRequireDefault(_templatesAmd); + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : {default: obj}; + } + let info = Object.assign(new _abstractDialogAmd2.default(".oe-info-dialog"), { + handleTotalEnergy(data) { + this.applyTpl("energy", { + energy: data, + bonds: _structureAmd2.default.structure.bonds.length + }); + this.show(); + }, + handleGradient(data) { + this.applyTpl("gradient", {grad: data}); + this.show(); + }, + applyTpl(tpl, data) { + this.$el.find(".oe-info-dialog-text").html(_templatesAmd2.default.get(tpl)(data)); + } + }); + info.listen([{ + type: "totalEnergy", + owner: _workerAmd2.default, + handler: "handleTotalEnergy" + }, { + type: "gradient", + owner: _workerAmd2.default, + handler: "handleGradient" + }]); + exports.default = info; + _appAmd2.default.addAction("calcEnergy", { + get enabled() { + return _structureAmd2.default.structure.potentials.size > 0; + }, + exec() { + _workerAmd2.default.invoke("totalEnergy"); + } + }); + _appAmd2.default.addAction("calcGrad", { + get enabled() { + return _structureAmd2.default.structure.potentials.size > 0; + }, + exec() { + _workerAmd2.default.invoke("gradient"); + } + }); +}); + +})(); +(function() { +var define = System.amdDefine; +define("file-processing.amd.js", ["exports", "./utils.amd.js", "./structure.amd.js"], function(exports, _utilsAmd, _structureAmd) { + "use strict"; + Object.defineProperty(exports, "__esModule", {value: true}); + var _utilsAmd2 = _interopRequireDefault(_utilsAmd); + var _structureAmd2 = _interopRequireDefault(_structureAmd); + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : {default: obj}; + } + let formats = {}; + formats.hin = { + parseMolecule(atomRecords, result) { + let {atoms, + bonds} = result, + inc = atoms.length, + spaceRE = /\s+/; + for (let i = 0, + len = atomRecords.length; i < len; i++) { + let items = atomRecords[i].trim().split(spaceRE); + atoms.push({ + el: items[3], + x: +items[7], + y: +items[8], + z: +items[9] + }); + for (let j = 11, + cn = 2 * items[10] + 11; j < cn; j += 2) { + if (items[j] - 1 > i) { + bonds.push({ + iAtm: i + inc, + jAtm: items[j] - 1 + inc, + type: items[j + 1] + }); + } + } + } + }, + parse(fileStr) { + let molRE = /\n\s*mol\s+(\d+)([\s\S]+)\n\s*endmol\s+\1/g, + atmRE = /^atom\s+\d+\s+.+$/gm, + result = { + atoms: [], + bonds: [] + }, + mol = molRE.exec(fileStr); + while (mol) { + this.parseMolecule(mol[2].match(atmRE), result); + mol = molRE.exec(fileStr); + } + return result; + } + }; + formats.ml2 = formats.mol2 = { + parseMolecule(atomRecords, bondRecords, result) { + let {atoms, + bonds} = result, + inc = atoms.length, + spaceRE = /\s+/; + for (let rec of atomRecords) { + let items = rec.trim().split(spaceRE); + let dotPos = items[5].indexOf("."); + atoms.push({ + el: dotPos > -1 ? items[5].slice(0, dotPos) : items[5], + x: +items[2], + y: +items[3], + z: +items[4] + }); + } + for (let rec of bondRecords) { + let items = rec.trim().split(spaceRE); + bonds.push({ + iAtm: items[1] - 1 + inc, + jAtm: items[2] - 1 + inc, + type: items[3] + }); + } + }, + parse(fileStr) { + let result = { + atoms: [], + bonds: [] + }, + molChunks = fileStr.split("@MOLECULE").slice(1), + atomRE = /@ATOM([\s\S]+?)(?:@|$)/, + bondRE = /@BOND([\s\S]+?)(?:@|$)/, + newLineRE = /(?:\r?\n)+/, + noRec = []; + for (let chunk of molChunks) { + let atomSection = chunk.match(atomRE); + let atomRecords = atomSection && atomSection[1].trim().split(newLineRE) || noRec; + let bondSection = chunk.match(bondRE); + let bondRecords = bondSection && bondSection[1].trim().split(newLineRE) || noRec; + this.parseMolecule(atomRecords, bondRecords, result); + } + return result; + } + }; + formats.xyz = { + parseAtomRecord(atomStr) { + let items = atomStr.trim().split(/\s+/); + return { + el: items[0], + x: +items[1], + y: +items[2], + z: +items[3] + }; + }, + parse(fileStr) { + let atomRecords = fileStr.split(/(?:\r?\n)+/).slice(2); + return atomRecords && { + atoms: atomRecords.map(this.parseAtomRecord, this), + bonds: [] + }; + } + }; + exports.default = { + load(fileRef) { + return _utilsAmd2.default.readFile(fileRef).then((contents) => { + let name = fileRef.name || String(fileRef), + type = name.slice(name.lastIndexOf(".") + 1).toLowerCase(), + format = formats[type] || formats.hin, + newStructure = format.parse(contents); + newStructure.name = name.replace(/.*[\/\\]/, "") || "unknown"; + _structureAmd2.default.overwrite(newStructure); + return contents; + }); + }, + makeFile(type, graphType) { + type = type.toUpperCase(); + if (typeof this[`make${type}`] === "function") { + return this[`make${type}`](graphType); + } + return false; + }, + makeHIN(graphType) { + let hin = ";The structure was saved in OpenEvolver\nforcefield mm+\n"; + if (graphType === "empty") { + let i = 0; + for (let {el, + x, + y, + z} of _structureAmd2.default.structure.atoms) { + hin += `mol ${++i} +atom 1 - ${el} ** - 0 ${x.toFixed(4)} ${y.toFixed(4)} ${z.toFixed(4)} 0 +endmol ${i} +`; + } + } else { + let nbors = new Array(_structureAmd2.default.structure.atoms.length); + for (let {type, + iAtm, + jAtm} of _structureAmd2.default.structure.bonds) { + if (graphType !== "basic" || type !== "x") { + (nbors[iAtm] || (nbors[iAtm] = [])).push(`${jAtm + 1} ${type}`); + (nbors[jAtm] || (nbors[jAtm] = [])).push(`${iAtm + 1} ${type}`); + } + } + hin += "mol 1\n"; + let i = -1; + for (let {el, + x, + y, + z} of _structureAmd2.default.structure.atoms) { + hin += `atom ${++i + 1} - ${el} ** - 0 ${x.toFixed(4)} ${y.toFixed(4)} ${z.toFixed(4)} ` + (nbors[i] ? `${nbors[i].length} ${nbors[i].join(" ")}` : "0") + "\n"; + } + hin += "endmol 1"; + } + return hin; + }, + makeML2(graphType) { + let ml2 = `# The structure was saved in OpenEvolver +@MOLECULE +**** +${_structureAmd2.default.structure.atoms.length} %BOND_COUNT% +SMALL +NO_CHARGES + + +@ATOM +`; + let i = 0; + for (let {el, + x, + y, + z} of _structureAmd2.default.structure.atoms) { + ml2 += `${++i} ${el} ${x.toFixed(4)} ${y.toFixed(4)} ${z.toFixed(4)} ${el} 1 **** 0.0000\n`; + } + let bondCount = 0; + if (graphType !== "empty") { + ml2 += "@BOND\n"; + for (let {type, + iAtm, + jAtm} of _structureAmd2.default.structure.bonds) { + if (graphType !== "basic" || type !== "x") { + bondCount++; + ml2 += `${bondCount} ${iAtm + 1} ${jAtm + 1} ${type}\n`; + } + } + } + ml2 += "@SUBSTRUCTURE\n1 **** 0"; + ml2 = ml2.replace("%BOND_COUNT%", bondCount.toString()); + return ml2; + }, + makeXYZ() { + let xyz = _structureAmd2.default.structure.atoms.length + "\nThe structure was saved in OpenEvolver"; + for (let {el, + x, + y, + z} of _structureAmd2.default.structure.atoms) { + xyz += `\n${el} ${x.toFixed(5)} ${y.toFixed(5)} ${z.toFixed(5)}`; + } + return xyz; + } + }; +}); + +})(); +(function() { +var define = System.amdDefine; +define("components/menu.amd.js", ["exports", "jquery", "../eventful.amd.js", "../app.amd.js", "../file-processing.amd.js"], function(exports, _jquery, _eventfulAmd, _appAmd, _fileProcessingAmd) { + "use strict"; + Object.defineProperty(exports, "__esModule", {value: true}); + var _jquery2 = _interopRequireDefault(_jquery); + var _eventfulAmd2 = _interopRequireDefault(_eventfulAmd); + var _appAmd2 = _interopRequireDefault(_appAmd); + var _fileProcessingAmd2 = _interopRequireDefault(_fileProcessingAmd); + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : {default: obj}; + } + let disabled; + let menu = Object.assign(new _eventfulAmd2.default(".oe-menu"), { + handleAppStateChange(busy) { + this.disabled = busy; + }, + handleGlobalClick(e) { + if (this.disabled) { + return; + } + let $target = (0, _jquery2.default)(e.target); + let $popups = this.$el.find("menu.expanded"); + if ($target.is(".oe-menu button[menu]")) { + let $targetPopup = (0, _jquery2.default)("#" + $target.attr("menu")).toggleClass("expanded"); + if ($targetPopup.hasClass("expanded")) { + this.setItemStates(); + } + $popups = $popups.not($targetPopup); + } + $popups.removeClass("expanded"); + }, + handleHover(e) { + if (this.disabled) { + return; + } + let $expandedMenu = this.$el.find("menu.expanded"); + if ($expandedMenu.length) { + let $targetMenu = (0, _jquery2.default)(e.target).siblings("menu"); + if (!$expandedMenu.is($targetMenu)) { + $expandedMenu.removeClass("expanded"); + $targetMenu.addClass("expanded"); + } + } + }, + handleAction(e) { + let action = (0, _jquery2.default)(e.target).data("action"); + if (action === "load") { + (0, _jquery2.default)("#oe-file").trigger("click"); + } else { + _appAmd2.default.execAction(action); + } + }, + handleFile(e) { + _appAmd2.default.execAction("load", e.target.files[0]); + }, + setItemStates(action) { + let $items = this.$el.find("menuitem[data-action]"); + if (action) { + $items = $items.filter(`[data-action="${action}"]`); + } + let actionStates = _appAmd2.default.getActionStates(); + $items.each((idx, item) => { + let state = actionStates.get(item.getAttribute("data-action")), + disabled = item.hasAttribute("disabled"); + if (state && disabled) { + item.removeAttribute("disabled"); + } else if (!state && !disabled) { + item.setAttribute("disabled", "disabled"); + } + }); + } + }); + Object.defineProperty(menu, "disabled", { + enumerable: true, + get() { + return disabled; + }, + set(state) { + disabled = !!state; + this.$el.toggleClass("oe-disabled", !!disabled); + if (disabled) { + this.$el.find("menu.expanded").removeClass("expanded"); + } + } + }); + menu.listen([{ + type: "app:stateChange", + owner: _appAmd2.default, + handler: "handleAppStateChange" + }, { + type: "click", + owner: document, + handler: "handleGlobalClick" + }, { + type: "mouseenter", + owner: ".oe-menu", + filter: "button[menu]", + handler: "handleHover" + }, { + type: "click", + owner: ".oe-menu", + filter: "menuitem[data-action]", + handler: "handleAction" + }, { + type: "change", + owner: "#oe-file", + handler: "handleFile" + }]); + menu.disabled = _appAmd2.default.busy; + exports.default = menu; + _appAmd2.default.addAction("load", { + get enabled() { + return true; + }, + exec(file) { + if (file) { + _fileProcessingAmd2.default.load(file); + } + } + }); +}); + +})(); +(function() { +var define = System.amdDefine; +define("eventful.amd.js", ["exports", "./observer.amd.js", "jquery"], function(exports, _observerAmd, _jquery) { + "use strict"; + Object.defineProperty(exports, "__esModule", {value: true}); + var _observerAmd2 = _interopRequireDefault(_observerAmd); + var _jquery2 = _interopRequireDefault(_jquery); + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : {default: obj}; + } + function to$(target) { + return target && target.jquery ? target : (0, _jquery2.default)(target); + } + exports.default = class { + constructor($el) { + this.$el = to$($el); + } + listen(config) { + for (let {type, + owner, + filter, + handler} of config) { + let handlerFn = typeof handler === "function" ? handler : this[handler]; + if (owner instanceof _observerAmd2.default) { + owner.on(type, handlerFn.bind(this)); + } else { + to$(owner || this.$el).on(type, filter || null, handlerFn.bind(this)); + } + } + } + }; +}); + +})(); +(function() { +var define = System.amdDefine; +define("cacheable.amd.js", ["exports"], function(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", {value: true}); + let cacheRegistry = new WeakMap(); + exports.default = class { + constructor(createFn) { + Object.defineProperty(this, "create", { + configurable: true, + value: createFn + }); + cacheRegistry.set(this, new Map()); + } + get(item) { + let cache = cacheRegistry.get(this); + if (!cache.has(item)) { + cache.set(item, this.create(item)); + } + return cache.get(item); + } + renew(item) { + cacheRegistry.get(this).delete(item); + } + }; +}); + +})(); +(function() { +var define = System.amdDefine; +define("utils.amd.js", ["exports", "./app.amd.js"], function(exports, _appAmd) { + "use strict"; + Object.defineProperty(exports, "__esModule", {value: true}); + var _appAmd2 = _interopRequireDefault(_appAmd); + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : {default: obj}; + } + let blobUrl; + let utils = { + atomicMasses: {}, + readFile(ref) { + return new Promise((resolve, reject) => { + if (typeof ref === "string") { + let xhr = new XMLHttpRequest(); + xhr.open("GET", ref, true); + xhr.addEventListener("load", () => { + if (xhr.status === 200) { + resolve(xhr.responseText); + } + }); + xhr.addEventListener("error", () => reject(xhr.status)); + xhr.send(null); + } else { + let reader = new FileReader(); + reader.addEventListener("load", () => resolve(reader.result)); + reader.addEventListener("error", () => reject(reader.error)); + reader.readAsText(ref); + } + }); + }, + getBlobURL(data, type = "text/plain") { + let blob = data instanceof Blob ? data : new Blob([data], {type}); + if (blobUrl) { + URL.revokeObjectURL(blobUrl); + } + blobUrl = URL.createObjectURL(blob); + return blobUrl; + } + }; + exports.default = utils; + _appAmd2.default.busy = true; + utils.readFile("lib.json").then((libText) => { + let lib = JSON.parse(libText); + utils.atomicMasses = Object.freeze(lib.atomicMasses); + _appAmd2.default.busy = false; + }); +}); + +})(); +(function() { +var define = System.amdDefine; +define("observer.amd.js", ["exports"], function(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", {value: true}); + let handlerRegistry = new WeakMap(); + class Observer { + constructor() { + handlerRegistry.set(this, new Map()); + } + static on(...params) { + return Observer.prototype.on.apply(Observer, params); + } + static off(...params) { + return Observer.prototype.off.apply(Observer, params); + } + static trigger(...params) { + return Observer.prototype.trigger.apply(Observer, params); + } + on(event, handler) { + let handlers = handlerRegistry.get(this); + if (!handlers.has(event)) { + handlers.set(event, []); + } + handlers.get(event).push(handler); + } + off(event, handler) { + let handlers = handlerRegistry.get(this); + if (!handlers.has(event)) { + return; + } + if (handler) { + let eventHandlers = handlers.get(event); + let handlerIndex = eventHandlers.indexOf(handler); + if (handlerIndex > -1) { + eventHandlers.splice(handlerIndex, 1); + if (eventHandlers.length === 0) { + handlers.delete(event); + } + } + } else { + handlers.get(event).length = 0; + handlers.delete(event); + } + } + trigger(event, ...params) { + let handlers = handlerRegistry.get(this); + if (handlers.has(event)) { + for (let handler of handlers.get(event)) { + handler.apply(null, params); + } + } + } + } + handlerRegistry.set(Observer, new Map()); + exports.default = Observer; +}); + +})(); +(function() { +var define = System.amdDefine; +define("app.amd.js", ["exports", "./observer.amd.js"], function(exports, _observerAmd) { + "use strict"; + Object.defineProperty(exports, "__esModule", {value: true}); + var _observerAmd2 = _interopRequireDefault(_observerAmd); + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : {default: obj}; + } + let actionStore = new Map(); + let app = Object.assign(new _observerAmd2.default(), { + addAction(name, action) { + actionStore.set(name, action); + }, + execAction(name, ...params) { + if (!this.actionEnabled(name)) { + throw new Error(`Action "${name}" is disabled and can't be executed`); + } + return actionStore.get(name).exec(...params); + }, + actionEnabled(name) { + return !this.busy && actionStore.get(name).enabled; + }, + getActionStates() { + let busy = this.busy; + let states = new Map(); + for (let [name, action] of actionStore) { + states.set(name, !busy && action.enabled); + } + return states; + } + }); + let busyCount = 0; + Object.defineProperty(app, "busy", { + configurable: true, + enumerable: true, + get() { + return busyCount > 0; + }, + set(value) { + let busyAnte = this.busy; + if (busyAnte || value) { + busyCount += value ? 1 : -1; + let busy = this.busy; + if (busyAnte !== busy) { + this.trigger("app:stateChange", busy); + } + } + } + }); + exports.default = app; +}); + +})(); +(function() { +var define = System.amdDefine; +define("worker.amd.js", ["exports", "./observer.amd.js", "./app.amd.js"], function(exports, _observerAmd, _appAmd) { + "use strict"; + Object.defineProperty(exports, "__esModule", {value: true}); + var _observerAmd2 = _interopRequireDefault(_observerAmd); + var _appAmd2 = _interopRequireDefault(_appAmd); + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : {default: obj}; + } + let calcWorker = new Worker("js/calc.js"); + let blockingMethod = "ready"; + _appAmd2.default.busy = true; + let worker = Object.assign(new _observerAmd2.default(), {invoke(method, data) { + if (blockingMethod) { + throw new Error(`Unable to run the method “${method}” as the blocking method “${blockingMethod}” is still running`); + } + blockingMethod = method; + _appAmd2.default.busy = true; + calcWorker.postMessage({ + method, + data + }); + }}); + calcWorker.addEventListener("message", ({data: {method, + data} = {}}) => { + if (method) { + if (method === blockingMethod) { + _appAmd2.default.busy = false; + blockingMethod = null; + } + worker.trigger(method, data); + } + }); + calcWorker.addEventListener("error", (e) => { + throw e; + }); + exports.default = worker; +}); + +})(); +(function() { +var define = System.amdDefine; +define("structure.amd.js", ["exports", "./observer.amd.js", "./app.amd.js", "./utils.amd.js", "./worker.amd.js"], function(exports, _observerAmd, _appAmd, _utilsAmd, _workerAmd) { + "use strict"; + Object.defineProperty(exports, "__esModule", {value: true}); + var _observerAmd2 = _interopRequireDefault(_observerAmd); + var _appAmd2 = _interopRequireDefault(_appAmd); + var _utilsAmd2 = _interopRequireDefault(_utilsAmd); + var _workerAmd2 = _interopRequireDefault(_workerAmd); + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : {default: obj}; + } + let structure = { + name: "", + atoms: [], + bonds: [], + potentials: new Map() + }; + let atomSet = new Set(); + let pairSet = new Set(); + let structureUtils = Object.assign(new _observerAmd2.default(), { + getPairList(type) { + switch (type) { + case "basic": + return [...pairSet].filter((pair) => !pair.startsWith("x-")); + case "extra": + return [...pairSet].filter((pair) => pair.startsWith("x-")); + default: + return [...pairSet]; + } + }, + getPairSet(type) { + return new Set(this.getPairList(type)); + }, + getAtomList() { + return [...atomSet]; + }, + getAtomSet() { + return new Set(this.getAtomList()); + }, + overwrite(newStructure, rescanAtoms = true, fromWorker = false) { + ({name: structure.name = "", + atoms: structure.atoms = [], + bonds: structure.bonds = [], + potentials: structure.potentials = new Map()} = newStructure); + if (rescanAtoms !== false) { + atomSet = new Set(structure.atoms.map((atom) => atom.el)); + let atomList = this.getAtomList(); + let pairList = []; + for (let [i, el] of atomList.entries()) { + pairList.push(...atomList.slice(i).map((elem) => el + elem)); + } + pairList.push(...pairList.map((pair) => `x-${pair}`)); + pairSet = new Set(pairList); + } + this.trigger("updateStructure", rescanAtoms !== false); + if (fromWorker !== true) { + _appAmd2.default.trigger("app:structure:loaded"); + syncWorker(); + } + }, + setPotentials(potentials) { + structure.potentials = potentials; + for (let bond of structure.bonds) { + let prefix = bond.type === "x" ? "x-" : ""; + let atoms = structure.atoms; + bond.potential = potentials.get(prefix + atoms[bond.iAtm].el + atoms[bond.jAtm].el) || potentials.get(prefix + atoms[bond.jAtm].el + atoms[bond.iAtm].el); + } + _appAmd2.default.trigger("app:structure:paramsSet"); + syncWorker(); + }, + getCenterOfMass() { + let result = { + x: 0, + y: 0, + z: 0 + }; + let mass = 0; + for (let {el, + x, + y, + z} of structure.atoms) { + const atomicMass = _utilsAmd2.default.atomicMasses[el]; + mass += atomicMass; + result.x += atomicMass * x; + result.y += atomicMass * y; + result.z += atomicMass * z; + } + result.x /= mass; + result.y /= mass; + result.z /= mass; + return result; + }, + translate(x, y, z) { + let center = this.getCenterOfMass(), + dx = x - center.x, + dy = y - center.y, + dz = z - center.z; + for (let atom of structure.atoms) { + atom.x += dx; + atom.y += dy; + atom.z += dz; + } + this.overwrite(structure, false, false); + }, + rotate(angle, axis) { + let axis2 = axis === "x" ? "y" : "x", + axis3 = axis === "z" ? "y" : "z", + sine = Math.sin(angle), + cosine = Math.cos(angle); + for (let atom of structure.atoms) { + let coord2 = atom[axis2]; + let coord3 = atom[axis3]; + atom[axis2] = coord2 * cosine + coord3 * sine; + atom[axis3] = coord3 * cosine - coord2 * sine; + } + this.overwrite(structure, false, false); + } + }); + Object.defineProperty(structureUtils, "structure", { + enumerable: true, + get() { + return structure; + } + }); + exports.default = structureUtils; + function syncWorker() { + _workerAmd2.default.invoke("setStructure", structureUtils.structure); + } + _workerAmd2.default.on("setStructure", (updatedStructure) => { + structureUtils.overwrite(updatedStructure, false, true); + }); + _workerAmd2.default.on("updateStructure", (updatedStructure) => { + structureUtils.overwrite(updatedStructure, false, true); + }); +}); + +})(); +(function() { +var define = System.amdDefine; +define("draw.amd.js", ["exports", "three", "./cacheable.amd.js", "./structure.amd.js"], function(exports, _three, _cacheableAmd, _structureAmd) { + "use strict"; + Object.defineProperty(exports, "__esModule", {value: true}); + var THREE = _interopRequireWildcard(_three); + var _cacheableAmd2 = _interopRequireDefault(_cacheableAmd); + var _structureAmd2 = _interopRequireDefault(_structureAmd); + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : {default: obj}; + } + function _interopRequireWildcard(obj) { + if (obj && obj.__esModule) { + return obj; + } else { + var newObj = {}; + if (obj != null) { + for (var key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) + newObj[key] = obj[key]; + } + } + newObj.default = obj; + return newObj; + } + } + let colors = new _cacheableAmd2.default((color) => new THREE.Color(color)); + let presets = Object.create({ + get(el) { + return this.hasOwnProperty(el) ? this[el] : this._def; + }, + set(el, value) { + if (this.hasOwnProperty(el)) { + Object.assign(this[el], value); + } else { + this[el] = Object.assign({}, this._def, value); + } + } + }, {_def: {value: Object.freeze({ + color: 0xFFFFFF, + radius: 1 + })}}); + presets.set("C", {color: 0xFF0000}); + presets.set("H", {radius: 0.7}); + let pointMaterials = new _cacheableAmd2.default((atom) => { + let preset = presets.get(atom); + return new THREE.PointsMaterial({ + color: preset.color, + sizeAttenuation: false + }); + }); + let atomMaterials = new _cacheableAmd2.default((atom) => { + let preset = presets.get(atom); + return new THREE.MeshLambertMaterial({color: preset.color}); + }); + let atomGeometries = new _cacheableAmd2.default((atom) => { + let preset = presets.get(atom); + return new THREE.SphereGeometry(preset.radius); + }); + let bondMaterials = new _cacheableAmd2.default((type) => { + if (type === "extra") { + return new THREE.LineDashedMaterial({ + dashSize: 0.2, + gapSize: 0.1, + vertexColors: THREE.VertexColors + }); + } else { + return new THREE.LineBasicMaterial({vertexColors: THREE.VertexColors}); + } + }); + let canvas; + let assets3; + let rotation = 0; + let draw = { + setup(canvasEl) { + canvas = { + el: canvasEl, + width: canvasEl.offsetWidth, + height: canvasEl.offsetHeight + }; + assets3 = { + scene: new THREE.Scene(), + group: new THREE.Object3D(), + camera: new THREE.PerspectiveCamera(75, canvas.width / canvas.height, 0.1, 1000), + spotLight: new THREE.SpotLight(0xFFFFFF), + renderer: new THREE.WebGLRenderer({canvas: canvasEl}) + }; + assets3.spotLight.position.set(-40, 60, 50); + assets3.scene.add(assets3.group, assets3.spotLight); + assets3.camera.position.x = 0; + assets3.camera.position.y = 0; + assets3.camera.position.z = 20; + assets3.camera.lookAt(assets3.scene.position); + assets3.renderer.setClearColor(0x000000); + assets3.renderer.setSize(canvas.width, canvas.height); + assets3.renderer.render(assets3.scene, assets3.camera); + }, + get canvas() { + return canvas.el; + }, + get rotation() { + return rotation; + }, + set rotation(value) { + if (value !== rotation && Number.isFinite(value)) { + rotation = value; + assets3.group.rotation.y += (rotation - assets3.group.rotation.y) * 0.05; + } + }, + appearance: "graph", + zoom(delta) { + assets3.camera.position.z += delta; + assets3.camera.lookAt(assets3.scene.position); + this.update(); + }, + resize(width = canvas.el.offsetWidth, height = canvas.el.offsetHeight) { + if (width > 0 && height > 0 && (width !== canvas.width || height !== canvas.height)) { + assets3.camera.aspect = width / height; + assets3.camera.updateProjectionMatrix(); + assets3.renderer.setSize(width, height, false); + canvas.width = width; + canvas.height = height; + this.update(); + } + }, + render() { + this.resetScene(); + this.update(); + }, + update() { + assets3.renderer.render(assets3.scene, assets3.camera); + if (this.autoUpdate) { + requestAnimationFrame(() => this.update()); + } + }, + getAtomColor(el) { + return presets.get(el).color; + }, + setAtomColors(colors) { + for (let [el, color] of colors) { + if (this.getAtomColor(el) !== color) { + presets.set(el, {color}); + atomMaterials.renew(el); + pointMaterials.renew(el); + } + } + }, + setBgColor(color) { + if (typeof color === "string") { + color = Number(color.replace("#", "0x")); + } + assets3.renderer.setClearColor(color); + }, + clearScene() { + let group = assets3.group; + let child = group.children[0]; + while (child) { + group.remove(child); + child = group.children[0]; + } + }, + resetScene() { + this.clearScene(); + if (this.appearance === "spheres") { + this.addSceneAtoms(); + } else if (_structureAmd2.default.structure.bonds.length) { + this.addSceneBonds(); + } else { + this.addScenePoints(); + } + }, + addSceneAtoms() { + let Mesh = THREE.Mesh, + group = assets3.group; + for (let {el, + x, + y, + z} of _structureAmd2.default.structure.atoms) { + let atom3 = new Mesh(atomGeometries.get(el), atomMaterials.get(el)); + atom3.position.x = x; + atom3.position.y = y; + atom3.position.z = z; + group.add(atom3); + } + }, + addSceneBonds() { + let Line = THREE.Line, + Vector3 = THREE.Vector3, + group = assets3.group, + atoms = _structureAmd2.default.structure.atoms, + bindMap = new Int8Array(atoms.length); + for (let {type, + iAtm, + jAtm} of _structureAmd2.default.structure.bonds) { + bindMap[iAtm] = bindMap[jAtm] = 1; + let bondGeometry = new THREE.Geometry(); + let atom = atoms[iAtm]; + bondGeometry.vertices.push(new Vector3(atom.x, atom.y, atom.z)); + bondGeometry.colors.push(colors.get(presets.get(atom.el).color)); + atom = atoms[jAtm]; + bondGeometry.vertices.push(new Vector3(atom.x, atom.y, atom.z)); + bondGeometry.colors.push(colors.get(presets.get(atom.el).color)); + if (type === "x") { + bondGeometry.computeLineDistances(); + group.add(new Line(bondGeometry, bondMaterials.get("extra"), THREE.LineStrip)); + } else { + group.add(new Line(bondGeometry, bondMaterials.get("basic"))); + } + } + let Points = THREE.Points; + let i = bindMap.indexOf(0); + while (i !== -1) { + let pointGeometry = new THREE.Geometry(); + let atom = atoms[i]; + pointGeometry.vertices.push(new Vector3(atom.x, atom.y, atom.z)); + group.add(new Points(pointGeometry, pointMaterials.get(atom.el))); + i = bindMap.indexOf(0, i + 1); + } + }, + addScenePoints() { + let Points = THREE.Points, + Vector3 = THREE.Vector3, + group = assets3.group; + for (let {el, + x, + y, + z} of _structureAmd2.default.structure.atoms) { + let pointGeometry = new THREE.Geometry(); + pointGeometry.vertices.push(new Vector3(x, y, z)); + group.add(new Points(pointGeometry, pointMaterials.get(el))); + } + }, + addAxes() { + if (!this.axes) { + this.axes = new THREE.AxisHelper(20); + assets3.scene.add(this.axes); + this.update(); + } + }, + removeAxes() { + if (this.axes) { + assets3.scene.remove(this.axes); + this.axes = undefined; + this.update(); + } + } + }; + exports.default = draw; + _structureAmd2.default.on("updateStructure", draw.render.bind(draw)); +}); + +})(); +(function() { +var define = System.amdDefine; +define("components/view.amd.js", ["exports", "../eventful.amd.js", "../app.amd.js", "../draw.amd.js"], function(exports, _eventfulAmd, _appAmd, _drawAmd) { + "use strict"; + Object.defineProperty(exports, "__esModule", {value: true}); + var _eventfulAmd2 = _interopRequireDefault(_eventfulAmd); + var _appAmd2 = _interopRequireDefault(_appAmd); + var _drawAmd2 = _interopRequireDefault(_drawAmd); + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : {default: obj}; + } + let view = Object.assign(new _eventfulAmd2.default("#oe-view"), { + rotData: { + startX: 0, + startRot: 0 + }, + handleDragEnterOver(e) { + e.preventDefault(); + if (e.type === "dragenter") { + e.currentTarget.classList.add("oe-droppable"); + } + }, + handleDragLeave(e) { + e.preventDefault(); + if (e.target === e.currentTarget) { + e.target.classList.remove("oe-droppable"); + } + }, + handleDrop(e) { + let dt = e.originalEvent.dataTransfer, + files = dt && dt.files; + if (files && files.length) { + e.preventDefault(); + _appAmd2.default.execAction("load", files[0]); + } + e.currentTarget.classList.remove("oe-droppable"); + }, + handleWheelZoom(e) { + _drawAmd2.default.zoom(e.originalEvent.deltaY < 0 ? 5 : -5); + e.preventDefault(); + }, + handleStartRotate(e) { + this.rotData.startX = e.pageX; + this.rotData.startRot = _drawAmd2.default.rotation; + this.$el.on("mouseup.oeViewRotation mouseleave.oeViewRotation", this.handleStopRotate.bind(this)).on("mousemove.oeViewRotation", this.handleRotate.bind(this)); + _drawAmd2.default.autoUpdate = true; + _drawAmd2.default.update(); + }, + handleStopRotate() { + _drawAmd2.default.autoUpdate = false; + this.$el.off(".oeViewRotation"); + }, + handleRotate(e) { + _drawAmd2.default.rotation = this.rotData.startRot + (e.pageX - this.rotData.startX) * 0.02; + }, + handleWndResize() { + if (!this._resizeTimer) { + this._resizeTimer = setTimeout(() => { + _drawAmd2.default.resize(); + this._resizeTimer = undefined; + }, 300); + } + } + }); + view.listen([{ + type: "dragenter dragover", + handler: "handleDragEnterOver" + }, { + type: "dragleave", + handler: "handleDragLeave" + }, { + type: "drop", + handler: "handleDrop" + }, { + type: "wheel", + handler: "handleWheelZoom" + }, { + type: "mousedown", + handler: "handleStartRotate" + }, { + type: "resize", + owner: window, + handler: "handleWndResize" + }]); + _drawAmd2.default.setup(view.$el.children("canvas")[0]); + exports.default = view; +}); + +})(); +(function() { +var define = System.amdDefine; +define("interface.amd.js", ["./structure.amd.js", "./app.amd.js", "./components/store.amd.js", "./components/save.amd.js", "./components/save-summary.amd.js", "./components/graph.amd.js", "./components/potentials.amd.js", "./components/transform.amd.js", "./components/report.amd.js", "./components/evolve.amd.js", "./components/save-log.amd.js", "./components/appearance.amd.js", "./components/info.amd.js", "./components/menu.amd.js", "./components/view.amd.js"], function(_structureAmd, _appAmd) { + "use strict"; + var _structureAmd2 = _interopRequireDefault(_structureAmd); + var _appAmd2 = _interopRequireDefault(_appAmd); + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : {default: obj}; + } + _structureAmd2.default.on("app:structure:loaded", () => { + document.title = `${_structureAmd2.default.structure.name} - Open evolver`; + }); + _appAmd2.default.on("app:stateChange", (busy) => { + document.body.classList.toggle("app-busy", busy); + }); +}); + +})(); \ No newline at end of file diff --git a/build/lib.json b/build/lib.json new file mode 100644 index 0000000..963fadc --- /dev/null +++ b/build/lib.json @@ -0,0 +1 @@ +{"atomicMasses":{"H":1.00794,"He":4.002602,"Li":6.941,"Be":9.01218,"B":10.811,"C":12.011,"N":14.0067,"O":15.9994,"F":18.998403,"Ne":20.179,"Na":22.98977,"Mg":24.305,"Al":26.98154,"Si":28.0855,"P":30.97376,"S":32.066,"Cl":35.453,"Ar":39.948,"K":39.0983,"Ca":40.078,"Sc":44.95591,"Ti":47.88,"V":50.9415,"Cr":51.9961,"Mn":54.938,"Fe":55.847,"Co":58.9332,"Ni":58.69,"Cu":63.546,"Zn":65.39,"Ga":69.723,"Ge":72.59,"As":74.9216,"Se":78.96,"Br":79.904,"Kr":83.8,"Rb":85.4678,"Sr":87.62,"Y":88.9059,"Zr":91.224,"Nb":92.9064,"Mo":95.94,"Tc":97.9072,"Ru":101.07,"Rh":102.9055,"Pd":106.42,"Ag":107.8682,"Cd":112.41,"In":114.82,"Sn":118.71,"Sb":121.75,"Te":127.6,"I":126.9045,"Xe":131.29,"Cs":132.9054,"Ba":137.33,"La":138.9055,"Ce":140.12,"Pr":140.9077,"Nd":144.24,"Pm":144.9128,"Sm":150.36,"Eu":151.96,"Gd":157.25,"Tb":158.9254,"Dy":162.5,"Ho":164.9304,"Er":167.26,"Tm":168.9342,"Yb":173.04,"Lu":174.967,"Hf":178.49,"Ta":180.9479,"W":183.85,"Re":186.207,"Os":190.2,"Ir":192.22,"Pt":195.08,"Au":196.9665,"Hg":200.59,"Tl":204.383,"Pb":207.2,"Bi":208.9804,"Po":208.9824,"At":209.9871,"Rn":222.0176,"Fr":223.0197,"Ra":226.0254,"Ac":227.0278,"Th":232.0381,"Pa":231.0359,"U":238.0289,"Np":237.0482,"Pu":244.0642,"Am":243.0614,"Cm":247.0703,"Bk":247.0703,"Cf":251.0796,"Es":252.0828,"Fm":257.0951,"Md":258.0986,"No":259.1009,"Lr":260.1054,"Rf":261,"Db":262,"Sg":263,"Bh":262,"Hs":265,"Mt":266}} \ No newline at end of file diff --git a/build/src/css/main.css b/build/src/css/main.css deleted file mode 100644 index 8eaab60..0000000 --- a/build/src/css/main.css +++ /dev/null @@ -1 +0,0 @@ -/*! normalize.css v3.0.2 | MIT License | git.io/normalize */img,legend{border:0}.oe-potentials,table{border-collapse:collapse}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}.hidden,[hidden],template{display:none}a{background-color:transparent;color:#0c0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,optgroup,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre,textarea{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}.oe-menu,legend,td,th{padding:0}table{border-spacing:0}body{margin:0;font:400 87.5%/1.57142857 Arial,sans-serif}a:hover{text-decoration:none}.oe-description{color:grey;font-size:.85em;line-height:1.2}.oe-acknowledgements{color:#dadada;font:.85714286em Arial,sans-serif;margin:3px 0 0 3px;position:absolute;z-index:1}.oe-acknowledgements:before{background:url(data:image/gif;base64,R0lGODlhBgAGAIABAP///zMzMyH5BAEAAAEALAAAAAAGAAYAAAIKRB5mibDnGmStAAA7) 50% 50% no-repeat #000;border:1px solid;border-radius:2px;content:"";cursor:pointer;display:inline-block;height:10px;margin:0 4px 0 0;vertical-align:middle;width:10px}#oe-view{float:left;font-size:0;line-height:0;margin-right:10px}#oe-view.oe-droppable{outline:#000 dashed 1px;position:relative}#oe-view.oe-droppable:after{background-image:repeating-linear-gradient(45deg,transparent,transparent 10px,rgba(255,255,255,.5) 10px,rgba(255,255,255,.5) 20px);content:"";height:100%;left:0;position:absolute;top:0;width:100%}#oe-view.oe-droppable *,#oe-view.oe-droppable:after{pointer-events:none}#oe-view,#oe-view canvas{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}#oe-view canvas{height:500px;width:600px}.oe-menu{background:#f0f0f0;border-bottom:1px solid #dadada;margin:0}.app-busy .oe-menu:after{content:url(../img/loader.gif);cursor:wait;float:right;margin:6px 8px 0 0}.oe-menu>li{display:inline-block;list-style:none;margin:0;padding:0;position:relative}.oe-menu button[menu]{cursor:pointer;background:0 0;border:none;padding:3px 10px}.oe-menu button[menu]:hover{background:#e6e6e6}.oe-menu button[menu]:before{content:attr(value)}.oe-menu menu[type=popup]{background:#f0f0f0;border:1px solid #dadada;margin:0;padding:0;position:absolute;visibility:hidden;z-index:2}.oe-menu menu[type=popup].expanded{visibility:visible}.oe-menu menuitem{cursor:pointer;display:block;max-width:250px;overflow:hidden;padding:5px 7px;text-overflow:ellipsis;white-space:nowrap}.oe-potentials,.oe-store-list{list-style:none;overflow:auto}.oe-menu menuitem:hover{background:#e6e6e6}.oe-menu menuitem:before{content:attr(label)}.oe-menu menuitem[disabled]{color:#a0a0a0}.oe-menu menuitem[disabled]:hover{background:0 0}.oe-menu menuitem[disabled]:before{text-shadow:0 1px 0 #fff}.oe-menu hr{border:1px inset #fff;color:#fff}#oe-file{cursor:pointer;display:none;height:32px;left:0;max-width:175px;opacity:.01;position:absolute;top:28px;z-index:2}menu.expanded+#oe-file{display:inline}.oe-dialog{background:#fff;border:1px solid #0e0e0e;border-radius:5px;left:50%;margin-left:-250px;padding:0 15px;position:absolute;top:50px;width:500px;z-index:20}.oe-dialog:before{background:rgba(255,255,255,.8);content:"";height:100%;left:0;position:fixed;top:0;width:100%;z-index:-1}.oe-dialog-btns input{min-width:85px}.oe-dialog fieldset{border-color:#dadada;margin:1em 0}.oe-dialog-btns{text-align:right;border:none;padding:0}.oe-store-list{max-height:250px;min-height:20px;padding:0}.oe-store-list-loading{background:url(../img/loader.gif) 50% 50% no-repeat}.oe-store-list>li{cursor:pointer;padding:.2em .5em}.oe-store-list h3{display:inline;font-size:1em;margin:0}.oe-store-list h3:after{color:#0c0;content:" …"}.oe-graph-form:before,.oe-store-list>.active h3:after{content:none}.oe-store-list p{color:transparent;display:inline;font-size:0;margin:0}.oe-store-list>.active{background:#e6e6e6}.oe-store-list>.active p{color:#999;font-size:.85em}.oe-save-form .oe-apply{margin-right:20px}.oe-graph-form{border-color:#dadada;border-radius:0 0 5px;border-width:0 1px 1px 0;left:600px;margin-left:0;top:29px;width:350px;z-index:1}.oe-cutoffs{border:1px solid #dadada;padding:0}.oe-cutoff{background:0 0;border:none;border-top:1px solid #dadada;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;display:block;padding:0;text-align:left;width:100%}.oe-cutoff:first-child{border-top:none}.oe-cutoff.active,.oe-cutoff:focus{background:#f5f5f5;outline:0}.oe-cutoff:before,.oe-potentials li:first-child>span{background:#f0f0f0}.oe-cutoff:before{border-right:1px solid #dadada;content:attr(data-pair);display:inline-block;margin-right:5px;min-width:75px;padding-left:5px}.oe-cutoff-slider{padding:0;width:100%}.oe-cutoff-exact,.oe-cutoff-max,.oe-cutoff-min{display:block;width:32%}.oe-cutoff-exact input,.oe-cutoff-max input,.oe-cutoff-min input{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;width:100%}.oe-cutoff-min{float:left}.oe-cutoff-max{float:right}.oe-cutoff-exact{margin-left:auto;margin-right:auto}.oe-potentials{border:1px solid #dadada;display:table;margin:0;max-height:300px;padding:0;table-layout:fixed;width:100%}.oe-potentials li{display:table-row;margin:0;padding:0}.oe-potentials li>label,.oe-potentials li>span{border:1px solid #dadada;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;display:table-cell;padding:2px 5px;vertical-align:middle;width:28%}.oe-potentials li>label:first-child,.oe-potentials li>span:first-child{background:#f0f0f0;width:16%}.oe-potentials input{background:0 0;border:none;display:block;font-size:1em;height:1.5em;margin:0;padding:0;width:100%}.oe-appearance-colors input,.oe-appearance-colors label,.oe-appearance-colors select,.oe-translate label{display:inline-block;vertical-align:top}.oe-potentials .missed label{background:url(data:image/gif;base64,R0lGODlhBQAFAIAAAOPj4////yH5BAAAAAAALAAAAAAFAAUAAAIHRH6GodhZAAA7)}.oe-potentials sub{font-style:normal}.potential-filing label{color:#0c0;cursor:pointer;overflow:hidden;position:relative;text-decoration:underline}.potential-filing label:hover{text-decoration:none}.potential-filing label input{cursor:pointer;height:100%;left:0;opacity:.01;position:absolute;top:0;width:100%}.oe-transform-form{border-color:#dadada;border-radius:0 0 5px;border-width:0 1px 1px 0;left:600px;margin-left:0;top:29px;width:350px;z-index:1}.oe-transform-form:before{content:none}.oe-translate>legend:after{content:"\21A6"}.oe-translate input[type=text]{width:75px}.oe-translate input[type=button]{color:#c00;display:inline-block;font:400 22px/20px monospace;vertical-align:top}.oe-rotate>legend:after{content:"\21BB"}.oe-rotate input[data-axis="x"]{color:#a00}.oe-rotate input[data-axis="y"]{color:#0a0}.oe-rotate input[data-axis="z"]{color:#00a}.oe-rotate>legend:after,.oe-translate>legend:after{font:400 20px/1px monospace;margin:0 5px}.oe-evolve-params{background:url(data:image/gif;base64,R0lGODlhAQABAIAAANra2gAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==) 50% 0 repeat-y;float:left;margin-bottom:1em;width:100%}.oe-evolve-params fieldset{border:none;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;float:right;margin:0;padding:0 0 0 25px;width:49.5%}.oe-evolve-params fieldset:first-child{float:left;padding:0 25px 0 0}.oe-evolve-params fieldset>p:first-child{margin-top:0}.oe-evolve-form .oe-dialog-btns{clear:both}.oe-appearance-form{margin-left:-150px;width:300px}.oe-appearance-colors label{margin-right:20px}#oe-report-progress{position:relative}#oe-report-progress:after{content:attr(value) "%";font-size:.8em;left:0;margin-left:5px;position:absolute;text-align:center;width:100%}#oe-report-data>dt{font-weight:700}#oe-report-data>dd{margin:0 0 .5em} \ No newline at end of file diff --git a/build/src/js/calc.js b/build/src/js/calc.js deleted file mode 100644 index c01a8af..0000000 --- a/build/src/js/calc.js +++ /dev/null @@ -1 +0,0 @@ -!function(t){"use strict";var o={},n={},e=null,r=0,a={},s={},i={};t.importScripts("utils.js"),o.setStructure=function(t){var o,n,a=t.bonds,s=a.length;for(o=0,n=0;s>o;o++)"x"===a[n].type?a.push(a.splice(n,1)[0]):n++;return r=n,e=t},o.updateStructure=function(){t.postMessage({method:"updateStructure",data:e})},o.totalEnergy=function(){return n.totalEnergy()},o.gradient=function(){return a.alloc(),n.gradient(),a.dispose(),n.norm},o.evolve=function(t){return n.evolveParams=t,n.evolve(),o.updateStructure(),{energy:n.totalEnergy(),norm:n.norm}},o.reconnectPairs=function(t){var a,s,i,u,m,l,c=t.pair.match(/[A-Z][^A-Z]*/g),d=t.cutoff*t.cutoff,f=e.atoms,h=f.length,p=e.bonds;for(i=0;h>i;i++){if(f[i].el===c[0])l=c[1];else{if(f[i].el!==c[1])continue;l=c[0]}for(u=i+1;h>u;u++)if(f[u].el===l){for(m=r,a=p[m],s=p.length;s>m&&!(a.iAtm===i&&a.jAtm===u||a.iAtm===u&&a.jAtm===i);a=p[++m]);n.sqrDistance(i,u)>d?a&&p.splice(m,1):a||p.push({iAtm:i,jAtm:u,type:"x"})}}o.updateStructure()},o.collectStats=function(){var t,o,r,a,s,i=e.atoms,u=e.bonds,m={};for(m.name=e.name,m.atomCount=o=i.length,m.atoms={},t=0;o>t;t++)m.atoms.hasOwnProperty(i[t].el)?m.atoms[i[t].el]++:m.atoms[i[t].el]=1;for(m.bondCount=o=u.length,m.bonds={},t=0;o>t;t++)r="x"===u[t].type?"x-":"",a=r+i[u[t].jAtm].el+i[u[t].iAtm].el,m.bonds.hasOwnProperty(a)||(a=r+i[u[t].iAtm].el+i[u[t].jAtm].el,m.bonds.hasOwnProperty(a)||(m.bonds[a]={count:0,avgLen:0,avgEnergy:0,totEnergy:0})),s=n.distance(u[t].iAtm,u[t].jAtm),m.bonds[a].count++,m.bonds[a].avgLen+=s,m.bonds[a].totEnergy+=n.morse(u[t].potential,s);for(a in m.bonds)m.bonds.hasOwnProperty(a)&&(m.bonds[a].avgLen/=m.bonds[a].count,m.bonds[a].avgEnergy=m.bonds[a].totEnergy/m.bonds[a].count);return m.potentials=e.potentials,m.totalEnergy=n.totalEnergy(),m},t.onmessage=function(n){var e=n.data&&n.data.method;"function"==typeof o[e]&&t.postMessage({method:e,data:o[e].call(o,n.data.data)})},a.alloc=s.alloc=function(){var t=e.atoms.length;this.x=new Float32Array(t),this.y=new Float32Array(t),this.z=new Float32Array(t)},a.dispose=s.dispose=function(){this.x=this.y=this.z=null},i.alloc=function(t){this.data={E:new Float32Array(t),grad:new Float32Array(t),dt:new Float32Array(t)}},i.dispose=function(){this.data=null},i.write=function(t){var o=this.data;o.E[t]=n.totalEnergy(),o.grad[t]=n.norm,o.dt[t]=n.timeStep()},n.sqrDistance=function(t,o){var n=e.atoms[t],r=e.atoms[o],a=n.x-r.x,s=n.y-r.y,i=n.z-r.z;return a*a+s*s+i*i},n.distance=function(t,o){return Math.sqrt(n.sqrDistance(t,o))},n.morse=function(t,o){var n=Math.exp(t.b*(t.R0-o));return t.D0*n*(n-2)},n.derivative=function(t,o){var n=t.D0*Math.exp(2*t.b*t.R0),e=-2*t.b,r=-2*Math.sqrt(t.D0*n),a=Math.exp(-t.b*o);return e*a*(n*a+.5*r)},n.gradComponent=function(t,o,r){var a=n.distance(t,o),s=n.derivative(e.bonds[r].potential,a)/a,i=e.atoms[t],u=e.atoms[o];return{x:s*(i.x-u.x),y:s*(i.y-u.y),z:s*(i.z-u.z)}},n.totalEnergy=function(){var t,o,r=0,a=e.bonds;for(t=0,o=a.length;o>t;t++)r+=n.morse(a[t].potential,n.distance(a[t].iAtm,a[t].jAtm));return r},n.gradient=function(){var o,r,s,i,u,m,l,c=e.atoms,d=c.length,f=e.bonds,h=f.length,p=t.OE.utils;for(n.norm=n.sumSqr=n.rootSumSqr=0,i=0;d>i;i++){for(a.x[i]=a.y[i]=a.z[i]=0,m=0;h>m;m++){if(f[m].iAtm===i)u=f[m].jAtm;else{if(f[m].jAtm!==i)continue;u=f[m].iAtm}o=n.gradComponent(i,u,m),a.x[i]+=o.x,a.y[i]+=o.y,a.z[i]+=o.z}r=a.x[i]*a.x[i]+a.y[i]*a.y[i]+a.z[i]*a.z[i],l=p.getAtomicMass(c[i].el),n.sumSqr+=r/l,n.rootSumSqr+=r/(l*l),n.norm+=r}for(n.rootSumSqr=Math.sqrt(n.rootSumSqr),n.norm=Math.sqrt(n.norm),s=1/n.norm,i=0;d>i;i++)a.x[i]*=s,a.y[i]*=s,a.z[i]*=s;return n.norm},n.stochGradient=function(){var o,r,i,u,m,l,c,d,f,h,p=e.atoms,y=p.length,g=e.bonds,v=g.length,x=t.OE.utils;for(i=n.norm=n.sumSqr=n.rootSumSqr=0,c=0;y>c;c++){for(a.x[c]=a.y[c]=a.z[c]=0,f=0;v>f;f++){if(g[f].iAtm===c)d=g[f].jAtm;else{if(g[f].jAtm!==c)continue;d=g[f].iAtm}o=n.gradComponent(c,d,f),a.x[c]+=o.x,a.y[c]+=o.y,a.z[c]+=o.z}r=a.x[c]*a.x[c]+a.y[c]*a.y[c]+a.z[c]*a.z[c],h=x.getAtomicMass(p[c].el),n.sumSqr+=r/h,n.rootSumSqr+=r/(h*h),n.norm+=r,s.x[c]=50-100*Math.random(),s.y[c]=50-100*Math.random(),s.z[c]=50-100*Math.random(),i+=s.x[c]*s.x[c]+s.y[c]*s.y[c]+s.z[c]*s.z[c]}for(n.rootSumSqr=Math.sqrt(n.rootSumSqr),n.norm=Math.sqrt(n.norm),i=Math.sqrt(i),l=0,u=1/n.norm,m=1/i,c=0;y>c;c++)a.x[c]*=u,a.y[c]*=u,a.z[c]*=u,s.x[c]*=m,s.y[c]*=m,s.z[c]*=m,a.x[c]+=s.x[c],a.y[c]+=s.y[c],a.z[c]+=s.z[c],l+=a.x[c]*a.x[c]+a.y[c]*a.y[c]+a.z[c]*a.z[c];for(l=Math.sqrt(l),u=1/l,c=0;y>c;c++)a.x[c]*=u,a.y[c]*=u,a.z[c]*=u;return n.norm},n.timeStep=function(){return 1.636886e-16*Math.sqrt(e.atoms.length*n.evolveParams.temperature/n.sumSqr)},n.tuneEvolver=function(){var o=n.evolveParams,e=o.logInterval,r={},u=[],m=[],l=[];return u.push(a.alloc.bind(a)),m.push(o.stoch?n.stochGradient.bind(n):n.gradient.bind(n)),l.push(a.dispose.bind(a)),o.stoch&&(u.push(s.alloc.bind(s)),l.push(s.dispose.bind(s))),e&&(u.push(i.alloc.bind(i,Math.floor(o.stepCount/e))),m.push(function(t){t%e===0&&i.write(t/e)}),l.push(function(){t.postMessage({method:"evolve.log",data:i.data}),i.dispose()})),r.initialize=function(){u.forEach(function(t){t()})},r.step=function(t){for(var o=0,n=m.length;n>o;o++)m[o](t)},r.finalize=function(){l.forEach(function(t){t()})},r},n.evolve=function(){var o,r,s,i,u=n.evolveParams,m=n.tuneEvolver(),l=e.atoms,c=l.length,d=12926e-8*c*u.temperature,f=Math.ceil(u.stepCount/100),h=100/u.stepCount,p={method:"evolve.progress"};for(m.initialize(),m.step(),o=0,r=u.stepCount;r>o;o++){for(s=d*n.rootSumSqr/n.sumSqr,i=0;c>i;i++)l[i].x-=s*a.x[i],l[i].y-=s*a.y[i],l[i].z-=s*a.z[i];o%f===0&&(p.data=o*h,t.postMessage(p)),m.step(o)}m.finalize()}}(this); \ No newline at end of file diff --git a/build/src/js/fallback.js b/build/src/js/fallback.js deleted file mode 100644 index a8bcd02..0000000 --- a/build/src/js/fallback.js +++ /dev/null @@ -1 +0,0 @@ -!function(r){"use strict";var e,n={jQuery:"../vendor/jquery.min.js",THREE:"../vendor/three.min.js",_:"../vendor/underscore-min.js"};for(e in n)n.hasOwnProperty(e)&&!r.hasOwnProperty(e)&&r.document.write("")}(this); \ No newline at end of file diff --git a/build/src/js/main.js b/build/src/js/main.js deleted file mode 100644 index 000fee0..0000000 --- a/build/src/js/main.js +++ /dev/null @@ -1 +0,0 @@ -!function(){"use strict";"indexOf"in Int8Array.prototype||Object.defineProperty(Int8Array.prototype,"indexOf",{configurable:!0,enumerable:!1,writable:!0,value:function(e,t){var a=this.length;for(void 0===t?t=0:0>t&&(t+=a);a>t;t++)if(this[t]===e)return t;return-1}})}(),function(e){"use strict";function t(e){var t=e._oid;return r[t]||(t=++o,Object.defineProperty(e,"_oid",{value:t}),r[t]={}),r[t]}var a=e.OE||(e.OE={}),n=a.observer={},r={},o=0;n.on=function(e,a){var n=t(this);n[e]||(n[e]=[]),n[e].push(a)},n.off=function(e,a){var n,r=t(this);r[e]&&(a?(n=r[e].indexOf(a),n>-1&&(r[e].splice(n,1),0===r[e].length&&delete r[e])):(r[e].length=0,delete r[e]))},n.trigger=function(e){var a,n,r,o=t(this);if(o[e])for(a=Array.prototype.slice.call(arguments,1),n=0,r=o[e].length;r>n;n++)o[e][n].apply(null,a)}}(this),function(e){"use strict";var t,a=e.OE||(e.OE={}),n=a.utils={};t={H:1.00794,He:4.002602,Li:6.941,Be:9.01218,B:10.811,C:12.011,N:14.0067,O:15.9994,F:18.998403,Ne:20.179,Na:22.98977,Mg:24.305,Al:26.98154,Si:28.0855,P:30.97376,S:32.066,Cl:35.453,Ar:39.948,K:39.0983,Ca:40.078,Sc:44.95591,Ti:47.88,V:50.9415,Cr:51.9961,Mn:54.938,Fe:55.847,Co:58.9332,Ni:58.69,Cu:63.546,Zn:65.39,Ga:69.723,Ge:72.59,As:74.9216,Se:78.96,Br:79.904,Kr:83.8,Rb:85.4678,Sr:87.62,Y:88.9059,Zr:91.224,Nb:92.9064,Mo:95.94,Tc:97.9072,Ru:101.07,Rh:102.9055,Pd:106.42,Ag:107.8682,Cd:112.41,In:114.82,Sn:118.71,Sb:121.75,Te:127.6,I:126.9045,Xe:131.29,Cs:132.9054,Ba:137.33,La:138.9055,Ce:140.12,Pr:140.9077,Nd:144.24,Pm:144.9128,Sm:150.36,Eu:151.96,Gd:157.25,Tb:158.9254,Dy:162.5,Ho:164.9304,Er:167.26,Tm:168.9342,Yb:173.04,Lu:174.967,Hf:178.49,Ta:180.9479,W:183.85,Re:186.207,Os:190.2,Ir:192.22,Pt:195.08,Au:196.9665,Hg:200.59,Tl:204.383,Pb:207.2,Bi:208.9804,Po:208.9824,At:209.9871,Rn:222.0176,Fr:223.0197,Ra:226.0254,Ac:227.0278,Th:232.0381,Pa:231.0359,U:238.0289,Np:237.0482,Pu:244.0642,Am:243.0614,Cm:247.0703,Bk:247.0703,Cf:251.0796,Es:252.0828,Fm:257.0951,Md:258.0986,No:259.1009,Lr:260.1054,Rf:261,Db:262,Sg:263,Bh:262,Hs:265,Mt:266},n.getAtomicMass=function(e){return t[e]},n.getReducedMass=function(e){var t,a,r=e.match(/[A-Z][^A-Z]*/g);if(!r)throw new Error("Cannot extract element labels from string "+e);return t=n.getAtomicMass(r[0]),a=n.getAtomicMass(r[1]),t*a/(t+a)}}(this),function(e){"use strict";var t,a,n=e._,r=e.OE,o=r.app=Object.create(r.observer),i=o.actions={},s={},l=0;Object.defineProperties(o,{STARTED:{value:Object.freeze({openStore:!0,load:!0,save:!1,saveSummary:!1,alterGraph:!1,setup:!1,transform:!1,calcEnergy:!1,calcGrad:!1,evolve:!1,alterView:!1})},STRUCTURE_LOADED:{value:Object.freeze({openStore:!0,load:!0,save:!0,saveSummary:!1,alterGraph:!0,setup:!0,transform:!0,calcEnergy:!1,calcGrad:!1,evolve:!1,alterView:!0})},PARAMS_SET:{value:Object.freeze({openStore:!0,load:!0,save:!0,saveSummary:!0,alterGraph:!0,setup:!0,transform:!0,calcEnergy:!0,calcGrad:!0,evolve:!0,alterView:!0})},BUSY:{value:Object.freeze({openStore:!1,load:!1,save:!1,saveSummary:!1,alterGraph:!1,setup:!1,transform:!1,calcEnergy:!1,calcGrad:!1,evolve:!1,alterView:!1})},IDLE:{value:Object.seal({openStore:void 0,load:void 0,save:void 0,saveSummary:void 0,alterGraph:void 0,setup:void 0,transform:void 0,calcEnergy:void 0,calcGrad:void 0,evolve:void 0,alterView:void 0})}}),function(e){o._isStatePredefined=function(t){if(t===o.IDLE)return!1;for(var a=e.length-1;a>=0;a--)if(o[e[a]]===t)return!0;return!1}}(Object.getOwnPropertyNames(o)),Object.defineProperty(o,"state",{configurable:!1,enumerable:!0,get:function(){return o._isStatePredefined(t)?t:n.clone(t)},set:function(e){var a;if(e===t)return void(t===o.BUSY&&l++);if(e===o.BUSY){l++;for(a in o.IDLE)o.IDLE.hasOwnProperty(a)&&(o.IDLE[a]=i[a].enabled)}else if(e===o.IDLE){if(t!==o.BUSY||--l>0)return}else if(t===o.BUSY)return;t=o._isStatePredefined(e)?e:n.clone(e),o.trigger("stateChange")}}),o.addAction=function(e,t){i[e]=Object.create(a,{name:{value:e}}),s[e]={exec:t}},a=Object.create(Object.prototype,{enabled:{enumerable:!0,configurable:!0,get:function(){return o.state[this.name]},set:function(e){var t=o.state;e=!!e,t[this.name]!==e&&(t[this.name]=e,o.state=t)}},exec:{configurable:!0,value:function(){var e=s[this.name];e.enabled&&e.exec.apply(this,arguments)}}}),o.addAction("openStore",function(){r.ui.store.show()}),o.addAction("load",function(e){e&&r.fileAPI.load(e,function(){r.ui.report.hide()})}),o.addAction("save",function(){r.ui.save.show()}),o.addAction("saveSummary",function(){r.worker.invoke("collectStats")}),o.addAction("alterGraph",function(){r.ui.graph.show()}),o.addAction("setup",function(){r.ui.potentials.show()}),o.addAction("transform",function(){r.ui.transform.show()}),o.addAction("calcEnergy",function(){r.worker.invoke("totalEnergy")}),o.addAction("calcGrad",function(){r.worker.invoke("gradient")}),o.addAction("evolve",function(){r.ui.evolve.show()}),o.addAction("alterView",function(){r.ui.appearance.show()}),o.on("stateChange",function(){var e,t=o.state;for(e in t)t.hasOwnProperty(e)&&i.hasOwnProperty(e)&&(s[e].enabled=t[e])}),o.state=o.STARTED}(this),function(e){"use strict";var t=e.OE,a=t.app,n=t.worker=Object.create(t.observer),r=new e.Worker("src/js/calc.js"),o=null;n.invoke=function(e,t){o=e,a.state=a.BUSY,r.postMessage({method:e,data:t})},r.addEventListener("error",function(e){throw e}),r.addEventListener("message",function(e){var t=e.data&&e.data.method;t&&(t===o&&(a.state=a.IDLE,o=null),n.trigger(t,e.data.data))})}(this),function(e){"use strict";var t=e._,a=e.OE,n=a.app,r=a.structureUtils=Object.create(a.observer);a.structure={name:"",atoms:[],bonds:[],potentials:{}},r.atomList=[],r.pairList=[],r.overwrite=function(e,t,o){var i,s,l,c,d,u;if(a.structure=e,t!==!1){for(s=r.atomList,l=r.pairList,s.length=l.length=0,i=a.structure.atoms,c=0,u=i.length;u>c;c++)-1===s.indexOf(i[c].el)&&s.push(i[c].el);for(c=0,u=s.length;u>c;c++)for(d=c;u>d;d++)l.push(s[c]+s[d]);r.pairList=l.concat(l.map(function(e){return"x-"+e}))}r.trigger("updateStructure",t!==!1),o!==!0&&(n.state=n.STRUCTURE_LOADED,r.syncWorker())},r.setPotentials=function(e){var o,i,s,l=a.structure.atoms,c=a.structure.bonds;for(t.each(e,function(e,t){e.b=e.w0*Math.sqrt(a.utils.getReducedMass(t)/e.D0)*.0013559906}),a.structure.potentials=e,i=0,s=c.length;s>i;i++)o="x"===c[i].type?"x-":"",c[i].potential=e[o+l[c[i].iAtm].el+l[c[i].jAtm].el]||e[o+l[c[i].jAtm].el+l[c[i].iAtm].el];n.state=n.PARAMS_SET,r.syncWorker()},r.getCenterOfMass=function(){var e,t,n,r=a.utils,o=a.structure.atoms,i={x:0,y:0,z:0},s=0;for(t=0,n=o.length;n>t;t++)e=r.getAtomicMass(o[t].el),s+=e,i.x+=e*o[t].x,i.y+=e*o[t].y,i.z+=e*o[t].z;return i.x/=s,i.y/=s,i.z/=s,i},r.translate=function(e,t,n){var o,i,s=a.structure.atoms,l=r.getCenterOfMass(),c=e-l.x,d=t-l.y,u=n-l.z;for(o=0,i=s.length;i>o;o++)s[o].x+=c,s[o].y+=d,s[o].z+=u;r.overwrite(a.structure,!1,!1),a.view.render()},r.rotate=function(e,t){var n,o,i,s,l=a.structure.atoms,c="x"===t?"y":"x",d="z"===t?"y":"z",u=Math.sin(e),p=Math.cos(e);for(i=0,s=l.length;s>i;i++)n=l[i][c],o=l[i][d],l[i][c]=n*p+o*u,l[i][d]=o*p-n*u;r.overwrite(a.structure,!1,!1),a.view.render()},r.syncWorker=function(){a.worker.invoke("setStructure",a.structure)},a.worker.on("setStructure",function(e){r.overwrite(e,!1,!0)}),a.worker.on("updateStructure",function(e){r.overwrite(e,!1,!0),a.view.render()})}(this),function(e){"use strict";var t,a=e.OE||(e.OE={}),n=a.fileAPI={},r={};r.hin={parseMolecule:function(e,t){var a,n,r,o,i,s=t.atoms,l=t.bonds,c=s.length,d=/\s+/;for(n=0,a=e.length;a>n;n++)for(r=e[n].trim().split(d),s.push({el:r[3],x:+r[7],y:+r[8],z:+r[9]}),i=11,o=2*r[10]+11;o>i;i+=2)r[i]-1>n&&l.push({iAtm:n+c,jAtm:r[i]-1+c,type:r[i+1]})},parse:function(e){for(var t,a=/\n\s*mol\s+(\d+)([\s\S]+)\n\s*endmol\s+\1/g,n=/^atom\s+\d+\s+.+$/gm,r={atoms:[],bonds:[]};t=a.exec(e);)this.parseMolecule(t[2].match(n),r);return r}},r.ml2=r.mol2={parseMolecule:function(e,t,a){var n,r,o,i,s=a.atoms,l=a.bonds,c=s.length,d=/\s+/;for(r=0,n=e.length;n>r;r++)o=e[r].trim().split(d),i=o[5].indexOf("."),s.push({el:i>-1?o[5].slice(0,i):o[5],x:+o[2],y:+o[3],z:+o[4]});for(r=0,n=t.length;n>r;r++)o=t[r].trim().split(d),l.push({iAtm:o[1]-1+c,jAtm:o[2]-1+c,type:o[3]})},parse:function(e){var t,a,n,r,o,i,s={atoms:[],bonds:[]},l=e.split("@MOLECULE").slice(1),c=/@ATOM([\s\S]+?)(?:@|$)/,d=/@BOND([\s\S]+?)(?:@|$)/,u=/(?:\r?\n)+/;for(t=0,a=l.length;a>t;t++)n=l[t].match(c),r=n&&n[1].trim().split(u)||[],o=l[t].match(d),i=o&&o[1].trim().split(u)||[],this.parseMolecule(r,i,s);return s}},r.xyz={parseAtomRecord:function(e){var t=e.trim().split(/\s+/);return{el:t[0],x:+t[1],y:+t[2],z:+t[3]}},parse:function(e){var t=e.split(/(?:\r?\n)+/).slice(2);return t&&{atoms:t.map(this.parseAtomRecord,this),bonds:[]}}},n.load=function(e,t){this.readFile(e,function(n){var o=e.name||String(e),i=o.slice(o.lastIndexOf(".")+1).toLowerCase(),s=r[i]||r.hin,l=s.parse(n);l.name=o.replace(/.*[\/\\]/,"")||"unknown",a.structureUtils.overwrite(l),a.view.render(),"function"==typeof t&&t(n)})},n.makeFile=function(e,t){return e=e.toUpperCase(),"function"==typeof this["make"+e]?this["make"+e](t):!1},n.makeHIN=function(e){var t,n,r,o,i,s,l=";The structure was saved in OpenEvolver\nforcefield mm+\n",c=a.structure.atoms,d=c.length;if("empty"===e)for(s=0;d>s;s++)t=c[s],l+="mol "+(s+1)+"\natom 1 - "+t.el+" ** - 0 "+t.x.toFixed(4)+" "+t.y.toFixed(4)+" "+t.z.toFixed(4)+" 0\nendmol "+(s+1)+"\n";else{for(n=a.structure.bonds,i=new Array(d),s=0,r=n.length;r>s;s++)o=n[s],("basic"!==e||"x"!==o.type)&&((i[o.iAtm]||(i[o.iAtm]=[])).push(o.jAtm+1+" "+o.type),(i[o.jAtm]||(i[o.jAtm]=[])).push(o.iAtm+1+" "+o.type));for(l+="mol 1\n",s=0;d>s;s++)t=c[s],l+="atom "+(s+1)+" - "+t.el+" ** - 0 "+t.x.toFixed(4)+" "+t.y.toFixed(4)+" "+t.z.toFixed(4)+" "+(i[s]?i[s].length+" "+i[s].join(" "):"0")+"\n";l+="endmol 1"}return l},n.makeML2=function(e){var t,n,r,o,i,s,l,c=a.structure.atoms,d=c.length;for(t="# The structure was saved in OpenEvolver\n@MOLECULE\n****\n"+d+" %BOND_COUNT%\nSMALL\nNO_CHARGES\n\n\n@ATOM\n",n=0,r=c.length;r>n;n++)o=c[n],t+=n+1+" "+o.el+" "+o.x.toFixed(4)+" "+o.y.toFixed(4)+" "+o.z.toFixed(4)+" "+o.el+" 1 **** 0.0000\n";if(i=0,"empty"!==e)for(s=a.structure.bonds,t+="@BOND\n",n=0,r=s.length;r>n;n++)l=s[n],("basic"!==e||"x"!==l.type)&&(i++,t+=i+" "+(l.iAtm+1)+" "+(l.jAtm+1)+" "+l.type+"\n");return t+="@SUBSTRUCTURE\n1 **** 0",t=t.replace("%BOND_COUNT%",i.toString())},n.makeXYZ=function(){var e,t,n=a.structure.atoms,r=n.length,o=r+"\nThe structure was saved in OpenEvolver";for(e=0;r>e;e++)t=n[e],o+="\n"+t.el+" "+t.x.toFixed(5)+" "+t.y.toFixed(5)+" "+t.z.toFixed(5);return o},n.readFile=function(t,a){var n,r;"string"==typeof t?(n=new XMLHttpRequest,n.open("GET",t,!0),n.addEventListener("load",function(){200===n.status&&a(n.responseText)},!1),n.send(null)):(r=new e.FileReader,r.addEventListener("load",function(){a(r.result)},!1),r.readAsText(t))},n.getBlobURL=function(a,n){var r;return r=a instanceof e.Blob?a:new e.Blob([a],{type:n||"text/plain"}),t&&e.URL.revokeObjectURL(t),t=e.URL.createObjectURL(r)}}(this),function(e){"use strict";var t=e._,a=e.THREE,n=e.OE||(e.OE={}),r=n.view={},o={el:null,width:600,height:500},i={init:function(e){return Object.defineProperties(this,{_cache:{value:{}},create:{configurable:!0,value:e}}),this},get:function(e){return this._cache.hasOwnProperty(e)||(this._cache[e]=this.create(e)),this._cache[e]},renew:function(e){delete this._cache[e]}};r.colors=Object.create(i).init(function(e){return new a.Color(e)}),r.presets=function(){var e=Object.freeze({color:16777215,radius:1});return Object.create({get:function(t){return this.hasOwnProperty(t)?this[t]:e},set:function(a,n){this.hasOwnProperty(a)||(this[a]=t.clone(e)),t.extend(this[a],n)}})}(),r.presets.set("C",{color:16711680}),r.presets.set("H",{radius:.7}),r.pointMaterials=Object.create(i).init(function(e){var t=r.presets.get(e);return new a.PointsMaterial({color:t.color,sizeAttenuation:!1})}),r.atomMaterials=Object.create(i).init(function(e){var t=r.presets.get(e);return new a.MeshLambertMaterial({color:t.color})}),r.atomGeometries=Object.create(i).init(function(e){var t=r.presets.get(e);return new a.SphereGeometry(t.radius)}),r.bondMaterials=Object.create(i).init(function(e){return"extra"===e?new a.LineDashedMaterial({dashSize:.2,gapSize:.1,vertexColors:a.VertexColors}):new a.LineBasicMaterial({vertexColors:a.VertexColors})}),r.THREE=function(){var e,t;return e={scene:new a.Scene,group:new a.Object3D,camera:new a.PerspectiveCamera(75,o.width/o.height,.1,1e3),renderer:new a.WebGLRenderer},o.el=e.renderer.domElement,e.scene.add(e.group),t=new a.SpotLight(16777215),t.position.set(-40,60,50),e.scene.add(t),e.camera.position.x=0,e.camera.position.y=0,e.camera.position.z=20,e.camera.lookAt(e.scene.position),e.renderer.setClearColor(0),e.renderer.setSize(o.width,o.height),e.renderer.render(e.scene,e.camera),document.getElementById("oe-view").appendChild(o.el),e}(),r.zoom=function(e){var t=r.THREE;t.camera.position.z+=e,t.camera.lookAt(t.scene.position),r.update()},r.render=function(){r.resetScene(),r.update()},r.rotation=0,r.update=function(){var e=r.THREE.group;e.rotation.y+=.05*(r.rotation-e.rotation.y),r.THREE.renderer.render(r.THREE.scene,r.THREE.camera),r.autoUpdate&&requestAnimationFrame(r.update)},r.getAtomColor=function(e){return this.presets.get(e).color},r.setAtomColors=function(e){var t,a=this.presets;for(t in e)e.hasOwnProperty(t)&&this.getAtomColor(t)!==e[t]&&(a.set(t,{color:e[t]}),this.atomMaterials.renew(t),this.pointMaterials.renew(t))},r.setBgColor=function(e){"string"==typeof e&&(e=parseInt(e.replace("#",""),16)),this.THREE.renderer.setClearColor(e)},r.clearScene=function(){for(var e,t=r.THREE.group;e=t.children[0];)t.remove(e)},r.appearance="graph",r.resetScene=function(){r.clearScene(),"spheres"===r.appearance?r.addSceneAtoms():n.structure.bonds.length?r.addSceneBonds():r.addScenePoints()},r.addSceneAtoms=function(){var e,t,o,i=a.Mesh,s=r.THREE.group,l=r.atomGeometries,c=r.atomMaterials,d=n.structure.atoms;for(e=0,t=d.length;t>e;e++)o=new i(l.get(d[e].el),c.get(d[e].el)),o.position.x=d[e].x,o.position.y=d[e].y,o.position.z=d[e].z,s.add(o)},r.addSceneBonds=function(){var e,t,o,i,s,l,c,d=a.Line,u=a.Points,p=a.Vector3,h=r.THREE.group,f=r.presets,v=r.colors,m=r.bondMaterials,g=r.pointMaterials,y=n.structure.atoms,w=n.structure.bonds,b=new Int8Array(y.length);for(e=0,t=w.length;t>e;e++)o=w[e].iAtm,i=w[e].jAtm,b[o]=b[i]=1,l=new a.Geometry,s=y[o],l.vertices.push(new p(s.x,s.y,s.z)),l.colors.push(v.get(f.get(s.el).color)),s=y[i],l.vertices.push(new p(s.x,s.y,s.z)),l.colors.push(v.get(f.get(s.el).color)),"x"===w[e].type?(l.computeLineDistances(),h.add(new d(l,m.get("extra"),a.LineStrip))):h.add(new d(l,m.get("basic")));for(e=b.indexOf(0);-1!==e;)c=new a.Geometry,s=y[e],c.vertices.push(new p(s.x,s.y,s.z)),h.add(new u(c,g.get(s.el))),e=b.indexOf(0,e+1)},r.addScenePoints=function(){var e,t,o,i,s=a.Points,l=a.Vector3,c=r.THREE.group,d=r.pointMaterials,u=n.structure.atoms;for(t=0,o=u.length;o>t;t++)e=new a.Geometry,i=u[t],e.vertices.push(new l(i.x,i.y,i.z)),c.add(new s(e,d.get(i.el)))},r.addAxes=function(){r.axes||(r.axes=new a.AxisHelper(20),r.THREE.scene.add(r.axes),r.update())},r.removeAxes=function(){r.axes&&(r.THREE.scene.remove(r.axes),delete r.axes,r.update())}}(this),function(e){"use strict";var t=e.jQuery,a=e._,n=e.OE,r=n.app,o=r.actions,i=n.ui||(n.ui={}),s=t(document),l=t(document.body);i.$=function(e){return e&&e.jquery?e:t(e)},i.loadTpls=function(){return t.getJSON("src/tpl/tpl.json").done(function(e){i.tpls={},a.each(e,function(e,t){i.tpls[t]=a.template(e,{variable:"data"})})})},i.proto={init:function(){var e=this.events||(this.events={});return a.each(e,function(e){var t="string"==typeof e.handler?this[e.handler]:e.handler;n.observer.isPrototypeOf(e.owner)?e.owner.on(e.type,t.bind(this)):i.$(e.owner||this.$el).on(e.type,e.filter||null,t.bind(this))},this),this}},i.abstractDialog=a.extend(Object.create(i.proto),{init:function(){return this.hasOwnProperty("events")&&(this.events=this.events.concat(i.abstractDialog.events)),i.proto.init.apply(this,arguments)},events:[{type:"click",filter:".oe-apply",handler:"handleApply"},{type:"click",filter:".oe-discard",handler:"handleDiscard"},{type:"keyup",owner:s,handler:"handleGlobalKeyUp"}],handleApply:function(){this.apply(),this.hide()},handleDiscard:function(){this.discard(),this.hide()},handleGlobalKeyUp:function(e){27===e.which&&(this.discard(),this.hide())},apply:t.noop,discard:t.noop,show:function(){this.$el.removeClass("hidden")},hide:function(){this.$el.addClass("hidden")},fix:function(e){var t,a,n;for(e||(e=this.$el[0].elements),t=0,a=e.length;a>t;t++)n=e[t],"checkbox"===n.type||"radio"===n.type?n.defaultChecked=n.checked:"OPTION"===n.nodeName.toUpperCase()?n.defaultSelected=n.selected:"defaultValue"in n?n.defaultValue=n.value:n.options&&this.fix(n.options)},reset:function(){this.$el[0].reset()}}),i.store=a.extend(Object.create(i.abstractDialog),{$el:t(".oe-store-form"),events:[{type:"click",owner:".oe-store-list",filter:"li[data-path]",handler:"handleSelect"},{type:"dblclick",owner:".oe-store-list",filter:"li[data-path]",handler:"handleApply"}],handleSelect:function(e){t(e.delegateTarget).children(".active").removeClass("active"),t(e.currentTarget).addClass("active")},show:function(){var e;return this.loaded||(e=this.$el.find(".oe-store-list"),e.addClass("oe-store-list-loading"),t.getJSON("../store/info.json").done(this.resetHTML.bind(this)).fail(this.resetHTML.bind(this,void 0)).always(function(){e.removeClass("oe-store-list-loading")}),this.loaded=!0),i.abstractDialog.show.apply(this,arguments)},apply:function(){var e=this.$el.find(".active[data-path]").data("path");e&&o.load.exec("../store/"+e)},resetHTML:function(e){var t=e&&e.length,a=t?i.tpls.store({records:e}):"
  • Data is empty or couldn't be loaded
  • ";this.$el.find(".oe-store-list").html(a),this.$el.find(".oe-apply").prop("disabled",!t)}}).init(),i.save=a.extend(Object.create(i.abstractDialog),{$el:t(".oe-save-form"),events:[{type:"change",owner:"#oe-file-type",handler:"handleTypeChange"},{type:"click",filter:".oe-apply",handler:"handleSave"}],handleTypeChange:function(e){this.$el.find(".type-description").addClass("hidden").filter("[data-type='"+e.target.value+"']").removeClass("hidden")},handleSave:function(e){var a=t("#oe-file-type").find("option:selected"),r=a.closest("optgroup").data("type"),o=a.data("graph"),i=n.fileAPI.makeFile(r,o);i&&(e.target.setAttribute("download","untitled."+r),e.target.href=n.fileAPI.getBlobURL(i))}}).init(),i.saveSummary=a.extend(Object.create(i.abstractDialog),{$el:t(".oe-save-summary-form"),data:null,events:[{type:"collectStats",owner:n.worker,handler:"handleCollectStats"},{type:"click",filter:"a[download]",handler:"handleSave"}],handleCollectStats:function(e){this.data=e,this.show()},handleSave:function(t){var a,r;if(this.data){switch(a=t.target.getAttribute("data-type")){case"text/html":r=i.tpls.summary(this.data);break;case"application/json":r=e.JSON.stringify(this.data,null,2);break;default:r="TBD"}t.target.href=n.fileAPI.getBlobURL(r,a),this.hide()}}}).init(),i.graph=a.extend(Object.create(i.abstractDialog),{$el:t(".oe-graph-form"),events:[{type:"updateStructure",owner:n.structureUtils,handler:"handleUpdateStructure"},{type:"click",filter:".oe-cutoffs .oe-cutoff",handler:"handlePairSelect"},{type:"change",filter:".oe-cutoff-slider",handler:"handleSliderChange"},{type:"input",owner:"#oe-cutoff-exact",handler:"handleCutoffInput"},{type:"change",owner:"#oe-cutoff-exact",handler:"handleCutoffChange"}],handleUpdateStructure:function(e){e&&this.resetHTML()},handlePairSelect:function(e){var a=t(e.target),n=a.text().trim();t(e.delegateTarget).find(".oe-cutoff").not(a).removeClass("active"),a.addClass("active"),t("#oe-cutoff-exact").val(n).get(0).select(),t(".oe-cutoff-slider").val(this.cutoff2Slider(+n).toFixed(2))},handleSliderChange:function(e){var a=this.slider2Cutoff(+e.target.value);t("#oe-cutoff-exact").val(a.toFixed(4)).trigger("input"),this.updateGraph(t(".oe-cutoff.active").data("pair"),a)},handleCutoffInput:function(e){t(".oe-cutoff.active").text(e.target.value)},handleCutoffChange:function(e){e.target.checkValidity()&&(t(".oe-cutoff-slider").val(this.cutoff2Slider(+e.target.value).toFixed(2)),this.updateGraph(t(".oe-cutoff.active").data("pair"),+e.target.value))},cutoff2Slider:function(e){var a=t(".oe-cutoff-slider")[0],n=+t("#oe-cutoff-min").val(),r=+t("#oe-cutoff-max").val(),o=+a.min,i=+a.max;return o+(e-n)*(i-o)/(r-n)},slider2Cutoff:function(e){var a=t(".oe-cutoff-slider")[0],n=+t("#oe-cutoff-min").val(),r=+t("#oe-cutoff-max").val(),o=+a.min,i=+a.max;return n+(e-o)*(r-n)/(i-o)},resetHTML:function(){this.$el.find(".oe-cutoffs").html(i.tpls.cutoffs({pairs:n.structureUtils.pairList.slice(0,n.structureUtils.pairList.length/2)})).find(".oe-cutoff").eq(0).addClass("active")},updateGraph:function(e,t){n.worker.invoke("reconnectPairs",{pair:e,cutoff:t})}}).init(),i.potentials=a.extend(Object.create(i.abstractDialog),{$el:t(".oe-potential-form"),events:[{type:"updateStructure",owner:n.structureUtils,handler:"handleUpdateStructure"},{type:"change",filter:".load-potentials",handler:"handleLoad"},{type:"click",filter:".save-potentials",handler:"handleSave"}],handleUpdateStructure:function(e){e&&this.resetHTML()},handleLoad:function(e){n.fileAPI.readFile(e.target.files[0],function(e){var t=e.split(/\r?\n/);a.each(t,function(e){var t=e.split(" ");i.potentials.$el.find("li[data-pair='"+t[0]+"'] input").val(function(e){return t[e+1]||""})})})},handleSave:function(e){var a=this.$el.find("li[data-pair]").map(function(){var e=t(this);return e.data("pair")+" "+e.find("input").map(function(){return this.value}).get().join(" ")}).get().join("\n");e.target.href=n.fileAPI.getBlobURL(a)},handleApply:function(){return this.$el[0].checkValidity()?i.abstractDialog.handleApply.apply(this,arguments):void window.alert("Please, fix invalid input first")},resetHTML:function(){this.$el.find("ul.oe-potentials").html(i.tpls.potentials({pairs:n.structureUtils.pairList}))},apply:function(){var e={};this.$el.find("li[data-pair]").each(function(a,n){var r={};n=t(n),n.find("input[data-param]").each(function(e,a){return a.value?void(r[t(a).data("param")]=+a.value):r=!1}),r&&(e[n.data("pair")]=r)}),n.structureUtils.setPotentials(e),this.fix()},discard:function(){this.reset()},show:function(){var e,a,r,o,s=n.structure.atoms,l=n.structure.bonds,c=l.length,d=[];for(o=0;c>o;o++)e="x"===l[o].type?"x-":"",a=s[l[o].iAtm].el,r=s[l[o].jAtm].el,-1===d.indexOf(e+a+r)&&(d.push(e+a+r),a!==r&&d.push(e+r+a));return this.$el.find("li[data-pair]").each(function(e,a){a=t(a),a.toggleClass("missed",-1===d.indexOf(a.data("pair")))}),i.abstractDialog.show.apply(this,arguments)}}).init(),i.transform=a.extend(Object.create(i.abstractDialog),{$el:t(".oe-transform-form"),events:[{type:"stateChange",owner:r,handler:"handleAppStateChange"},{type:"click",owner:"#oe-translate-apply",handler:"handleTranslate"},{type:"click",filter:".oe-rotate [data-axis]",handler:"handleRotate"}],handleAppStateChange:function(){this.$el.find("fieldset").prop("disabled",r.state===r.BUSY)},handleTranslate:function(){var e=this.$el.find(".oe-translate"),t=+e.find("[data-axis='x']").val(),a=+e.find("[data-axis='y']").val(),r=+e.find("[data-axis='z']").val();n.structureUtils.translate(t,a,r)},handleRotate:function(e){var a=t("#oe-rotate-angle").val()*Math.PI/180,r=e.target.getAttribute("data-axis");n.structureUtils.rotate(a,r)},show:function(){var e=n.structureUtils.getCenterOfMass();return this.$el.find(".oe-translate input[data-axis]").val(function(){return e[t(this).data("axis")].toFixed(5)}),n.view.addAxes(),i.abstractDialog.show.apply(this,arguments)},hide:function(){return n.view.removeAxes(),i.abstractDialog.hide.apply(this,arguments)}}).init(),i.report=a.extend(Object.create(i.abstractDialog),{$el:t(".oe-report"),events:[{type:"evolve.progress",owner:n.worker,handler:"updateProgress"}],handleGlobalKeyUp:t.noop,print:function(e){this.updateProgress(100),t("#oe-report-data").html(i.tpls.report({energy:e.energy,grad:e.norm}))},updateProgress:function(e){t("#oe-report-progress").attr("value",e)}}).init(),i.evolve=a.extend(Object.create(i.abstractDialog),{$el:t(".oe-evolve-form"),events:[{type:"evolve",owner:n.worker,handler:"handleEvolveStop"},{type:"change",owner:"#oe-keep-log",handler:"handleKeepLogChange"}],handleEvolveStop:function(e){i.report.print(e)},handleApply:function(){return this.$el[0].checkValidity()?i.abstractDialog.handleApply.apply(this,arguments):void window.alert("Please, fix invalid input first")},handleKeepLogChange:function(e){t("#oe-log-interval").prop("disabled",!e.target.checked).val("0")},apply:function(){this.fix(),n.worker.invoke("evolve",{stepCount:+t("#oe-step-count").val(),temperature:+t("#oe-temperature").val(),stoch:t("#oe-stoch").prop("checked"),logInterval:+t("#oe-log-interval").val()}),i.report.show()},discard:function(){this.reset()}}).init(),i.saveLog=a.extend(Object.create(i.abstractDialog),{$el:t(".oe-save-log-dialog"),events:[{type:"evolve.log",owner:n.worker,handler:"handleEvolveLog"},{type:"click",filter:"a[download]",handler:"handleSave"}],handleEvolveLog:function(e){this.data=e,this.show()},handleSave:function(e){var t,a,r,o=this.data,i=o.E,s=o.grad,l=o.dt,c=0,d=e.target.getAttribute("data-delimiter");for(t="t, ps"+d+"E, eV"+d+"||grad E||, eV/Å"+d+"dt, fs",a=0,r=i.length;r>a;a++)t+="\n"+c.toExponential(4)+d+i[a].toExponential(4)+d+s[a].toExponential(4)+d+(1e15*l[a]).toExponential(4),c+=1e12*l[a];e.target.href=n.fileAPI.getBlobURL(t),this.hide(),this.data=null}}).init(),i.appearance=a.extend(Object.create(i.abstractDialog),{$el:t(".oe-appearance-form"),events:[{type:"updateStructure",owner:n.structureUtils,handler:"handleUpdateStructure"},{type:"change",owner:"#oe-appearance-element",handler:"setCurrElementColor"},{type:"change",owner:"#oe-appearance-color",handler:"handleColorChange"}],handleUpdateStructure:function(e){e&&(t("#oe-appearance-element").html(""),this.setCurrElementColor())},handleColorChange:function(e){var a=parseInt(e.target.value.slice(1),16);isNaN(a)||(this.tmpClrPresets||(this.tmpClrPresets={}),this.tmpClrPresets[t("#oe-appearance-element").val()]=a)},apply:function(){n.view.appearance=this.$el.find("input[name='appearance']:checked").data("appearance"),n.view.setBgColor(t("#oe-bg-color").val()),this.tmpClrPresets&&(n.view.setAtomColors(this.tmpClrPresets),delete this.tmpClrPresets),n.view.render(),this.fix()},discard:function(){this.reset(),delete this.tmpClrPresets,this.setCurrElementColor()},setCurrElementColor:function(){var e,a=t("#oe-appearance-element").val();e=this.tmpClrPresets&&a in this.tmpClrPresets?this.tmpClrPresets[a]:n.view.getAtomColor(a),t("#oe-appearance-color").val("#"+("000000"+e.toString(16)).slice(-6))}}).init(),i.info=a.extend(Object.create(i.abstractDialog),{$el:t(".oe-info-dialog"),events:[{type:"totalEnergy",owner:n.worker,handler:"handleTotalEnergy"},{type:"gradient",owner:n.worker,handler:"handleGradient"}],handleTotalEnergy:function(e){this.applyTpl("energy",{energy:e,bonds:n.structure.bonds.length}),this.show()},handleGradient:function(e){this.applyTpl("gradient",{grad:e}),this.show()},applyTpl:function(e,t){this.$el.find(".oe-info-dialog-text").html(i.tpls[e](t))}}).init(),i.menu=a.extend(Object.create(i.proto),{$el:t(".oe-menu"),init:function(){return this.setItemStates(),i.proto.init.apply(this,arguments)},events:[{type:"stateChange",owner:n.app,handler:"setItemStates"},{type:"click.oe",owner:s,handler:"handleGlobalClick"},{type:"mouseenter",owner:".oe-menu",filter:"button[menu]",handler:"handleHover"},{type:"click",owner:".oe-menu",filter:"menuitem[data-action]",handler:"handleAction"},{type:"change",owner:"#oe-file",handler:"handleFile"}],handleGlobalClick:function(e){var a=t(e.target),n=this.$el.find("menu.expanded");a.is(".oe-menu button[menu]")&&(n=n.not(t("#"+a.attr("menu")).toggleClass("expanded"))),n.removeClass("expanded")},handleHover:function(e){var a,n=this.$el.find("menu.expanded");n.length&&(a=t(e.target).siblings("menu"),n.is(a)||(n.removeClass("expanded"),a.addClass("expanded")))},handleAction:function(e){var a=t(e.target).data("action");o[a]&&o[a].exec&&("load"===a?t("#oe-file").trigger("click"):o[a].exec())},handleFile:function(e){o.load.exec(e.target.files[0])},setItemStates:function(e){var a=t("menuitem[data-action]");e&&(a=a.filter("[data-action='"+e+"']")),a.each(function(e,t){var a=o[t.getAttribute("data-action")].enabled,n=t.hasAttribute("disabled");a&&n?t.removeAttribute("disabled"):a||n||t.setAttribute("disabled","disabled")})}}).init(),i.view=a.extend(Object.create(i.proto),{$el:t("#oe-view"),rotData:{startX:0,startRot:0},events:[{type:"click",owner:".oe-acknowledgements",handler:"handleACKClick"},{type:"dragenter dragover",handler:"handleDragEnterOver"},{type:"dragleave",handler:"handleDragLeave"},{type:"drop",handler:"handleDrop"},{type:"wheel",handler:"handleWheelZoom"},{type:"mousedown",handler:"handleStartRotate"}],handleACKClick:function(e){e.target===e.delegateTarget&&(e.target.className+=" hidden")},handleDragEnterOver:function(e){e.preventDefault(),"dragenter"===e.type&&e.currentTarget.classList.add("oe-droppable")},handleDragLeave:function(e){e.preventDefault(),e.target===e.currentTarget&&e.target.classList.remove("oe-droppable")},handleDrop:function(e){var t=e.originalEvent.dataTransfer,a=t&&t.files;a&&a.length&&(e.preventDefault(),o.load.exec(a[0])),e.currentTarget.classList.remove("oe-droppable")},handleWheelZoom:function(e){n.view.zoom(e.originalEvent.deltaY<0?5:-5),e.preventDefault()},handleStartRotate:function(e){var t=this.rotData,a=n.view;t.startX=e.pageX,t.startRot=a.rotation,this.$el.on("mouseup.oeViewRotation mouseleave.oeViewRotation",this.handleStopRotate.bind(this)).on("mousemove.oeViewRotation",this.handleRotate.bind(this)),a.autoUpdate=!0,a.update()},handleStopRotate:function(){n.view.autoUpdate=!1,this.$el.off(".oeViewRotation")},handleRotate:function(e){var t=this.rotData;n.view.rotation=t.startRot+.02*(e.pageX-t.startX)}}).init(),n.structureUtils.on("updateStructure",function(e){e&&(document.title=n.structure.name+" - Open evolver")}),r.on("stateChange",function(){l.toggleClass("app-busy",r.state===r.BUSY)}),r.state=r.BUSY,i.loadTpls().done(function(){r.state=r.IDLE})}(this),function(e){"use strict";function t(e){var t=s.contentDocument;return t.open(),t.write(e),t.close(),t}function a(e){var t=l.contentDocument;return t.getElementsByTagName("img")[0].src=e,t}var n,r,o,i,s,l;"download"in document.createElement("a")||(n={},r=e.URL,o=r.createObjectURL,i=r.revokeObjectURL,r.createObjectURL=function(t){var a,r=o.apply(this,arguments),i=t.type;return 0===i.indexOf("text/")?(a=new e.FileReader,a.addEventListener("load",function(e){n[r]=e.target.result}),a.readAsText(t)):0===i.indexOf("image/")&&(n[r]="\x00"),r},r.revokeObjectURL=function(e){return n.hasOwnProperty(e)&&delete n[e],i.apply(this,arguments)},s=document.createElement("iframe"),s.src="about:blank",l=document.createElement("iframe"),l.src="src/img/dot.gif",s.style.cssText=l.style.cssText="position: absolute; top: -10000px; left: -10000px;",document.body.appendChild(s),document.body.appendChild(l),document.body.addEventListener("click",function(e){setTimeout(function(){var r,o,i=e.target;if("a"===i.nodeName.toLowerCase()&&i.hasAttribute("download")){if(r=i.getAttribute("href"),0===r.indexOf("blob:")&&n.hasOwnProperty(r))o="\x00"===n[r]?a(r):t(n[r]);else if(0===r.indexOf("data:text/"))o=t(atob(r.replace(/^data:text\/\w+;base64,/,"")));else{if(0!==r.indexOf("data:image/"))return;o=a(r)}setTimeout(function(){o.execCommand("SaveAs",!0,i.getAttribute("download"))},0),e.preventDefault()}},0)},!1))}(this); \ No newline at end of file diff --git a/build/src/js/utils.js b/build/src/js/utils.js deleted file mode 100644 index 6175e4a..0000000 --- a/build/src/js/utils.js +++ /dev/null @@ -1 +0,0 @@ -!function(t){"use strict";var e,r=t.OE||(t.OE={}),s=r.utils={};e={H:1.00794,He:4.002602,Li:6.941,Be:9.01218,B:10.811,C:12.011,N:14.0067,O:15.9994,F:18.998403,Ne:20.179,Na:22.98977,Mg:24.305,Al:26.98154,Si:28.0855,P:30.97376,S:32.066,Cl:35.453,Ar:39.948,K:39.0983,Ca:40.078,Sc:44.95591,Ti:47.88,V:50.9415,Cr:51.9961,Mn:54.938,Fe:55.847,Co:58.9332,Ni:58.69,Cu:63.546,Zn:65.39,Ga:69.723,Ge:72.59,As:74.9216,Se:78.96,Br:79.904,Kr:83.8,Rb:85.4678,Sr:87.62,Y:88.9059,Zr:91.224,Nb:92.9064,Mo:95.94,Tc:97.9072,Ru:101.07,Rh:102.9055,Pd:106.42,Ag:107.8682,Cd:112.41,In:114.82,Sn:118.71,Sb:121.75,Te:127.6,I:126.9045,Xe:131.29,Cs:132.9054,Ba:137.33,La:138.9055,Ce:140.12,Pr:140.9077,Nd:144.24,Pm:144.9128,Sm:150.36,Eu:151.96,Gd:157.25,Tb:158.9254,Dy:162.5,Ho:164.9304,Er:167.26,Tm:168.9342,Yb:173.04,Lu:174.967,Hf:178.49,Ta:180.9479,W:183.85,Re:186.207,Os:190.2,Ir:192.22,Pt:195.08,Au:196.9665,Hg:200.59,Tl:204.383,Pb:207.2,Bi:208.9804,Po:208.9824,At:209.9871,Rn:222.0176,Fr:223.0197,Ra:226.0254,Ac:227.0278,Th:232.0381,Pa:231.0359,U:238.0289,Np:237.0482,Pu:244.0642,Am:243.0614,Cm:247.0703,Bk:247.0703,Cf:251.0796,Es:252.0828,Fm:257.0951,Md:258.0986,No:259.1009,Lr:260.1054,Rf:261,Db:262,Sg:263,Bh:262,Hs:265,Mt:266},s.getAtomicMass=function(t){return e[t]},s.getReducedMass=function(t){var e,r,a=t.match(/[A-Z][^A-Z]*/g);if(!a)throw new Error("Cannot extract element labels from string "+t);return e=s.getAtomicMass(a[0]),r=s.getAtomicMass(a[1]),e*r/(e+r)}}(this); \ No newline at end of file diff --git a/build/src/tpl/tpl.json b/build/src/tpl/tpl.json deleted file mode 100644 index 164c9f7..0000000 --- a/build/src/tpl/tpl.json +++ /dev/null @@ -1 +0,0 @@ -{"cutoffs":"<% _.each(data.pairs, function (pair) { %>\n \n<% }); %>\n","energy":"

    Total energy & total bonds

    \n

    Total energy is E = <%= data.energy.toFixed(5) %> eV

    \n

    Bond count is B = <%= data.bonds %>

    \n","gradient":"

    Norm of gradient

    \n

    Norm of gradient is ||grad E|| = <%= data.grad.toFixed(5) %> eV/Å

    \n","potentials":"
  • \n Pair\n D0, eV\n R0, Å\n ω0, cm−1\n
  • \n<% _.each(data.pairs, function (pair) { %>\n
  • \">\n \n \n \n \n
  • \n<% }); %>\n","report":"
    Total binding energy:
    \n
    <%= data.energy.toFixed(5) %> eV
    \n
    Norm of gradient:
    \n
    <%= data.grad.toFixed(5) %> eV/Å
    \n","store":"<% data.records.forEach(function (record, index) { %>\n
  • \"<% if (index === 0) { %> class=\"active\"<% } %>>\n

    <%- record.name %>

    \n

    <%- record.description %>

    \n
  • \n<% }); %>","summary":"\n\n\n \n Structure summary\n \n\n\n

    Summary data for
    “<%- data.name %>”

    \n

    Structure composition

    \n

    <%- _.map(data.atoms, function (count, atom) {return atom + \"(\" + count + \")\"}).join(\" \") %>
    <%= data.atomCount %> atoms in total

    \n
    \n

    Potential parameters

    \n\n \n \n \n \n \n \n \n \n \n <% _.each(data.potentials, function (potential, pair) { %>\n \n \n \n \n \n \n <% }); %>\n \n
    PairD0, eVR0, Åω0, cm−1
    <%- pair %><%= potential.D0.toFixed(5) %><%= potential.R0.toFixed(5) %><%= potential.w0.toFixed(5) %>
    \n
    \n

    Structure characteristics & stats

    \n\n \n \n \n \n \n \n \n \n \n \n <% _.each(data.bonds, function (stats, pair) { %>\n \n \n \n \n \n \n \n <% }); %>\n \n \n \n \n \n \n \n \n \n \n
    BondNB<LB>, Å<EB>, eVEB, eV
    <%- pair %><%= stats.count %><%= stats.avgLen.toFixed(5) %><%= stats.avgEnergy.toFixed(5) %><%= stats.totEnergy.toFixed(5) %>
    Totals<%= data.bondCount %>  <%= data.totalEnergy.toFixed(5) %>
    \n

    Date & time of saving: <% print((new Date()).toLocaleString()); %>

    \n\n"} \ No newline at end of file diff --git a/build/tpl/tpl.json b/build/tpl/tpl.json new file mode 100644 index 0000000..7788e37 --- /dev/null +++ b/build/tpl/tpl.json @@ -0,0 +1 @@ +{"cutoffs":"<% for (let pair of data.pairs) { %>\n \n<% } %>\n","energy":"

    Total energy & total bonds

    \n

    Total energy is E = <%= data.energy.toFixed(5) %> eV

    \n

    Bond count is B = <%= data.bonds %>

    \n","gradient":"

    Norm of gradient

    \n

    Norm of gradient is ||grad E|| = <%= data.grad.toFixed(5) %> eV/Å

    \n","potentials":"
  • \n Pair\n D0, eV\n R0, Å\n ω0, cm−1\n
  • \n<% for (let pair of data.pairs) { %>\n
  • \">\n \n \n \n \n
  • \n<% } %>\n","report":"
    Total binding energy:
    \n
    <%= data.energy.toFixed(5) %> eV
    \n
    Norm of gradient:
    \n
    <%= data.grad.toFixed(5) %> eV/Å
    \n","store":"<% for (let [index, {path, name, description}] of data.records.entries()) { %>\n
  • \"<% if (index === 0) { %> class=\"active\"<% } %>>\n

    <%- name %>

    \n

    <%- description %>

    \n
  • \n<% } %>","summary":"\n\n\n \n Structure summary\n \n\n\n

    Summary data for
    “<%- data.name %>”

    \n

    Structure composition

    \n

    <% for (let [atom, count] of data.atoms) print(`${atom}(${count}) `); %>
    <%= data.atomCount %> atoms in total

    \n
    \n

    Potential parameters

    \n\n \n \n \n \n \n \n \n \n \n <% for (let [pair, {D0, R0, w0}] of data.potentials) { %>\n \n \n \n \n \n \n <% } %>\n \n
    PairD0, eVR0, Åω0, cm−1
    <%- pair %><%= D0.toFixed(5) %><%= R0.toFixed(5) %><%= w0.toFixed(5) %>
    \n
    \n

    Structure characteristics & stats

    \n\n \n \n \n \n \n \n \n \n \n \n <% for (let [pair, stats] of data.bonds) { %>\n \n \n \n \n \n \n \n <% } %>\n \n \n \n \n \n \n \n \n \n \n
    BondNB<LB>, Å<EB>, eVEB, eV
    <%- pair %><%= stats.count %><%= stats.avgLen.toFixed(5) %><%= stats.avgEnergy.toFixed(5) %><%= stats.totEnergy.toFixed(5) %>
    Totals<%= data.bondCount %>  <%= data.totalEnergy.toFixed(5) %>
    \n

    Date & time of saving: <% print((new Date()).toLocaleString()); %>

    \n\n"} \ No newline at end of file diff --git a/dev/src/css/dialogs/appearance.less b/dev/css/dialogs/appearance.less similarity index 100% rename from dev/src/css/dialogs/appearance.less rename to dev/css/dialogs/appearance.less diff --git a/dev/src/css/dialogs/dialogs.less b/dev/css/dialogs/dialogs.less similarity index 100% rename from dev/src/css/dialogs/dialogs.less rename to dev/css/dialogs/dialogs.less diff --git a/dev/src/css/dialogs/evolve.less b/dev/css/dialogs/evolve.less similarity index 100% rename from dev/src/css/dialogs/evolve.less rename to dev/css/dialogs/evolve.less diff --git a/dev/src/css/dialogs/graph.less b/dev/css/dialogs/graph.less similarity index 93% rename from dev/src/css/dialogs/graph.less rename to dev/css/dialogs/graph.less index dd55bf5..be58df1 100644 --- a/dev/src/css/dialogs/graph.less +++ b/dev/css/dialogs/graph.less @@ -3,10 +3,11 @@ .oe-graph-form { border-color:@border-light-color; - border-radius:0 0 5px 0; - border-width:0 1px 1px 0; - left:600px; + border-radius:0 0 0 5px; + border-width:0 0 1px 1px; + left:auto; margin-left:0; + right:0; top:@root-line-height + 3px * 2 + 1px; // top menu height width:350px; z-index:1; diff --git a/dev/src/css/dialogs/potentials.less b/dev/css/dialogs/potentials.less similarity index 100% rename from dev/src/css/dialogs/potentials.less rename to dev/css/dialogs/potentials.less diff --git a/dev/src/css/dialogs/report.less b/dev/css/dialogs/report.less similarity index 53% rename from dev/src/css/dialogs/report.less rename to dev/css/dialogs/report.less index 7d8d3a8..f796af9 100644 --- a/dev/src/css/dialogs/report.less +++ b/dev/css/dialogs/report.less @@ -1,3 +1,17 @@ +@import (reference) "../ui"; + +.oe-report { + background:#fff; + border-color:@border-light-color; + border-radius:0 0 0 5px; + border-width:0 0 1px 1px; + padding:0 15px; + position:absolute; + right:0; + top:@root-line-height + 3px * 2 + 1px; // top menu height + z-index:1; +} + #oe-report-progress { position:relative; &:after { diff --git a/dev/src/css/dialogs/save.less b/dev/css/dialogs/save.less similarity index 100% rename from dev/src/css/dialogs/save.less rename to dev/css/dialogs/save.less diff --git a/dev/src/css/dialogs/store.less b/dev/css/dialogs/store.less similarity index 100% rename from dev/src/css/dialogs/store.less rename to dev/css/dialogs/store.less diff --git a/dev/src/css/dialogs/transform.less b/dev/css/dialogs/transform.less similarity index 91% rename from dev/src/css/dialogs/transform.less rename to dev/css/dialogs/transform.less index d0f87cf..33413f5 100644 --- a/dev/src/css/dialogs/transform.less +++ b/dev/css/dialogs/transform.less @@ -3,10 +3,11 @@ .oe-transform-form { border-color:@border-light-color; - border-radius:0 0 5px 0; - border-width:0 1px 1px 0; - left:600px; + border-radius:0 0 0 5px; + border-width:0 0 1px 1px; + left:auto; margin-left:0; + right:0; top:@root-line-height + 3px * 2 + 1px; // top menu height width:350px; z-index:1; diff --git a/dev/src/css/main.less b/dev/css/main.less similarity index 80% rename from dev/src/css/main.less rename to dev/css/main.less index 05fc6d9..e79c594 100644 --- a/dev/src/css/main.less +++ b/dev/css/main.less @@ -1,6 +1,6 @@ @import (reference) "ui"; -@import (less) "../../../vendor/normalize.css"; +@import (less) "../../vendor/normalize.css"; body { font:normal percentage(unit((@root-font-size / 16))) ~"/" unit((@root-line-height / @root-font-size)) @base-font-family; @@ -12,6 +12,10 @@ a { } } +button, input, optgroup, select, textarea { + line-height:unit((@root-line-height / @root-font-size)); +} + .hidden { display:none; } diff --git a/dev/src/css/menu.less b/dev/css/menu.less similarity index 100% rename from dev/src/css/menu.less rename to dev/css/menu.less diff --git a/dev/src/css/ui.less b/dev/css/ui.less similarity index 100% rename from dev/src/css/ui.less rename to dev/css/ui.less diff --git a/dev/src/css/utils.less b/dev/css/utils.less similarity index 100% rename from dev/src/css/utils.less rename to dev/css/utils.less diff --git a/dev/src/css/view.less b/dev/css/view.less similarity index 54% rename from dev/src/css/view.less rename to dev/css/view.less index 79199b8..ce1f786 100644 --- a/dev/src/css/view.less +++ b/dev/css/view.less @@ -1,29 +1,11 @@ @import (reference) "ui"; -.oe-acknowledgements { - color:#dadada; - font:unit((12px / @root-font-size), em) @base-font-family; - margin:3px 0 0 3px; - position:absolute; - z-index:1; - &:before { - background:#000 url(data:image/gif;base64,R0lGODlhBgAGAIABAP///zMzMyH5BAEAAAEALAAAAAAGAAYAAAIKRB5mibDnGmStAAA7) 50% 50% no-repeat; - border:1px solid; - border-radius:2px; - content:""; - cursor:pointer; - display:inline-block; - height:10px; - margin:0 4px 0 0; - vertical-align:middle; - width:10px; - } -} #oe-view { - float:left; font-size:0; + height:~"calc(100vh - @{root-line-height} - 3px * 2 - 1px)"; // 100% - top menu height line-height:0; margin-right:10px; + width:100%; &.oe-droppable { outline:1px dashed #000; position:relative; @@ -49,7 +31,7 @@ user-select:none; } canvas { - height:500px; - width:600px; + height:100% !important; + width:100% !important; } } \ No newline at end of file diff --git a/dev/src/img/dot.gif b/dev/img/dot.gif similarity index 100% rename from dev/src/img/dot.gif rename to dev/img/dot.gif diff --git a/dev/src/img/loader.gif b/dev/img/loader.gif similarity index 100% rename from dev/src/img/loader.gif rename to dev/img/loader.gif diff --git a/dev/index.html b/dev/index.html index c1a9552..f4fe771 100644 --- a/dev/index.html +++ b/dev/index.html @@ -4,7 +4,7 @@ Open evolver - + @@ -18,7 +18,7 @@
    - +
  • @@ -224,29 +224,29 @@

    Save evolution log

    -

    Powered by three.js

    -
    +
    - - - - - - - - - - - - - - - - + + \ No newline at end of file diff --git a/dev/js/app.js b/dev/js/app.js new file mode 100644 index 0000000..e5eff76 --- /dev/null +++ b/dev/js/app.js @@ -0,0 +1,52 @@ +import Observer from "./observer.js"; + +let actionStore = new Map(); + +let app = Object.assign(new Observer(), { + addAction(name, action) { + actionStore.set(name, action); + }, + + execAction(name, ...params) { + if (!this.actionEnabled(name)) { + throw new Error(`Action "${name}" is disabled and can't be executed`); + } + return actionStore.get(name).exec(...params); + }, + + actionEnabled(name) { + return !this.busy && actionStore.get(name).enabled; + }, + + getActionStates() { + let busy = this.busy; + let states = new Map(); + for (let [name, action] of actionStore) { + states.set(name, !busy && action.enabled); + } + return states; + } +}); + +let busyCount = 0; + +Object.defineProperty(app, "busy", { + configurable: true, + enumerable: true, + get() { + return busyCount > 0; + }, + set(value) { + let busyAnte = this.busy; + // There is nothing to do if the value is false and the application is already idle + if (busyAnte || value) { + busyCount += value ? 1 : -1; + let busy = this.busy; + if (busyAnte !== busy) { + this.trigger("app:stateChange", busy); + } + } + } +}); + +export default app; \ No newline at end of file diff --git a/dev/js/cacheable.js b/dev/js/cacheable.js new file mode 100644 index 0000000..1dfd782 --- /dev/null +++ b/dev/js/cacheable.js @@ -0,0 +1,21 @@ +let cacheRegistry = new WeakMap(); + +export default class { + constructor(createFn) { + Object.defineProperty(this, "create", {configurable: true, value: createFn}); + cacheRegistry.set(this, new Map()); + } + get(item) { + let cache = cacheRegistry.get(this); + // Create an item only on actual need (when requested for the first time) and then cache it + if (!cache.has(item)) { + cache.set(item, this.create(item)); + } + return cache.get(item); + } + renew(item) { + // Delete an item from cache. + // Next time the item will be requested via `.get()`, its actual value will be stored in cache again + cacheRegistry.get(this).delete(item); + } +} \ No newline at end of file diff --git a/dev/js/calc.js b/dev/js/calc.js new file mode 100644 index 0000000..1363f3c --- /dev/null +++ b/dev/js/calc.js @@ -0,0 +1,439 @@ +"use strict"; + +let structure = null, + tightBondCount = 0; + +let atomicMasses = {}; + +let xhr = new XMLHttpRequest(); +xhr.open("GET", "../lib.json", true); +xhr.addEventListener("load", () => { + if (xhr.status === 200) { + let lib = JSON.parse(xhr.responseText); + atomicMasses = lib.atomicMasses; + self.postMessage({method: "ready"}); + } +}); +xhr.send(null); + + +let api = { + setStructure(data) { + let j = 0; + // Move all existing extra-bonds to the end of a bond array. + // This allows to speed up iterations through bonds of extra-graph + for (let i = 0, bonds = data.bonds, bondCount = bonds.length; i < bondCount; i++) { + if (bonds[j].type === "x") { + bonds.push(bonds.splice(j, 1)[0]); + } else { + j++; + } + } + for (let [pair, params] of data.potentials) { + params.b = core.stiffness(params.w0, params.D0, core.reducedMass(pair)); + } + tightBondCount = j; + structure = data; + return structure; + }, + + updateStructure() { + self.postMessage({method: "updateStructure", data: structure}); + }, + + totalEnergy() { + return core.totalEnergy(); + }, + + gradient() { + grad.alloc(); + core.gradient(); + grad.dispose(); + return core.norm; + }, + + evolve(data) { + core.evolveParams = data; + core.evolve(); + this.updateStructure(); + return {energy: core.totalEnergy(), norm: core.norm}; + }, + + reconnectPairs({pair, cutoff}) { + let [el1, el2] = pair.match(/[A-Z][^A-Z]*/g), + cutoff2 = cutoff * cutoff, + {atoms, bonds} = structure; + for (let i = 0, aLen = atoms.length; i < aLen; i++) { + let jEl; + if (atoms[i].el === el1) { + jEl = el2; + } else if (atoms[i].el === el2) { + jEl = el1; + } else { + continue; + } + for (let j = i + 1; j < aLen; j++) { + if (atoms[j].el === jEl) { + let k, bond, bLen; + for (k = tightBondCount, bond = bonds[k], bLen = bonds.length; k < bLen; bond = bonds[++k]) { + if ((bond.iAtm === i && bond.jAtm === j) || (bond.iAtm === j && bond.jAtm === i)) { + break; + } + } + if (core.sqrDistance(i, j) > cutoff2) { + if (bond) { + bonds.splice(k, 1); // break x-bond, as the distance is greater than cutoff + } + } else if (!bond) { + bonds.push({iAtm: i, jAtm: j, type: "x"}); // create x-bond, as one doesn't exist yet + } + } + } + } + this.updateStructure(); + }, + + collectStats() { + let {atoms, bonds} = structure, + data = {}; + + data.name = structure.name; + data.atomCount = atoms.length; + data.atoms = new Map(); + for (let {el} of atoms) { + let count = data.atoms.get(el); + data.atoms.set(el, count ? count + 1 : 1); + } + + data.bondCount = bonds.length; + data.bonds = new Map(); + for (let {type, iAtm, jAtm, potential} of bonds) { + let prefix = (type === "x") ? "x-" : ""; + let pair = prefix + atoms[jAtm].el + atoms[iAtm].el; + if (!data.bonds.has(pair)) { + pair = prefix + atoms[iAtm].el + atoms[jAtm].el; + if (!data.bonds.has(pair)) { + data.bonds.set(pair, {count: 0, avgLen: 0, avgEnergy: 0, totEnergy: 0}); + } + } + let distance = core.distance(iAtm, jAtm); + let bondData = data.bonds.get(pair); + bondData.count++; + bondData.avgLen += distance; + bondData.totEnergy += core.morse(potential, distance); + } + for (let [, bondData] of data.bonds) { + bondData.avgLen /= bondData.count; + bondData.avgEnergy = bondData.totEnergy / bondData.count; + } + + data.potentials = structure.potentials; + data.totalEnergy = core.totalEnergy(); + return data; + } +}; + +self.onmessage = function ({data: {method, data} = {}}) { + if (typeof api[method] === "function") { + self.postMessage({ + method, + data: api[method](data) + }); + } +}; + + +let grad = { + alloc() { + let atomCount = structure.atoms.length; + this.x = new Float32Array(atomCount); + this.y = new Float32Array(atomCount); + this.z = new Float32Array(atomCount); + }, + + dispose() { + this.x = this.y = this.z = null; + } +}; +let rndGrad = Object.assign({}, grad); + + +let log = { + alloc(size) { + this.data = { + E: new Float32Array(size), + grad: new Float32Array(size), + dt: new Float32Array(size) + }; + }, + + dispose() { + this.data = null; + }, + + write(index) { + let data = this.data; + data.E[index] = core.totalEnergy(); + data.grad[index] = core.norm; + data.dt[index] = core.timeStep(); + } +}; + + +let core = { + /** + * Calculate reduced mass for a given pair of atoms + * @param {String} pair A pair of element labels, e.g. "ZnO". + * Extra-graph pairs ("x-" prefixed) are also acceptable. + * @returns {Number} + */ + reducedMass(pair) { + let elements = pair.match(/[A-Z][^A-Z]*/g); + if (!elements || elements.length < 2) { + throw new Error(`Cannot extract element labels from string ${pair}`); + } + const mass1 = atomicMasses[elements[0]]; + const mass2 = atomicMasses[elements[1]]; + return mass1 * mass2 / (mass1 + mass2); + }, + + stiffness(w0, D0, m) { + // b{1/Å} = w0{1/cm} * 2*pi*c{cm/s} * sqrt[µ{a.m.u.}*1.6605655E-27 / (2*D0{eV}*1.6021892E-19)] / 1E+10 + // i.e. + // b{1/Å} = w0{1/cm} * sqrt[µ{a.m.u.} / D0{eV}] * 1.3559906E-3 + return w0 * Math.sqrt(m / D0) * 1.3559906E-3; + }, + + // In intensive calculations use this method for comparisons rather than `core.distance` + sqrDistance(atom1, atom2) { + let at1 = structure.atoms[atom1], + at2 = structure.atoms[atom2], + dx = at1.x - at2.x, + dy = at1.y - at2.y, + dz = at1.z - at2.z; + return dx * dx + dy * dy + dz * dz; + }, + + distance(atom1, atom2) { + return Math.sqrt(this.sqrDistance(atom1, atom2)); + }, + + morse({D0, R0, b}, distance) { + let exponent = Math.exp(b * (R0 - distance)); + return D0 * exponent * (exponent - 2); + }, + + derivative({D0, R0, b}, distance) { + let cA = D0 * Math.exp(2 * b * R0), + cB = -2 * b, + cC = -2 * Math.sqrt(D0 * cA), + cD = Math.exp(-b * distance); + return cB * cD * (cA * cD + 0.5 * cC); + }, + + gradComponent(atom1, atom2, bond) { + let distance = this.distance(atom1, atom2), + factor = this.derivative(structure.bonds[bond].potential, distance) / distance, + at1 = structure.atoms[atom1], + at2 = structure.atoms[atom2]; + return { + x: factor * (at1.x - at2.x), + y: factor * (at1.y - at2.y), + z: factor * (at1.z - at2.z) + }; + }, + + totalEnergy() { + let energy = 0; + for (let {potential, iAtm, jAtm} of structure.bonds) { + energy += this.morse(potential, this.distance(iAtm, jAtm)); + } + return energy; + }, + + gradient() { + let {atoms, bonds} = structure, + atomCount = atoms.length, + bondCount = bonds.length; + + this.norm = this.sumSqr = this.rootSumSqr = 0; + + for (let i = 0; i < atomCount; i++) { + grad.x[i] = grad.y[i] = grad.z[i] = 0; + for (let b = 0; b < bondCount; b++) { + let j; + if (bonds[b].iAtm === i) { + j = bonds[b].jAtm; + } else if (bonds[b].jAtm === i) { + j = bonds[b].iAtm; + } else { + continue; + } + let gradComponent = this.gradComponent(i, j, b); + grad.x[i] += gradComponent.x; + grad.y[i] += gradComponent.y; + grad.z[i] += gradComponent.z; + } + + let sqrForce = grad.x[i] * grad.x[i] + grad.y[i] * grad.y[i] + grad.z[i] * grad.z[i]; + let mass = atomicMasses[atoms[i].el]; + this.sumSqr += sqrForce / mass; + this.rootSumSqr += sqrForce / (mass * mass); + this.norm += sqrForce; + } + + this.rootSumSqr = Math.sqrt(this.rootSumSqr); + this.norm = Math.sqrt(this.norm); + + // Calc unit vector of internal gradient + let invNorm = 1 / this.norm; + for (let i = 0; i < atomCount; i++) { + grad.x[i] *= invNorm; + grad.y[i] *= invNorm; + grad.z[i] *= invNorm; + } + + return this.norm; + }, + + stochGradient() { + let {atoms, bonds} = structure, + atomCount = atoms.length, + bondCount = bonds.length; + + let rndNorm = this.norm = this.sumSqr = this.rootSumSqr = 0; + + for (let i = 0; i < atomCount; i++) { + grad.x[i] = grad.y[i] = grad.z[i] = 0; + for (let b = 0; b < bondCount; b++) { + let j; + if (bonds[b].iAtm === i) { + j = bonds[b].jAtm; + } else if (bonds[b].jAtm === i) { + j = bonds[b].iAtm; + } else { + continue; + } + let gradComponent = this.gradComponent(i, j, b); + grad.x[i] += gradComponent.x; + grad.y[i] += gradComponent.y; + grad.z[i] += gradComponent.z; + } + + let sqrForce = grad.x[i] * grad.x[i] + grad.y[i] * grad.y[i] + grad.z[i] * grad.z[i]; + let mass = atomicMasses[atoms[i].el]; + this.sumSqr += sqrForce / mass; + this.rootSumSqr += sqrForce / (mass * mass); + this.norm += sqrForce; + + rndGrad.x[i] = 50 - Math.random() * 100; + rndGrad.y[i] = 50 - Math.random() * 100; + rndGrad.z[i] = 50 - Math.random() * 100; + rndNorm += rndGrad.x[i] * rndGrad.x[i] + rndGrad.y[i] * rndGrad.y[i] + rndGrad.z[i] * rndGrad.z[i]; + } + + this.rootSumSqr = Math.sqrt(this.rootSumSqr); + this.norm = Math.sqrt(this.norm); + rndNorm = Math.sqrt(rndNorm); + let rsltNorm = 0; + + // Calc unit vectors of internal and external gradient as well as resulting gradient + let invNorm = 1 / this.norm; + let invRndNorm = 1 / rndNorm; + for (let i = 0; i < atomCount; i++) { + grad.x[i] *= invNorm; + grad.y[i] *= invNorm; + grad.z[i] *= invNorm; + rndGrad.x[i] *= invRndNorm; + rndGrad.y[i] *= invRndNorm; + rndGrad.z[i] *= invRndNorm; + grad.x[i] += rndGrad.x[i]; + grad.y[i] += rndGrad.y[i]; + grad.z[i] += rndGrad.z[i]; + rsltNorm += grad.x[i] * grad.x[i] + grad.y[i] * grad.y[i] + grad.z[i] * grad.z[i]; + } + + rsltNorm = Math.sqrt(rsltNorm); + + // Calc unit vector of resulting gradient + invNorm = 1 / rsltNorm; + for (let i = 0; i < atomCount; i++) { + grad.x[i] *= invNorm; + grad.y[i] *= invNorm; + grad.z[i] *= invNorm; + } + + return this.norm; + }, + + timeStep() { + // dt=sqrt(3NkT/sumSqr); [sumSqr]=eV^2/(angst^2*amu) + return 1.636886e-16 * Math.sqrt(structure.atoms.length * this.evolveParams.temperature / this.sumSqr); + }, + + tuneEvolver() { + let params = this.evolveParams, + initFns = [], // functions to be called before the evolution procedure + stepFns = [], // functions to be called at every evolution step + finFns = []; // functions to be called after the evolution procedure is finished + initFns.push(grad.alloc.bind(grad)); + stepFns.push(params.stoch ? this.stochGradient.bind(this) : this.gradient.bind(this)); + finFns.push(grad.dispose.bind(grad)); + if (params.stoch) { + initFns.push(rndGrad.alloc.bind(rndGrad)); + finFns.push(rndGrad.dispose.bind(rndGrad)); + } + let logInterval = params.logInterval; + if (logInterval) { + initFns.push(log.alloc.bind(log, Math.floor(params.stepCount / logInterval))); + stepFns.push(stepNo => { + let index = stepNo / logInterval; + if (Number.isInteger(index)) { + log.write(index); + } + }); + finFns.push(() => { + self.postMessage({method: "evolve:log", data: log.data}); + log.dispose(); + }); + } + return { + initialize() { + initFns.forEach(fn => fn()); + }, + step(stepNo) { + stepFns.forEach(fn => fn(stepNo)); + }, + finalize() { + finFns.forEach(fn => fn()); + } + }; + }, + + evolve() { + let params = this.evolveParams, + functor = this.tuneEvolver(), + atoms = structure.atoms, + atomCount = atoms.length, + factor = 1.2926E-4 * atomCount * params.temperature, // 1.5NkT [eV] + interval = Math.ceil(params.stepCount / 100), + progressFactor = 100 / params.stepCount, + progressMsg = {method: "evolve:progress", data: 0}; + functor.initialize(); + functor.step(); // pre-calculate current value of gradient before the 1st step + for (let stepNo = 0, stepCount = params.stepCount; stepNo < stepCount; stepNo++) { + let step = factor * this.rootSumSqr / this.sumSqr; + for (let i = 0; i < atomCount; i++) { + atoms[i].x -= step * grad.x[i]; + atoms[i].y -= step * grad.y[i]; + atoms[i].z -= step * grad.z[i]; + } + if (stepNo % interval === 0) { + progressMsg.data = stepNo * progressFactor; + self.postMessage(progressMsg); + } + functor.step(stepNo); + } + functor.finalize(); + } +}; \ No newline at end of file diff --git a/dev/js/components/abstract-dialog.js b/dev/js/components/abstract-dialog.js new file mode 100644 index 0000000..6e9fda7 --- /dev/null +++ b/dev/js/components/abstract-dialog.js @@ -0,0 +1,69 @@ +import Eventful from "../eventful.js"; + +// We cannot use methods of AbstractDialog as direct event handlers because instances should have a chance +// to override those methods and implement their custom behavior. Therefore handlers are just anonymous functions +// which call corresponding methods (either inherited or own) on instances +let events = [ + {type: "click", filter: ".oe-apply", handler(...params) { this.handleApply(...params); }}, + {type: "click", filter: ".oe-discard", handler(...params) { this.handleDiscard(...params); }}, + {type: "keyup", owner: document, handler(...params) { this.handleGlobalKeyUp(...params); }} +]; + +export default class extends Eventful { + constructor($el) { + super($el); + this.listen(events); + } + + handleApply() { + this.apply(); + this.hide(); + } + + handleDiscard() { + this.discard(); + this.hide(); + } + + handleGlobalKeyUp(e) { + if (e.which === 27) { + this.discard(); + this.hide(); + } + } + + apply() {} + + discard() {} + + show() { + this.$el.removeClass("hidden"); + } + + hide() { + this.$el.addClass("hidden"); + } + + fix(fields) { + if (!fields) { + fields = this.$el[0].elements; // `this.$el` should be a form, otherwise the `fix` method is useless + } + for (let field of Array.from(fields)) { + // Setting the `defaultValue`/`defaultChecked`/`defaultSelected` prop allows using + // `form.reset()` on possible future discards (see the `reset()` method) + if (field.type === "checkbox" || field.type === "radio") { + field.defaultChecked = field.checked; + } else if (field.nodeName.toUpperCase() === "OPTION") { + field.defaultSelected = field.selected; + } else if ("defaultValue" in field) { + field.defaultValue = field.value; + } else if (field.options) { // selects + this.fix(field.options); + } + } + } + + reset() { + this.$el[0].reset(); // `this.$el` should be a form, otherwise the `reset` method is useless + } +} \ No newline at end of file diff --git a/dev/js/components/appearance.js b/dev/js/components/appearance.js new file mode 100644 index 0000000..6750634 --- /dev/null +++ b/dev/js/components/appearance.js @@ -0,0 +1,73 @@ +import $ from "jquery"; +import AbstractDialog from "./abstract-dialog.js"; +import structure from "../structure.js"; +import draw from "../draw.js"; +import app from "../app.js"; + +let appearance = Object.assign(new AbstractDialog(".oe-appearance-form"), { + handleUpdateStructure(rescanAtoms) { + if (rescanAtoms) { + $("#oe-appearance-element") + .html(""); + this.setCurrElementColor(); + } + }, + + handleColorChange(e) { + let color = parseInt(e.target.value.slice(1), 16); // skip the leading # sign + if (isNaN(color)) { + return; + } + if (!this.tmpClrPresets) { + this.tmpClrPresets = new Map(); + } + // Store the value in a temporal map (it will be copied to view's presets if the dialog won't be discarded) + this.tmpClrPresets.set($("#oe-appearance-element").val(), color); + }, + + apply() { + draw.appearance = this.$el.find("input[name='appearance']:checked").data("appearance"); + draw.setBgColor($("#oe-bg-color").val()); + if (this.tmpClrPresets) { + draw.setAtomColors(this.tmpClrPresets); + this.tmpClrPresets = undefined; + } + draw.render(); + this.fix(); + }, + + discard() { + this.reset(); + this.tmpClrPresets = undefined; + this.setCurrElementColor(); + }, + + setCurrElementColor() { + let el = $("#oe-appearance-element").val(), + color; + if (this.tmpClrPresets && (this.tmpClrPresets.has(el))) { + color = this.tmpClrPresets.get(el); + } else { + color = draw.getAtomColor(el); + } + $("#oe-appearance-color").val("#" + ("000000" + color.toString(16)).slice(-6)); + } +}); + +appearance.listen([ + {type: "updateStructure", owner: structure, handler: "handleUpdateStructure"}, + + {type: "change", owner: "#oe-appearance-element", handler: "setCurrElementColor"}, + {type: "change", owner: "#oe-appearance-color", handler: "handleColorChange"} +]); + +export default appearance; + +app.addAction("alterView", { + get enabled() { + return structure.structure.atoms.length > 0; + }, + exec() { + appearance.show(); + } +}); \ No newline at end of file diff --git a/dev/js/components/evolve.js b/dev/js/components/evolve.js new file mode 100644 index 0000000..364c2f9 --- /dev/null +++ b/dev/js/components/evolve.js @@ -0,0 +1,55 @@ +import $ from "jquery"; +import AbstractDialog from "./abstract-dialog.js"; +import worker from "../worker.js"; +import report from "./report.js"; +import app from "../app.js"; +import structure from "../structure.js"; + +let evolve = Object.assign(new AbstractDialog(".oe-evolve-form"), { + handleEvolveStop(data) { + report.print(data); + }, + + handleApply() { + if (this.$el[0].checkValidity()) { + return Object.getPrototypeOf(this).handleApply.apply(this, arguments); + } else { + window.alert("Please, fix invalid input first"); + } + }, + + handleKeepLogChange(e) { + $("#oe-log-interval").prop("disabled", !e.target.checked).val("0"); + }, + + apply() { + this.fix(); + worker.invoke("evolve", { + stepCount: +$("#oe-step-count").val(), + temperature: +$("#oe-temperature").val(), + stoch: $("#oe-stoch").prop("checked"), + logInterval: +$("#oe-log-interval").val() + }); + report.show(); + }, + + discard() { + this.reset(); + } +}); + +evolve.listen([ + {type: "evolve", owner: worker, handler: "handleEvolveStop"}, + {type: "change", owner: "#oe-keep-log", handler: "handleKeepLogChange"} +]); + +export default evolve; + +app.addAction("evolve", { + get enabled() { + return structure.structure.potentials.size > 0; + }, + exec() { + evolve.show(); + } +}); \ No newline at end of file diff --git a/dev/js/components/graph.js b/dev/js/components/graph.js new file mode 100644 index 0000000..48ee25e --- /dev/null +++ b/dev/js/components/graph.js @@ -0,0 +1,88 @@ +import $ from "jquery"; +import AbstractDialog from "./abstract-dialog.js"; +import structure from "../structure.js"; +import worker from "../worker.js"; +import app from "../app.js"; +import templates from "../templates.js"; + +let graph = Object.assign(new AbstractDialog(".oe-graph-form"), { + handleUpdateStructure(pairsUpdated) { + if (pairsUpdated) { + this.resetHTML(); + } + }, + + handlePairSelect(e) { + let $target = $(e.target); + $(e.delegateTarget).find(".oe-cutoff").not($target).removeClass("active"); + $target.addClass("active"); + let cutoff = $target.text().trim(); + $("#oe-cutoff-exact").val(cutoff).get(0).select(); + $(".oe-cutoff-slider").val(this.cutoff2Slider(+cutoff).toFixed(2)); + }, + + handleSliderChange(e) { + let cutoff = this.slider2Cutoff(+e.target.value); + $("#oe-cutoff-exact").val(cutoff.toFixed(4)).trigger("input"); + this.updateGraph($(".oe-cutoff.active").data("pair"), cutoff); + }, + + handleCutoffInput(e) { + $(".oe-cutoff.active").text(e.target.value); + }, + + handleCutoffChange(e) { + if (e.target.checkValidity()) { + $(".oe-cutoff-slider").val(this.cutoff2Slider(+e.target.value).toFixed(2)); + this.updateGraph($(".oe-cutoff.active").data("pair"), +e.target.value); + } + }, + + cutoff2Slider(cutoff) { + let slider = $(".oe-cutoff-slider")[0], + minBound = +$("#oe-cutoff-min").val(), + maxBound = +$("#oe-cutoff-max").val(), + min = +slider.min, + max = +slider.max; + return min + (cutoff - minBound) * (max - min) / (maxBound - minBound); + }, + + slider2Cutoff(value) { + let slider = $(".oe-cutoff-slider")[0], + minBound = +$("#oe-cutoff-min").val(), + maxBound = +$("#oe-cutoff-max").val(), + min = +slider.min, + max = +slider.max; + return minBound + (value - min) * (maxBound - minBound) / (max - min); + }, + + resetHTML() { + this.$el.find(".oe-cutoffs") + .html(templates.get("cutoffs")({pairs: structure.getPairList("basic")})) + .find(".oe-cutoff").eq(0).addClass("active"); + }, + + updateGraph(pair, cutoff) { + worker.invoke("reconnectPairs", {pair, cutoff}); + } +}); + +graph.listen([ + {type: "updateStructure", owner: structure, handler: "handleUpdateStructure"}, + + {type: "click", filter: ".oe-cutoffs .oe-cutoff", handler: "handlePairSelect"}, + {type: "change", filter: ".oe-cutoff-slider", handler: "handleSliderChange"}, + {type: "input", owner: "#oe-cutoff-exact", handler: "handleCutoffInput"}, + {type: "change", owner: "#oe-cutoff-exact", handler: "handleCutoffChange"} +]); + +export default graph; + +app.addAction("alterGraph", { + get enabled() { + return structure.structure.atoms.length > 0; + }, + exec() { + graph.show(); + } +}); \ No newline at end of file diff --git a/dev/js/components/info.js b/dev/js/components/info.js new file mode 100644 index 0000000..aad112e --- /dev/null +++ b/dev/js/components/info.js @@ -0,0 +1,49 @@ +import AbstractDialog from "./abstract-dialog.js"; +import worker from "../worker.js"; +import structure from "../structure.js"; +import app from "../app.js"; +import templates from "../templates.js"; + +let info = Object.assign(new AbstractDialog(".oe-info-dialog"), { + handleTotalEnergy(data) { + this.applyTpl("energy", { + energy: data, + bonds: structure.structure.bonds.length + }); + this.show(); + }, + + handleGradient(data) { + this.applyTpl("gradient", {grad: data}); + this.show(); + }, + + applyTpl(tpl, data) { + this.$el.find(".oe-info-dialog-text").html(templates.get(tpl)(data)); + } +}); + +info.listen([ + {type: "totalEnergy", owner: worker, handler: "handleTotalEnergy"}, + {type: "gradient", owner: worker, handler: "handleGradient"} +]); + +export default info; + +app.addAction("calcEnergy", { + get enabled() { + return structure.structure.potentials.size > 0; + }, + exec() { + worker.invoke("totalEnergy"); + } +}); + +app.addAction("calcGrad", { + get enabled() { + return structure.structure.potentials.size > 0; + }, + exec() { + worker.invoke("gradient"); + } +}); \ No newline at end of file diff --git a/dev/js/components/menu.js b/dev/js/components/menu.js new file mode 100644 index 0000000..fa5dc6a --- /dev/null +++ b/dev/js/components/menu.js @@ -0,0 +1,110 @@ +import $ from "jquery"; +import Eventful from "../eventful.js"; +import app from "../app.js"; +import fileAPI from "../file-processing.js"; + +let disabled; + +let menu = Object.assign(new Eventful(".oe-menu"), { + handleAppStateChange(busy) { + this.disabled = busy; + }, + + handleGlobalClick(e) { + if (this.disabled) { + return; + } + let $target = $(e.target); + let $popups = this.$el.find("menu.expanded"); + if ($target.is(".oe-menu button[menu]")) { + let $targetPopup = $("#" + $target.attr("menu")).toggleClass("expanded"); + if ($targetPopup.hasClass("expanded")) { + this.setItemStates(); + } + $popups = $popups.not($targetPopup); + } + $popups.removeClass("expanded"); + }, + + handleHover(e) { + if (this.disabled) { + return; + } + let $expandedMenu = this.$el.find("menu.expanded"); + if ($expandedMenu.length) { + let $targetMenu = $(e.target).siblings("menu"); + if (!$expandedMenu.is($targetMenu)) { + $expandedMenu.removeClass("expanded"); + $targetMenu.addClass("expanded"); + } + } + }, + + handleAction(e) { + let action = $(e.target).data("action"); + if (action === "load") { // the action "load" requires a file to be specified + $("#oe-file").trigger("click"); + } else { + app.execAction(action); + } + }, + + handleFile(e) { + app.execAction("load", e.target.files[0]); + }, + + setItemStates(action) { + let $items = this.$el.find("menuitem[data-action]"); + if (action) { + $items = $items.filter(`[data-action="${action}"]`); + } + let actionStates = app.getActionStates(); + $items.each((idx, item) => { + let state = actionStates.get(item.getAttribute("data-action")), + disabled = item.hasAttribute("disabled"); + if (state && disabled) { + item.removeAttribute("disabled"); + } else if (!state && !disabled) { + item.setAttribute("disabled", "disabled"); + } + }); + } +}); + +Object.defineProperty(menu, "disabled", { + enumerable: true, + get() { + return disabled; + }, + set(state) { + disabled = !!state; + this.$el.toggleClass("oe-disabled", !!disabled); + if (disabled) { + this.$el.find("menu.expanded").removeClass("expanded"); + } + } +}); + +menu.listen([ + {type: "app:stateChange", owner: app, handler: "handleAppStateChange"}, + + {type: "click", owner: document, handler: "handleGlobalClick"}, + {type: "mouseenter", owner: ".oe-menu", filter: "button[menu]", handler: "handleHover"}, + {type: "click", owner: ".oe-menu", filter: "menuitem[data-action]", handler: "handleAction"}, + {type: "change", owner: "#oe-file", handler: "handleFile"} +]); + +menu.disabled = app.busy; + +export default menu; + +app.addAction("load", { + get enabled() { + return true; + }, + exec(file) { + if (file) { + fileAPI.load(file); + } + } +}); \ No newline at end of file diff --git a/dev/js/components/potentials.js b/dev/js/components/potentials.js new file mode 100644 index 0000000..9ceda4c --- /dev/null +++ b/dev/js/components/potentials.js @@ -0,0 +1,105 @@ +import $ from "jquery"; +import AbstractDialog from "./abstract-dialog.js"; +import structure from "../structure.js"; +import utils from "../utils.js"; +import app from "../app.js"; +import templates from "../templates.js"; + +let potentials = Object.assign(new AbstractDialog(".oe-potential-form"), { + handleUpdateStructure(pairsUpdated) { + if (pairsUpdated) { + this.resetHTML(); + } + }, + + handleLoad(e) { + utils.readFile(e.target.files[0]).then(contents => { + let rows = contents.split(/\r?\n/); + for (let row of rows) { + let params = row.split("\t"); + this.$el.find(`li[data-pair="${params[0]}"] input`).val(idx => params[idx + 1] || ""); + } + }); + }, + + handleSave(e) { + let text = this.$el.find("li[data-pair]") + .map((idx, row) => { + let $row = $(row); + return $row.data("pair") + "\t" + + $row.find("input").map((idx, input) => input.value).get().join("\t"); + }).get().join("\n"); + e.target.href = utils.getBlobURL(text); + }, + + handleApply() { + if (this.$el[0].checkValidity()) { + return Object.getPrototypeOf(this).handleApply.apply(this, arguments); + } else { + window.alert("Please, fix invalid input first"); + } + }, + + resetHTML() { + this.$el.find("ul.oe-potentials").html(templates.get("potentials")({pairs: structure.getPairList()})); + }, + + apply() { + let potentials = new Map(); + this.$el.find("li[data-pair]").each((idx, row) => { + let params = {}; + row = $(row); + row.find("input[data-param]").each((idx, el) => { + if (el.value) { + params[$(el).data("param")] = +el.value; + } else { + return (params = false); + } + }); + if (params) { + potentials.set(row.data("pair"), params); + } + }); + structure.setPotentials(potentials); + this.fix(); + }, + + discard() { + this.reset(); + }, + + show() { + let atoms = structure.structure.atoms, + pairs = new Set(); + for (let {type, iAtm, jAtm} of structure.structure.bonds) { + let prefix = (type === "x") ? "x-" : ""; + let el1 = atoms[iAtm].el; + let el2 = atoms[jAtm].el; + // Add both variants AB and BA to simplify further search + pairs.add(prefix + el1 + el2).add(prefix + el2 + el1); + } + this.$el.find("li[data-pair]").each((idx, row) => { + row = $(row); + row.toggleClass("missed", !pairs.has(row.data("pair"))); + }); + return Object.getPrototypeOf(this).show.apply(this, arguments); + } +}); + +potentials.listen([ + {type: "updateStructure", owner: structure, handler: "handleUpdateStructure"}, + + {type: "change", filter: ".load-potentials", handler: "handleLoad"}, + {type: "click", filter: ".save-potentials", handler: "handleSave"} +]); + +export default potentials; + +app.addAction("setup", { + get enabled() { + return structure.structure.atoms.length > 0; + }, + exec() { + potentials.show(); + } +}); \ No newline at end of file diff --git a/dev/js/components/report.js b/dev/js/components/report.js new file mode 100644 index 0000000..3c721da --- /dev/null +++ b/dev/js/components/report.js @@ -0,0 +1,25 @@ +import $ from "jquery"; +import AbstractDialog from "./abstract-dialog.js"; +import app from "../app.js"; +import worker from "../worker.js"; +import templates from "../templates.js"; + +let report = Object.assign(new AbstractDialog(".oe-report"), { + handleGlobalKeyUp: $.noop, // override the inherited behavior hiding the dialog on Esc key press + + print(data) { + this.updateProgress(100); + $("#oe-report-data").html(templates.get("report")({energy: data.energy, grad: data.norm})); + }, + + updateProgress(value) { + $("#oe-report-progress").attr("value", value); + } +}); + +report.listen([ + {type: "app:structure:loaded", owner: app, handler: "hide"}, + {type: "evolve:progress", owner: worker, handler: "updateProgress"} +]); + +export default report; \ No newline at end of file diff --git a/dev/js/components/save-log.js b/dev/js/components/save-log.js new file mode 100644 index 0000000..d0bbadf --- /dev/null +++ b/dev/js/components/save-log.js @@ -0,0 +1,35 @@ +import AbstractDialog from "./abstract-dialog.js"; +import worker from "../worker.js"; +import utils from "../utils.js"; + +let saveLog = Object.assign(new AbstractDialog(".oe-save-log-dialog"), { + handleEvolveLog(data) { + this.data = data; + this.show(); + }, + + handleSave(e) { + let data = this.data, + E = data.E, + grad = data.grad, + dt = data.dt, + t = 0, + dl = e.target.getAttribute("data-delimiter"), + log = `t, ps${dl}E, eV${dl}||grad E||, eV/Å${dl}dt, fs`; + for (let i = 0, len = E.length; i < len; i++) { + log += "\n" + t.toExponential(4) + dl + E[i].toExponential(4) + dl + + grad[i].toExponential(4) + dl + (dt[i] * 1E15).toExponential(4); + t += dt[i] * 1E12; + } + e.target.href = utils.getBlobURL(log); + this.hide(); + this.data = null; + } +}); + +saveLog.listen([ + {type: "evolve:log", owner: worker, handler: "handleEvolveLog"}, + {type: "click", filter: "a[download]", handler: "handleSave"} +]); + +export default saveLog; \ No newline at end of file diff --git a/dev/js/components/save-summary.js b/dev/js/components/save-summary.js new file mode 100644 index 0000000..a3952ae --- /dev/null +++ b/dev/js/components/save-summary.js @@ -0,0 +1,62 @@ +import AbstractDialog from "./abstract-dialog.js"; +import worker from "../worker.js"; +import utils from "../utils.js"; +import app from "../app.js"; +import structure from "../structure.js"; +import templates from "../templates.js"; + +function nodeVisitor(name, node) { + if (node instanceof Map) { + let result = Object.create(null); + for (let [key, value] of node) { + result[key] = value; + } + return result; + } + return node; +} + +let saveSummary = Object.assign(new AbstractDialog(".oe-save-summary-form"), { + data: null, + + handleCollectStats(data) { + this.data = data; + this.show(); + }, + + handleSave(e) { + if (this.data) { + let type = e.target.getAttribute("data-type"), + text; + switch (type) { + case "text/html": + text = templates.get("summary")(this.data); + break; + case "application/json": + text = JSON.stringify(this.data, nodeVisitor, 2); + break; + default: + text = "TBD"; + break; + } + e.target.href = utils.getBlobURL(text, type); + this.hide(); + } + } +}); + +saveSummary.listen([ + {type: "collectStats", owner: worker, handler: "handleCollectStats"}, + {type: "click", filter: "a[download]", handler: "handleSave"} +]); + +export default saveSummary; + +app.addAction("saveSummary", { + get enabled() { + return structure.structure.potentials.size > 0; + }, + exec() { + worker.invoke("collectStats"); + } +}); \ No newline at end of file diff --git a/dev/js/components/save.js b/dev/js/components/save.js new file mode 100644 index 0000000..2e964e6 --- /dev/null +++ b/dev/js/components/save.js @@ -0,0 +1,41 @@ +import $ from "jquery"; +import AbstractDialog from "./abstract-dialog.js"; +import fileAPI from "../file-processing.js"; +import app from "../app.js"; +import structure from "../structure.js"; +import utils from "../utils.js"; + +let save = Object.assign(new AbstractDialog(".oe-save-form"), { + handleTypeChange(e) { + this.$el.find(".type-description") + .addClass("hidden") + .filter(`[data-type="${e.target.value}"]`).removeClass("hidden"); + }, + + handleSave(e) { + let selected = $("#oe-file-type").find("option:selected"), + type = selected.closest("optgroup").data("type"), + graphType = selected.data("graph"), + file = fileAPI.makeFile(type, graphType); + if (file) { + e.target.setAttribute("download", `untitled.${type}`); + e.target.href = utils.getBlobURL(file); + } + } +}); + +save.listen([ + {type: "change", owner: "#oe-file-type", handler: "handleTypeChange"}, + {type: "click", filter: ".oe-apply", handler: "handleSave"} +]); + +export default save; + +app.addAction("save", { + get enabled() { + return structure.structure.atoms.length > 0; + }, + exec() { + save.show(); + } +}); \ No newline at end of file diff --git a/dev/js/components/store.js b/dev/js/components/store.js new file mode 100644 index 0000000..1ab9a21 --- /dev/null +++ b/dev/js/components/store.js @@ -0,0 +1,54 @@ +import $ from "jquery"; +import AbstractDialog from "./abstract-dialog.js"; +import app from "../app.js"; +import templates from "../templates.js"; + +let store = Object.assign(new AbstractDialog(".oe-store-form"), { + handleSelect(e) { + $(e.delegateTarget).children(".active").removeClass("active"); + $(e.currentTarget).addClass("active"); + }, + + show() { + if (!this.loaded) { + let $list = this.$el.find(".oe-store-list"); + $list.addClass("oe-store-list-loading"); + $.getJSON("../store/info.json") + .done(data => this.resetHTML(data)) + .fail(() => this.resetHTML()) + .always(() => $list.removeClass("oe-store-list-loading")); + this.loaded = true; + } + return Object.getPrototypeOf(this).show.apply(this, arguments); + }, + + apply() { + let path = this.$el.find(".active[data-path]").data("path"); + if (path) { + app.execAction("load", `../store/${path}`); + } + }, + + resetHTML(data) { + let hasData = data && data.length, + html = hasData ? templates.get("store")({records: data}) : "
  • Data is empty or couldn't be loaded
  • "; + this.$el.find(".oe-store-list").html(html); + this.$el.find(".oe-apply").prop("disabled", !hasData); + } +}); + +store.listen([ + {type: "click", owner: ".oe-store-list", filter: "li[data-path]", handler: "handleSelect"}, + {type: "dblclick", owner: ".oe-store-list", filter: "li[data-path]", handler: "handleApply"} +]); + +export default store; + +app.addAction("openStore", { + get enabled() { + return true; + }, + exec() { + store.show(); + } +}); \ No newline at end of file diff --git a/dev/js/components/transform.js b/dev/js/components/transform.js new file mode 100644 index 0000000..31d007e --- /dev/null +++ b/dev/js/components/transform.js @@ -0,0 +1,58 @@ +import $ from "jquery"; +import AbstractDialog from "./abstract-dialog.js"; +import app from "../app.js"; +import structure from "../structure.js"; +import draw from "../draw.js"; + +let transform = Object.assign(new AbstractDialog(".oe-transform-form"), { + handleAppStateChange(busy) { + // Transformation of coordinates may take a while for large structures. + // Block interface while the app is busy + this.$el.find("fieldset").prop("disabled", busy); + }, + + handleTranslate() { + let $fieldSet = this.$el.find(".oe-translate"), + x = +$fieldSet.find("[data-axis='x']").val(), + y = +$fieldSet.find("[data-axis='y']").val(), + z = +$fieldSet.find("[data-axis='z']").val(); + structure.translate(x, y, z); + }, + + handleRotate(e) { + let angle = $("#oe-rotate-angle").val() * Math.PI / 180, + axis = e.target.getAttribute("data-axis"); + structure.rotate(angle, axis); + }, + + show() { + let center = structure.getCenterOfMass(); + let $fields = this.$el.find(".oe-translate input[data-axis]"); + $fields.val(idx => center[$fields.eq(idx).data("axis")].toFixed(5)); + draw.addAxes(); + return Object.getPrototypeOf(this).show.apply(this, arguments); + }, + + hide() { + draw.removeAxes(); + return Object.getPrototypeOf(this).hide.apply(this, arguments); + } +}); + +transform.listen([ + {type: "app:stateChange", owner: app, handler: "handleAppStateChange"}, + + {type: "click", owner: "#oe-translate-apply", handler: "handleTranslate"}, + {type: "click", filter: ".oe-rotate [data-axis]", handler: "handleRotate"} +]); + +export default transform; + +app.addAction("transform", { + get enabled() { + return structure.structure.atoms.length > 0; + }, + exec() { + transform.show(); + } +}); \ No newline at end of file diff --git a/dev/js/components/view.js b/dev/js/components/view.js new file mode 100644 index 0000000..4d5dc68 --- /dev/null +++ b/dev/js/components/view.js @@ -0,0 +1,80 @@ +import Eventful from "../eventful.js"; +import app from "../app.js"; +import draw from "../draw.js"; + +let view = Object.assign(new Eventful("#oe-view"), { + rotData: { + startX: 0, + startRot: 0 + }, + + handleDragEnterOver(e) { + e.preventDefault(); + if (e.type === "dragenter") { + e.currentTarget.classList.add("oe-droppable"); + } + }, + + handleDragLeave(e) { + e.preventDefault(); + if (e.target === e.currentTarget) { // skip event when fired by children + e.target.classList.remove("oe-droppable"); + } + }, + + handleDrop(e) { + let dt = e.originalEvent.dataTransfer, + files = dt && dt.files; + if (files && files.length) { + e.preventDefault(); + app.execAction("load", files[0]); + } + e.currentTarget.classList.remove("oe-droppable"); + }, + + handleWheelZoom(e) { + draw.zoom((e.originalEvent.deltaY) < 0 ? 5 : -5); + e.preventDefault(); + }, + + handleStartRotate(e) { + this.rotData.startX = e.pageX; + this.rotData.startRot = draw.rotation; + this.$el + .on("mouseup.oeViewRotation mouseleave.oeViewRotation", this.handleStopRotate.bind(this)) + .on("mousemove.oeViewRotation", this.handleRotate.bind(this)); + draw.autoUpdate = true; + draw.update(); + }, + + handleStopRotate() { + draw.autoUpdate = false; + this.$el.off(".oeViewRotation"); + }, + + handleRotate(e) { + draw.rotation = this.rotData.startRot + (e.pageX - this.rotData.startX) * 0.02; + }, + + handleWndResize() { + if (!this._resizeTimer) { + this._resizeTimer = setTimeout(() => { + draw.resize(); + this._resizeTimer = undefined; + }, 300); + } + } +}); + +view.listen([ + {type: "dragenter dragover", handler: "handleDragEnterOver"}, + {type: "dragleave", handler: "handleDragLeave"}, + {type: "drop", handler: "handleDrop"}, + {type: "wheel", handler: "handleWheelZoom"}, + {type: "mousedown", handler: "handleStartRotate"}, + {type: "resize", owner: window, handler: "handleWndResize"} +]); + +draw.setup(view.$el.children("canvas")[0]); + +export default view; \ No newline at end of file diff --git a/dev/js/draw.js b/dev/js/draw.js new file mode 100644 index 0000000..b046e17 --- /dev/null +++ b/dev/js/draw.js @@ -0,0 +1,241 @@ +import * as THREE from "three"; // http://stackoverflow.com/a/40251527 +import Cacheable from "./cacheable.js"; +import structure from "./structure.js"; + +let colors = new Cacheable(color => new THREE.Color(color)); + +let presets = Object.create({ + get(el) { + return this.hasOwnProperty(el) ? this[el] : this._def; + }, + set(el, value) { + if (this.hasOwnProperty(el)) { + Object.assign(this[el], value); + } else { + this[el] = Object.assign({}, this._def, value); + } + } +}, { + _def: {value: Object.freeze({color: 0xFFFFFF, radius: 1})} +}); + +presets.set("C", {color: 0xFF0000}); +presets.set("H", {radius: 0.7}); + + +let pointMaterials = new Cacheable(atom => { + let preset = presets.get(atom); + return new THREE.PointsMaterial({color: preset.color, sizeAttenuation: false}); +}); + +let atomMaterials = new Cacheable(atom => { + let preset = presets.get(atom); + return new THREE.MeshLambertMaterial({color: preset.color}); +}); + +let atomGeometries = new Cacheable(atom => { + let preset = presets.get(atom); + return new THREE.SphereGeometry(preset.radius); +}); + +let bondMaterials = new Cacheable(type => { + // There are only 2 graph types: "basic" and "extra", however construction of bond materials through `Cacheable` + // has an advantage of lazy instantiation of complex objects (instances will be created only on actual need) + if (type === "extra") { + return new THREE.LineDashedMaterial({dashSize: 0.2, gapSize: 0.1, vertexColors: THREE.VertexColors}); + } else { // type === "basic" + return new THREE.LineBasicMaterial({vertexColors: THREE.VertexColors}); + } +}); + +let canvas; +let assets3; + +let rotation = 0; + +let draw = { + setup(canvasEl) { + canvas = {el: canvasEl, width: canvasEl.offsetWidth, height: canvasEl.offsetHeight}; + assets3 = { + scene: new THREE.Scene(), + group: new THREE.Object3D(), + camera: new THREE.PerspectiveCamera(75, canvas.width / canvas.height, 0.1, 1000), + spotLight: new THREE.SpotLight(0xFFFFFF), + renderer: new THREE.WebGLRenderer({canvas: canvasEl}) + }; + assets3.spotLight.position.set(-40, 60, 50); + assets3.scene.add(assets3.group, assets3.spotLight); + assets3.camera.position.x = 0; + assets3.camera.position.y = 0; + assets3.camera.position.z = 20; + assets3.camera.lookAt(assets3.scene.position); + assets3.renderer.setClearColor(0x000000); + assets3.renderer.setSize(canvas.width, canvas.height); + assets3.renderer.render(assets3.scene, assets3.camera); + }, + + get canvas() { + return canvas.el; + }, + + get rotation() { + return rotation; + }, + set rotation(value) { + if (value !== rotation && Number.isFinite(value)) { + rotation = value; + assets3.group.rotation.y += (rotation - assets3.group.rotation.y) * 0.05; + } + }, + + appearance: "graph", + + zoom(delta) { + assets3.camera.position.z += delta; + assets3.camera.lookAt(assets3.scene.position); + this.update(); + }, + + resize(width = canvas.el.offsetWidth, height = canvas.el.offsetHeight) { + if (width > 0 && height > 0 && (width !== canvas.width || height !== canvas.height)) { + assets3.camera.aspect = width / height; + assets3.camera.updateProjectionMatrix(); + assets3.renderer.setSize(width, height, false); + canvas.width = width; + canvas.height = height; + this.update(); + } + }, + + render() { + this.resetScene(); + this.update(); + }, + + update() { + assets3.renderer.render(assets3.scene, assets3.camera); + if (this.autoUpdate) { + requestAnimationFrame(() => this.update()); + } + }, + + getAtomColor(el) { + return presets.get(el).color; + }, + + setAtomColors(colors) { + for (let [el, color] of colors) { + if (this.getAtomColor(el) !== color) { + presets.set(el, {color}); + // Sphere and point materials (and colors) are cached. Forces colors to update + atomMaterials.renew(el); + pointMaterials.renew(el); + } + } + }, + + setBgColor(color) { + if (typeof color === "string") { + color = Number(color.replace("#", "0x")); + } + assets3.renderer.setClearColor(color); + }, + + clearScene() { + let group = assets3.group; + let child = group.children[0]; + while (child) { + group.remove(child); + child = group.children[0]; + } + }, + + resetScene() { + this.clearScene(); + if (this.appearance === "spheres") { + this.addSceneAtoms(); + } else if (structure.structure.bonds.length) { + this.addSceneBonds(); + } else { + this.addScenePoints(); + } + }, + + addSceneAtoms() { + let Mesh = THREE.Mesh, + group = assets3.group; + for (let {el, x, y, z} of structure.structure.atoms) { + let atom3 = new Mesh(atomGeometries.get(el), atomMaterials.get(el)); + atom3.position.x = x; + atom3.position.y = y; + atom3.position.z = z; + group.add(atom3); + } + }, + + addSceneBonds() { + let Line = THREE.Line, + Vector3 = THREE.Vector3, + group = assets3.group, + atoms = structure.structure.atoms, + bindMap = new Int8Array(atoms.length); + for (let {type, iAtm, jAtm} of structure.structure.bonds) { + bindMap[iAtm] = bindMap[jAtm] = 1; + let bondGeometry = new THREE.Geometry(); + let atom = atoms[iAtm]; + bondGeometry.vertices.push(new Vector3(atom.x, atom.y, atom.z)); + bondGeometry.colors.push(colors.get(presets.get(atom.el).color)); + atom = atoms[jAtm]; + bondGeometry.vertices.push(new Vector3(atom.x, atom.y, atom.z)); + bondGeometry.colors.push(colors.get(presets.get(atom.el).color)); + if (type === "x") { + bondGeometry.computeLineDistances(); + group.add(new Line(bondGeometry, bondMaterials.get("extra"), THREE.LineStrip)); + } else { + group.add(new Line(bondGeometry, bondMaterials.get("basic"))); + } + } + + let Points = THREE.Points; + // Draw points for unbound atoms (if any) + let i = bindMap.indexOf(0); + while (i !== -1) { + let pointGeometry = new THREE.Geometry(); + let atom = atoms[i]; + pointGeometry.vertices.push(new Vector3(atom.x, atom.y, atom.z)); + group.add(new Points(pointGeometry, pointMaterials.get(atom.el))); + i = bindMap.indexOf(0, i + 1); + } + }, + + addScenePoints() { + let Points = THREE.Points, + Vector3 = THREE.Vector3, + group = assets3.group; + for (let {el, x, y, z} of structure.structure.atoms) { + let pointGeometry = new THREE.Geometry(); + pointGeometry.vertices.push(new Vector3(x, y, z)); + group.add(new Points(pointGeometry, pointMaterials.get(el))); + } + }, + + addAxes() { + if (!this.axes) { + this.axes = new THREE.AxisHelper(20); + assets3.scene.add(this.axes); + this.update(); + } + }, + + removeAxes() { + if (this.axes) { + assets3.scene.remove(this.axes); + this.axes = undefined; + this.update(); + } + } +}; + +export default draw; + +structure.on("updateStructure", draw.render.bind(draw)); \ No newline at end of file diff --git a/dev/js/eventful.js b/dev/js/eventful.js new file mode 100644 index 0000000..f07db1f --- /dev/null +++ b/dev/js/eventful.js @@ -0,0 +1,25 @@ +import Observer from "./observer.js"; +import $ from "jquery"; + +function to$(target) { + return (target && target.jquery) ? target : $(target); +} + +export default class { + constructor($el) { + this.$el = to$($el); + } + + listen(config) { + for (let {type, owner, filter, handler} of config) { + let handlerFn = (typeof handler === "function") ? handler : this[handler]; + if (owner instanceof Observer) { + // Events triggered on instances of the Observer class + owner.on(type, handlerFn.bind(this)); + } else { + // DOM events and custom events processed by jQuery + to$(owner || this.$el).on(type, filter || null, handlerFn.bind(this)); + } + } + } +} \ No newline at end of file diff --git a/dev/js/file-processing.js b/dev/js/file-processing.js new file mode 100644 index 0000000..d6166d4 --- /dev/null +++ b/dev/js/file-processing.js @@ -0,0 +1,199 @@ +import utils from "./utils.js"; +import structure from "./structure.js"; + +let formats = {}; + +formats.hin = { + /* HIN file syntax defines the atom record as follows: + atom + 0 1 2 3 4 5 6 7 8 9 10 11... */ + parseMolecule(atomRecords, result) { + let {atoms, bonds} = result, + inc = atoms.length, // total number of atoms added into the structure previously + spaceRE = /\s+/; + for (let i = 0, len = atomRecords.length; i < len; i++) { + let items = atomRecords[i].trim().split(spaceRE); + atoms.push({el: items[3], x: +items[7], y: +items[8], z: +items[9]}); + for (let j = 11, cn = 2 * items[10] + 11; j < cn; j += 2) { + if (items[j] - 1 > i) { + bonds.push({ + iAtm: i + inc, + jAtm: items[j] - 1 + inc, + type: items[j + 1] + }); + } + } + } + }, + + parse(fileStr) { + let molRE = /\n\s*mol\s+(\d+)([\s\S]+)\n\s*endmol\s+\1/g, + atmRE = /^atom\s+\d+\s+.+$/gm, + result = {atoms: [], bonds: []}, + mol = molRE.exec(fileStr); + while (mol) { + this.parseMolecule(mol[2].match(atmRE), result); + mol = molRE.exec(fileStr); + } + return result; + } +}; + +formats.ml2 = formats.mol2 = { + /* MOL2 file syntax defines the atom record as follows: + atom_id atom_name x y z atom_type [subst_id [subst_name [charge [status_bit]]]] + 0 1 2 3 4 5 6 7 8 9 + MOL2 file syntax defines the bond record as follows: + bond_id origin_atom_id target_atom_id bond_type [status_bits] + 0 1 2 3 4 */ + parseMolecule(atomRecords, bondRecords, result) { + let {atoms, bonds} = result, + inc = atoms.length, // total number of atoms added into the structure previously + spaceRE = /\s+/; + for (let rec of atomRecords) { + let items = rec.trim().split(spaceRE); + let dotPos = items[5].indexOf("."); // atom_type may look like "C.3" + atoms.push({ + el: (dotPos > -1) ? items[5].slice(0, dotPos) : items[5], + x: +items[2], + y: +items[3], + z: +items[4] + }); + } + for (let rec of bondRecords) { + let items = rec.trim().split(spaceRE); + bonds.push({ + iAtm: items[1] - 1 + inc, + jAtm: items[2] - 1 + inc, + type: items[3] + }); + } + }, + + parse(fileStr) { + let result = {atoms: [], bonds: []}, + molChunks = fileStr.split("@MOLECULE").slice(1), + atomRE = /@ATOM([\s\S]+?)(?:@|$)/, + bondRE = /@BOND([\s\S]+?)(?:@|$)/, + newLineRE = /(?:\r?\n)+/, + noRec = []; + for (let chunk of molChunks) { + let atomSection = chunk.match(atomRE); + let atomRecords = (atomSection && atomSection[1].trim().split(newLineRE)) || noRec; + let bondSection = chunk.match(bondRE); + let bondRecords = (bondSection && bondSection[1].trim().split(newLineRE)) || noRec; + this.parseMolecule(atomRecords, bondRecords, result); + } + return result; + } +}; + +formats.xyz = { + parseAtomRecord(atomStr) { + let items = atomStr.trim().split(/\s+/); + return { + el: items[0], + x: +items[1], + y: +items[2], + z: +items[3] + }; + }, + + parse(fileStr) { + let atomRecords = fileStr.split(/(?:\r?\n)+/).slice(2); + return atomRecords && { + atoms: atomRecords.map(this.parseAtomRecord, this), + bonds: [] + }; + } +}; + + +export default { + load(fileRef) { + return utils.readFile(fileRef).then(contents => { + let name = fileRef.name || String(fileRef), + type = name.slice(name.lastIndexOf(".") + 1).toLowerCase(), + format = formats[type] || formats.hin, + newStructure = format.parse(contents); + newStructure.name = name.replace(/.*[\/\\]/, "") || "unknown"; + structure.overwrite(newStructure); + return contents; + }); + }, + + makeFile(type, graphType) { + type = type.toUpperCase(); + if (typeof this[`make${type}`] === "function") { + return this[`make${type}`](graphType); + } + return false; + }, + + makeHIN(graphType) { + let hin = ";The structure was saved in OpenEvolver\nforcefield mm+\n"; + if (graphType === "empty") { + let i = 0; + for (let {el, x, y, z} of structure.structure.atoms) { + hin += `mol ${++i} +atom 1 - ${el} ** - 0 ${x.toFixed(4)} ${y.toFixed(4)} ${z.toFixed(4)} 0 +endmol ${i} +`; + } + } else { + let nbors = new Array(structure.structure.atoms.length); + for (let {type, iAtm, jAtm} of structure.structure.bonds) { + if (graphType !== "basic" || type !== "x") { + (nbors[iAtm] || (nbors[iAtm] = [])).push(`${jAtm + 1} ${type}`); + (nbors[jAtm] || (nbors[jAtm] = [])).push(`${iAtm + 1} ${type}`); + } + } + hin += "mol 1\n"; // multiple molecule cases are not supported in this version + let i = -1; + for (let {el, x, y, z} of structure.structure.atoms) { + hin += `atom ${++i + 1} - ${el} ** - 0 ${x.toFixed(4)} ${y.toFixed(4)} ${z.toFixed(4)} ` + + (nbors[i] ? `${nbors[i].length} ${nbors[i].join(" ")}` : "0") + "\n"; + } + hin += "endmol 1"; + } + return hin; + }, + + makeML2(graphType) { + let ml2 = `# The structure was saved in OpenEvolver +@MOLECULE +**** +${structure.structure.atoms.length} %BOND_COUNT% +SMALL +NO_CHARGES + + +@ATOM +`; // bond count is TBD later + let i = 0; + for (let {el, x, y, z} of structure.structure.atoms) { + ml2 += `${++i} ${el} ${x.toFixed(4)} ${y.toFixed(4)} ${z.toFixed(4)} ${el} 1 **** 0.0000\n`; + } + let bondCount = 0; + if (graphType !== "empty") { + ml2 += "@BOND\n"; + for (let {type, iAtm, jAtm} of structure.structure.bonds) { + if (graphType !== "basic" || type !== "x") { + bondCount++; + ml2 += `${bondCount} ${iAtm + 1} ${jAtm + 1} ${type}\n`; + } + } + } + ml2 += "@SUBSTRUCTURE\n1 **** 0"; + ml2 = ml2.replace("%BOND_COUNT%", bondCount.toString()); + return ml2; + }, + + makeXYZ() { + let xyz = structure.structure.atoms.length + "\nThe structure was saved in OpenEvolver"; + for (let {el, x, y, z} of structure.structure.atoms) { + xyz += `\n${el} ${x.toFixed(5)} ${y.toFixed(5)} ${z.toFixed(5)}`; + } + return xyz; + } +}; \ No newline at end of file diff --git a/dev/js/interface.js b/dev/js/interface.js new file mode 100644 index 0000000..4a74c61 --- /dev/null +++ b/dev/js/interface.js @@ -0,0 +1,24 @@ +import structure from "./structure.js"; +import app from "./app.js"; +import "./components/store.js"; +import "./components/save.js"; +import "./components/save-summary.js"; +import "./components/graph.js"; +import "./components/potentials.js"; +import "./components/transform.js"; +import "./components/report.js"; +import "./components/evolve.js"; +import "./components/save-log.js"; +import "./components/appearance.js"; +import "./components/info.js"; +import "./components/menu.js"; +import "./components/view.js"; + + +structure.on("app:structure:loaded", () => { + document.title = `${structure.structure.name} - Open evolver`; +}); + +app.on("app:stateChange", busy => { + document.body.classList.toggle("app-busy", busy); +}); \ No newline at end of file diff --git a/dev/js/observer.js b/dev/js/observer.js new file mode 100644 index 0000000..63e235c --- /dev/null +++ b/dev/js/observer.js @@ -0,0 +1,63 @@ +let handlerRegistry = new WeakMap(); + +class Observer { + constructor() { + handlerRegistry.set(this, new Map()); + } + + // These static methods make the Observer class itself implement the Observer interface. + // This may be used for broadcast messaging with the Observer object as a target + static on(...params) { + return Observer.prototype.on.apply(Observer, params); + } + + static off(...params) { + return Observer.prototype.off.apply(Observer, params); + } + + static trigger(...params) { + return Observer.prototype.trigger.apply(Observer, params); + } + + on(event, handler) { + let handlers = handlerRegistry.get(this); + if (!handlers.has(event)) { + handlers.set(event, []); + } + handlers.get(event).push(handler); + } + + off(event, handler) { + let handlers = handlerRegistry.get(this); + if (!handlers.has(event)) { + return; + } + if (handler) { + let eventHandlers = handlers.get(event); + let handlerIndex = eventHandlers.indexOf(handler); + if (handlerIndex > -1) { + eventHandlers.splice(handlerIndex, 1); + if (eventHandlers.length === 0) { + handlers.delete(event); + } + } + } else { + handlers.get(event).length = 0; + handlers.delete(event); + } + } + + trigger(event, ...params) { + let handlers = handlerRegistry.get(this); + if (handlers.has(event)) { + for (let handler of handlers.get(event)) { + handler.apply(null, params); + } + } + } +} + +// Observer class itself implements the Observer interface (this is instead of the constructor call) +handlerRegistry.set(Observer, new Map()); + +export default Observer; \ No newline at end of file diff --git a/dev/js/structure.js b/dev/js/structure.js new file mode 100644 index 0000000..145eeaa --- /dev/null +++ b/dev/js/structure.js @@ -0,0 +1,196 @@ +import Observer from "./observer.js"; +import app from "./app.js"; +import utils from "./utils.js"; +import worker from "./worker.js"; + +/** + * An atom record in a structure + * @typedef {Object} Atom + * @property {String} el - Element + * @property {Number} x - X-coordinate of an atom + * @property {Number} y - Y-coordinate of an atom + * @property {Number} z - Z-coordinate of an atom + */ + +/** + * A bond record in a structure's bond registry + * @typedef {Object} Bond + * @property {Number} iAtm - The bond's first atom index in the `atoms` array + * @property {Number} jAtm - The bond's second atom index in the `atoms` array + * @property {String} type - Bond type (e.g. "s" for single bond, "a" for aromatic, "x" for extra) + * @property {Object} potential - A reference to the Potential record for this bond + */ + +/** + * A bond potential parameters + * @typedef {Object} Potential + * @property {Number} D0 - Dissociation energy (in eV) + * @property {Number} R0 - Equilibrium bond distance (in Å) + * @property {Number} w0 - Vibration frequency (in 1/cm) + * @property {Number} b - Potential stiffness coefficient (in 1/Å) + */ + +/** + * Current structure data + * @typedef {Object} Structure + * @property {String} name - The name of a structure + * @property {Array.} atoms - Atom list + * @property {Array.} bonds - Bond list + * @property {Map.} potentials - Maps atom pair strings onto potential data objects + */ + +/** + * @type {Structure} + */ +let structure = { + name: "", + atoms: [], + bonds: [], + potentials: new Map() +}; + +let atomSet = new Set(); +let pairSet = new Set(); + +let structureUtils = Object.assign(new Observer(), { + /** + * Get a list of all possible (chemically bound or not) atomic pairs for the current structure + * @param {String} [type] Pairs of which graph type to return: basic, extra, or both + * @returns {Array} + */ + getPairList(type) { + switch (type) { + case "basic": + return [...pairSet].filter(pair => !pair.startsWith("x-")); + case "extra": + return [...pairSet].filter(pair => pair.startsWith("x-")); + default: + return [...pairSet]; + } + }, + + getPairSet(type) { + return new Set(this.getPairList(type)); + }, + + /** + * Get a list of all chemical elements present in the current structure + * @returns {Array} + */ + getAtomList() { + return [...atomSet]; + }, + + getAtomSet() { + return new Set(this.getAtomList()); + }, + + /** + * Completely overwrites the `structure` object. Call this method when a new file is opened, + * or when the structure needs to be updated according the result of a worker calculations + * @param {Object} newStructure + * @param {Boolean} [rescanAtoms] Pass `false` to prevent `atomSet` and `pairSet` update. + * By default they are updated as well + * @param {Boolean} [fromWorker] For internal use. Pass `true` to prevent notifying the worker + */ + overwrite(newStructure, rescanAtoms = true, fromWorker = false) { + ({name: structure.name = "", atoms: structure.atoms = [], bonds: structure.bonds = [], + potentials: structure.potentials = new Map()} = newStructure); + if (rescanAtoms !== false) { + atomSet = new Set(structure.atoms.map(atom => atom.el)); + let atomList = this.getAtomList(); + let pairList = []; + for (let [i, el] of atomList.entries()) { + pairList.push(...atomList.slice(i).map(elem => el + elem)); + } + // Add extra-graph pairs + pairList.push(...pairList.map(pair => `x-${pair}`)); + pairSet = new Set(pairList); + } + this.trigger("updateStructure", rescanAtoms !== false); + if (fromWorker !== true) { + app.trigger("app:structure:loaded"); + syncWorker(); + } + }, + + setPotentials(potentials) { + structure.potentials = potentials; + for (let bond of structure.bonds) { + let prefix = (bond.type === "x") ? "x-" : ""; + let atoms = structure.atoms; + bond.potential = + potentials.get(prefix + atoms[bond.iAtm].el + atoms[bond.jAtm].el) || + potentials.get(prefix + atoms[bond.jAtm].el + atoms[bond.iAtm].el); + } + app.trigger("app:structure:paramsSet"); + syncWorker(); + }, + + getCenterOfMass() { + let result = {x: 0, y: 0, z: 0}; + let mass = 0; + for (let {el, x, y, z} of structure.atoms) { + const atomicMass = utils.atomicMasses[el]; + mass += atomicMass; + result.x += atomicMass * x; + result.y += atomicMass * y; + result.z += atomicMass * z; + } + result.x /= mass; + result.y /= mass; + result.z /= mass; + return result; + }, + + translate(x, y, z) { + let center = this.getCenterOfMass(), + dx = x - center.x, + dy = y - center.y, + dz = z - center.z; + for (let atom of structure.atoms) { + atom.x += dx; + atom.y += dy; + atom.z += dz; + } + this.overwrite(structure, false, false); + }, + + rotate(angle, axis) { + let axis2 = (axis === "x") ? "y" : "x", + axis3 = (axis === "z") ? "y" : "z", + sine = Math.sin(angle), + cosine = Math.cos(angle); + for (let atom of structure.atoms) { + let coord2 = atom[axis2]; + let coord3 = atom[axis3]; + atom[axis2] = coord2 * cosine + coord3 * sine; + atom[axis3] = coord3 * cosine - coord2 * sine; + } + this.overwrite(structure, false, false); + } +}); + +Object.defineProperty(structureUtils, "structure", { + enumerable: true, + get() { + return structure; // not safe but fast… Freezing would result in performance degradation + } +}); + +export default structureUtils; + + +function syncWorker() { + worker.invoke("setStructure", structureUtils.structure); +} + +worker.on("setStructure", updatedStructure => { + // The worker optimizes structure's bond array. So, do sync + structureUtils.overwrite(updatedStructure, false, true); +}); + +worker.on("updateStructure", updatedStructure => { + // No need to rescan atom list, since only coordinates were changed + structureUtils.overwrite(updatedStructure, false, true); +}); \ No newline at end of file diff --git a/dev/js/templates.js b/dev/js/templates.js new file mode 100644 index 0000000..70e99e8 --- /dev/null +++ b/dev/js/templates.js @@ -0,0 +1,20 @@ +import app from "./app.js"; +import utils from "./utils.js"; +import _ from "_"; + +let templates = new Map(); + +app.busy = true; + +utils.readFile("tpl/tpl.json").then(json => { + let tpls = JSON.parse(json); + let tplSettings = {variable: "data"}; + for (let name of Object.keys(tpls)) { + templates.set(name, _.template(tpls[name], tplSettings)); + } + app.busy = false; +}); + +export default { + get: templates.get.bind(templates) +}; \ No newline at end of file diff --git a/dev/js/utils.js b/dev/js/utils.js new file mode 100644 index 0000000..58bfdf2 --- /dev/null +++ b/dev/js/utils.js @@ -0,0 +1,53 @@ +import app from "./app.js"; + +let blobUrl; + +let utils = { + atomicMasses: {}, + + /** + * Read a file + * @param {String|Object} ref File reference - either a path, or a file object (or Blob) + * @returns {Promise} + */ + readFile(ref) { + return new Promise((resolve, reject) => { + if (typeof ref === "string") { // file path was passed + let xhr = new XMLHttpRequest(); + xhr.open("GET", ref, true); + xhr.addEventListener("load", () => { + if (xhr.status === 200) { + resolve(xhr.responseText); + } + }); + xhr.addEventListener("error", () => reject(xhr.status)); + xhr.send(null); + } else { // file object or blob was passed + let reader = new FileReader(); + reader.addEventListener("load", () => resolve(reader.result)); + reader.addEventListener("error", () => reject(reader.error)); + reader.readAsText(ref); + } + }); + }, + + getBlobURL(data, type = "text/plain") { + let blob = (data instanceof Blob) ? data : new Blob([data], {type}); + if (blobUrl) { + // Blob URLs are used only short periods of time (e.g. at the moment a hyperlink is clicked). + // So, revoke the previous URL before creating the new one. + URL.revokeObjectURL(blobUrl); + } + blobUrl = URL.createObjectURL(blob); + return blobUrl; + } +}; + +export default utils; + +app.busy = true; +utils.readFile("lib.json").then(libText => { + let lib = JSON.parse(libText); + utils.atomicMasses = Object.freeze(lib.atomicMasses); + app.busy = false; +}); \ No newline at end of file diff --git a/dev/js/worker.js b/dev/js/worker.js new file mode 100644 index 0000000..9a650ce --- /dev/null +++ b/dev/js/worker.js @@ -0,0 +1,31 @@ +import Observer from "./observer.js"; +import app from "./app.js"; + +let calcWorker = new Worker("js/calc.js"); +let blockingMethod = "ready"; +app.busy = true; // calc worker needs some initialization before it can be used + +let worker = Object.assign(new Observer(), { + invoke(method, data) { + if (blockingMethod) { + throw new Error(`Unable to run the method “${method}” as the blocking method “${blockingMethod}” is still running`); + } + blockingMethod = method; + app.busy = true; // note that every worker invocation turns the application into busy state + calcWorker.postMessage({method, data}); + } +}); + +calcWorker.addEventListener("message", ({data: {method, data} = {}}) => { + if (method) { + if (method === blockingMethod) { + app.busy = false; + blockingMethod = null; + } + worker.trigger(method, data); + } +}); + +calcWorker.addEventListener("error", e => {throw e;}); + +export default worker; \ No newline at end of file diff --git a/dev/lib.json b/dev/lib.json new file mode 100644 index 0000000..b7dc96e --- /dev/null +++ b/dev/lib.json @@ -0,0 +1,113 @@ +{ + "atomicMasses": { + "H": 1.00794, + "He": 4.002602, + "Li": 6.941, + "Be": 9.01218, + "B": 10.811, + "C": 12.011, + "N": 14.0067, + "O": 15.9994, + "F": 18.998403, + "Ne": 20.179, + "Na": 22.98977, + "Mg": 24.305, + "Al": 26.98154, + "Si": 28.0855, + "P": 30.97376, + "S": 32.066, + "Cl": 35.453, + "Ar": 39.948, + "K": 39.0983, + "Ca": 40.078, + "Sc": 44.95591, + "Ti": 47.88, + "V": 50.9415, + "Cr": 51.9961, + "Mn": 54.938, + "Fe": 55.847, + "Co": 58.9332, + "Ni": 58.69, + "Cu": 63.546, + "Zn": 65.39, + "Ga": 69.723, + "Ge": 72.59, + "As": 74.9216, + "Se": 78.96, + "Br": 79.904, + "Kr": 83.8, + "Rb": 85.4678, + "Sr": 87.62, + "Y": 88.9059, + "Zr": 91.224, + "Nb": 92.9064, + "Mo": 95.94, + "Tc": 97.9072, + "Ru": 101.07, + "Rh": 102.9055, + "Pd": 106.42, + "Ag": 107.8682, + "Cd": 112.41, + "In": 114.82, + "Sn": 118.71, + "Sb": 121.75, + "Te": 127.6, + "I": 126.9045, + "Xe": 131.29, + "Cs": 132.9054, + "Ba": 137.33, + "La": 138.9055, + "Ce": 140.12, + "Pr": 140.9077, + "Nd": 144.24, + "Pm": 144.9128, + "Sm": 150.36, + "Eu": 151.96, + "Gd": 157.25, + "Tb": 158.9254, + "Dy": 162.5, + "Ho": 164.9304, + "Er": 167.26, + "Tm": 168.9342, + "Yb": 173.04, + "Lu": 174.967, + "Hf": 178.49, + "Ta": 180.9479, + "W": 183.85, + "Re": 186.207, + "Os": 190.2, + "Ir": 192.22, + "Pt": 195.08, + "Au": 196.9665, + "Hg": 200.59, + "Tl": 204.383, + "Pb": 207.2, + "Bi": 208.9804, + "Po": 208.9824, + "At": 209.9871, + "Rn": 222.0176, + "Fr": 223.0197, + "Ra": 226.0254, + "Ac": 227.0278, + "Th": 232.0381, + "Pa": 231.0359, + "U": 238.0289, + "Np": 237.0482, + "Pu": 244.0642, + "Am": 243.0614, + "Cm": 247.0703, + "Bk": 247.0703, + "Cf": 251.0796, + "Es": 252.0828, + "Fm": 257.0951, + "Md": 258.0986, + "No": 259.1009, + "Lr": 260.1054, + "Rf": 261, + "Db": 262, + "Sg": 263, + "Bh": 262, + "Hs": 265, + "Mt": 266 + } +} \ No newline at end of file diff --git a/dev/src/css/dialogs/appearance.css b/dev/src/css/dialogs/appearance.css deleted file mode 100644 index b560e5e..0000000 --- a/dev/src/css/dialogs/appearance.css +++ /dev/null @@ -1 +0,0 @@ -.oe-appearance-colors input,.oe-appearance-colors label,.oe-appearance-colors select{display:inline-block;vertical-align:top}.oe-appearance-form{margin-left:-150px;width:300px}.oe-appearance-colors label{margin-right:20px} \ No newline at end of file diff --git a/dev/src/css/dialogs/dialogs.css b/dev/src/css/dialogs/dialogs.css deleted file mode 100644 index 609a9ba..0000000 --- a/dev/src/css/dialogs/dialogs.css +++ /dev/null @@ -1 +0,0 @@ -.oe-dialog{background:#fff;border:1px solid #0e0e0e;border-radius:5px;left:50%;margin-left:-250px;padding:0 15px;position:absolute;top:50px;width:500px;z-index:20}.oe-dialog:before{background:rgba(255,255,255,.8);content:"";height:100%;left:0;position:fixed;top:0;width:100%;z-index:-1}.oe-dialog-btns input{min-width:85px}.oe-dialog fieldset{border-color:#dadada;margin:1em 0}.oe-dialog-btns{text-align:right;border:none;padding:0} \ No newline at end of file diff --git a/dev/src/css/dialogs/evolve.css b/dev/src/css/dialogs/evolve.css deleted file mode 100644 index be1e040..0000000 --- a/dev/src/css/dialogs/evolve.css +++ /dev/null @@ -1 +0,0 @@ -.oe-evolve-params{background:url(data:image/gif;base64,R0lGODlhAQABAIAAANra2gAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==) 50% 0 repeat-y;float:left;margin-bottom:1em;width:100%}.oe-evolve-params fieldset{border:none;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;float:right;margin:0;padding:0 0 0 25px;width:49.5%}.oe-evolve-params fieldset:first-child{float:left;padding:0 25px 0 0}.oe-evolve-params fieldset>p:first-child{margin-top:0}.oe-evolve-form .oe-dialog-btns{clear:both} \ No newline at end of file diff --git a/dev/src/css/dialogs/graph.css b/dev/src/css/dialogs/graph.css deleted file mode 100644 index a11dc33..0000000 --- a/dev/src/css/dialogs/graph.css +++ /dev/null @@ -1 +0,0 @@ -.oe-graph-form{border-color:#dadada;border-radius:0 0 5px;border-width:0 1px 1px 0;left:600px;margin-left:0;top:29px;width:350px;z-index:1}.oe-graph-form:before{content:none}.oe-cutoffs{border:1px solid #dadada;padding:0}.oe-cutoff{background:0 0;border:none;border-top:1px solid #dadada;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;display:block;padding:0;text-align:left;width:100%}.oe-cutoff:first-child{border-top:none}.oe-cutoff.active,.oe-cutoff:focus{background:#f5f5f5;outline:0}.oe-cutoff:before{background:#f0f0f0;border-right:1px solid #dadada;content:attr(data-pair);display:inline-block;margin-right:5px;min-width:75px;padding-left:5px}.oe-cutoff-slider{padding:0;width:100%}.oe-cutoff-exact,.oe-cutoff-max,.oe-cutoff-min{display:block;width:32%}.oe-cutoff-exact input,.oe-cutoff-max input,.oe-cutoff-min input{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;width:100%}.oe-cutoff-min{float:left}.oe-cutoff-max{float:right}.oe-cutoff-exact{margin-left:auto;margin-right:auto} \ No newline at end of file diff --git a/dev/src/css/dialogs/potentials.css b/dev/src/css/dialogs/potentials.css deleted file mode 100644 index bf91c00..0000000 --- a/dev/src/css/dialogs/potentials.css +++ /dev/null @@ -1 +0,0 @@ -.oe-potentials{border:1px solid #dadada;border-collapse:collapse;display:table;list-style:none;margin:0;max-height:300px;overflow:auto;padding:0;table-layout:fixed;width:100%}.oe-potentials li{display:table-row;margin:0;padding:0}.oe-potentials li>label,.oe-potentials li>span{border:1px solid #dadada;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;display:table-cell;padding:2px 5px;vertical-align:middle;width:28%}.oe-potentials li>label:first-child,.oe-potentials li>span:first-child{background:#f0f0f0;width:16%}.oe-potentials li:first-child>span{background:#f0f0f0}.oe-potentials input{background:0 0;border:none;display:block;font-size:1em;height:1.5em;margin:0;padding:0;width:100%}.oe-potentials .missed label{background:url(data:image/gif;base64,R0lGODlhBQAFAIAAAOPj4////yH5BAAAAAAALAAAAAAFAAUAAAIHRH6GodhZAAA7) repeat}.oe-potentials sub{font-style:normal}.potential-filing label{color:#0c0;cursor:pointer;overflow:hidden;position:relative;text-decoration:underline}.potential-filing label:hover{text-decoration:none}.potential-filing label input{cursor:pointer;height:100%;left:0;opacity:.01;position:absolute;top:0;width:100%} \ No newline at end of file diff --git a/dev/src/css/dialogs/report.css b/dev/src/css/dialogs/report.css deleted file mode 100644 index d4af043..0000000 --- a/dev/src/css/dialogs/report.css +++ /dev/null @@ -1 +0,0 @@ -#oe-report-progress{position:relative}#oe-report-progress:after{content:attr(value) "%";font-size:.8em;left:0;margin-left:5px;position:absolute;text-align:center;width:100%}#oe-report-data>dt{font-weight:700}#oe-report-data>dd{margin:0 0 .5em} \ No newline at end of file diff --git a/dev/src/css/dialogs/save.css b/dev/src/css/dialogs/save.css deleted file mode 100644 index 002b106..0000000 --- a/dev/src/css/dialogs/save.css +++ /dev/null @@ -1 +0,0 @@ -.oe-save-form .oe-apply{margin-right:20px} \ No newline at end of file diff --git a/dev/src/css/dialogs/store.css b/dev/src/css/dialogs/store.css deleted file mode 100644 index 77439e5..0000000 --- a/dev/src/css/dialogs/store.css +++ /dev/null @@ -1 +0,0 @@ -.oe-store-list{list-style:none;max-height:250px;min-height:20px;overflow:auto;padding:0}.oe-store-list-loading{background:url(../../img/loader.gif) 50% 50% no-repeat}.oe-store-list>li{cursor:pointer;padding:.2em .5em}.oe-store-list h3{display:inline;font-size:1em;margin:0}.oe-store-list h3:after{color:#0c0;content:" …"}.oe-store-list p{color:transparent;display:inline;font-size:0;margin:0}.oe-store-list>.active{background:#e6e6e6}.oe-store-list>.active h3:after{content:none}.oe-store-list>.active p{color:#999;font-size:.85em} \ No newline at end of file diff --git a/dev/src/css/dialogs/transform.css b/dev/src/css/dialogs/transform.css deleted file mode 100644 index f0b3472..0000000 --- a/dev/src/css/dialogs/transform.css +++ /dev/null @@ -1 +0,0 @@ -.oe-transform-form{border-color:#dadada;border-radius:0 0 5px 0;border-width:0 1px 1px 0;left:600px;margin-left:0;top:29px;width:350px;z-index:1}.oe-transform-form:before{content:none}.oe-translate>legend:after{content:"\21A6"}.oe-translate label{display:inline-block;vertical-align:top}.oe-translate input[type=text]{width:75px}.oe-translate input[type=button]{color:#c00;display:inline-block;font:400 22px/20px monospace;vertical-align:top}.oe-rotate>legend:after{content:"\21BB"}.oe-rotate input[data-axis="x"]{color:#a00}.oe-rotate input[data-axis="y"]{color:#0a0}.oe-rotate input[data-axis="z"]{color:#00a}.oe-rotate>legend:after,.oe-translate>legend:after{font:400 20px/1px monospace;margin:0 5px} \ No newline at end of file diff --git a/dev/src/css/main.css b/dev/src/css/main.css deleted file mode 100644 index 8eaab60..0000000 --- a/dev/src/css/main.css +++ /dev/null @@ -1 +0,0 @@ -/*! normalize.css v3.0.2 | MIT License | git.io/normalize */img,legend{border:0}.oe-potentials,table{border-collapse:collapse}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}.hidden,[hidden],template{display:none}a{background-color:transparent;color:#0c0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,optgroup,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre,textarea{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}.oe-menu,legend,td,th{padding:0}table{border-spacing:0}body{margin:0;font:400 87.5%/1.57142857 Arial,sans-serif}a:hover{text-decoration:none}.oe-description{color:grey;font-size:.85em;line-height:1.2}.oe-acknowledgements{color:#dadada;font:.85714286em Arial,sans-serif;margin:3px 0 0 3px;position:absolute;z-index:1}.oe-acknowledgements:before{background:url(data:image/gif;base64,R0lGODlhBgAGAIABAP///zMzMyH5BAEAAAEALAAAAAAGAAYAAAIKRB5mibDnGmStAAA7) 50% 50% no-repeat #000;border:1px solid;border-radius:2px;content:"";cursor:pointer;display:inline-block;height:10px;margin:0 4px 0 0;vertical-align:middle;width:10px}#oe-view{float:left;font-size:0;line-height:0;margin-right:10px}#oe-view.oe-droppable{outline:#000 dashed 1px;position:relative}#oe-view.oe-droppable:after{background-image:repeating-linear-gradient(45deg,transparent,transparent 10px,rgba(255,255,255,.5) 10px,rgba(255,255,255,.5) 20px);content:"";height:100%;left:0;position:absolute;top:0;width:100%}#oe-view.oe-droppable *,#oe-view.oe-droppable:after{pointer-events:none}#oe-view,#oe-view canvas{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}#oe-view canvas{height:500px;width:600px}.oe-menu{background:#f0f0f0;border-bottom:1px solid #dadada;margin:0}.app-busy .oe-menu:after{content:url(../img/loader.gif);cursor:wait;float:right;margin:6px 8px 0 0}.oe-menu>li{display:inline-block;list-style:none;margin:0;padding:0;position:relative}.oe-menu button[menu]{cursor:pointer;background:0 0;border:none;padding:3px 10px}.oe-menu button[menu]:hover{background:#e6e6e6}.oe-menu button[menu]:before{content:attr(value)}.oe-menu menu[type=popup]{background:#f0f0f0;border:1px solid #dadada;margin:0;padding:0;position:absolute;visibility:hidden;z-index:2}.oe-menu menu[type=popup].expanded{visibility:visible}.oe-menu menuitem{cursor:pointer;display:block;max-width:250px;overflow:hidden;padding:5px 7px;text-overflow:ellipsis;white-space:nowrap}.oe-potentials,.oe-store-list{list-style:none;overflow:auto}.oe-menu menuitem:hover{background:#e6e6e6}.oe-menu menuitem:before{content:attr(label)}.oe-menu menuitem[disabled]{color:#a0a0a0}.oe-menu menuitem[disabled]:hover{background:0 0}.oe-menu menuitem[disabled]:before{text-shadow:0 1px 0 #fff}.oe-menu hr{border:1px inset #fff;color:#fff}#oe-file{cursor:pointer;display:none;height:32px;left:0;max-width:175px;opacity:.01;position:absolute;top:28px;z-index:2}menu.expanded+#oe-file{display:inline}.oe-dialog{background:#fff;border:1px solid #0e0e0e;border-radius:5px;left:50%;margin-left:-250px;padding:0 15px;position:absolute;top:50px;width:500px;z-index:20}.oe-dialog:before{background:rgba(255,255,255,.8);content:"";height:100%;left:0;position:fixed;top:0;width:100%;z-index:-1}.oe-dialog-btns input{min-width:85px}.oe-dialog fieldset{border-color:#dadada;margin:1em 0}.oe-dialog-btns{text-align:right;border:none;padding:0}.oe-store-list{max-height:250px;min-height:20px;padding:0}.oe-store-list-loading{background:url(../img/loader.gif) 50% 50% no-repeat}.oe-store-list>li{cursor:pointer;padding:.2em .5em}.oe-store-list h3{display:inline;font-size:1em;margin:0}.oe-store-list h3:after{color:#0c0;content:" …"}.oe-graph-form:before,.oe-store-list>.active h3:after{content:none}.oe-store-list p{color:transparent;display:inline;font-size:0;margin:0}.oe-store-list>.active{background:#e6e6e6}.oe-store-list>.active p{color:#999;font-size:.85em}.oe-save-form .oe-apply{margin-right:20px}.oe-graph-form{border-color:#dadada;border-radius:0 0 5px;border-width:0 1px 1px 0;left:600px;margin-left:0;top:29px;width:350px;z-index:1}.oe-cutoffs{border:1px solid #dadada;padding:0}.oe-cutoff{background:0 0;border:none;border-top:1px solid #dadada;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;display:block;padding:0;text-align:left;width:100%}.oe-cutoff:first-child{border-top:none}.oe-cutoff.active,.oe-cutoff:focus{background:#f5f5f5;outline:0}.oe-cutoff:before,.oe-potentials li:first-child>span{background:#f0f0f0}.oe-cutoff:before{border-right:1px solid #dadada;content:attr(data-pair);display:inline-block;margin-right:5px;min-width:75px;padding-left:5px}.oe-cutoff-slider{padding:0;width:100%}.oe-cutoff-exact,.oe-cutoff-max,.oe-cutoff-min{display:block;width:32%}.oe-cutoff-exact input,.oe-cutoff-max input,.oe-cutoff-min input{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;width:100%}.oe-cutoff-min{float:left}.oe-cutoff-max{float:right}.oe-cutoff-exact{margin-left:auto;margin-right:auto}.oe-potentials{border:1px solid #dadada;display:table;margin:0;max-height:300px;padding:0;table-layout:fixed;width:100%}.oe-potentials li{display:table-row;margin:0;padding:0}.oe-potentials li>label,.oe-potentials li>span{border:1px solid #dadada;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;display:table-cell;padding:2px 5px;vertical-align:middle;width:28%}.oe-potentials li>label:first-child,.oe-potentials li>span:first-child{background:#f0f0f0;width:16%}.oe-potentials input{background:0 0;border:none;display:block;font-size:1em;height:1.5em;margin:0;padding:0;width:100%}.oe-appearance-colors input,.oe-appearance-colors label,.oe-appearance-colors select,.oe-translate label{display:inline-block;vertical-align:top}.oe-potentials .missed label{background:url(data:image/gif;base64,R0lGODlhBQAFAIAAAOPj4////yH5BAAAAAAALAAAAAAFAAUAAAIHRH6GodhZAAA7)}.oe-potentials sub{font-style:normal}.potential-filing label{color:#0c0;cursor:pointer;overflow:hidden;position:relative;text-decoration:underline}.potential-filing label:hover{text-decoration:none}.potential-filing label input{cursor:pointer;height:100%;left:0;opacity:.01;position:absolute;top:0;width:100%}.oe-transform-form{border-color:#dadada;border-radius:0 0 5px;border-width:0 1px 1px 0;left:600px;margin-left:0;top:29px;width:350px;z-index:1}.oe-transform-form:before{content:none}.oe-translate>legend:after{content:"\21A6"}.oe-translate input[type=text]{width:75px}.oe-translate input[type=button]{color:#c00;display:inline-block;font:400 22px/20px monospace;vertical-align:top}.oe-rotate>legend:after{content:"\21BB"}.oe-rotate input[data-axis="x"]{color:#a00}.oe-rotate input[data-axis="y"]{color:#0a0}.oe-rotate input[data-axis="z"]{color:#00a}.oe-rotate>legend:after,.oe-translate>legend:after{font:400 20px/1px monospace;margin:0 5px}.oe-evolve-params{background:url(data:image/gif;base64,R0lGODlhAQABAIAAANra2gAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==) 50% 0 repeat-y;float:left;margin-bottom:1em;width:100%}.oe-evolve-params fieldset{border:none;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box;float:right;margin:0;padding:0 0 0 25px;width:49.5%}.oe-evolve-params fieldset:first-child{float:left;padding:0 25px 0 0}.oe-evolve-params fieldset>p:first-child{margin-top:0}.oe-evolve-form .oe-dialog-btns{clear:both}.oe-appearance-form{margin-left:-150px;width:300px}.oe-appearance-colors label{margin-right:20px}#oe-report-progress{position:relative}#oe-report-progress:after{content:attr(value) "%";font-size:.8em;left:0;margin-left:5px;position:absolute;text-align:center;width:100%}#oe-report-data>dt{font-weight:700}#oe-report-data>dd{margin:0 0 .5em} \ No newline at end of file diff --git a/dev/src/css/menu.css b/dev/src/css/menu.css deleted file mode 100644 index 5582ea8..0000000 --- a/dev/src/css/menu.css +++ /dev/null @@ -1 +0,0 @@ -.oe-menu{background:#f0f0f0;border-bottom:1px solid #dadada;margin:0;padding:0}.app-busy .oe-menu:after{content:url(../img/loader.gif);cursor:wait;float:right;margin:6px 8px 0 0}.oe-menu>li{display:inline-block;list-style:none;margin:0;padding:0;position:relative}.oe-menu button[menu]{cursor:pointer;background:0 0;border:none;padding:3px 10px}.oe-menu button[menu]:hover{background:#e6e6e6}.oe-menu button[menu]:before{content:attr(value)}.oe-menu menu[type=popup]{background:#f0f0f0;border:1px solid #dadada;margin:0;padding:0;position:absolute;visibility:hidden;z-index:2}.oe-menu menu[type=popup].expanded{visibility:visible}.oe-menu menuitem{cursor:pointer;display:block;max-width:250px;overflow:hidden;padding:5px 7px;text-overflow:ellipsis;white-space:nowrap}.oe-menu menuitem:hover{background:#e6e6e6}.oe-menu menuitem:before{content:attr(label)}.oe-menu menuitem[disabled]{color:#a0a0a0}.oe-menu menuitem[disabled]:hover{background:0 0}.oe-menu menuitem[disabled]:before{text-shadow:0 1px 0 #fff}.oe-menu hr{border:1px inset #fff;color:#fff}#oe-file{cursor:pointer;display:none;height:32px;left:0;max-width:175px;opacity:.01;position:absolute;top:28px;z-index:2}menu.expanded+#oe-file{display:inline} \ No newline at end of file diff --git a/dev/src/css/ui.css b/dev/src/css/ui.css deleted file mode 100644 index e69de29..0000000 diff --git a/dev/src/css/utils.css b/dev/src/css/utils.css deleted file mode 100644 index e69de29..0000000 diff --git a/dev/src/css/view.css b/dev/src/css/view.css deleted file mode 100644 index f554f24..0000000 --- a/dev/src/css/view.css +++ /dev/null @@ -1 +0,0 @@ -.oe-acknowledgements{color:#dadada;font:.85714286em Arial,sans-serif;margin:3px 0 0 3px;position:absolute;z-index:1}.oe-acknowledgements:before{background:url(data:image/gif;base64,R0lGODlhBgAGAIABAP///zMzMyH5BAEAAAEALAAAAAAGAAYAAAIKRB5mibDnGmStAAA7) 50% 50% no-repeat #000;border:1px solid;border-radius:2px;content:"";cursor:pointer;display:inline-block;height:10px;margin:0 4px 0 0;vertical-align:middle;width:10px}#oe-view{float:left;font-size:0;line-height:0;margin-right:10px}#oe-view.oe-droppable{outline:#000 dashed 1px;position:relative}#oe-view.oe-droppable:after{background-image:repeating-linear-gradient(45deg,transparent,transparent 10px,rgba(255,255,255,.5) 10px,rgba(255,255,255,.5) 20px);content:"";height:100%;left:0;position:absolute;top:0;width:100%}#oe-view.oe-droppable *,#oe-view.oe-droppable:after{pointer-events:none}#oe-view,#oe-view canvas{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}#oe-view canvas{height:500px;width:600px} \ No newline at end of file diff --git a/dev/src/js/app.js b/dev/src/js/app.js deleted file mode 100644 index e2ab259..0000000 --- a/dev/src/js/app.js +++ /dev/null @@ -1,249 +0,0 @@ -(function (global) { - -"use strict"; - -var _ = global._, - OE = global.OE, - app = OE.app = Object.create(OE.observer), - actions = app.actions = {}, - actionStore = {}, - busyCount = 0, - appState, - actionProto; - - -/** - * A set of predefined application states - * Important: do not add any properties into the `app` object until all the predefined states - * are declared. Once the states are created, their names will be kept in the `predefinedStateNames` - * variable - */ -Object.defineProperties(app, { - STARTED: { - value: Object.freeze({ - openStore: true, - load: true, - save: false, - saveSummary: false, - alterGraph: false, - setup: false, - transform: false, - calcEnergy: false, - calcGrad: false, - evolve: false, - alterView: false - }) - }, - STRUCTURE_LOADED: { - value: Object.freeze({ - openStore: true, - load: true, - save: true, - saveSummary: false, - alterGraph: true, - setup: true, - transform: true, - calcEnergy: false, - calcGrad: false, - evolve: false, - alterView: true - }) - }, - PARAMS_SET: { - value: Object.freeze({ - openStore: true, - load: true, - save: true, - saveSummary: true, - alterGraph: true, - setup: true, - transform: true, - calcEnergy: true, - calcGrad: true, - evolve: true, - alterView: true - }) - }, - BUSY: { - value: Object.freeze({ - openStore: false, - load: false, - save: false, - saveSummary: false, - alterGraph: false, - setup: false, - transform: false, - calcEnergy: false, - calcGrad: false, - evolve: false, - alterView: false - }) - }, - IDLE: { - value: Object.seal({ - openStore: undefined, - load: undefined, - save: undefined, - saveSummary: undefined, - alterGraph: undefined, - setup: undefined, - transform: undefined, - calcEnergy: undefined, - calcGrad: undefined, - evolve: undefined, - alterView: undefined - }) - } -}); - -// There should be no other own properties except predefined state objects at this point! -(function (predefinedStateNames) { - app._isStatePredefined = function (state) { - if (state === app.IDLE) { // IDLE is a special case since it can mutate - return false; - } - for (var i = predefinedStateNames.length - 1; i >= 0; i--) { - if (app[predefinedStateNames[i]] === state) { - return true; - } - } - return false; - }; -})(Object.getOwnPropertyNames(app)); - -Object.defineProperty(app, "state", { - configurable: false, - enumerable: true, - get: function () { - return (app._isStatePredefined(appState)) ? appState : _.clone(appState); - }, - set: function (actionStates) { - var action; - if (actionStates === appState) { - if (appState === app.BUSY) { - busyCount++; - } - return; - } - if (actionStates === app.BUSY) { - busyCount++; - // Store the current action states to be able to reproduce them on returning to idle - for (action in app.IDLE) { - if (app.IDLE.hasOwnProperty(action)) { - app.IDLE[action] = actions[action].enabled; - } - } - } else if (actionStates === app.IDLE) { - // It's not possible to fall into idle if not currently busy. - // And if busyCount stays positive after decrementing then the app remains busy so far - if ((appState !== app.BUSY) || (--busyCount > 0)) { - return; - } - } else if (appState === app.BUSY) { - return; // IDLE is the only state the busy application can fall into - } - // If `actionStates` is one of the predefined state objects then assign it to appState - // directly (as is), else clone `actionStates` - appState = app._isStatePredefined(actionStates) ? actionStates : _.clone(actionStates); - app.trigger("stateChange"); - } -}); - -/** - * Create an application-level action - * @param {String} name The unique name of an action to be created - * @param {Function} execFn A function to be called when the action is executed - */ -app.addAction = function (name, execFn) { - actions[name] = Object.create(actionProto, {name: {value: name}}); - actionStore[name] = {exec: execFn}; -}; - -actionProto = Object.create(Object.prototype, { - enabled: { - enumerable: true, - configurable: true, - get: function () { - return app.state[this.name]; - }, - set: function (state) { - var actionStates = app.state; - state = !!state; - if (actionStates[this.name] !== state) { - actionStates[this.name] = state; - app.state = actionStates; - } - } - }, - exec: { - configurable: true, - value: function () { - var action = actionStore[this.name]; - if (action.enabled) { - action.exec.apply(this, arguments); - } - } - } -}); - -app.addAction("openStore", function () { - OE.ui.store.show(); -}); - -app.addAction("load", function (file) { - if (file) { - OE.fileAPI.load(file, function () { - OE.ui.report.hide(); - }); - } -}); - -app.addAction("save", function () { - OE.ui.save.show(); -}); - -app.addAction("saveSummary", function () { - OE.worker.invoke("collectStats"); -}); - -app.addAction("alterGraph", function () { - OE.ui.graph.show(); -}); - -app.addAction("setup", function () { - OE.ui.potentials.show(); -}); - -app.addAction("transform", function () { - OE.ui.transform.show(); -}); - -app.addAction("calcEnergy", function () { - OE.worker.invoke("totalEnergy"); -}); - -app.addAction("calcGrad", function () { - OE.worker.invoke("gradient"); -}); - -app.addAction("evolve", function () { - OE.ui.evolve.show(); -}); - -app.addAction("alterView", function () { - OE.ui.appearance.show(); -}); - -app.on("stateChange", function () { - var actionStates = app.state, - action; - for (action in actionStates) { - if (actionStates.hasOwnProperty(action) && actions.hasOwnProperty(action)) { - actionStore[action].enabled = actionStates[action]; - } - } -}); - -app.state = app.STARTED; - -})(this); \ No newline at end of file diff --git a/dev/src/js/calc.js b/dev/src/js/calc.js deleted file mode 100644 index c29e28f..0000000 --- a/dev/src/js/calc.js +++ /dev/null @@ -1,435 +0,0 @@ -(function (global) { - -"use strict"; - -var api = {}, - core = {}, - structure = null, - tightBondCount = 0, - grad = {}, - rndGrad = {}, - log = {}; - - -global.importScripts("utils.js"); // will add `OE.utils` into the global context of the worker - -api.setStructure = function (data) { - var bonds = data.bonds, - bondCount = bonds.length, - i, j; - // Move all existing extra-bonds to the end of a bond array. - // This allows to speed up iterations through bonds of extra-graph - for (i = 0, j = 0; i < bondCount; i++) { - if (bonds[j].type === "x") { - bonds.push(bonds.splice(j, 1)[0]); - } else { - j++; - } - } - tightBondCount = j; - structure = data; - return structure; -}; - -api.updateStructure = function () { - global.postMessage({method: "updateStructure", data: structure}); -}; - -api.totalEnergy = function () { - return core.totalEnergy(); -}; - -api.gradient = function () { - grad.alloc(); - core.gradient(); - grad.dispose(); - return core.norm; -}; - -api.evolve = function (data) { - core.evolveParams = data; - core.evolve(); - api.updateStructure(); - return {energy: core.totalEnergy(), norm: core.norm}; -}; - -api.reconnectPairs = function (data) { - var elements = data.pair.match(/[A-Z][^A-Z]*/g), - cutoff2 = data.cutoff * data.cutoff, - atoms = structure.atoms, - aLen = atoms.length, - bonds = structure.bonds, - bond, bLen, - i, j, k, - jEl; - for (i = 0; i < aLen; i++) { - if (atoms[i].el === elements[0]) { - jEl = elements[1]; - } else if (atoms[i].el === elements[1]) { - jEl = elements[0]; - } else { - continue; - } - for (j = i + 1; j < aLen; j++) { - if (atoms[j].el === jEl) { - for (k = tightBondCount, bond = bonds[k], bLen = bonds.length; k < bLen; bond = bonds[++k]) { - if ((bond.iAtm === i && bond.jAtm === j) || (bond.iAtm === j && bond.jAtm === i)) { - break; - } - } - if (core.sqrDistance(i, j) > cutoff2) { - if (bond) { - bonds.splice(k, 1); // break x-bond, as the distance is greater than cutoff - } - } else if (!bond) { - bonds.push({iAtm: i, jAtm: j, type: "x"}); // create x-bond, as one isn't exist yet - } - } - } - } - api.updateStructure(); -}; - -api.collectStats = function () { - var atoms = structure.atoms, - bonds = structure.bonds, - data = {}, - i, len, - prefix, pair, - distance; - - data.name = structure.name; - data.atomCount = len = atoms.length; - data.atoms = {}; - for (i = 0; i < len; i++) { - if (data.atoms.hasOwnProperty(atoms[i].el)) { - data.atoms[atoms[i].el]++; - } else { - data.atoms[atoms[i].el] = 1; - } - } - - data.bondCount = len = bonds.length; - data.bonds = {}; - for (i = 0; i < len; i++) { - prefix = (bonds[i].type === "x") ? "x-" : ""; - pair = prefix + atoms[bonds[i].jAtm].el + atoms[bonds[i].iAtm].el; - if (!data.bonds.hasOwnProperty(pair)) { - pair = prefix + atoms[bonds[i].iAtm].el + atoms[bonds[i].jAtm].el; - if (!data.bonds.hasOwnProperty(pair)) { - data.bonds[pair] = {count: 0, avgLen: 0, avgEnergy: 0, totEnergy: 0}; - } - } - distance = core.distance(bonds[i].iAtm, bonds[i].jAtm); - data.bonds[pair].count++; - data.bonds[pair].avgLen += distance; - data.bonds[pair].totEnergy += core.morse(bonds[i].potential, distance); - } - for (pair in data.bonds) { - if (data.bonds.hasOwnProperty(pair)) { - data.bonds[pair].avgLen /= data.bonds[pair].count; - data.bonds[pair].avgEnergy = data.bonds[pair].totEnergy / data.bonds[pair].count; - } - } - - data.potentials = structure.potentials; - data.totalEnergy = core.totalEnergy(); - return data; -}; - - -global.onmessage = function (e) { - var method = e.data && e.data.method; - if (typeof api[method] === "function") { - global.postMessage({ - method: method, - data: api[method].call(api, e.data.data) - }); - } -}; - - -grad.alloc = rndGrad.alloc = function () { - var atomCount = structure.atoms.length; - this.x = new Float32Array(atomCount); - this.y = new Float32Array(atomCount); - this.z = new Float32Array(atomCount); -}; -grad.dispose = rndGrad.dispose = function () { - this.x = this.y = this.z = null; -}; - - -log.alloc = function (size) { - this.data = { - E: new Float32Array(size), - grad: new Float32Array(size), - dt: new Float32Array(size) - }; -}; -log.dispose = function () { - this.data = null; -}; -log.write = function (index) { - var data = this.data; - data.E[index] = core.totalEnergy(); - data.grad[index] = core.norm; - data.dt[index] = core.timeStep(); -}; - - -/** - * In intensive calculations use this method for comparisons rather than `core.distance` - */ -core.sqrDistance = function (atom1, atom2) { - var at1 = structure.atoms[atom1], - at2 = structure.atoms[atom2], - dx = at1.x - at2.x, - dy = at1.y - at2.y, - dz = at1.z - at2.z; - return dx * dx + dy * dy + dz * dz; -}; - -core.distance = function (atom1, atom2) { - return Math.sqrt(core.sqrDistance(atom1, atom2)); -}; - -core.morse = function (params, distance) { - var exponent = Math.exp(params.b * (params.R0 - distance)); - return params.D0 * exponent * (exponent - 2); -}; - -core.derivative = function (params, distance) { - var cA = params.D0 * Math.exp(2 * params.b * params.R0), - cB = -2 * params.b, - cC = -2 * Math.sqrt(params.D0 * cA), - cD = Math.exp(-params.b * distance); - return cB * cD * (cA * cD + 0.5 * cC); -}; - -core.gradComponent = function (atom1, atom2, bond) { - var distance = core.distance(atom1, atom2), - factor = core.derivative(structure.bonds[bond].potential, distance) / distance, - at1 = structure.atoms[atom1], - at2 = structure.atoms[atom2]; - return { - x: factor * (at1.x - at2.x), - y: factor * (at1.y - at2.y), - z: factor * (at1.z - at2.z) - }; -}; - -core.totalEnergy = function () { - var energy = 0, - bonds = structure.bonds, - i, len; - for (i = 0, len = bonds.length; i < len; i++) { - energy += core.morse(bonds[i].potential, core.distance(bonds[i].iAtm, bonds[i].jAtm)); - } - return energy; -}; - -core.gradient = function () { - var atoms = structure.atoms, - atomCount = atoms.length, - bonds = structure.bonds, - bondCount = bonds.length, - utils = global.OE.utils, - gradComponent, - sqrForce, invNorm, - i, j, b, - mass; - - core.norm = core.sumSqr = core.rootSumSqr = 0; - - for (i = 0; i < atomCount; i++) { - grad.x[i] = grad.y[i] = grad.z[i] = 0; - for (b = 0; b < bondCount; b++) { - if (bonds[b].iAtm === i) { - j = bonds[b].jAtm; - } else if (bonds[b].jAtm === i) { - j = bonds[b].iAtm; - } else { - continue; - } - gradComponent = core.gradComponent(i, j, b); - grad.x[i] += gradComponent.x; - grad.y[i] += gradComponent.y; - grad.z[i] += gradComponent.z; - } - - sqrForce = grad.x[i] * grad.x[i] + grad.y[i] * grad.y[i] + grad.z[i] * grad.z[i]; - mass = utils.getAtomicMass(atoms[i].el); - core.sumSqr += sqrForce / mass; - core.rootSumSqr += sqrForce / (mass * mass); - core.norm += sqrForce; - } - - core.rootSumSqr = Math.sqrt(core.rootSumSqr); - core.norm = Math.sqrt(core.norm); - - // Calc unit vector of internal gradient - invNorm = 1 / core.norm; - for (i = 0; i < atomCount; i++) { - grad.x[i] *= invNorm; - grad.y[i] *= invNorm; - grad.z[i] *= invNorm; - } - - return core.norm; -}; - -core.stochGradient = function () { - var atoms = structure.atoms, - atomCount = atoms.length, - bonds = structure.bonds, - bondCount = bonds.length, - utils = global.OE.utils, - gradComponent, - sqrForce, rndNorm, invNorm, invRndNorm, rsltNorm, - i, j, b, - mass; - - rndNorm = core.norm = core.sumSqr = core.rootSumSqr = 0; - - for (i = 0; i < atomCount; i++) { - grad.x[i] = grad.y[i] = grad.z[i] = 0; - for (b = 0; b < bondCount; b++) { - if (bonds[b].iAtm === i) { - j = bonds[b].jAtm; - } else if (bonds[b].jAtm === i) { - j = bonds[b].iAtm; - } else { - continue; - } - gradComponent = core.gradComponent(i, j, b); - grad.x[i] += gradComponent.x; - grad.y[i] += gradComponent.y; - grad.z[i] += gradComponent.z; - } - - sqrForce = grad.x[i] * grad.x[i] + grad.y[i] * grad.y[i] + grad.z[i] * grad.z[i]; - mass = utils.getAtomicMass(atoms[i].el); - core.sumSqr += sqrForce / mass; - core.rootSumSqr += sqrForce / (mass * mass); - core.norm += sqrForce; - - rndGrad.x[i] = 50 - Math.random() * 100; - rndGrad.y[i] = 50 - Math.random() * 100; - rndGrad.z[i] = 50 - Math.random() * 100; - rndNorm += rndGrad.x[i] * rndGrad.x[i] + rndGrad.y[i] * rndGrad.y[i] + rndGrad.z[i] * rndGrad.z[i]; - } - - core.rootSumSqr = Math.sqrt(core.rootSumSqr); - core.norm = Math.sqrt(core.norm); - rndNorm = Math.sqrt(rndNorm); - rsltNorm = 0; - - // Calc unit vectors of internal and external gradient as well as resulting gradient - invNorm = 1 / core.norm; - invRndNorm = 1 / rndNorm; - for (i = 0; i < atomCount; i++) { - grad.x[i] *= invNorm; - grad.y[i] *= invNorm; - grad.z[i] *= invNorm; - rndGrad.x[i] *= invRndNorm; - rndGrad.y[i] *= invRndNorm; - rndGrad.z[i] *= invRndNorm; - grad.x[i] += rndGrad.x[i]; - grad.y[i] += rndGrad.y[i]; - grad.z[i] += rndGrad.z[i]; - rsltNorm += grad.x[i] * grad.x[i] + grad.y[i] * grad.y[i] + grad.z[i] * grad.z[i]; - } - - rsltNorm = Math.sqrt(rsltNorm); - - // Calc unit vector of resulting gradient - invNorm = 1 / rsltNorm; - for (i = 0; i < atomCount; i++) { - grad.x[i] *= invNorm; - grad.y[i] *= invNorm; - grad.z[i] *= invNorm; - } - - return core.norm; -}; - -core.timeStep = function () { - // dt=sqrt(3NkT/sumSqr); [sumSqr]=eV^2/(angst^2*amu) - return 1.636886e-16 * Math.sqrt(structure.atoms.length * core.evolveParams.temperature / core.sumSqr); -}; - -core.tuneEvolver = function () { - var params = core.evolveParams, - logInterval = params.logInterval, - functor = {}, - initFns = [], // functions to be called before the evolution procedure - stepFns = [], // functions to be called at every evolution step - finFns = []; // functions to be called after the evolution procedure is finished - initFns.push(grad.alloc.bind(grad)); - stepFns.push(params.stoch ? core.stochGradient.bind(core) : core.gradient.bind(core)); - finFns.push(grad.dispose.bind(grad)); - if (params.stoch) { - initFns.push(rndGrad.alloc.bind(rndGrad)); - finFns.push(rndGrad.dispose.bind(rndGrad)); - } - if (logInterval) { - initFns.push(log.alloc.bind(log, Math.floor(params.stepCount / logInterval))); - stepFns.push(function (stepNo) { - if (stepNo % logInterval === 0) { - log.write(stepNo / logInterval); - } - }); - finFns.push(function () { - global.postMessage({method: "evolve.log", data: log.data}); - log.dispose(); - }); - } - functor.initialize = function () { - initFns.forEach(function (fn) { - fn(); - }); - }; - functor.step = function (stepNo) { - // Prefer the `for` loop over `stepFns.forEach` for performance reasons - for (var i = 0, len = stepFns.length; i < len; i++) { - stepFns[i](stepNo); - } - }; - functor.finalize = function () { - finFns.forEach(function (fn) { - fn(); - }); - }; - return functor; -}; - -core.evolve = function () { - var params = core.evolveParams, - functor = core.tuneEvolver(), - atoms = structure.atoms, - atomCount = atoms.length, - factor = 1.2926E-4 * atomCount * params.temperature, // 1.5NkT [eV] - interval = Math.ceil(params.stepCount / 100), - progressFactor = 100 / params.stepCount, - progressMsg = {method: "evolve.progress"}, - stepNo, stepCount, step, i; - functor.initialize(); - functor.step(); // pre-calculate current value of gradient before the 1st step - for (stepNo = 0, stepCount = params.stepCount; stepNo < stepCount; stepNo++) { - step = factor * core.rootSumSqr / core.sumSqr; - for (i = 0; i < atomCount; i++) { - atoms[i].x -= step * grad.x[i]; - atoms[i].y -= step * grad.y[i]; - atoms[i].z -= step * grad.z[i]; - } - if (stepNo % interval === 0) { - progressMsg.data = stepNo * progressFactor; - global.postMessage(progressMsg); - } - functor.step(stepNo); - } - functor.finalize(); -}; - -})(this); \ No newline at end of file diff --git a/dev/src/js/core-polyfills.js b/dev/src/js/core-polyfills.js deleted file mode 100644 index 3fde349..0000000 --- a/dev/src/js/core-polyfills.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * A polyfill for Int8Array.prototype.indexOf() (mainly for IE) - * The method is used in view.js - */ -(function () { - -"use strict"; - -if ("indexOf" in Int8Array.prototype) { - return; -} - -Object.defineProperty(Int8Array.prototype, "indexOf", { - configurable: true, - enumerable: false, - writable: true, - value: function (searchElement, fromIndex) { - var len = this.length; - if (fromIndex === undefined) { - fromIndex = 0; - } else if (fromIndex < 0) { - fromIndex += len; - } - for (; fromIndex < len; fromIndex++) { - if (this[fromIndex] === searchElement) { - return fromIndex; - } - } - return -1; - } -}); - -})(); \ No newline at end of file diff --git a/dev/src/js/download-shim-ie.js b/dev/src/js/download-shim-ie.js deleted file mode 100644 index cbbda30..0000000 --- a/dev/src/js/download-shim-ie.js +++ /dev/null @@ -1,101 +0,0 @@ -(function (global) { - -"use strict"; - -var blobStore, - URL, createObjectURL, revokeObjectURL, - txtFrame, imgFrame; - -if ("download" in document.createElement("a")) { - return; -} - - -/** - * Here, we override the native methods `URL.createObjectURL` and `URL.revokeObjectURL` in order - * to make the `download` attribute work with hyperlinks using blob urls, since it is hardly - * possible to extract a blob type and contents from the previously constructed blob url string. - * The `blobStore` is a hash which maps blob urls onto either the text contents of the corresponding - * blob object, or, if it is an image, onto the string flag "\x00" (image contents makes no odds) - */ -blobStore = {}; -URL = global.URL; -createObjectURL = URL.createObjectURL; -revokeObjectURL = URL.revokeObjectURL; -URL.createObjectURL = function (blob) { - var result = createObjectURL.apply(this, arguments), - type = blob.type, - reader; - if (type.indexOf("text/") === 0) { - reader = new global.FileReader(); - reader.addEventListener("load", function (e) { - blobStore[result] = e.target.result; - }); - reader.readAsText(blob); - } else if (type.indexOf("image/") === 0) { - blobStore[result] = "\x00"; - } - return result; -}; -URL.revokeObjectURL = function (url) { - if (blobStore.hasOwnProperty(url)) { - delete blobStore[url]; - } - return revokeObjectURL.apply(this, arguments); -}; - - -/** - * Helper frame elements are used to invoke the `execCommand` method in context of their - * document objects. The `txtFrame` element is used to save text data, while the `imgFrame` element - * is used to save images. A separate frame element special for images is required since it seems - * that IE can only save a document as an image if the window location is an image file. - * The contents of the frames is dynamically changed when a hyperlink having the `download` attribute - * is clicked. Supported URI scheme list includes only "blob:" and "data:" - */ -txtFrame = document.createElement("iframe"); -txtFrame.src = "about:blank"; -imgFrame = document.createElement("iframe"); -imgFrame.src = "src/img/dot.gif"; -txtFrame.style.cssText = imgFrame.style.cssText = "position: absolute; top: -10000px; left: -10000px;"; -document.body.appendChild(txtFrame); -document.body.appendChild(imgFrame); - -function writeTxtDoc(text) { - var doc = txtFrame.contentDocument; - doc.open(); - doc.write(text); - doc.close(); - return doc; -} -function writeImgDoc(src) { - var doc = imgFrame.contentDocument; - doc.getElementsByTagName("img")[0].src = src; - return doc; -} - -document.body.addEventListener("click", function (e) { - // Defer for the case with `href` or `download` attribute changing on click - setTimeout(function () { - var target = e.target, - href, doc; - if ((target.nodeName.toLowerCase() === "a") && target.hasAttribute("download")) { - href = target.getAttribute("href"); - if ((href.indexOf("blob:") === 0) && blobStore.hasOwnProperty(href)) { - doc = (blobStore[href] === "\x00") ? writeImgDoc(href) : writeTxtDoc(blobStore[href]); - } else if (href.indexOf("data:text/") === 0) { - doc = writeTxtDoc(atob(href.replace(/^data:text\/\w+;base64,/, ""))); - } else if (href.indexOf("data:image/") === 0) { - doc = writeImgDoc(href); - } else { - return; - } - setTimeout(function () { - doc.execCommand("SaveAs", true, target.getAttribute("download")); - }, 0); - e.preventDefault(); - } - }, 0); -}, false); - -})(this); \ No newline at end of file diff --git a/dev/src/js/fallback.js b/dev/src/js/fallback.js deleted file mode 100644 index 062faac..0000000 --- a/dev/src/js/fallback.js +++ /dev/null @@ -1,18 +0,0 @@ -(function (global) { - -"use strict"; - -var fallback = { - jQuery: "../vendor/jquery.min.js", - THREE: "../vendor/three.min.js", - _: "../vendor/underscore-min.js" - }, - i; - -for (i in fallback) { - if (fallback.hasOwnProperty(i) && !global.hasOwnProperty(i)) { - global.document.write("