@ccp-nc/crystvis-js 0.4.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/.eslintrc.json +16 -0
  2. package/.github/workflows/test-mocha.yml +30 -0
  3. package/.vscode/settings.json +4 -0
  4. package/LICENSE +21 -0
  5. package/README.html +1127 -0
  6. package/README.md +76 -0
  7. package/demo/demo.css +30 -0
  8. package/demo/index.html +76 -0
  9. package/demo/main.js +143 -0
  10. package/docs/.nojekyll +0 -0
  11. package/docs-tutorials/Events.md +57 -0
  12. package/docs-tutorials/Queries.md +50 -0
  13. package/fonts/Rubik/OFL.txt +93 -0
  14. package/fonts/Rubik/README.txt +77 -0
  15. package/fonts/Rubik/Rubik-Italic-VariableFont_wght.ttf +0 -0
  16. package/fonts/Rubik/Rubik-VariableFont_wght.ttf +0 -0
  17. package/fonts/Rubik/static/Rubik-Black.ttf +0 -0
  18. package/fonts/Rubik/static/Rubik-BlackItalic.ttf +0 -0
  19. package/fonts/Rubik/static/Rubik-Bold.ttf +0 -0
  20. package/fonts/Rubik/static/Rubik-BoldItalic.ttf +0 -0
  21. package/fonts/Rubik/static/Rubik-ExtraBold.ttf +0 -0
  22. package/fonts/Rubik/static/Rubik-ExtraBoldItalic.ttf +0 -0
  23. package/fonts/Rubik/static/Rubik-Italic.ttf +0 -0
  24. package/fonts/Rubik/static/Rubik-Light.ttf +0 -0
  25. package/fonts/Rubik/static/Rubik-LightItalic.ttf +0 -0
  26. package/fonts/Rubik/static/Rubik-Medium.ttf +0 -0
  27. package/fonts/Rubik/static/Rubik-MediumItalic.ttf +0 -0
  28. package/fonts/Rubik/static/Rubik-Regular.ttf +0 -0
  29. package/fonts/Rubik/static/Rubik-SemiBold.ttf +0 -0
  30. package/fonts/Rubik/static/Rubik-SemiBoldItalic.ttf +0 -0
  31. package/index.html +25 -0
  32. package/index.js +11 -0
  33. package/jsconf.json +14 -0
  34. package/lib/assets/fonts/Rubik-Medium.fnt +297 -0
  35. package/lib/assets/fonts/Rubik-Medium.png +0 -0
  36. package/lib/assets/fonts/bmpfonts.in.js +16 -0
  37. package/lib/assets/fonts/bmpfonts.js +9 -0
  38. package/lib/assets/fonts/font.js +82 -0
  39. package/lib/assets/fonts/index.js +14 -0
  40. package/lib/assets/fonts/threebmfont.js +28 -0
  41. package/lib/data.js +125 -0
  42. package/lib/formats/cell.js +114 -0
  43. package/lib/formats/cif.js +22 -0
  44. package/lib/formats/magres.js +337 -0
  45. package/lib/formats/xyz.js +124 -0
  46. package/lib/loader.js +87 -0
  47. package/lib/model.js +2076 -0
  48. package/lib/modelview.js +382 -0
  49. package/lib/nmrdata.js +2898 -0
  50. package/lib/orbit.js +1233 -0
  51. package/lib/primitives/atoms.js +261 -0
  52. package/lib/primitives/cell.js +160 -0
  53. package/lib/primitives/dither.js +156 -0
  54. package/lib/primitives/ellipsoid.js +183 -0
  55. package/lib/primitives/geometries.js +20 -0
  56. package/lib/primitives/index.js +48 -0
  57. package/lib/primitives/isosurface.js +171 -0
  58. package/lib/primitives/shapes.js +100 -0
  59. package/lib/primitives/sprites.js +172 -0
  60. package/lib/query.js +158 -0
  61. package/lib/render.js +440 -0
  62. package/lib/selbox.js +361 -0
  63. package/lib/shaders/aura.frag +26 -0
  64. package/lib/shaders/aura.vert +37 -0
  65. package/lib/shaders/dither.frag +42 -0
  66. package/lib/shaders/dither.vert +8 -0
  67. package/lib/shaders/index.in.js +17 -0
  68. package/lib/shaders/index.js +25 -0
  69. package/lib/shaders/msdf300.frag +25 -0
  70. package/lib/shaders/msdf300.vert +45 -0
  71. package/lib/tensor.js +227 -0
  72. package/lib/utils.js +168 -0
  73. package/lib/visualizer.js +480 -0
  74. package/package.json +106 -0
  75. package/scripts/build-bundle.js +17 -0
  76. package/scripts/build-fonts.js +43 -0
  77. package/scripts/build-resources.js +46 -0
  78. package/scripts/plugins-shim.js +10 -0
  79. package/test/chemdata.js +69 -0
  80. package/test/data/CHA.cif +74 -0
  81. package/test/data/H2O.xyz +8 -0
  82. package/test/data/H2_bound.xyz +4 -0
  83. package/test/data/ethanol.cell +25 -0
  84. package/test/data/ethanol.magres +238 -0
  85. package/test/data/example_single.cif +789 -0
  86. package/test/data/frac.cell +8 -0
  87. package/test/data/org.cif +427 -0
  88. package/test/data/pyridine.xyz +13 -0
  89. package/test/data/si8.xyz +10 -0
  90. package/test/loader.js +107 -0
  91. package/test/model.js +368 -0
  92. package/test/query.js +135 -0
  93. package/test/tensor.js +133 -0
  94. package/test/test-html/examples.js +1485 -0
  95. package/test/test-html/index.html +33 -0
  96. package/test/test-html/index.js +279 -0
  97. package/tools/compile_colors.py +120 -0
  98. package/tools/compile_periodic.py +96 -0
  99. package/tools/ptable.json +497 -0
  100. package/tools/test +5844 -0
package/lib/model.js ADDED
@@ -0,0 +1,2076 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @fileoverview Class holding the atomic models to be plotted
5
+ * @module
6
+ */
7
+
8
+ import _ from 'lodash';
9
+ import * as mjs from 'mathjs';
10
+ import {
11
+ PeriodicTable as PeriodicTable
12
+ } from 'mendeleev';
13
+
14
+ import {
15
+ Atoms as Atoms
16
+ } from 'crystcif-parse';
17
+
18
+ import * as utils from './utils.js';
19
+ import * as data from './data.js';
20
+ import {
21
+ QueryParser as QueryParser
22
+ } from './query.js';
23
+ import {
24
+ ModelView as ModelView
25
+ } from './modelview.js';
26
+
27
+
28
+ const LABEL_HEIGHT = 0.04; // For now fixed, just a value that works
29
+
30
+
31
+ /** An 'image' of a single atom from a model. This represents a specific periodic copy of that atom (if applicable). */
32
+ class AtomImage {
33
+
34
+ /**
35
+ * @class
36
+ * @param {Model} model The model from which the image is from
37
+ * @param {int} index Index of the atom in the model
38
+ * @param {Array} ijk Indices of the cell in which the image is located
39
+ */
40
+ constructor(model, index, ijk) {
41
+
42
+ this._model = model;
43
+ this._index = index;
44
+ this._ijk = ijk || [0, 0, 0];
45
+
46
+ // String ID
47
+ this._id = this._index + '_' + _.join(this._ijk, '_');
48
+ // Integer index
49
+ this._img_index = utils.supercellIndex(index, this._ijk,
50
+ model.supercell, model.length);
51
+
52
+ this._xyz0 = model._positions[index];
53
+
54
+ this._bondsFrom = []; // BondImages of bonds for which this is atom1
55
+ this._bondsTo = []; // BondImages of bonds for which this is atom2
56
+
57
+ if (!model.periodic) {
58
+ this._fxyz0 = null;
59
+ this._fxyz = null;
60
+ this._xyz = this._xyz0;
61
+ } else {
62
+ this._fxyz0 = model._scaled_positions[index];
63
+ this._fxyz = [this._fxyz0[0] + ijk[0],
64
+ this._fxyz0[1] + ijk[1],
65
+ this._fxyz0[2] + ijk[2]
66
+ ];
67
+ this._xyz = mjs.multiply(this._fxyz, model._cell);
68
+ }
69
+
70
+ this._isotope = null; // By default look up the model
71
+
72
+ // Visual properties
73
+ this._visible = false;
74
+ this._color = this.cpkColor;
75
+ this._uses_cpk = true;
76
+ this._base_radius = this.vdwRadius / 4.0;
77
+ this._scale = 1.0;
78
+ this._opacity = 1.0;
79
+ this._highlighted = false;
80
+
81
+ this._mesh = null; // Will be created when first requested
82
+ this._aura = null;
83
+
84
+ this._labels = {};
85
+ this._ellipsoids = {};
86
+ }
87
+
88
+ /**
89
+ * Model this atom belongs to
90
+ * @readonly
91
+ * @type {Model}
92
+ */
93
+ get model() {
94
+ return this._model;
95
+ }
96
+
97
+ /**
98
+ * Renderer used by this atom
99
+ * @readonly
100
+ * @type {Renderer}
101
+ */
102
+ get renderer() {
103
+ var m = this.model;
104
+
105
+ if (m) {
106
+ return m._renderer;
107
+ }
108
+
109
+ return null;
110
+ }
111
+
112
+ /**
113
+ * Index of the atom
114
+ * @readonly
115
+ * @type {int}
116
+ */
117
+ get index() {
118
+ return this._index;
119
+ }
120
+
121
+ /**
122
+ * String ID of the image
123
+ * @readonly
124
+ * @type {String}
125
+ */
126
+ get id() {
127
+ return this._id;
128
+ }
129
+
130
+ /**
131
+ * Index of this image
132
+ * @readonly
133
+ * @type {int}
134
+ */
135
+ get imgIndex() {
136
+ return this._img_index;
137
+ }
138
+
139
+ /**
140
+ * Index of the species of this atom
141
+ * @readonly
142
+ * @type {int}
143
+ */
144
+ get speciesIndex() {
145
+ return this._model._species_indices[this._index];
146
+ }
147
+
148
+ /**
149
+ * Symbol of this atom's element
150
+ * @readonly
151
+ * @type {String}
152
+ */
153
+ get element() {
154
+ return this._model._elems[this._index];
155
+ }
156
+
157
+ /**
158
+ * Crystal site label of this atom
159
+ * @readonly
160
+ * @type {String}
161
+ */
162
+ get crystLabel() {
163
+ return this._model._labels[this._index];
164
+ }
165
+
166
+ /**
167
+ * Periodic table information for this atom's element
168
+ * @readonly
169
+ * @type {Object}
170
+ */
171
+ get elementData() {
172
+ return data.getElementData(this.element);
173
+ }
174
+
175
+ /**
176
+ * Information for this atom's isotope
177
+ * @readonly
178
+ * @type {Object}
179
+ */
180
+ get isotopeData() {
181
+ let idata = this._isotope;
182
+ if (idata === null)
183
+ idata = this._model._isotopes[this._index];
184
+ return idata;
185
+ }
186
+
187
+ /**
188
+ * Atomic mass of this atom's isotope
189
+ * @type {int}
190
+ */
191
+ get isotope() {
192
+ return this.isotopeData.A;
193
+ }
194
+
195
+ set isotope(A) {
196
+ this._isotope = data.getIsotopeData(this.element, A);
197
+ if (this._isotope === null)
198
+ throw Error('Isotope does not exist for this element');
199
+ // Reset color
200
+ if (this._uses_cpk) {
201
+ this.color = null;
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Atomic mass of the global isotope set as default for this atom's species
207
+ * @type {int}
208
+ */
209
+ set isotopeGlobal(A) {
210
+ // Set the isotope for this atom in the model
211
+ let iso = data.getIsotopeData(this.element, A);
212
+ if (iso === null)
213
+ throw Error('Isotope does not exist for this element');
214
+ this._model._isotopes[this._index] = iso;
215
+ // Reset color
216
+ if (this._uses_cpk) {
217
+ this.color = null;
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Atomic number of element
223
+ * @readonly
224
+ * @type {int}
225
+ */
226
+ get number() {
227
+ var el = PeriodicTable.getElement(this.element);
228
+ return (el ? el.number : 0);
229
+ }
230
+
231
+ /**
232
+ * Hex integer code of the conventional CPK color used for this element
233
+ * (altered in case of non-standard isotopes)
234
+ * @readonly
235
+ * @type {int}
236
+ */
237
+ get cpkColor() {
238
+ return data.getCpkColor(this.element, this.isotope);
239
+ }
240
+
241
+ /**
242
+ * Van dew Waals radius for this element
243
+ * @readonly
244
+ * @type {float}
245
+ */
246
+ get vdwRadius() {
247
+ return data.getVdwRadius(this.element);
248
+ }
249
+
250
+ /**
251
+ * Bonds from this atom
252
+ * @readonly
253
+ * @type {BondImage[]}
254
+ */
255
+ get bondsFrom() {
256
+ return Array.from(this._bondsFrom);
257
+ }
258
+
259
+ /**
260
+ * Bonds to this atom
261
+ * @readonly
262
+ * @type {BondImage[]}
263
+ */
264
+ get bondsTo() {
265
+ return Array.from(this._bondsTo);
266
+ }
267
+
268
+ /**
269
+ * All bonds connected to this atom
270
+ * @readonly
271
+ * @type {BondImage[]}
272
+ */
273
+ get bonds() {
274
+ return _.concat(this._bondsFrom, this._bondsTo);
275
+ }
276
+
277
+ /**
278
+ * All atoms bonded to this atom
279
+ * @readonly
280
+ * @type {AtomImage[]}
281
+ */
282
+ get bondedAtoms() {
283
+ return _.concat(_.map(this._bondsFrom, function(b) {
284
+ return b.atom2;
285
+ }),
286
+ _.map(this._bondsTo, function(b) {
287
+ return b.atom1;
288
+ }));
289
+ }
290
+
291
+ /**
292
+ * Cell indices of this atom image
293
+ * @readonly
294
+ * @type {int[]}
295
+ */
296
+ get ijk() {
297
+ return Array.from(this._ijk);
298
+ }
299
+
300
+ /**
301
+ * Position of this atom's original
302
+ * @readonly
303
+ * @type {float[]}
304
+ */
305
+ get xyz0() {
306
+ return Array.from(this._xyz0);
307
+ }
308
+
309
+ /**
310
+ * Position of this atom image
311
+ * @readonly
312
+ * @type {float[]}
313
+ */
314
+ get xyz() {
315
+ return Array.from(this._xyz);
316
+ }
317
+
318
+ /**
319
+ * Fractional coordinates of this atom's original
320
+ * @readonly
321
+ * @type {float[]}
322
+ */
323
+ get fxyz0() {
324
+ return Array.from(this._fxyz0);
325
+ }
326
+
327
+ /**
328
+ * Fractional coordinates of this atom image
329
+ * @readonly
330
+ * @type {float[]}
331
+ */
332
+ get fxyz() {
333
+ return Array.from(this._fxyz);
334
+ }
335
+
336
+ /**
337
+ * Index of the molecule this atom belongs to
338
+ * @readonly
339
+ * @type {int}
340
+ */
341
+ get moleculeIndex() {
342
+ return this._model._molinds[this._index];
343
+ }
344
+
345
+ /**
346
+ * Mesh corresponding to this atom image
347
+ * @readonly
348
+ * @type {AtomMesh}
349
+ */
350
+ get mesh() {
351
+ var r = this.renderer;
352
+ if (!this._mesh && r) {
353
+ this._mesh = new r.Primitives.AtomMesh(this._xyz, this.radius, this._color);
354
+ this._mesh.image = this;
355
+ }
356
+ return this._mesh;
357
+ }
358
+
359
+ /**
360
+ * Aura used to highlight this atom image
361
+ * @readonly
362
+ * @type {AuraMesh}
363
+ */
364
+ get aura() {
365
+ var r = this.renderer;
366
+ if (!this._aura && r) {
367
+ this._aura = new r.Primitives.AuraMesh({
368
+ radius: this.radius,
369
+ scale: 0.02
370
+ });
371
+ this.mesh.add(this._aura);
372
+ }
373
+
374
+ return this._aura;
375
+ }
376
+
377
+ // Get and set graphical properties
378
+
379
+ /**
380
+ * Whether the atom is visible
381
+ * @type {bool}
382
+ */
383
+ get visible() {
384
+ return this._visible;
385
+ }
386
+
387
+ set visible(v) {
388
+
389
+ this._visible = v;
390
+
391
+ var mesh = this.mesh;
392
+
393
+ if (v) {
394
+ this.renderer.add(mesh, 'model');
395
+ } else {
396
+ this.renderer.remove(mesh, 'model');
397
+ }
398
+
399
+ // Update aura visibility
400
+ this.highlighted = this._highlighted;
401
+
402
+ // Update connected bonds' visibility
403
+ for (let i = 0; i < this._bondsFrom.length; ++i) {
404
+ let b = this._bondsFrom[i];
405
+ b.visible = b._visible;
406
+ }
407
+
408
+ for (let i = 0; i < this._bondsTo.length; ++i) {
409
+ let b = this._bondsTo[i];
410
+ b.visible = b._visible;
411
+ }
412
+ }
413
+
414
+ /**
415
+ * Starting radius of the atom
416
+ * @type {float}
417
+ */
418
+ get baseRadius() {
419
+ return this._base_radius;
420
+ }
421
+
422
+ set baseRadius(r) {
423
+ if (r == null) { // Default value
424
+ r = this.vdwRadius / 4.0;
425
+ }
426
+ this._base_radius = r;
427
+ var mesh = this.mesh;
428
+ mesh.atom_radius = this.radius;
429
+ }
430
+
431
+ /**
432
+ * Scale of the atom
433
+ * @type {float}
434
+ */
435
+ get scale() {
436
+ return this._scale;
437
+ }
438
+
439
+ set scale(s) {
440
+ if (s == null) {
441
+ s = 1;
442
+ }
443
+ this._scale = s;
444
+ var mesh = this.mesh;
445
+ mesh.atom_radius = this.radius;
446
+ }
447
+
448
+ /**
449
+ * Final radius of the atom (starting radius * scale)
450
+ * @type {float}
451
+ */
452
+ get radius() {
453
+ return this._scale * this._base_radius;
454
+ }
455
+
456
+ set radius(r) {
457
+ if (r == null) {
458
+ r == this.baseRadius;
459
+ }
460
+ this.scale = r / this._base_radius;
461
+ }
462
+
463
+ /**
464
+ * Color of the atom
465
+ * @type {int}
466
+ */
467
+ get color() {
468
+ return this._color;
469
+ }
470
+
471
+ set color(c) {
472
+ if (c === null) {
473
+ c = this.cpkColor;
474
+ this._uses_cpk = true;
475
+ }
476
+ else {
477
+ this._uses_cpk = false;
478
+ }
479
+ this._color = c;
480
+ var mesh = this.mesh;
481
+ if (mesh) {
482
+ mesh.atom_color = c;
483
+ }
484
+
485
+ _.map(this._bondsFrom, function(b) {
486
+ b.color1 = c;
487
+ });
488
+
489
+ _.map(this._bondsTo, function(b) {
490
+ b.color2 = c;
491
+ });
492
+
493
+ }
494
+
495
+ /**
496
+ * Opacity of the atom
497
+ * @type {float}
498
+ */
499
+ get opacity() {
500
+ return this._opacity;
501
+ }
502
+
503
+ set opacity(o) {
504
+ if (o == null) {
505
+ o = 1;
506
+ }
507
+ this._opacity = o;
508
+ var mesh = this.mesh;
509
+ mesh.atom_opacity = o;
510
+
511
+ _.map(this._bondsFrom, function(b) {
512
+ b.opacity1 = o;
513
+ });
514
+
515
+ _.map(this._bondsTo, function(b) {
516
+ b.opacity2 = o;
517
+ });
518
+ }
519
+
520
+ /**
521
+ * Whether the atom is highlighted
522
+ * @type {bool}
523
+ */
524
+ get highlighted() {
525
+ return this._highlighted;
526
+ }
527
+
528
+ set highlighted(h) {
529
+ if (h == null) {
530
+ h = false;
531
+ }
532
+ this._highlighted = h;
533
+ var aura = this.aura;
534
+ if (h && this._visible) {
535
+ aura.visible = true;
536
+ } else {
537
+ aura.visible = false;
538
+ }
539
+ }
540
+
541
+ /**
542
+ * Add a text label to the atom.
543
+ *
544
+ * @param {String} text Content of the label
545
+ * @param {String} name Name to use to refer to the label (necessary to overwrite/erase later)
546
+ * @param {Object} parameters Dictionary of other options (e.g. font family, text color, etc. See TextSprite)
547
+ */
548
+ addLabel(text, name, parameters = {}) {
549
+ this.removeLabel(name); // Precautionary
550
+
551
+ var defaults = {
552
+ faceCamera: true,
553
+ fixScale: true,
554
+ shift: [1.0*this.radius, 0, 0], // This just works well
555
+ height: LABEL_HEIGHT,
556
+ };
557
+
558
+ parameters = _.merge(defaults, parameters);
559
+ parameters.position = [0, 0, 0]; // This is not customizable
560
+
561
+ var r = this.renderer;
562
+ if (r) {
563
+ var label = new r.Primitives.TextSprite(text, parameters);
564
+ this._labels[name] = label;
565
+ this.mesh.add(label);
566
+ }
567
+ }
568
+
569
+ /**
570
+ * Remove the label of a given name
571
+ *
572
+ * @param {String} name Name of the label
573
+ */
574
+ removeLabel(name) {
575
+
576
+ let l = this._labels[name];
577
+ if (l)
578
+ this._mesh.remove(l);
579
+ delete this._labels[name];
580
+ }
581
+
582
+ /**
583
+ * Retrieve or set a label's properties
584
+ *
585
+ * @param {String} name Name of the label
586
+ * @param {String} property Property to set
587
+ * @param {?} value Value to set. If omitted, returns the current
588
+ * value instead.
589
+ */
590
+ labelProperty(name, property, value = null) {
591
+ if (value) {
592
+ this._labels[name][property] = value;
593
+ } else {
594
+ return this._labels[name][property];
595
+ }
596
+ }
597
+
598
+ /**
599
+ * Add an ellipsoid to the atom.
600
+ *
601
+ * @param {TensorData | Object | Array} data The data to base the
602
+ * ellipsoid on. Can be:
603
+ * - a TensorData object;
604
+ * - an Object with 'eigenvalues'
605
+ * and 'eigenvectors' members
606
+ * - an Array of the form
607
+ * [eigenvalues, eigenvectors]
608
+ * @param {String} name Name of the ellipsoid
609
+ * @param {Object} parameters Additional options to
610
+ * pass (see EllipsoidMesh)
611
+ */
612
+ addEllipsoid(data, name, parameters = {}) {
613
+ this.removeEllipsoid(name);
614
+
615
+ parameters = _.clone(parameters); // Avoid editing the reference object
616
+
617
+ if (data instanceof Array) {
618
+ parameters.eigenvalues = data[0];
619
+ parameters.eigenvectors = data[1];
620
+ } else {
621
+ parameters.eigenvalues = data.eigenvalues;
622
+ parameters.eigenvectors = data.eigenvectors;
623
+ }
624
+
625
+ if (parameters.ditherSeed == null) {
626
+ // As long as it's consistent for a given atom, the actual value is irrelevant
627
+ let seed = utils.hashCode(this._fxyz + name);
628
+ parameters.ditherSeed = seed/4294967295.0; // Reduce to ]0.5,-0.5]
629
+ }
630
+ else {
631
+
632
+ }
633
+
634
+ var r = this.renderer;
635
+ if (r) {
636
+ var ellips = new r.Primitives.EllipsoidMesh(parameters);
637
+ this._ellipsoids[name] = ellips;
638
+ this.mesh.add(ellips);
639
+ }
640
+ }
641
+
642
+ /**
643
+ * Remove the ellipsoid with a given name
644
+ *
645
+ * @param {String} name Name of the ellipsoid
646
+ */
647
+ removeEllipsoid(name) {
648
+ let l = this._ellipsoids[name];
649
+ if (l)
650
+ this._mesh.remove(l);
651
+ delete this._ellipsoids[name];
652
+ }
653
+
654
+ /**
655
+ * Retrieve or set an ellipsoid's properties
656
+ *
657
+ * @param {String} name Name of the ellipsoid
658
+ * @param {String} property Property to set
659
+ * @param {?} value Value to set. If omitted, returns the current
660
+ * value instead.
661
+ */
662
+ ellipsoidProperty(name, property, value = null) {
663
+ if (value) {
664
+ this._ellipsoids[name][property] = value;
665
+ } else {
666
+ return this._ellipsoids[name][property];
667
+ }
668
+ }
669
+
670
+ /**
671
+ * Get the value for one array for this image
672
+ * @param {String} name Name of the array
673
+ *
674
+ * @return {*} Value of the array for this atom
675
+ */
676
+ getArrayValue(name) {
677
+ return this._model.getArray(name)[this._index];
678
+ }
679
+
680
+ // Check equality with another image
681
+ equals(ai) {
682
+ return (this._model == ai._model &&
683
+ this._index == ai._index &&
684
+ _.isEqual(this._ijk, ai._ijk));
685
+ }
686
+
687
+ // Return a copy, possibly shifted to a different cell
688
+ copy(shift = [0, 0, 0]) {
689
+ return new AtomImage(this._model,
690
+ this._index,
691
+ mjs.add(this._ijk, shift));
692
+ }
693
+ }
694
+
695
+ /** An 'image' of a single bond in the model. This represents the connection
696
+ between two specific AtomImages */
697
+ class BondImage {
698
+
699
+
700
+ /**
701
+ * @class
702
+ * @param {Model} model The model from which the image is from
703
+ * @param {AtomImage} im1 AtomImage from which the bond starts
704
+ * @param {AtomImage} im2 AtomImage to which the bond ends
705
+ */
706
+ constructor(model, im1, im2) {
707
+
708
+ this._model = model;
709
+ this._im1 = im1;
710
+ this._im2 = im2;
711
+
712
+ this._im1._bondsFrom.push(this);
713
+ this._im2._bondsTo.push(this);
714
+
715
+ this._length = mjs.distance(this._im1.xyz, this._im2.xyz);
716
+
717
+ this._key = this._im1.imgIndex + '_' + this._im2.imgIndex;
718
+
719
+ // Visual properties
720
+ this._visible = true;
721
+ this._radius = 0.2;
722
+ this._opacity = 1.0;
723
+
724
+ this._mesh = null; // Created on first request
725
+
726
+ }
727
+
728
+ /**
729
+ * Model this bond belongs to
730
+ * @readonly
731
+ * @type {Model}
732
+ */
733
+ get model() {
734
+ return this._model;
735
+ }
736
+
737
+ /**
738
+ * Renderer used by this bond
739
+ * @readonly
740
+ * @type {Renderer}
741
+ */
742
+ get renderer() {
743
+ var m = this.model;
744
+ if (m) {
745
+ return m._renderer;
746
+ }
747
+ return null;
748
+ }
749
+
750
+ /**
751
+ * First atom connected to this bond
752
+ * @readonly
753
+ * @type {AtomImage}
754
+ */
755
+ get atom1() {
756
+ return this._im1;
757
+ }
758
+
759
+ /**
760
+ * Second atom connected to this bond
761
+ * @readonly
762
+ * @type {AtomImage}
763
+ */
764
+ get atom2() {
765
+ return this._im2;
766
+ }
767
+
768
+ /**
769
+ * A unique string key used to quickly reference the bond
770
+ * @readonly
771
+ * @type {String}
772
+ */
773
+ get key() {
774
+ // Used in dictionary for quick reference
775
+ return this._key;
776
+ }
777
+
778
+ /**
779
+ * Bond length in Angstroms
780
+ * @readonly
781
+ * @type {float}
782
+ */
783
+ get length() {
784
+ return this._length;
785
+ }
786
+
787
+ /**
788
+ * Mesh corresponding to this bond image
789
+ * @readonly
790
+ * @type {AtomMesh}
791
+ */
792
+ get mesh() {
793
+ var r = this.renderer;
794
+ if (!this._mesh && r) {
795
+ this._mesh = new r.Primitives.BondMesh(this.atom1.xyz, this.atom2.xyz,
796
+ this._radius,
797
+ this.atom1.color, this.atom2.color);
798
+ }
799
+ return this._mesh;
800
+ }
801
+
802
+ /**
803
+ * Radius of the bond
804
+ * @type {float}
805
+ */
806
+ get radius() {
807
+ return this._radius;
808
+ }
809
+
810
+ set radius(r) {
811
+ if (r == null) {
812
+ r = 0.2;
813
+ }
814
+ this._radius = r;
815
+ var mesh = this.mesh;
816
+ if (mesh) {
817
+ mesh.bond_radius = r;
818
+ }
819
+ }
820
+
821
+ /**
822
+ * First color of the bond
823
+ * @type {int}
824
+ */
825
+ set color1(c) {
826
+ if (c == null) {
827
+ c = this._im1.color;
828
+ }
829
+ var mesh = this.mesh;
830
+ if (mesh) {
831
+ mesh.bond_color_1 = c;
832
+ }
833
+ }
834
+
835
+ /**
836
+ * Second color of the bond
837
+ * @type {int}
838
+ */
839
+ set color2(c) {
840
+ if (c == null) {
841
+ c = this._im2.color;
842
+ }
843
+ var mesh = this.mesh;
844
+ if (mesh) {
845
+ mesh.bond_color_2 = c;
846
+ }
847
+ }
848
+
849
+ /**
850
+ * First opacity of the bond
851
+ * @type {float}
852
+ */
853
+ set opacity1(o) {
854
+ if (o == null) {
855
+ o = this._im1.opacity;
856
+ }
857
+ var mesh = this.mesh;
858
+ if (mesh) {
859
+ mesh.bond_opacity_1 = o;
860
+ }
861
+ }
862
+
863
+ /**
864
+ * Second opacity of the bond
865
+ * @type {float}
866
+ */
867
+ set opacity2(o) {
868
+ if (o == null) {
869
+ o = this._im2.opacity;
870
+ }
871
+ var mesh = this.mesh;
872
+ if (mesh) {
873
+ mesh.bond_opacity_2 = o;
874
+ }
875
+ }
876
+
877
+ /**
878
+ * Whether the bond is visible
879
+ * @type {bool}
880
+ */
881
+ get visible() {
882
+ return this._visible;
883
+ }
884
+
885
+ set visible(v) {
886
+
887
+ this._visible = v;
888
+ v = v && this.atom1.visible && this.atom2.visible;
889
+
890
+ var mesh = this.mesh;
891
+ if (v) {
892
+ this.renderer.add(mesh, 'model');
893
+ } else {
894
+ this.renderer.remove(mesh, 'model');
895
+ }
896
+
897
+ }
898
+ }
899
+
900
+ class Model {
901
+
902
+
903
+ /**
904
+ * An object containing an Atomic structure and taking care of its periodic
905
+ * nature, allowing querying and selection, and so on.
906
+ * @class
907
+ * @param {crystcif.Atoms} atoms Atomic structure, in crystcif's Atoms format
908
+ * @param {Object} parameters Additional options:
909
+ *
910
+ * - `supercell`
911
+ * - `molecularCrystal` (if true, load full molecules in central unit cell)
912
+ * - `useNMRActiveIsotopes` (if true, all isotopes are set by default to the most common
913
+ * one with non-zero spin)
914
+ * - `vdwScaling` (scale van der Waals radii by a constant factor)
915
+ * - `vdwElementScaling` (table of per-element factors to scale VdW radii by)
916
+ */
917
+ constructor(atoms, parameters = {}) {
918
+
919
+ var defaults = {
920
+ supercell: [1, 1, 1],
921
+ molecularCrystal: false,
922
+ useNMRActiveIsotopes: false,
923
+ vdwScaling: 1.0,
924
+ vdwElementScaling: {}
925
+ };
926
+
927
+ parameters = _.merge(defaults, parameters);
928
+
929
+ this._vdwScaling = parameters.vdwScaling;
930
+ this._vdwElementScaling = parameters.vdwElementScaling;
931
+
932
+ const initMolecules = ((atoms, supercell) => {
933
+
934
+ if (!(atoms instanceof Atoms)) {
935
+ throw new Error('Model must be initialised with a loaded Atoms object');
936
+ }
937
+
938
+ this._atoms_base = atoms;
939
+ this._data = {};
940
+
941
+ /* Load the positions, cell, and other key data
942
+ Important: to save memory, we're simply storing references.
943
+ These are NOT to be changed!
944
+ */
945
+
946
+
947
+ this._elems = this._atoms_base._arrays['symbols'];
948
+ this._isotopes = this._elems.map((el) => {
949
+ const iso = parameters.useNMRActiveIsotopes? 'nmr' : null;
950
+ let isodata = data.getIsotopeData(el, iso);
951
+ if (isodata === null) {
952
+ // No NMR active isotope?
953
+ isodata = data.getIsotopeData(el);
954
+ }
955
+
956
+ return isodata;
957
+ });
958
+ this._nums = this._atoms_base._arrays['numbers'];
959
+ this._positions = this._atoms_base._arrays['positions'];
960
+ this._cell = this._atoms_base._cell;
961
+ this._pbc = this._atoms_base._pbc;
962
+ this._periodic = !this._pbc.includes(false);
963
+ this._inv_cell = this._atoms_base._inv_cell;
964
+ this._supercell = [1, 1, 1];
965
+ this._supercell_grid = [
966
+ [0, 0, 0]
967
+ ];
968
+
969
+ // Species indices (used for labels)
970
+ let sp_count = {};
971
+ this._species_indices = [];
972
+ this._species_indices = this._elems.map((s, i) => {
973
+ let c = sp_count[s];
974
+ c = c? c : 0;
975
+ sp_count[s] = c+1;
976
+ return c;
977
+ });
978
+
979
+ // Crystallographic labels
980
+ if ('labels' in this._atoms_base._arrays) {
981
+ this._labels = this._atoms_base._arrays['labels'];
982
+ }
983
+ else {
984
+ // Build them
985
+ this._labels = [];
986
+ for (let i = 0; i < this._elems.length; ++i) {
987
+ this._labels.push(this._elems[i] + '_' + (this._species_indices[i]+1));
988
+ }
989
+ }
990
+
991
+
992
+ if (this._periodic) {
993
+ // R matrix: indispensable for calculations of periodic distances
994
+ this._r_matrix = mjs.multiply(this._cell, mjs.transpose(this._cell));
995
+ var ediag = mjs.eigs(this._r_matrix);
996
+ // Sort by eigenvalue
997
+ ediag = _.zip(ediag.values, ediag.vectors);
998
+ ediag = _.sortBy(ediag, function(x) {
999
+ return x[0];
1000
+ });
1001
+ ediag = _.unzip(ediag);
1002
+
1003
+ this._r_diag = {
1004
+ values: ediag[0],
1005
+ vectors: ediag[1],
1006
+ }
1007
+
1008
+ this._supercell = supercell; // Default
1009
+ this._supercell_grid = utils.supercellGrid(supercell);
1010
+ this._scaled_positions = this._atoms_base.get_scaled_positions();
1011
+ }
1012
+
1013
+ // Compile all images for this supercell
1014
+ this._atom_images = this._atomImages();
1015
+
1016
+ this._computeBonds();
1017
+ this._computeMolecules();
1018
+
1019
+
1020
+ }).bind(this);
1021
+
1022
+ initMolecules(atoms, parameters.supercell);
1023
+
1024
+ if (parameters.molecularCrystal) {
1025
+ atoms = _.cloneDeep(atoms);
1026
+ var pos = this.positions;
1027
+ for (let i = 0; i < this.length; ++i) {
1028
+ let mol_i = this._molinds[i];
1029
+ let mol = this._molecules[mol_i];
1030
+ for (let j = 0; j < mol.length; ++j) {
1031
+ var a = mol[j];
1032
+ if (a.index == i) {
1033
+ pos[i] = mjs.add(pos[i], this.fracToAbs(a.cell));
1034
+ }
1035
+ }
1036
+ }
1037
+
1038
+ atoms.set_array('positions', pos);
1039
+ initMolecules(atoms, parameters.supercell);
1040
+ }
1041
+
1042
+ this._primitives = {}; // Any additional primitives drawn on this model
1043
+
1044
+ this._bond_images = this._bondImages();
1045
+
1046
+ // A special ModelView for convenience
1047
+ this._all = new ModelView(this, _.range(this._atom_images.length));
1048
+
1049
+ // Parser for queries
1050
+ this._qparse = new QueryParser({
1051
+ 'all': this._queryAll,
1052
+ 'indices': this._queryIndices,
1053
+ 'elements': this._queryElements,
1054
+ 'cell': this._queryCell,
1055
+ 'box': this._queryBox,
1056
+ 'sphere': this._querySphere,
1057
+ 'bonded': this._queryBonded,
1058
+ 'molecule': this._queryMolecule,
1059
+ }, this);
1060
+
1061
+ // By default no rendering
1062
+ this.renderer = null;
1063
+ }
1064
+
1065
+ // Using the .get_ methods of _atoms guarantees these are copies,
1066
+ // not pointers to the real thing
1067
+
1068
+ /**
1069
+ * Number of atoms in this model's original cell
1070
+ * @readonly
1071
+ * @type {int}
1072
+ */
1073
+ get length() {
1074
+ return this._atoms_base.length();
1075
+ }
1076
+
1077
+ /**
1078
+ * Chemical symbols in this model's original cell
1079
+ * @readonly
1080
+ * @type {String[]}
1081
+ */
1082
+ get symbols() {
1083
+ return this._atoms_base.get_chemical_symbols();
1084
+ }
1085
+
1086
+ /**
1087
+ * Atomic numbers in this model's original cell
1088
+ * @readonly
1089
+ * @type {int[]}
1090
+ */
1091
+ get numbers() {
1092
+ return this._atoms_base.get_atomic_numbers();
1093
+ }
1094
+
1095
+ /**
1096
+ * Coordinates of the atoms in this model's original cell
1097
+ * @readonly
1098
+ * @type {Array[]}
1099
+ */
1100
+ get positions() {
1101
+ return this._atoms_base.get_positions();
1102
+ }
1103
+
1104
+ /**
1105
+ * Fractional coordinates of the atoms in this model's original cell
1106
+ * @readonly
1107
+ * @type {Array[]}
1108
+ */
1109
+ get scaledPositions() {
1110
+ return this._atoms_base.get_scaled_positions();
1111
+ }
1112
+
1113
+ /**
1114
+ * Unit cell of the model's original cell
1115
+ * @readonly
1116
+ * @type {Array[]}
1117
+ */
1118
+ get cell() {
1119
+ return this._atoms_base.get_cell();
1120
+ }
1121
+
1122
+ /**
1123
+ * Periodic boundary conditions
1124
+ * @readonly
1125
+ * @type {bool[]}
1126
+ */
1127
+ get pbc() {
1128
+ return this._atoms_base.get_pbc();
1129
+ }
1130
+
1131
+ /**
1132
+ * Additional information from the model's original cell
1133
+ * @readonly
1134
+ * @type {Object}
1135
+ */
1136
+ get info() {
1137
+ return this._atoms_base.info;
1138
+ }
1139
+
1140
+ /**
1141
+ * Whether this model is periodic in all three directions of space
1142
+ * @readonly
1143
+ * @type {bool}
1144
+ */
1145
+ get periodic() {
1146
+ return this._periodic;
1147
+ }
1148
+
1149
+ /**
1150
+ * Indices of each atom by their species (e.g. C1, C2, H1, C3, H2, etc.)
1151
+ * @readonly
1152
+ * @type {int[]}
1153
+ */
1154
+ get speciesIndices() {
1155
+ return Array.from(this._species_indices);
1156
+ }
1157
+
1158
+ /**
1159
+ * Crystallographic labels of each atom
1160
+ * @readonly
1161
+ * @type {String[]}
1162
+ */
1163
+ get crystalLabels() {
1164
+ return Array.from(this._labels);
1165
+ }
1166
+
1167
+ /**
1168
+ * Shape of the supercell for this model
1169
+ * @readonly
1170
+ * @type {int[]}
1171
+ */
1172
+ get supercell() {
1173
+ return Array.from(this._supercell);
1174
+ }
1175
+
1176
+ /**
1177
+ * Full grid of origin coordinates of the cells making up the supercell
1178
+ * @readonly
1179
+ * @type {Array[]}
1180
+ */
1181
+ get supercellGrid() {
1182
+ return JSON.parse(JSON.stringify(this._supercell_grid));
1183
+ }
1184
+
1185
+ /**
1186
+ * Atom images in this model
1187
+ * @readonly
1188
+ * @type {AtomImage[]}
1189
+ */
1190
+ get atoms() {
1191
+ return Array.from(this._atom_images);
1192
+ }
1193
+
1194
+ /**
1195
+ * ModelView containing all the atoms of the image
1196
+ * @readonly
1197
+ * @type {ModelView}
1198
+ */
1199
+ get all() {
1200
+ return this._all;
1201
+ }
1202
+
1203
+ /**
1204
+ * Graphical object representing the unit cell's axes
1205
+ * @readonly
1206
+ * @type {AxesMesh}
1207
+ */
1208
+ get axes() {
1209
+ return this._cartesian_axes;
1210
+ }
1211
+
1212
+ /**
1213
+ * Graphical object representing the unit cell's box
1214
+ * @readonly
1215
+ * @type {BoxMesh}
1216
+ */
1217
+ get box() {
1218
+ return this._cartesian_box;
1219
+ }
1220
+
1221
+ /**
1222
+ * Global scaling factor for Van der Waals radii
1223
+ * @readonly
1224
+ * @type {float}
1225
+ */
1226
+ get vdwScaling() {
1227
+ return this._vdwScaling;
1228
+ }
1229
+
1230
+ /**
1231
+ * Table of scaling factors by element for Van der Waals radii
1232
+ * @readonly
1233
+ * @type {Object}
1234
+ */
1235
+ get vdwElementScaling() {
1236
+ return JSON.parse(JSON.stringify(this._vdwElementScaling));
1237
+ }
1238
+
1239
+ /**
1240
+ * Renderer used for this model's graphics
1241
+ * @type {Renderer}
1242
+ */
1243
+ set renderer(r) {
1244
+
1245
+
1246
+ if (r) {
1247
+ this._renderer = r;
1248
+ if (this.periodic) {
1249
+ // Create axes and box
1250
+ if (!this._cartesian_box) {
1251
+ this._cartesian_box = new r.Primitives.BoxMesh(this.cell);
1252
+ }
1253
+ if (!this._cartesian_axes) {
1254
+ this._cartesian_axes = new r.Primitives.AxesMesh(this.cell, {
1255
+ linewidth: 1.5
1256
+ });
1257
+ }
1258
+ r.add(this._cartesian_box);
1259
+ r.add(this._cartesian_axes);
1260
+ }
1261
+
1262
+ // And the primitives
1263
+ for (var name in this._primitives) {
1264
+ var p = this._primitives[name];
1265
+ r.add(p);
1266
+ }
1267
+
1268
+ } else {
1269
+
1270
+ if (this._renderer)
1271
+ this._renderer.clear();
1272
+
1273
+ this._renderer = null;
1274
+
1275
+ }
1276
+ }
1277
+
1278
+ // Set and get arrays on the underlying Atoms object
1279
+ /**
1280
+ * Set an array for the underlying Atoms object
1281
+ * @param {String} name Name of the array to use
1282
+ * @param {Array} arr Array to store
1283
+ */
1284
+ setArray(name, arr) {
1285
+ this._atoms_base.set_array(name, arr);
1286
+ }
1287
+
1288
+ /**
1289
+ * Retrieve an array from the underlying Atoms object
1290
+ * @param {String} name Name of the array to retrieve
1291
+ * @return {Array} Retrieved array
1292
+ */
1293
+ getArray(name) {
1294
+ return this._atoms_base.get_array(name);
1295
+ }
1296
+
1297
+ /**
1298
+ * Check if an array exists in the underlying Atoms object
1299
+ * @param {String} name Name of the array to check
1300
+ * @return {bool} Whether the array exists
1301
+ */
1302
+ hasArray(name) {
1303
+ return (name in this._atoms_base._arrays);
1304
+ }
1305
+
1306
+ /**
1307
+ * Delete an array from the underlying Atoms object
1308
+ * @param {String} name Name of the array to delete
1309
+ */
1310
+ deleteArray(name) {
1311
+ delete this._atoms_base._arrays[name];
1312
+ }
1313
+
1314
+ // These functions are for adding and removing graphical representations
1315
+ // that are meant to be drawn on to of the existing 3D model
1316
+
1317
+ /**
1318
+ * Add link drawn on model
1319
+ *
1320
+ * @param {Atom | Array} from Starting point
1321
+ * @param {Atom | Array} to End point
1322
+ * @param {String} name Name to use for the link object
1323
+ * @param {String} label Text label to add to the link
1324
+ * @param {Object} parameters Additional parameters (see LineMesh)
1325
+ */
1326
+ addLink(from, to, name = 'link', label = null, parameters = {}) {
1327
+
1328
+ this.removeGraphics(name);
1329
+
1330
+ parameters = _.clone(parameters); // Avoid editing the reference object
1331
+
1332
+ var r = this._renderer;
1333
+ if (r) {
1334
+ var link = new r.Primitives.LineMesh(from, to, parameters);
1335
+
1336
+ this._primitives[name] = link;
1337
+ r.add(link);
1338
+
1339
+ if (label) {
1340
+ var text = new r.Primitives.TextSprite(label, {
1341
+ color: parameters.color,
1342
+ fixScale: true,
1343
+ faceCamera: true,
1344
+ height: LABEL_HEIGHT,
1345
+ shift: [LABEL_HEIGHT, 0, 0],
1346
+ onOverlay: parameters.onOverlay
1347
+ });
1348
+ link.add(text);
1349
+ }
1350
+ }
1351
+ }
1352
+
1353
+ /**
1354
+ * Add a sphere drawn on model
1355
+ *
1356
+ * @param {Atom | Array} center Center of the sphere
1357
+ * @param {float} radius Radius of the sphere
1358
+ * @param {String} name Name to use for the sphere object
1359
+ * @param {Object} parameters Additional parameters (see EllipsoidMesh)
1360
+ */
1361
+ addSphere(center, radius, name='sphere', parameters = {}) {
1362
+
1363
+ this.removeGraphics(name);
1364
+
1365
+ var r = this._renderer;
1366
+ if (r) {
1367
+
1368
+ parameters = _.merge({
1369
+ color: 0xffffff,
1370
+ opacity: 0.5,
1371
+ opacityMode: r.Primitives.EllipsoidMesh.DITHER,
1372
+ showCircles: true,
1373
+ showAxes: true
1374
+ }, parameters); // Avoid editing the reference object
1375
+
1376
+ var sph = new r.Primitives.EllipsoidMesh({
1377
+ color: parameters.color,
1378
+ opacity: parameters.opacity,
1379
+ opacityMode: parameters.opacityMode,
1380
+ showCircles: parameters.showCircles,
1381
+ showAxes: parameters.showAxes,
1382
+ scalingFactor: radius,
1383
+ center: center
1384
+ });
1385
+
1386
+ this._primitives[name] = sph;
1387
+ r.add(sph);
1388
+ }
1389
+ }
1390
+
1391
+ /**
1392
+ * Remove the graphical object with a given name
1393
+ *
1394
+ * @param {String} name Name of the graphical object to remove
1395
+ */
1396
+ removeGraphics(name) {
1397
+ var g = this._primitives[name];
1398
+ var r = this._renderer;
1399
+ if (g && r)
1400
+ r.remove(g);
1401
+ delete this._primitives[name];
1402
+ }
1403
+
1404
+ /**
1405
+ * Remove all graphical objects
1406
+ */
1407
+ clearGraphics() {
1408
+ var r = this._renderer;
1409
+
1410
+ if (r) {
1411
+ _.map(this._primitives, function(g) {
1412
+ r.remove(g);
1413
+ });
1414
+ }
1415
+
1416
+ this._primitives = {};
1417
+ }
1418
+
1419
+ /**
1420
+ * Compute the bonds within the model. For internal use
1421
+ * @private
1422
+ */
1423
+ _computeBonds() {
1424
+
1425
+ var N = this.length;
1426
+ this._bondmat = Array(N); // Bond matrix
1427
+ this._bondmat = _.map(this._bondmat, function() {
1428
+ return _.map(Array(N), function() {
1429
+ return [];
1430
+ });
1431
+ });
1432
+
1433
+ // Van der Waals radii by element
1434
+ var vdwf = this._vdwScaling;
1435
+ var vdwf_table = this._vdwElementScaling;
1436
+
1437
+ var vdwr = _.map(this.symbols, function(s) {
1438
+ var f = vdwf;
1439
+
1440
+ if (s in vdwf_table) {
1441
+ f = vdwf_table[s];
1442
+ }
1443
+
1444
+ return data.getVdwRadius(s)*f;
1445
+ });
1446
+
1447
+ var maxr = _.max(vdwr);
1448
+
1449
+ var cell = this.cell;
1450
+ var sgrid = [
1451
+ [0, 0, 0]
1452
+ ];
1453
+ var p = this._positions;
1454
+
1455
+ if (this._periodic) {
1456
+ var scell = this.minimumSupercell(maxr);
1457
+ sgrid = utils.supercellGrid(scell);
1458
+ }
1459
+
1460
+ // Now iterate over all atom pairs
1461
+ for (let i = 0; i < this.length; ++i) {
1462
+
1463
+ var p1 = p[i];
1464
+
1465
+ for (let j = i; j < this.length; ++j) {
1466
+
1467
+ var p2 = p[j];
1468
+
1469
+ for (let k = 0; k < sgrid.length; ++k) {
1470
+ var c = sgrid[k];
1471
+ if ((i == j) && (c[0] == 0 && c[1] == 0 && c[2] == 0)) {
1472
+ // Just the same atom, skip
1473
+ continue;
1474
+ }
1475
+ var r = [0, 0, 0];
1476
+ // Here we write the algebra explicitly
1477
+ // for efficiency reasons
1478
+ if (this._periodic) {
1479
+ r[0] = c[0] * cell[0][0] + c[1] * cell[1][0] + c[2] * cell[2][0];
1480
+ r[1] = c[0] * cell[0][1] + c[1] * cell[1][1] + c[2] * cell[2][1];
1481
+ r[2] = c[0] * cell[0][2] + c[1] * cell[1][2] + c[2] * cell[2][2];
1482
+ }
1483
+ r = [p2[0] - p1[0] + r[0], p2[1] - p1[1] + r[1], p2[2] - p1[2] + r[2]];
1484
+ r = Math.sqrt(r[0] * r[0] + r[1] * r[1] + r[2] * r[2]);
1485
+ if (r < (vdwr[i] + vdwr[j]) / 2.0) {
1486
+ // Bond!
1487
+ this._bondmat[i][j].push([c[0], c[1], c[2]]);
1488
+ this._bondmat[j][i].push([-c[0], -c[1], -c[2]]);
1489
+ }
1490
+ }
1491
+ }
1492
+ }
1493
+ }
1494
+
1495
+ /**
1496
+ * Compute the molecules within the model. For internal use
1497
+ * @private
1498
+ */
1499
+ _computeMolecules() {
1500
+
1501
+ this._molecules = [];
1502
+ this._molinds = [];
1503
+
1504
+ if (this.length < 2) {
1505
+ // No molecules can be computed
1506
+ this._molecules = null;
1507
+ return;
1508
+ }
1509
+
1510
+ var mol_sets = [];
1511
+ var unsorted_atoms = _.range(this.length);
1512
+
1513
+ while (unsorted_atoms.length > 0) {
1514
+ var mol_queue = [
1515
+ [unsorted_atoms.shift(), [0, 0, 0]]
1516
+ ];
1517
+ var current_mol = [];
1518
+ var current_mol_cells = [];
1519
+ while (mol_queue.length > 0) {
1520
+ var ac1 = mol_queue.shift();
1521
+ var a1 = ac1[0];
1522
+ var c1 = ac1[1];
1523
+
1524
+ current_mol.push(a1);
1525
+ current_mol_cells.push(c1);
1526
+ // Find linked atoms
1527
+ var link1 = this._bondmat[a1];
1528
+ for (let i in link1) {
1529
+ var a2 = parseInt(i);
1530
+ var link12 = link1[i];
1531
+ // Is a2 still unsorted?
1532
+ if (!unsorted_atoms.includes(a2) || link12.length == 0)
1533
+ continue;
1534
+
1535
+ for (let j = 0; j < link12.length; ++j) {
1536
+ var c2 = link12[j];
1537
+ mol_queue.push([a2, mjs.add(c1, c2)]);
1538
+ }
1539
+
1540
+ unsorted_atoms.splice(unsorted_atoms.indexOf(a2), 1);
1541
+ }
1542
+ }
1543
+ mol_sets.push([
1544
+ current_mol,
1545
+ current_mol_cells
1546
+ ]);
1547
+ }
1548
+
1549
+ for (let i = 0; i < mol_sets.length; ++i) {
1550
+
1551
+ var mol = [];
1552
+ for (let j = 0; j < mol_sets[i][0].length; ++j) {
1553
+ mol.push({
1554
+ 'index': mol_sets[i][0][j],
1555
+ 'cell': mol_sets[i][1][j]
1556
+ });
1557
+ }
1558
+
1559
+ this._molecules.push(mol);
1560
+ }
1561
+
1562
+ // Assign the molecule's index for each atom
1563
+ this._molinds = _.range(this.length);
1564
+
1565
+ for (let i = 0; i < this._molecules.length; ++i) {
1566
+ var m = this._molecules[i];
1567
+ for (let j = 0; j < m.length; ++j) {
1568
+ var a = m[j];
1569
+ this._molinds[a.index] = i;
1570
+ }
1571
+ }
1572
+ }
1573
+
1574
+ /**
1575
+ * Return a list of all AtomImages within the given supercell.
1576
+ *
1577
+ * @private
1578
+ * @return {AtomImage[]} List of AtomImage objects
1579
+ */
1580
+ _atomImages() {
1581
+ var sgrid = this._supercell_grid;
1582
+ var imgs = [];
1583
+ var indices = _.range(this.length);
1584
+ var model = this;
1585
+ for (let i = 0; i < sgrid.length; ++i) {
1586
+ var cell = sgrid[i];
1587
+ imgs = imgs.concat(_.map(indices, function(a) {
1588
+ return new AtomImage(model, a, cell);
1589
+ }));
1590
+ }
1591
+ return imgs;
1592
+ }
1593
+
1594
+ /**
1595
+ * Return a list of all BondImages within the given supercell.
1596
+ *
1597
+ * @private
1598
+ * @return {BondImage[]} List of BondImage objects
1599
+ */
1600
+ _bondImages() {
1601
+ var bondimgs = [];
1602
+
1603
+ for (let ii = 0; ii < this._atom_images.length; ++ii) {
1604
+ var im1 = this._atom_images[ii];
1605
+ var i = im1.index;
1606
+ var bonds = this._bondmat[i];
1607
+ var c1 = im1.ijk;
1608
+ for (let j = i; j < this.length; ++j) {
1609
+ var blist = bonds[j];
1610
+ for (let k = 0; k < blist.length; ++k) {
1611
+ var r = blist[k];
1612
+ var c2 = [c1[0] + r[0], c1[1] + r[1], c1[2] + r[2]];
1613
+ var jj = utils.supercellIndex(j, c2, this._supercell, this.length);
1614
+ if (jj >= 0 && jj < this._atom_images.length) {
1615
+ var im2 = this._atom_images[jj];
1616
+ var bimg = new BondImage(this, im1, im2);
1617
+ bondimgs.push(bimg);
1618
+ }
1619
+ }
1620
+ }
1621
+ }
1622
+
1623
+ return bondimgs;
1624
+ }
1625
+
1626
+ /**
1627
+ * Convert fractional coordinates to absolute
1628
+ *
1629
+ * @param {float[]} fx Fractional coordinates
1630
+ * @return {float[]} Absolute coordinates
1631
+ */
1632
+ fracToAbs(fx) {
1633
+ if (!this.periodic) {
1634
+ return null
1635
+ }
1636
+ var c = this._atoms_base._cell;
1637
+ return [fx[0] * c[0][0] + fx[1] * c[1][0] + fx[2] * c[2][0],
1638
+ fx[0] * c[0][1] + fx[1] * c[1][1] + fx[2] * c[2][1],
1639
+ fx[0] * c[0][2] + fx[1] * c[1][2] + fx[2] * c[2][2]
1640
+ ];
1641
+ }
1642
+
1643
+ /**
1644
+ * Convert absolute coordinates to fractional
1645
+ *
1646
+ * @param {float[]} x Absolute coordinates
1647
+ * @return {float[]} Fractional coordinates
1648
+ */
1649
+ absToFrac(x) {
1650
+ if (!this.periodic) {
1651
+ return null
1652
+ }
1653
+ var ic = this._atoms_base._inv_cell;
1654
+ return [x[0] * ic[0][0] + x[1] * ic[1][0] + x[2] * ic[2][0],
1655
+ x[0] * ic[0][1] + x[1] * ic[1][1] + x[2] * ic[2][1],
1656
+ x[0] * ic[0][2] + x[1] * ic[1][2] + x[2] * ic[2][2]
1657
+ ];
1658
+ }
1659
+
1660
+ /**
1661
+ * Compute and return the minimum supercell that guarantees
1662
+ * containing all atoms at a maximum distance r from those in the
1663
+ * [0,0,0] cell.
1664
+ *
1665
+ * @param {float} r Maximum distance that must be contained within the supercell
1666
+ */
1667
+ minimumSupercell(r) {
1668
+
1669
+ var diag = _.map(this._r_diag.values, function(x) {
1670
+ return mjs.pow(x, -0.5)
1671
+ });
1672
+ var utransf_mat = mjs.multiply(this._r_diag.vectors, mjs.diag(diag));
1673
+ var utransf_norm = mjs.transpose(utransf_mat);
1674
+ for (let i = 0; i < 3; ++i) {
1675
+ var norm = mjs.norm(utransf_mat[i]);
1676
+ for (let j = 0; j < 3; ++j) {
1677
+ utransf_norm[j][i] *= r / norm;
1678
+ }
1679
+ }
1680
+ var qmatrix = mjs.multiply(utransf_mat, utransf_norm);
1681
+ var scell = [];
1682
+ for (let i = 0; i < 3; ++i) {
1683
+ var b = 0;
1684
+ for (let j = 0; j < 3; ++j) {
1685
+ b = Math.max(Math.ceil(Math.abs(qmatrix[i][j])), b);
1686
+ }
1687
+ scell.push(2 * b + 1);
1688
+ }
1689
+
1690
+ return scell;
1691
+ }
1692
+
1693
+ /**
1694
+ * Find a group of atoms based on a given query and return as AtomImages
1695
+ * @param {Array} query A search query for atoms. Must use nested lists
1696
+ * of types and arguments, and can use logic
1697
+ * operators $and, $or and $xor.
1698
+ * @return {ModelView} ModelView object for found atoms
1699
+ */
1700
+ find(query) {
1701
+ var found = this._qparse.parse(query);
1702
+ return this.view(found);
1703
+ }
1704
+
1705
+ /** Create a new ModelView for this model, using a given list of indices
1706
+ * @param {Array} indices Indices of atoms to include in the ModelView
1707
+ *
1708
+ * @return {ModelView} ModelView object for specified indices
1709
+ */
1710
+ view(indices) {
1711
+ return new ModelView(this, indices);
1712
+ }
1713
+
1714
+ /**
1715
+ * Set a property on a series of atom images
1716
+ *
1717
+ * @private
1718
+ * @param {AtomImage[]} aimages List of AtomImages, or their indices
1719
+ * @param {String} name Name of the property to set
1720
+ * @param {String} value Value to set to the property
1721
+ */
1722
+ _setAtomsProperty(aimages, name, value) {
1723
+
1724
+ // Value can be a single value or an Array
1725
+ var isarr = (value instanceof Array);
1726
+
1727
+ for (let i = 0; i < aimages.length; ++i) {
1728
+ var id = aimages[i];
1729
+ if (id instanceof AtomImage)
1730
+ id = id.imgIndex;
1731
+ this._atom_images[id][name] = isarr ? value[i] : value;
1732
+ }
1733
+
1734
+ }
1735
+
1736
+ /**
1737
+ * Set a property on a series of bond images
1738
+ *
1739
+ * @private
1740
+ * @param {BondImage[]} aimages List of BondImages
1741
+ * @param {String} name Name of the property to set
1742
+ * @param {String} value Value to set to the property
1743
+ */
1744
+ _setBondsProperty(bimages, name, value) {
1745
+
1746
+ // Value can be a single value or an Array
1747
+ var isarr = (value instanceof Array);
1748
+
1749
+ for (let i = 0; i < bimages.length; ++i) {
1750
+ var bimg = bimages[i];
1751
+ bimg[name] = isarr ? value[i] : value;
1752
+ }
1753
+
1754
+ }
1755
+
1756
+ // Query functions. These are for internal use. They return the indices of
1757
+ // AtomImages in the _atom_images array.
1758
+
1759
+ /**
1760
+ * @private
1761
+ */
1762
+ _queryAll() {
1763
+ // All atoms
1764
+ return _.range(this._atom_images.length);
1765
+ }
1766
+
1767
+ /**
1768
+ * @private
1769
+ */
1770
+ _queryIndices(indices) {
1771
+
1772
+ if (typeof(indices) == 'number') {
1773
+ indices = [indices]; // A single index
1774
+ }
1775
+
1776
+ var scell = this.supercell;
1777
+ var n = this.length;
1778
+ var scgrid = this._supercell_grid;
1779
+
1780
+ var found = _.map(indices, function(i) {
1781
+ return _.map(scgrid, function(ijk) {
1782
+ return utils.supercellIndex(i, ijk, scell, n);
1783
+ });
1784
+ });
1785
+
1786
+ return _.flatten(found);
1787
+ }
1788
+
1789
+ /**
1790
+ * @private
1791
+ */
1792
+ _queryElements(elems) {
1793
+ if (_.isString(elems)) {
1794
+ elems = [elems]; // A single symbol
1795
+ }
1796
+
1797
+ var indices = _.reduce(this._elems, function(inds, s, i) {
1798
+ if (elems.indexOf(s) > -1) {
1799
+ inds.push(i);
1800
+ }
1801
+ return inds;
1802
+ }, []);
1803
+
1804
+ return this._queryIndices(indices);
1805
+ }
1806
+
1807
+ /**
1808
+ * @private
1809
+ */
1810
+ _queryCell(ijk) {
1811
+
1812
+ // Check if ijk is contained in the supercell's limits
1813
+ var ind = _.findIndex(this._supercell_grid, function(x) {
1814
+ return _.isEqual(x, ijk);
1815
+ });
1816
+
1817
+ if (ind < 0) {
1818
+ return [];
1819
+ }
1820
+
1821
+ var scell = this.supercell;
1822
+ var n = this.length;
1823
+ var found = _.map(_.range(n), function(x) {
1824
+ return utils.supercellIndex(x, ijk, scell, n);
1825
+ });
1826
+
1827
+ return found;
1828
+ }
1829
+
1830
+ /**
1831
+ * @private
1832
+ */
1833
+ _queryBox(x0, x1) {
1834
+
1835
+ if (x0 instanceof AtomImage) {
1836
+ x0 = x0.xyz;
1837
+ }
1838
+ if (x1 instanceof AtomImage) {
1839
+ x1 = x1.xyz;
1840
+ }
1841
+
1842
+ // Box sides?
1843
+ var box = _.zip(x0, x1);
1844
+ var xmin = _.map(box, _.min);
1845
+ var xmax = _.map(box, _.max);
1846
+
1847
+ var fxmin;
1848
+ var fxmax;
1849
+ if (this.periodic) {
1850
+ var fx0 = this.absToFrac(x0);
1851
+ var fx1 = this.absToFrac(x1);
1852
+ var fbox = _.zip(fx0, fx1);
1853
+ fxmin = _.map(fbox, _.min);
1854
+ fxmax = _.map(fbox, _.max);
1855
+ fxmin = _.map(fxmin, Math.floor);
1856
+ fxmax = _.map(fxmax, Math.ceil);
1857
+
1858
+ // Now add supercell limits
1859
+ var scmin = this._supercell_grid[0];
1860
+ var scmax = this._supercell_grid[this._supercell_grid.length - 1];
1861
+ fxmin = _.zipWith(fxmin, scmin, function(f, s) {
1862
+ return Math.max(f, s);
1863
+ });
1864
+ fxmax = _.zipWith(fxmax, scmax, function(f, s) {
1865
+ return Math.min(f, s + 1);
1866
+ });
1867
+ } else {
1868
+ fxmin = [0, 0, 0];
1869
+ fxmax = [1, 1, 1];
1870
+ }
1871
+
1872
+ var found = []
1873
+
1874
+ // Now iterate over the cells, and atoms
1875
+ for (let i = fxmin[0]; i < fxmax[0]; ++i) {
1876
+ for (let j = fxmin[1]; j < fxmax[1]; ++j) {
1877
+ for (let k = fxmin[2]; k < fxmax[2]; ++k) {
1878
+ // var p0 = this.fracToAbs([i, j, k]);
1879
+ for (let a = 0; a < this.length; ++a) {
1880
+
1881
+ var ind = utils.supercellIndex(a, [i, j, k], this._supercell,
1882
+ this.length);
1883
+ var aimg = this._atom_images[ind];
1884
+
1885
+ // Is it in the box?
1886
+ var isin = _.reduce(aimg.xyz, function(r, x, e) {
1887
+ return (r && (xmin[e] <= x) && (x <= xmax[e]));
1888
+ }, true);
1889
+
1890
+ if (isin)
1891
+ found.push(ind);
1892
+ }
1893
+ }
1894
+ }
1895
+ }
1896
+
1897
+ return found;
1898
+ }
1899
+
1900
+ /**
1901
+ * @private
1902
+ */
1903
+ _querySphere(x0, r) {
1904
+
1905
+ if (x0 instanceof AtomImage) {
1906
+ x0 = x0.xyz; // Can use an atom as centre
1907
+ }
1908
+
1909
+ var scell = [1, 1, 1];
1910
+ var cell0 = [0, 0, 0];
1911
+ var fx0 = this.absToFrac(x0);
1912
+
1913
+ if (this.periodic) {
1914
+ // Supercell necessary for the search?
1915
+ scell = this.minimumSupercell(r);
1916
+ cell0 = _.map(fx0, Math.floor);
1917
+ }
1918
+
1919
+ var fxmin;
1920
+ var fxmax;
1921
+ if (this.periodic) {
1922
+ fxmin = _.zipWith(cell0, scell, function(c0, s) {
1923
+ return c0 - (s - 1) / 2;
1924
+ });
1925
+ fxmax = _.zipWith(cell0, scell, function(c0, s) {
1926
+ return c0 + (s + 1) / 2;
1927
+ });
1928
+
1929
+ // Now add supercell limits
1930
+ var scmin = this._supercell_grid[0];
1931
+ var scmax = this._supercell_grid[this._supercell_grid.length - 1];
1932
+ fxmin = _.zipWith(fxmin, scmin, function(f, s) {
1933
+ return Math.max(f, s);
1934
+ });
1935
+ fxmax = _.zipWith(fxmax, scmax, function(f, s) {
1936
+ return Math.min(f, s + 1);
1937
+ });
1938
+ } else {
1939
+ fxmin = [0, 0, 0];
1940
+ fxmax = [1, 1, 1];
1941
+ }
1942
+
1943
+ var found = [];
1944
+
1945
+ for (let i = fxmin[0]; i < fxmax[0]; ++i) {
1946
+ for (let j = fxmin[1]; j < fxmax[1]; ++j) {
1947
+ for (let k = fxmin[2]; k < fxmax[2]; ++k) {
1948
+ for (let a = 0; a < this.length; ++a) {
1949
+
1950
+ var ind = utils.supercellIndex(a, [i, j, k], this._supercell,
1951
+ this.length);
1952
+ var aimg = this._atom_images[ind];
1953
+
1954
+ // Is it in the sphere?
1955
+ var isin = mjs.distance(aimg.xyz, x0) <= r;
1956
+
1957
+ if (isin)
1958
+ found.push(ind);
1959
+ }
1960
+ }
1961
+ }
1962
+ }
1963
+
1964
+ return found;
1965
+ }
1966
+
1967
+ /**
1968
+ * @private
1969
+ */
1970
+ _queryBonded(atoms, distance = 1, exact = false) {
1971
+
1972
+ if (atoms instanceof AtomImage || typeof(atoms) == 'number') {
1973
+ atoms = [atoms];
1974
+ }
1975
+ if (atoms instanceof ModelView) {
1976
+ atoms = atoms._images;
1977
+ }
1978
+ if (atoms instanceof Array && typeof(atoms[0]) == 'number') {
1979
+ var imgs = this._atom_images;
1980
+ atoms = _.map(atoms, function(i) {
1981
+ return imgs[i];
1982
+ });
1983
+ }
1984
+
1985
+ if (distance < 1) {
1986
+ return [];
1987
+ }
1988
+
1989
+ function a2ii(a) {
1990
+ return a.imgIndex;
1991
+ }
1992
+
1993
+ // Find all atoms that are at most [distance] bonds away from the ones
1994
+ // passed as argument
1995
+
1996
+ var bonded_tree = [atoms]; // We start with distance zero and build up
1997
+ var found = [];
1998
+
1999
+ for (let d = 1; d <= distance; ++d) {
2000
+ var previous = bonded_tree[d - 1];
2001
+ var next = [];
2002
+ for (let i = 0; i < previous.length; ++i) {
2003
+ next = next.concat(previous[i].bondedAtoms);
2004
+ }
2005
+ bonded_tree.push(next);
2006
+ if (!exact) {
2007
+ found = found.concat(_.map(next, a2ii));
2008
+ } else if (d == distance) {
2009
+ found = _.map(next, a2ii);
2010
+ }
2011
+ }
2012
+
2013
+ found = _.uniq(found); // Remove duplicate values
2014
+ found = _.difference(found, _.map(bonded_tree[0], function(a) {
2015
+ return a.imgIndex;
2016
+ })); // Remove the starting atoms
2017
+
2018
+ return found;
2019
+ }
2020
+
2021
+ /**
2022
+ * @private
2023
+ */
2024
+ _queryMolecule(atoms) {
2025
+
2026
+ if (atoms instanceof AtomImage || typeof(atoms) == 'number') {
2027
+ atoms = [atoms];
2028
+ }
2029
+ if (atoms instanceof ModelView) {
2030
+ atoms = atoms._images;
2031
+ }
2032
+ if (atoms instanceof Array && typeof(atoms[0]) == 'number') {
2033
+ var imgs = this._atom_images;
2034
+ atoms = _.map(atoms, function(i) {
2035
+ return imgs[i];
2036
+ });
2037
+ }
2038
+
2039
+ var found = [];
2040
+
2041
+ // For each atom, select the whole molecule
2042
+ for (let i = 0; i < atoms.length; ++atoms) {
2043
+ var a = atoms[i];
2044
+
2045
+ var ind = a.index;
2046
+ var mol_ind = this._molinds[ind];
2047
+ var mol = this._molecules[mol_ind];
2048
+
2049
+ // Identify the atom
2050
+ var c0 = _.find(mol, function(am) {
2051
+ return am.index == ind;
2052
+ }).cell;
2053
+
2054
+ // Supercell indices?
2055
+ for (let j = 0; j < mol.length; ++j) {
2056
+ var am = mol[j];
2057
+ var cm = mjs.subtract(am.cell, c0);
2058
+ var im = am.index;
2059
+ var iim = utils.supercellIndex(im, cm, this._supercell, this.length);
2060
+ if (iim >= 0 && iim < this._atom_images.length) {
2061
+ found.push(iim);
2062
+ }
2063
+ }
2064
+ }
2065
+
2066
+ return found;
2067
+
2068
+ }
2069
+
2070
+ }
2071
+
2072
+ export {
2073
+ AtomImage,
2074
+ BondImage,
2075
+ Model
2076
+ }