UNPKG

@ccp-nc/crystvis-js

Version:

A Three.js based crystallographic visualisation tool

399 lines (311 loc) 12.1 kB
'use strict'; import * as chai from 'chai'; import chaiAlmost from 'chai-almost' import _ from 'lodash'; import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import { Atoms as Atoms } from '@ccp-nc/crystcif-parse'; import { Model, AtomImage, BondImage } from '../lib/model.js'; import { ModelView as ModelView } from '../lib/modelview.js'; import { Loader as Loader } from '../lib/loader.js'; chai.use(chaiAlmost(1e-3)); const expect = chai.expect const __dirname = path.dirname(fileURLToPath( import.meta.url)); // Load test files var cif = fs.readFileSync(path.join(__dirname, 'data', 'CHA.cif'), "utf8"); var cha = Atoms.readCif(cif)['CHA']; var chamodel = new Model(cha); var chamodel3 = new Model(cha, { supercell: [3, 3, 3] }); cif = fs.readFileSync(path.join(__dirname, 'data', 'org.cif'), "utf8"); var org = Atoms.readCif(cif)['1501936']; var orgmodel = new Model(org); var xyz = fs.readFileSync(path.join(__dirname, 'data', 'pyridine_nocell.xyz'), "utf8"); var loader = new Loader(); // try to load an xyz with no unit cell, catch the error try { loader.load(xyz, 'xyz'); } catch (e) { expect(e.message).to.be.equal('No unit cell found in xyz file'); } // manually added in dummy cell: xyz = fs.readFileSync(path.join(__dirname, 'data', 'pyridine.xyz'), "utf8"); var pyr = loader.load(xyz, 'xyz')['xyz']; var pyrmodel = new Model(pyr); xyz = fs.readFileSync(path.join(__dirname, 'data', 'si8.xyz'), "utf8"); var si = loader.load(xyz, 'xyz')['xyz']; var simodel = new Model(si); var simodel3 = new Model(si, { supercell: [3, 3, 3] }); xyz = fs.readFileSync(path.join(__dirname, 'data', 'H2O.xyz'), "utf8"); var h2o = loader.load(xyz, 'xyz')['xyz']; var h2omodel = new Model(h2o); describe('#atomimage', function() { it('should correctly compute the periodic copy position', function() { var aim = new AtomImage(chamodel, 0, [1, 0, 0]); [25.339775, 1.16060394, 1.8119109].forEach(function(v, i) { expect(aim.xyz[i]).to.be.closeTo(v, 1e-5); }); aim = new AtomImage(chamodel, 0, [-1, 1, 1]); [-8.847725, 13.00350134, 16.5789109].forEach(function(v, i) { expect(aim.xyz[i]).to.be.closeTo(v, 1e-5); }); }); it('should correctly identify equalities', function() { var ai0 = new AtomImage(chamodel, 0, [0, 0, 1]); var ai1 = new AtomImage(chamodel, 0, [0, 0, 1]); var ai2 = new AtomImage(chamodel, 0, [1, 0, 0]); var ai3 = new AtomImage(simodel, 0, [0, 0, 1]); expect(ai0.equals(ai1)).to.be.equal(true); expect(ai0.equals(ai2)).to.be.equal(false); expect(ai0.equals(ai3)).to.be.equal(false); }); it('should correctly generate string IDs', function() { var ai = new AtomImage(chamodel, 2, [3, -1, 2]); expect(ai.id).to.equal('2_3_-1_2'); }); it('should correctly calculate its integer index', function() { for (var i = 0; i < simodel3.atoms.length; ++i) { var ai = simodel3.atoms[i]; expect(ai.imgIndex).to.equal(i); } }); it('should correctly identify the closest bonding neighbours', function() { // This one relies on Model to compute the right bonds var atoms = h2omodel.atoms; var a = atoms[0]; expect(a.bondedAtoms).to.deep.equal([atoms[1], atoms[2]]); }); it('should correctly retrieve a corresponding array value', function() { simodel.setArray('test_arr', _.range(simodel.length)); for (var i = 0; i < simodel.length; ++i) { expect(simodel.atoms[i].getArrayValue('test_arr'), i); } }); }); describe('#bondimage', function() { it('should correctly compute the distance between atoms in the bond', function() { var a1 = new AtomImage(h2omodel, 0, [0, 0, 0]); var a2 = new AtomImage(h2omodel, 1, [0, 0, 0]); var b = new BondImage(h2omodel, a1, a2); expect(b.length).to.almost.equal(0.9686); }); }); describe('#model', function() { it('should generate the right atomic labels', function() { expect(h2omodel._labels).to.deep.equal(['O_1', 'H_1', 'H_2', 'O_2', 'H_3', 'H_4']); }); it('should correctly compute a supercell grid', function() { expect(chamodel3.supercellGrid.length).to.be.equal(27); }); it('should correctly compute the minimum supercell for given radii', function() { expect(orgmodel.minimumSupercell(5)).to.deep.equal([3, 3, 3]); expect(orgmodel.minimumSupercell(10)).to.deep.equal([5, 5, 3]); expect(orgmodel.minimumSupercell(20)).to.deep.equal([7, 7, 5]); }); it('should correctly return its various properties', function() { expect(pyrmodel.length).to.equal(11); expect(chamodel.periodic).to.be.true; expect(pyrmodel.periodic).to.be.true; expect(simodel.periodic).to.be.true; }); it('should correctly identify CH bond presence', function() { expect(chamodel._queryCHBond()).to.be.false; expect(simodel._queryCHBond()).to.be.false; expect(pyrmodel._queryCHBond()).to.be.true; expect(h2omodel._queryCHBond()).to.be.false; expect(orgmodel._queryCHBond()).to.be.true; }); it('should correctly query for atoms in various ways', function() { // Here we only test the raw query functions, not meant for // public use var found = pyrmodel._queryElements(['C']); expect(found).to.deep.equal([0, 1, 2, 4, 5]); // Cell found = chamodel._queryCell([5, 5, 5]); // Beyond the supercell size expect(found).to.deep.equal([]); found = chamodel3._queryCell([1, 1, 1]); expect(found.length).to.equal(chamodel.length); expect(found[0]).to.equal(26 * chamodel.length); // Box //TODO why doesn't this work when I have a cell defined? // found = pyrmodel._queryBox([-1, -0.5, -2.3], [0, 0.5, 1.7]); // found.sort(); // expect(found).to.deep.equal([0, 3, 6]); found = simodel._queryBox([-1.5, -1.5, -1.5], [1.5, 1.5, 1.5]); expect(found).to.deep.equal([0, 1]); // Bigger supercell found = simodel3._queryBox([-1.5, -1.5, -1.5], [1.5, 1.5, 1.5]); expect(found).to.deep.equal([11, 29, 79, 104, 105]); found = simodel3._querySphere([0, 0, 0], 2.4); expect(found).to.deep.equal([11, 29, 79, 104, 105]); // Indices found = simodel3._queryIndices(0); expect(found).to.deep.equal(_.range(27).map((x) => { return 8*x; })); // Using an atom as the centre found = simodel._querySphere(simodel.atoms[0], 2.4); expect(found).to.deep.equal([0, 1]); // Bonds found = h2omodel._queryBonded(h2omodel.atoms[0]); expect(found).to.deep.equal([1, 2]); found = h2omodel._queryBonded(h2omodel.atoms[0], 2); expect(found).to.deep.equal([1, 2]); found = h2omodel._queryBonded(h2omodel.atoms[0], 2, true); expect(found.length).to.equal(0); found = pyrmodel._queryBonded(pyrmodel.atoms[3], 2, true); found.sort(); expect(found).to.deep.equal([1, 5, 8, 9]); // Molecules found = h2omodel._queryMolecule(h2omodel.atoms[0]); found.sort(); expect(found).to.deep.equal([0, 1, 2]); found = h2omodel._queryMolecule(h2omodel.atoms[3]); // Out of bounds expect(found).to.deep.equal([3]); // Test a more complex query found = simodel3.find({ '$and': [{ 'box': [ [0, 0, 0], [2, 2, 2] ] }, { 'box': [ [1, 1, 1], [3, 3, 3] ] }] }); expect(found.length).to.equal(1); expect(found.atoms[0].index).to.equal(1); }); it('should identify the right bonds', function() { var bonds = h2omodel._bondmat; expect(bonds[0][1]).to.deep.equal([ [0, 0, 0] ]); expect(bonds[0][2]).to.deep.equal([ [0, 0, 0] ]); expect(bonds[3][4]).to.deep.equal([ [0, 0, -1] ]); expect(bonds[3][5]).to.deep.equal([ [0, -1, -1] ]); }); it('should identify the right molecules', function() { expect(h2omodel._molinds).to.deep.equal([0, 0, 0, 1, 1, 1]); }); it('should correctly load a model as molecular crystal', function() { var h2omolcryst = new Model(h2o, { molecularCrystal: true }); for (let i = 0; i < h2omolcryst._molecules.length; ++i) { let mol = h2omolcryst._molecules[i]; for (let j = 0; j < mol.length; ++j) { expect(mol[j].cell).to.deep.equal([0, 0, 0]); } } // Check that it didn't alter the original Atoms object expect(h2omolcryst.positions).to.not.deep.equal(h2omodel.positions); }); it('should correctly work with isotopes', function() { var h2omodel = new Model(h2o, { supercell: [2,1,1] }); var a0 = h2omodel.find({indices: 0}).atoms; expect(a0[0].isotope).to.equal(16); expect(a0[1].isotope).to.equal(16); // Now set them both a0[0].isotopeGlobal = 17; expect(a0[0].isotope).to.equal(17); expect(a0[1].isotope).to.equal(17); // Now only one a0[1].isotope = 18; expect(a0[0].isotope).to.equal(17); expect(a0[1].isotope).to.equal(18); // And attached data expect(a0[0].isotopeData.gamma/1e7).to.almost.equal(-3.628); expect(a0[1].isotopeData.gamma).to.equal(null); // No spin // Load NMR active isotopes h2omodel = new Model(h2o, { supercell: [1,1,1], useNMRActiveIsotopes: true }); expect(h2omodel.atoms[0].isotope).to.equal(17); expect(h2omodel.atoms[1].isotope).to.equal(1); }); }); describe('#modelview', function() { it('should correctly AND two successive queries', function() { var mv1 = h2omodel.find({ 'cell': [ [0, 0, 0] ] }); var mv2 = mv1.find({ 'elements': 'O' }); expect(mv2.indices).to.deep.equal([0, 3]); }); it('should correctly perform boolean operations between views', function() { var mv1 = h2omodel.find({ 'elements': 'O' }); var mv2 = h2omodel.find({ 'elements': 'H' }); var mv3 = h2omodel.find({ 'sphere': [ [0, 0, 0], 1 ] }); var mvAnd = mv1.and(mv2); expect(mvAnd.length).to.equal(0); var mvOr = mv1.or(mv2); expect(mvOr.indices.sort()).to.deep.equal([0, 1, 2, 3, 4, 5]); var mvXor = mv1.xor(mv3); expect(mvXor.indices.sort()).to.deep.equal([1, 2, 3]); var mvNot = mv3.not(); expect(mvNot.indices.sort()).to.deep.equal([3, 4, 5]); // Throw exception var mvSi = simodel.find({ 'all': [] }); expect(function() { mv1.and(mvSi); }).to.throw('The two ModelViews do not refer to the same Model'); }); it('should correctly identify the unique sites based on the labels', function() { // modelview with all atoms var mv = chamodel.find({'all': []}); // let newMV = mv.uniqueSites(); // O1, O2, O3, O4, T1 (5 sites) expect(newMV.length).to.equal(5); expect(newMV._indices.sort()).to.deep.equal([0, 18, 36, 54, 72]); // make sure it works with subsets var mv2 = chamodel.find({'elements': ['Si']}); newMV = mv2.uniqueSites(); expect(newMV.length).to.equal(1); expect(newMV._indices.sort()).to.deep.equal([72]); }); });