@ccp-nc/crystvis-js 0.6.0 → 0.6.1

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.
@@ -7,31 +7,87 @@
7
7
 
8
8
  import _ from 'lodash';
9
9
  import { Atoms } from '@ccp-nc/crystcif-parse';
10
+ import { TensorData } from '../tensor.js';
10
11
 
11
- function load(contents, filename='xyz') {
12
+ /**
13
+ * Load an Extended XYZ file (optionally multi-frame), extracting NMR tensor data as TensorData objects.
14
+ *
15
+ * Only the Extended XYZ format (with a unit cell specified via the Lattice property) is supported.
16
+ * Standard/plain XYZ files (without a cell) are not supported and will result in an error.
17
+ * Extended XYZ files can contain additional per-atom properties, including NMR tensors (e.g., magnetic shielding or EFG tensors)
18
+ * stored as 9 real values per atom. These are automatically converted to TensorData objects if the property name matches
19
+ * the provided shielding_tag or efg_tag (defaults: 'ms' and 'efg').
20
+ *
21
+ * Multi-frame files are supported: use the index argument to select a frame (default: -1, last frame).
22
+ *
23
+ * @param {string} contents - The contents of the Extended XYZ file.
24
+ * @param {string} [filename='xyz'] - Optional name for the returned structure (used as key in result object).
25
+ * @param {string} [shielding_tag='ms'] - Property name to treat as magnetic shielding tensor (9 real values per atom).
26
+ * @param {string} [efg_tag='efg'] - Property name to treat as EFG tensor (9 real values per atom).
27
+ * @param {number} [index=-1] - Frame index to load (0-based, negative counts from end; default -1 = last frame).
28
+ * @returns {Object} Dictionary mapping filename to Atoms object. Per-atom tensor arrays are stored as TensorData objects.
29
+ * @throws {Error} If the file is not valid Extended XYZ (e.g., missing cell), or if tensor arrays are malformed.
30
+ *
31
+ * Example usage:
32
+ * load(contents, 'mystruct', 'ms', 'efg', -1)
33
+ * // Returns: { mystruct: Atoms }
34
+ * // Atoms.get_array('ms')[0] is a TensorData instance
35
+ */
36
+ function load(contents, filename='xyz', shielding_tag='ms', efg_tag='efg', index=-1) {
12
37
 
13
- // Split the file into lines
38
+ // Split into frames
14
39
  let lines = _.split(contents, '\n');
40
+ let frames = [];
41
+ let i = 0;
42
+ while (i < lines.length) {
43
+ // Skip empty lines
44
+ while (i < lines.length && _.trim(lines[i]) === '') i++;
45
+ if (i >= lines.length) break;
46
+ let lineContent = _.trim(lines[i]);
47
+ if (lineContent === '') {
48
+ i++;
49
+ continue;
50
+ }
51
+ let N = parseInt(lineContent);
52
+ if (isNaN(N) || N <= 0) {
53
+ i++;
54
+ continue;
55
+ }
56
+ // Check if we have enough lines for this frame
57
+ if (i + N + 1 >= lines.length) break;
58
+ let frame = lines.slice(i, i + N + 2);
59
+ frames.push(frame);
60
+ i += N + 2;
61
+ }
62
+ if (frames.length === 0) {
63
+ throw Error('No valid frames found in XYZ/Extended XYZ file.');
64
+ }
65
+ // Select frame
66
+ let frame_idx = index < 0 ? frames.length + index : index;
67
+ if (frame_idx < 0 || frame_idx >= frames.length) {
68
+ throw Error('Frame index out of range for XYZ/Extended XYZ file.');
69
+ }
70
+ lines = frames[frame_idx];
15
71
 
16
72
  // Parse the first line: number of atoms
17
73
  let N = parseInt(_.trim(lines[0]));
18
74
  if (isNaN(N) || lines.length < N + 2) {
19
- throw Error('Invalid XYZ file');
75
+ throw Error('Invalid XYZ/Extended XYZ frame: could not parse atom count or frame is too short.');
20
76
  }
21
77
 
22
78
  // Parse the second line: comments
23
79
  let info = lines[1];
24
80
  // Check if it's extended format
25
- let ext = false;
26
- let rext = /([A-Za-z]+)=(([A-Za-z0-9.:]+)|"([\s0-9.e-]+)")/g;
27
- let m = rext.exec(info);
81
+ let rext = /([A-Za-z_]+)=(([A-Za-z0-9.:_-]+)|"([^"]+)")/g;
28
82
  let matches = [];
29
83
  let cell = null;
30
- let arrays = [];
31
- while (m != null) {
84
+ let columns = [];
85
+
86
+ let m;
87
+ while ((m = rext.exec(info)) !== null) {
32
88
  matches.push(m);
33
- m = rext.exec(info);
34
89
  }
90
+
35
91
  if (matches.length > 0) {
36
92
  // It's extended! But does it have the right keywords?
37
93
  let props = {};
@@ -40,35 +96,28 @@ function load(contents, filename='xyz') {
40
96
  props[m[1]] = m[3] || m[4];
41
97
  }
42
98
  if ((_.has(props, 'Lattice') && _.has(props, 'Properties'))) {
43
- // It's valid!
44
- ext = true;
45
99
  // Parse the lattice
46
- let latt = _.split(props['Lattice'], /\s+/);
100
+ let latt = _.split(props['Lattice'], /\s+/).filter(s => s.length > 0);
47
101
  cell = [];
48
102
  for (let i = 0; i < 3; ++i) {
49
- cell.push(_.map(latt.slice(3 * i, 3 * i + 3), parseFloat));
50
- if (cell[i].indexOf(NaN) > -1) {
51
- throw Error('Invalid Extended XYZ file');
103
+ let row = _.map(latt.slice(3 * i, 3 * i + 3), parseFloat);
104
+ if (row.some(v => isNaN(v))) {
105
+ throw Error('Invalid Extended XYZ file: could not parse cell row.');
52
106
  }
107
+ cell.push(row);
53
108
  }
54
- if (props['Properties'].slice(0, 19) != 'species:S:1:pos:R:3') {
55
- throw Error('Invalid Extended XYZ file');
109
+ if (!props['Properties'].startsWith('species:S:1:pos:R:3')) {
110
+ throw Error('Invalid Extended XYZ file: Properties string does not start with species:S:1:pos:R:3');
56
111
  }
57
112
  // Parse the properties
58
- let propre = /([A-Za-z]+):(S|R|I):([0-9]+)/g;
59
- let columns = [];
60
- m = propre.exec(props['Properties'])
61
- while (m != null) {
113
+ let propre = /([A-Za-z_]+):(S|R|I):([0-9]+)/g;
114
+ while ((m = propre.exec(props['Properties'])) !== null) {
62
115
  columns.push({
63
116
  'name': m[1],
64
117
  'type': m[2],
65
118
  'n': parseInt(m[3]),
66
- 'val': [],
67
119
  });
68
- m = propre.exec(props['Properties']);
69
120
  }
70
- // We know the first two are just species and pos
71
- arrays = columns.slice(2);
72
121
  }
73
122
 
74
123
  info = _.omit(props, ['Lattice', 'Properties']);
@@ -78,46 +127,94 @@ function load(contents, filename='xyz') {
78
127
  };
79
128
  }
80
129
 
130
+ // Check if we have a cell
131
+ if (cell === null) {
132
+ throw Error('No unit cell (Lattice property) found in this file. Only Extended XYZ format with a cell is supported; plain XYZ files are not supported.');
133
+ }
134
+
135
+ // Initialize arrays for additional properties (skip species and pos which are columns 0 and 1)
136
+ let arrays = {};
137
+ for (let i = 2; i < columns.length; ++i) {
138
+ arrays[columns[i].name] = [];
139
+ }
140
+
81
141
  // Parse the following lines: atoms
82
142
  let elems = [];
83
143
  let pos = [];
144
+ // Determine expected number of tokens per atom line:
145
+ // species (1) + pos (3) + sum of additional property token counts
146
+ let expected_tokens = 1 + 3;
147
+ for (let c = 2; c < columns.length; ++c) {
148
+ expected_tokens += columns[c].n;
149
+ }
150
+
84
151
  for (let i = 0; i < N; ++i) {
85
- let lspl = _.split(lines[i + 2], /\s+/);
152
+ let line_index = i + 2;
153
+ let line = lines[line_index];
154
+ let lspl = _.split(line, /\s+/).filter(s => s.length > 0);
155
+
156
+ // Expect the atom line to contain all tokens; do not attempt to merge
157
+ // subsequent physical lines. If token count is insufficient, fail fast.
158
+ if (lspl.length < expected_tokens) {
159
+ throw Error('Invalid Extended XYZ file: insufficient property values on line ' + (i + 3));
160
+ }
161
+
162
+ // Read species (column 0)
86
163
  elems.push(lspl[0]);
87
- pos.push(_.map(lspl.slice(1, 4), parseFloat));
88
- if (pos.indexOf(NaN) > -1) {
89
- throw Error('Invalid XYZ file');
164
+
165
+ // Track current position in split line
166
+ let col_idx = 1;
167
+
168
+ // Read positions (column 1: pos:R:3)
169
+ let position = [];
170
+ for (let j = 0; j < 3; ++j) {
171
+ position.push(parseFloat(lspl[col_idx++]));
90
172
  }
91
-
92
- // Any additional parsing required?
93
- if (ext) {
94
- lspl.splice(0, 4);
95
- for (let j = 0; j < arrays.length; ++j) {
96
- let v = [];
97
- let parser = {
98
- 'S': String,
99
- 'R': parseFloat,
100
- 'I': parseInt,
101
- }[arrays[j].type];
102
- for (let k = 0; k < arrays[j].n; ++k) {
103
- v.push(parser(lspl.splice(0)));
173
+ if (position.some(v => isNaN(v))) {
174
+ throw Error('Invalid XYZ file: could not parse atom position.');
175
+ }
176
+ pos.push(position);
177
+
178
+ // Read additional properties (columns 2+)
179
+ for (let c = 2; c < columns.length; ++c) {
180
+ let col = columns[c];
181
+ let v = [];
182
+ let parser = {
183
+ 'S': function(s) { return String(s); },
184
+ 'R': function(s) { return parseFloat(s); },
185
+ 'I': function(s) { return parseInt(s); },
186
+ }[col.type];
187
+
188
+ for (let k = 0; k < col.n; ++k) {
189
+ if (col_idx >= lspl.length) {
190
+ throw Error('Invalid Extended XYZ file: insufficient property values on line ' + (i + 3));
104
191
  }
192
+ v.push(parser(lspl[col_idx++]));
193
+ }
194
+
195
+ // Convert 9-element real arrays into TensorData immediately for shielding/EFG
196
+ if (col.type === 'R' && col.n === 9 && (col.name === shielding_tag || col.name === efg_tag)) {
197
+ if (v.length !== 9) {
198
+ throw Error('Invalid tensor array for ' + col.name);
199
+ }
200
+ v = new TensorData([
201
+ [v[0], v[1], v[2]],
202
+ [v[3], v[4], v[5]],
203
+ [v[6], v[7], v[8]]
204
+ ]);
205
+ } else {
105
206
  v = v.length > 1 ? v : v[0];
106
- arrays[j].val.push(v);
107
207
  }
208
+
209
+ arrays[col.name].push(v);
108
210
  }
109
211
  }
110
212
 
111
- // check if we have a cell
112
- if (cell === null) {
113
- throw Error('No cell found in XYZ file. Please use the Extended XYZ format.');
114
- }
115
-
116
213
  let a = new Atoms(elems, pos, cell, info);
117
- if (ext) {
118
- for (let i = 0; i < arrays.length; ++i) {
119
- a.set_array(arrays[i].name, arrays[i].val);
120
- }
214
+
215
+ // Add all arrays to atoms object
216
+ for (let name in arrays) {
217
+ a.set_array(name, arrays[name]);
121
218
  }
122
219
 
123
220
  let structs = {};
package/lib/loader.js CHANGED
@@ -31,16 +31,17 @@ class Loader {
31
31
  return this._error;
32
32
  }
33
33
 
34
- /** Load file from its contents and format
34
+ /**
35
+ * Load file from its contents and format
35
36
  *
36
37
  * @param {String} contents File contents
37
38
  * @param {String} format File extension
38
39
  * @param {String} filename Name of the file. If provided, this will be
39
40
  * added as a prefix to all the names in the dictionary
40
- *
41
+ * @param {Object} [options] Optional parser-specific options (e.g. {shielding_tag, efg_tag} for xyz)
41
42
  * @return {Object} Dictionary of parsed structure(s)
42
43
  */
43
- load(contents, format='cif', filename=null) {
44
+ load(contents, format='cif', filename=null, options={}) {
44
45
 
45
46
  const parsers = {
46
47
  cif: CIF,
@@ -50,21 +51,41 @@ class Loader {
50
51
  };
51
52
 
52
53
  format = format.toLowerCase();
54
+ // Treat extxyz as xyz
55
+ if (format === 'extxyz') {
56
+ format = 'xyz';
57
+ }
58
+
59
+ this._error = '';
53
60
 
54
61
  if (!(format in parsers)) {
55
62
  throw Error('Invalid file format');
56
63
  }
57
64
 
58
- this._error = '';
59
-
60
65
  let structs;
61
66
 
62
67
  try {
63
- if (filename)
68
+ if (format === 'xyz') {
69
+ // Support shielding_tag, efg_tag, and index for xyz
70
+ const shielding = options.shielding_tag || 'ms';
71
+ const efg = options.efg_tag || 'efg';
72
+ const index = (typeof options.index === 'number') ? options.index : -1;
73
+ // Pass filename only if provided; otherwise let parser use its default
74
+ if (filename) {
75
+ structs = parsers[format].load(contents, filename, shielding, efg, index);
76
+ } else {
77
+ structs = parsers[format].load(contents, undefined, shielding, efg, index);
78
+ }
79
+ } else if (filename) {
64
80
  structs = parsers[format].load(contents, filename);
65
- else
81
+ } else {
66
82
  structs = parsers[format].load(contents);
83
+ }
67
84
  } catch (err) {
85
+ // For XYZ parsing, let errors propagate so callers/tests can catch them.
86
+ if (format === 'xyz') {
87
+ throw err;
88
+ }
68
89
  this._status = Loader.STATUS_ERROR;
69
90
  this._error = err.message || err;
70
91
  return;
package/lib/model.js CHANGED
@@ -627,9 +627,6 @@ class AtomImage {
627
627
  let seed = utils.hashCode(this._fxyz + name);
628
628
  parameters.ditherSeed = seed/4294967295.0; // Reduce to ]0.5,-0.5]
629
629
  }
630
- else {
631
-
632
- }
633
630
 
634
631
  var r = this.renderer;
635
632
  if (r) {
@@ -13,9 +13,6 @@ import {
13
13
  addStaticVar
14
14
  } from '../utils.js';
15
15
 
16
- // Basic materials
17
- const _phong = new THREE.MeshPhongMaterial({});
18
-
19
16
  class EllipsoidMesh extends THREE.Mesh {
20
17
 
21
18
  constructor(parameters = {}) {
@@ -176,7 +173,6 @@ class EllipsoidMesh extends THREE.Mesh {
176
173
 
177
174
  set color(c) {
178
175
  // Change all colors
179
- const color = new THREE.Color(c);
180
176
 
181
177
  // Handle DitherMaterial or standard material
182
178
  if (this.material.uniforms && this.material.uniforms.color) {
File without changes
@@ -8,9 +8,6 @@ import {
8
8
  } from './dither.js';
9
9
  import { cellMatrix3, addStaticVar } from '../utils.js';
10
10
 
11
- // Basic materials
12
- const _phong = new THREE.MeshPhongMaterial({});
13
-
14
11
  class IsosurfaceMesh extends THREE.Mesh {
15
12
 
16
13
  constructor(field, threshold, lattice, parameters={}) {
@@ -60,7 +57,7 @@ class IsosurfaceMesh extends THREE.Mesh {
60
57
  dims[0] = field.length;
61
58
  dims[1] = field[0].length;
62
59
  dims[2] = field[0][0].length;
63
- } catch (e) {
60
+ } catch {
64
61
  // If we're here, something is wrong with field
65
62
  throw Error('Invalid field for isosurface rendering');
66
63
  }
package/lib/query.js CHANGED
@@ -57,7 +57,7 @@ class QueryParser {
57
57
  switch (names.length) {
58
58
  case 0:
59
59
  return [];
60
- case 1:
60
+ case 1: {
61
61
  let name = names[0];
62
62
  let args = query[name];
63
63
 
@@ -78,41 +78,16 @@ class QueryParser {
78
78
  default:
79
79
  throw new Error('Invalid query: query name not recognized');
80
80
  }
81
- default:
81
+ }
82
+ default: {
82
83
  let queries = _.map(query, (v, k) => {
83
84
  var q = {};
84
85
  q[k] = v;
85
86
  return q;
86
87
  });
87
88
  return this.and(queries);
88
- }
89
-
90
-
91
- for (let i = 0; i < names.length; ++i) {
92
- let name = names[i];
93
- let args = query[name];
94
-
95
- if (!(args instanceof Array)) {
96
- args = [args]; // Just for a single argument
97
89
  }
98
-
99
- if (name in this._methods) {
100
- // Then call the method
101
- return this._methods[name].apply(this._context, args);
102
- } else switch (name) {
103
- case '$and':
104
- return this.and(args);
105
- case '$or':
106
- return this.or(args);
107
- case '$xor':
108
- return this.xor(args);
109
- default:
110
- throw new Error('Invalid query: query name not recognized');
111
- }
112
-
113
90
  }
114
-
115
- var name = query[0];
116
91
  }
117
92
 
118
93
  and(queries) {
package/lib/tensor.js CHANGED
@@ -7,7 +7,6 @@
7
7
 
8
8
  import _ from 'lodash';
9
9
  import * as mjs from 'mathjs';
10
- import * as THREE from 'three';
11
10
 
12
11
  const efg2hz = 234964.77815245767;
13
12
  const isc2hz = 1.6784031762379067e-16; // hbar/(2*pi)*1e19
package/outdated.txt ADDED
@@ -0,0 +1,12 @@
1
+ Package Current Wanted Latest Location Depended by
2
+ @babel/eslint-parser 7.28.0 7.28.6 7.28.6 node_modules/@babel/eslint-parser crystvis-js
3
+ chai 5.2.1 5.3.3 6.2.2 node_modules/chai crystvis-js
4
+ chroma-js 3.1.2 3.2.0 3.2.0 node_modules/chroma-js crystvis-js
5
+ esbuild 0.25.6 0.25.12 0.27.2 node_modules/esbuild crystvis-js
6
+ eslint 9.31.0 9.39.2 9.39.2 node_modules/eslint crystvis-js
7
+ glob 11.1.0 11.1.0 13.0.0 node_modules/glob crystvis-js
8
+ jquery 3.7.1 3.7.1 4.0.0 node_modules/jquery crystvis-js
9
+ jsdoc 4.0.4 4.0.5 4.0.5 node_modules/jsdoc crystvis-js
10
+ mathjs 14.5.3 14.9.1 15.1.0 node_modules/mathjs crystvis-js
11
+ mocha 11.7.1 11.7.5 11.7.5 node_modules/mocha crystvis-js
12
+ three 0.178.0 0.178.0 0.182.0 node_modules/three crystvis-js
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ccp-nc/crystvis-js",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "A Three.js based crystallographic visualisation tool",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -72,33 +72,32 @@
72
72
  "@ccp-nc/crystcif-parse": "^0.2.9",
73
73
  "@jkshenton/three-bmfont-text": "^4.0.1",
74
74
  "buffer": "^6.0.3",
75
- "chroma-js": "^3.1.2",
75
+ "chroma-js": "^3.2.0",
76
76
  "isosurface": "^1.0.0",
77
77
  "jquery": "^3.7.1",
78
78
  "load-bmfont": "^1.4.2",
79
79
  "lodash": "^4.17.21",
80
- "mathjs": "^14.5.3",
80
+ "mathjs": "^14.9.1",
81
81
  "three": "^0.178.0",
82
82
  "yargs-parser": "^22.0.0"
83
83
  },
84
84
  "devDependencies": {
85
- "@babel/eslint-parser": "^7.28.0",
86
- "chai": "^5.2.1",
85
+ "@babel/eslint-parser": "^7.28.6",
86
+ "chai": "^5.3.3",
87
87
  "chai-almost": "^1.0.1",
88
88
  "clean-jsdoc-theme": "^4.3.0",
89
89
  "datauri": "^4.1.0",
90
- "elliptic": ">=6.6.1",
91
- "esbuild": "^0.25.6",
92
- "eslint": "^9.31.0",
90
+ "esbuild": "^0.25.12",
91
+ "eslint": "^9.39.2",
93
92
  "gh-pages": "^6.3.0",
94
- "glob": "^11.0.3",
95
- "jpeg-js": ">=0.4.4",
96
- "jsdoc": "^4.0.4",
93
+ "glob": "^11.1.0",
94
+ "jpeg-js": "^0.4.4",
95
+ "jsdoc": "^4.0.5",
97
96
  "minimist": "^1.2.8",
98
- "mocha": "^11.7.1",
99
- "msdf-bmfont-xml": "^2.7.0",
97
+ "mocha": "^11.7.5",
98
+ "msdf-bmfont-xml": "^2.8.0",
100
99
  "npm-watch": "^0.13.0",
101
- "serve": "^14.2.4"
100
+ "serve": "^14.2.5"
102
101
  },
103
102
  "overrides": {
104
103
  "minimist": "$minimist"
@@ -0,0 +1,33 @@
1
+ 9
2
+ Lattice="20.0 0.0 0.0 0.0 20.0 0.0 0.0 0.0 20.0" Properties=species:S:1:pos:R:3:ms:R:9 pbc="T T T"
3
+ C 0.05539939 -0.60057807 0.62649405 125.81813960 -9.69204056 1.47151834 -9.69204056 152.57296730 -15.37444798 1.47151834 -15.37444798 153.40518054
4
+ C -0.30393478 0.62801927 -0.09541083 67.25341634 -10.54059929 -10.70968980 -10.54059929 76.17154239 7.81637325 -10.70968980 7.81637325 131.81262332
5
+ O 0.02847236 0.40911809 -1.55058241 142.74157885 -23.92176821 -21.41240976 -23.92176821 150.62543764 7.18471731 -21.41240976 7.18471731 205.63479341
6
+ H -0.71042240 -0.84152627 1.36951625 31.54030509 0.67457781 -2.05943366 0.67457781 27.78821965 -2.78219193 -2.05943366 -2.78219193 29.56439885
7
+ H 0.05685210 -1.46800745 -0.03997531 25.12335152 -1.32245778 -1.01062680 -1.32245778 29.43896220 -0.62651903 -1.01062680 -0.62651903 26.16607133
8
+ H 1.01360786 -0.50363368 1.14519882 27.17911795 -2.04842122 0.83359139 -2.04842122 29.62583333 -0.39001171 0.83359139 -0.39001171 29.84956008
9
+ H 0.29595155 1.48046374 0.23638874 22.79911499 -1.48276746 -1.60906958 -1.48276746 23.25594937 -1.80556513 -1.60906958 -1.80556513 25.48107695
10
+ H -1.38620961 0.77880669 -0.04469536 21.43976417 -1.06047575 -2.00845616 -1.06047575 26.04861314 -0.56302310 -2.00845616 -0.56302310 28.05747534
11
+ H 0.95028353 0.11733764 -1.64693403 26.05496271 -0.59980912 1.29686340 -0.59980912 27.35984859 1.09066721 1.29686340 1.09066721 11.92762208
12
+ 9
13
+ Lattice="20.0 0.0 0.0 0.0 20.0 0.0 0.0 0.0 20.0" Properties=species:S:1:pos:R:3:ms:R:9 pbc="T T T"
14
+ C -0.40320021 -0.45122528 0.73024166 132.90497460 3.59552794 -12.43391876 3.59552794 127.77706275 -12.28247854 -12.43391876 -12.28247854 148.26339821
15
+ C -0.08346952 0.15676123 -0.68547010 122.86689596 -2.07500597 -3.96961991 -2.07500597 66.98638751 -15.21036672 -3.96961991 -15.21036672 91.39017108
16
+ O 1.32339168 0.48240674 -0.82522243 224.49194781 16.57134021 0.49540797 16.57134021 165.51833992 -2.95162455 0.49540797 -2.95162455 201.51744849
17
+ H -1.45097303 -0.17661801 0.88316929 31.39533363 -1.50216432 -2.08575051 -1.50216432 27.90719213 -2.17636659 -2.08575051 -2.17636659 24.58283530
18
+ H -0.38172311 -1.54280162 0.66229165 28.71581869 0.65206504 0.24523305 0.65206504 33.47771386 -0.13534390 0.24523305 -0.13534390 28.16152002
19
+ H 0.34883970 -0.09896098 1.44225454 29.07964610 -0.53596286 -0.80798570 -0.53596286 29.54542507 -0.15879025 -0.80798570 -0.15879025 30.87386769
20
+ H -0.49669987 1.16926062 -0.71196169 30.60576444 0.72622326 -1.87887897 0.72622326 21.85971975 -3.21777764 -1.87887897 -3.21777764 22.96413413
21
+ H -0.47664648 -0.52174139 -1.44814277 29.76964783 -1.12461680 -0.30705060 -1.12461680 24.74071487 -2.76789311 -0.30705060 -2.76789311 26.03554994
22
+ H 1.62048090 0.98291862 -0.04716024 15.17890396 -2.46795510 2.53393948 -2.46795510 26.09809172 2.27749188 2.53393948 2.27749188 27.54652654
23
+ 9
24
+ Lattice="20.0 0.0 0.0 0.0 20.0 0.0 0.0 0.0 20.0" Properties=species:S:1:pos:R:3:ms:R:9 pbc="T T T"
25
+ C -0.69807410 -0.44723904 0.24317208 152.92455307 16.26391385 -4.26821306 16.26391385 125.77540629 -16.76320152 -4.26821306 -16.76320152 130.73689699
26
+ C 0.34105554 0.29010150 -0.60698903 102.07784519 13.93734131 16.33479642 13.93734131 80.61538795 -4.05889589 16.33479642 -4.05889589 102.80989037
27
+ O 1.46549475 0.45275295 0.25985932 199.78462184 11.22005439 7.45063951 11.22005439 154.96249301 15.23597911 7.45063951 15.23597911 211.90087809
28
+ H -0.70180583 -0.01803672 1.24934685 29.98454565 -0.25724168 -2.21732555 -0.25724168 27.45870874 -0.14606383 -2.21732555 -0.14606383 29.16124917
29
+ H -1.72323835 -0.53104490 -0.12916072 34.48333304 -0.20546525 2.22402884 -0.20546525 27.38393293 0.02054883 2.22402884 0.02054883 25.44008375
30
+ H -0.45866823 -1.50883543 0.35415125 27.22086782 -0.14588137 -0.17633240 -0.14588137 31.74670019 0.04446484 -0.17633240 0.04446484 28.30452212
31
+ H -0.09631128 1.20396113 -1.01952636 26.48360645 2.31248543 1.04328519 2.31248543 24.46949466 -2.22404337 1.04328519 -2.22404337 24.50418879
32
+ H 0.70887178 -0.35195473 -1.41265452 26.93480331 1.70750614 1.51654145 1.70750614 22.16202488 -2.45242451 1.51654145 -2.45242451 23.38735678
33
+ H 1.16267574 0.91029525 1.06180108 15.18096611 -3.57189977 -7.34818913 -3.57189977 25.05233266 -0.90280873 -7.34818913 -0.90280873 24.27668518