• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

materialsproject / pymatgen / 4075885785

pending completion
4075885785

push

github

Shyue Ping Ong
Merge branch 'master' of github.com:materialsproject/pymatgen

96 of 96 new or added lines in 27 files covered. (100.0%)

81013 of 102710 relevant lines covered (78.88%)

0.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

76.23
/pymatgen/analysis/tests/test_molecule_matcher.py
1
# Copyright (c) Pymatgen Development Team.
2
# Distributed under the terms of the MIT License.
3

4
from __future__ import annotations
1✔
5

6
import os
1✔
7
import unittest
1✔
8

9
import numpy as np
1✔
10
import pytest
1✔
11
from pytest import approx
1✔
12

13
from pymatgen.analysis.molecule_matcher import (
1✔
14
    BruteForceOrderMatcher,
15
    GeneticOrderMatcher,
16
    HungarianOrderMatcher,
17
    InchiMolAtomMapper,
18
    IsomorphismMolAtomMapper,
19
    KabschMatcher,
20
    MoleculeMatcher,
21
)
22
from pymatgen.core.operations import SymmOp
1✔
23
from pymatgen.core.structure import Lattice, Molecule, Structure
1✔
24
from pymatgen.util.testing import PymatgenTest
1✔
25

26
try:
1✔
27
    import openbabel as ob
1✔
28

29
except (ImportError, RuntimeError):
1✔
30
    ob = None
1✔
31

32
test_dir = os.path.join(PymatgenTest.TEST_FILES_DIR, "molecules", "molecule_matcher")
1✔
33

34

35
obalign_missing = ob is None or "OBAlign" not in dir(ob)
1✔
36

37

38
def rotate(mol, seed):
1✔
39
    """Performs a random rotation of the sites in a structure.
40

41
    Args:
42
        mol (Molecule): The Molecule object which will be transformed.
43
        seed (int): The seed value for the random generator.
44
    """
45
    rng = np.random.default_rng(seed=seed)
×
46

47
    op = SymmOp.from_origin_axis_angle([0, 0, 0], rng.random(3), 360 * rng.random())
×
48
    for site in mol:
×
49
        site.coords = op.operate(site.coords)
×
50

51

52
def perturb(mol, scale, seed):
1✔
53
    """Performs a random perturbation of the sites in a structure.
54

55
    Args:
56
        scale (float): Distance in angstroms by which to perturb each site.
57
        seed (int): The seed value for the random generator.
58
    """
59
    rng = np.random.default_rng(seed=seed)
×
60

61
    dV = rng.normal(scale=scale, size=(len(mol), 3))
×
62
    for site, dv in zip(mol.sites, dV):
×
63
        site.coords += dv
×
64

65

66
def permute(mol, seed):
1✔
67
    """Performs a random permutation of the sites in a structure.
68

69
    Args:
70
        seed (int): The seed value for the random generator.
71
    """
72
    rng = np.random.default_rng(seed=seed)
×
73

74
    inds = rng.permutation(len(mol))
×
75
    mol._sites = [mol[i] for i in inds]
×
76

77

78
def generate_Si_cluster():
1✔
79
    from pymatgen.io.xyz import XYZ
×
80

81
    coords = [[0, 0, 0], [0.75, 0.5, 0.75]]
×
82
    lattice = Lattice.from_parameters(a=3.84, b=3.84, c=3.84, alpha=120, beta=90, gamma=60)
×
83

84
    struct = Structure(lattice, ["Si", "Si"], coords)
×
85
    struct.make_supercell([2, 2, 2])
×
86

87
    # Creating molecule for testing
88
    mol = Molecule.from_sites(struct)
×
89
    XYZ(mol).write_file(os.path.join(test_dir, "Si_cluster.xyz"))
×
90

91
    # Rorate the whole molecule
92
    mol_rotated = mol.copy()
×
93
    rotate(mol_rotated, seed=42)
×
94
    XYZ(mol_rotated).write_file(os.path.join(test_dir, "Si_cluster_rotated.xyz"))
×
95

96
    # Perturbing the atom positions
97
    mol_perturbed = mol.copy()
×
98
    perturb(mol_perturbed, 0.3, seed=42)
×
99
    XYZ(mol_perturbed).write_file(os.path.join(test_dir, "Si_cluster_perturbed.xyz"))
×
100

101
    # Permuting the order of the atoms
102
    mol_permuted = mol.copy()
×
103
    permute(mol_permuted, seed=42)
×
104
    XYZ(mol_permuted).write_file(os.path.join(test_dir, "Si_cluster_permuted.xyz"))
×
105

106
    # All-in-one
107
    mol2 = mol.copy()
×
108
    rotate(mol2, seed=42)
×
109
    perturb(mol2, 0.3, seed=42)
×
110
    permute(mol2, seed=42)
×
111
    XYZ(mol2).write_file(os.path.join(test_dir, "Si_cluster_2.xyz"))
×
112

113

114
def generate_Si2O_cluster():
1✔
115
    from pymatgen.io.xyz import XYZ
×
116

117
    coords = [
×
118
        [0.625, 0.625, 0.625],
119
        [0.625, 0.625, 0.125],
120
        [0.625, 0.125, 0.625],
121
        [0.125, 0.625, 0.625],
122
        [0.500, 0.500, 0.500],
123
        [0.750, 0.750, 0.750],
124
    ]
125

126
    lattice = Lattice.from_parameters(a=6.61657069, b=6.61657069, c=6.61657069, alpha=60, beta=60, gamma=60)
×
127
    struct = Structure(lattice, ["Si", "Si", "Si", "Si", "O", "O"], coords)
×
128
    # struct.make_supercell([2, 2, 2])
129

130
    # Creating molecule for testing
131
    mol = Molecule.from_sites(struct)
×
132
    XYZ(mol).write_file(os.path.join(test_dir, "Si2O_cluster.xyz"))
×
133

134
    # Rorate the whole molecule
135
    mol_rotated = mol.copy()
×
136
    rotate(mol_rotated, seed=42)
×
137
    XYZ(mol_rotated).write_file(os.path.join(test_dir, "Si2O_cluster_rotated.xyz"))
×
138

139
    # Perturbing the atom positions
140
    mol_perturbed = mol.copy()
×
141
    perturb(mol_perturbed, 0.3, seed=42)
×
142
    XYZ(mol_perturbed).write_file(os.path.join(test_dir, "Si2O_cluster_perturbed.xyz"))
×
143

144
    # Permuting the order of the atoms
145
    mol_permuted = mol.copy()
×
146
    permute(mol_permuted, seed=42)
×
147
    XYZ(mol_permuted).write_file(os.path.join(test_dir, "Si2O_cluster_permuted.xyz"))
×
148

149
    # All-in-one
150
    mol2 = mol.copy()
×
151
    rotate(mol2, seed=42)
×
152
    perturb(mol2, 0.3, seed=42)
×
153
    permute(mol2, seed=42)
×
154
    XYZ(mol2).write_file(os.path.join(test_dir, "Si2O_cluster_2.xyz"))
×
155

156

157
@unittest.skipIf(obalign_missing, "OBAlign is missing, Skipping")
1✔
158
class MoleculeMatcherTest(unittest.TestCase):
1✔
159
    def test_fit(self):
1✔
160
        self.fit_with_mapper(IsomorphismMolAtomMapper())
×
161
        self.fit_with_mapper(InchiMolAtomMapper())
×
162

163
    def test_get_rmsd(self):
1✔
164
        mm = MoleculeMatcher()
×
165
        mol1 = Molecule.from_file(os.path.join(test_dir, "t3.xyz"))
×
166
        mol2 = Molecule.from_file(os.path.join(test_dir, "t4.xyz"))
×
167
        assert f"{mm.get_rmsd(mol1, mol2):7.3}" == "0.00488"
×
168

169
    def test_group_molecules(self):
1✔
170
        mm = MoleculeMatcher(tolerance=0.001)
×
171
        with open(os.path.join(test_dir, "mol_list.txt")) as f:
×
172
            filename_list = [line.strip() for line in f.readlines()]
×
173
        mol_list = [Molecule.from_file(os.path.join(test_dir, f)) for f in filename_list]
×
174
        mol_groups = mm.group_molecules(mol_list)
×
175
        filename_groups = [[filename_list[mol_list.index(m)] for m in g] for g in mol_groups]
×
176
        with open(os.path.join(test_dir, "grouped_mol_list.txt")) as f:
×
177
            grouped_text = f.read().strip()
×
178
        assert str(filename_groups) == grouped_text
×
179

180
    def test_to_and_from_dict(self):
1✔
181
        mm = MoleculeMatcher(tolerance=0.5, mapper=InchiMolAtomMapper(angle_tolerance=50.0))
×
182
        d = mm.as_dict()
×
183
        mm2 = MoleculeMatcher.from_dict(d)
×
184
        assert d == mm2.as_dict()
×
185

186
        mm = MoleculeMatcher(tolerance=0.5, mapper=IsomorphismMolAtomMapper())
×
187
        d = mm.as_dict()
×
188
        mm2 = MoleculeMatcher.from_dict(d)
×
189
        assert d == mm2.as_dict()
×
190

191
    def fit_with_mapper(self, mapper):
1✔
192
        coords = [
×
193
            [0.000000, 0.000000, 0.000000],
194
            [0.000000, 0.000000, 1.089000],
195
            [1.026719, 0.000000, -0.363000],
196
            [-0.513360, -0.889165, -0.363000],
197
            [-0.513360, 0.889165, -0.363000],
198
        ]
199
        mol1 = Molecule(["C", "H", "H", "H", "H"], coords)
×
200
        op = SymmOp.from_origin_axis_angle([0, 0, 0], [0.1, 0.2, 0.3], 60)
×
201
        rotcoords = [op.operate(c) for c in coords]
×
202
        mol2 = Molecule(["C", "H", "H", "H", "H"], rotcoords)
×
203
        mm = MoleculeMatcher(mapper=mapper)
×
204
        assert mm.fit(mol1, mol2)
×
205

206
        mol1 = Molecule.from_file(os.path.join(test_dir, "benzene1.xyz"))
×
207
        mol2 = Molecule.from_file(os.path.join(test_dir, "benzene2.xyz"))
×
208
        assert mm.fit(mol1, mol2)
×
209

210
        mol1 = Molecule.from_file(os.path.join(test_dir, "benzene1.xyz"))
×
211
        mol2 = Molecule.from_file(os.path.join(test_dir, "t2.xyz"))
×
212
        assert not mm.fit(mol1, mol2)
×
213

214
        mol1 = Molecule.from_file(os.path.join(test_dir, "c1.xyz"))
×
215
        mol2 = Molecule.from_file(os.path.join(test_dir, "c2.xyz"))
×
216
        assert mm.fit(mol1, mol2)
×
217

218
        mol1 = Molecule.from_file(os.path.join(test_dir, "t3.xyz"))
×
219
        mol2 = Molecule.from_file(os.path.join(test_dir, "t4.xyz"))
×
220
        assert mm.fit(mol1, mol2)
×
221

222
        mol1 = Molecule.from_file(os.path.join(test_dir, "j1.xyz"))
×
223
        mol2 = Molecule.from_file(os.path.join(test_dir, "j2.xyz"))
×
224
        assert mm.fit(mol1, mol2)
×
225

226
        mol1 = Molecule.from_file(os.path.join(test_dir, "ethene1.xyz"))
×
227
        mol2 = Molecule.from_file(os.path.join(test_dir, "ethene2.xyz"))
×
228
        assert mm.fit(mol1, mol2)
×
229

230
        mol1 = Molecule.from_file(os.path.join(test_dir, "toluene1.xyz"))
×
231
        mol2 = Molecule.from_file(os.path.join(test_dir, "toluene2.xyz"))
×
232
        assert mm.fit(mol1, mol2)
×
233

234
        mol1 = Molecule.from_file(os.path.join(test_dir, "cyclohexane1.xyz"))
×
235
        mol2 = Molecule.from_file(os.path.join(test_dir, "cyclohexane2.xyz"))
×
236
        assert mm.fit(mol1, mol2)
×
237

238
        mol1 = Molecule.from_file(os.path.join(test_dir, "oxygen1.xyz"))
×
239
        mol2 = Molecule.from_file(os.path.join(test_dir, "oxygen2.xyz"))
×
240
        assert mm.fit(mol1, mol2)
×
241

242
        mm = MoleculeMatcher(tolerance=0.001, mapper=mapper)
×
243
        mol1 = Molecule.from_file(os.path.join(test_dir, "t3.xyz"))
×
244
        mol2 = Molecule.from_file(os.path.join(test_dir, "t4.xyz"))
×
245
        assert not mm.fit(mol1, mol2)
×
246

247
    def test_strange_inchi(self):
1✔
248
        mm = MoleculeMatcher(tolerance=0.05, mapper=InchiMolAtomMapper())
×
249
        mol1 = Molecule.from_file(os.path.join(test_dir, "k1.sdf"))
×
250
        mol2 = Molecule.from_file(os.path.join(test_dir, "k2.sdf"))
×
251
        assert mm.fit(mol1, mol2)
×
252

253
    def test_thiane(self):
1✔
254
        mm = MoleculeMatcher(tolerance=0.05, mapper=InchiMolAtomMapper())
×
255
        mol1 = Molecule.from_file(os.path.join(test_dir, "thiane1.sdf"))
×
256
        mol2 = Molecule.from_file(os.path.join(test_dir, "thiane2.sdf"))
×
257
        assert not mm.fit(mol1, mol2)
×
258

259
    def test_thiane_ethynyl(self):
1✔
260
        mm = MoleculeMatcher(tolerance=0.05, mapper=InchiMolAtomMapper())
×
261
        mol1 = Molecule.from_file(os.path.join(test_dir, "thiane_ethynyl1.sdf"))
×
262
        mol2 = Molecule.from_file(os.path.join(test_dir, "thiane_ethynyl2.sdf"))
×
263
        assert not mm.fit(mol1, mol2)
×
264

265
    def test_cdi_23(self):
1✔
266
        mm = MoleculeMatcher(tolerance=0.05, mapper=InchiMolAtomMapper())
×
267
        mol1 = Molecule.from_file(os.path.join(test_dir, "cdi_23_1.xyz"))
×
268
        mol2 = Molecule.from_file(os.path.join(test_dir, "cdi_23_2.xyz"))
×
269
        assert not mm.fit(mol1, mol2)
×
270

271

272
class KabschMatcherTest(unittest.TestCase):
1✔
273
    def test_get_rmsd(self):
1✔
274
        mol1 = Molecule.from_file(os.path.join(test_dir, "t3.xyz"))
1✔
275
        mol2 = Molecule.from_file(os.path.join(test_dir, "t4.xyz"))
1✔
276

277
        mm = KabschMatcher(mol1)
1✔
278
        _, _, rmsd = mm.match(mol2)
1✔
279
        assert rmsd == approx(0.0028172956033732936, abs=1e-6)
1✔
280

281
    def test_to_and_from_dict(self):
1✔
282
        mol1 = Molecule.from_file(os.path.join(test_dir, "t3.xyz"))
1✔
283

284
        mm_source = KabschMatcher(mol1)
1✔
285
        d_source = mm_source.as_dict()
1✔
286

287
        mm_target = KabschMatcher.from_dict(d_source)
1✔
288
        assert d_source == mm_target.as_dict()
1✔
289

290
    def test_rotated_molecule(self):
1✔
291
        coords = [
1✔
292
            [0.000000, 0.000000, 0.000000],
293
            [0.000000, 0.000000, 1.089000],
294
            [1.026719, 0.000000, -0.363000],
295
            [-0.513360, -0.889165, -0.363000],
296
            [-0.513360, 0.889165, -0.363000],
297
        ]
298

299
        op = SymmOp.from_origin_axis_angle([0, 0, 0], [0.1, 0.2, 0.3], 60)
1✔
300
        rotcoords = [op.operate(c) for c in coords]
1✔
301

302
        mol1 = Molecule(["C", "H", "H", "H", "H"], coords)
1✔
303
        mol2 = Molecule(["C", "H", "H", "H", "H"], rotcoords)
1✔
304

305
        mm = KabschMatcher(mol1)
1✔
306
        _, rmsd = mm.fit(mol2)
1✔
307
        assert rmsd == approx(0, abs=6)
1✔
308

309
    def test_mismatched_atom_composition(self):
1✔
310
        mol1 = Molecule.from_file(os.path.join(test_dir, "benzene1.xyz"))
1✔
311
        mol2 = Molecule.from_file(os.path.join(test_dir, "t2.xyz"))
1✔
312

313
        mm = KabschMatcher(mol1)
1✔
314

315
        with pytest.raises(ValueError):
1✔
316
            _, rmsd = mm.fit(mol2)
1✔
317

318
    def test_missmatched_atom_order(self):
1✔
319
        mol1 = Molecule.from_file(os.path.join(test_dir, "benzene1.xyz"))
1✔
320
        mol2 = Molecule.from_file(os.path.join(test_dir, "benzene2.xyz"))
1✔
321

322
        mm = KabschMatcher(mol1)
1✔
323

324
        with pytest.raises(ValueError):
1✔
325
            _, rmsd = mm.fit(mol2)
1✔
326

327
        mol1 = Molecule.from_file(os.path.join(test_dir, "c1.xyz"))
1✔
328
        mol2 = Molecule.from_file(os.path.join(test_dir, "c2.xyz"))
1✔
329

330
        mm = KabschMatcher(mol1)
1✔
331

332
        with pytest.raises(ValueError):
1✔
333
            _, rmsd = mm.fit(mol2)
1✔
334

335
    def test_fit(self):
1✔
336
        mol1 = Molecule.from_file(os.path.join(test_dir, "t3.xyz"))
1✔
337
        mol2 = Molecule.from_file(os.path.join(test_dir, "t4.xyz"))
1✔
338

339
        mm = KabschMatcher(mol1)
1✔
340

341
        _, rmsd = mm.fit(mol2)
1✔
342
        assert rmsd == approx(0.0028172956033732936, abs=1e-6)
1✔
343

344
        mol1 = Molecule.from_file(os.path.join(test_dir, "oxygen1.xyz"))
1✔
345
        mol2 = Molecule.from_file(os.path.join(test_dir, "oxygen2.xyz"))
1✔
346
        mm = KabschMatcher(mol1)
1✔
347

348
        _, rmsd = mm.fit(mol2)
1✔
349
        assert rmsd == approx(0, abs=6)
1✔
350

351

352
class HungarianOrderMatcherTest(unittest.TestCase):
1✔
353
    def test_get_rmsd(self):
1✔
354
        mol1 = Molecule.from_file(os.path.join(test_dir, "t3.xyz"))
1✔
355
        mol2 = Molecule.from_file(os.path.join(test_dir, "t4.xyz"))
1✔
356

357
        mm = HungarianOrderMatcher(mol1)
1✔
358

359
        _, rmsd = mm.fit(mol2)
1✔
360
        assert rmsd == approx(0.002825344731118855, abs=1e-6)
1✔
361

362
    def test_to_and_from_dict(self):
1✔
363
        mol1 = Molecule.from_file(os.path.join(test_dir, "t3.xyz"))
1✔
364

365
        mm_source = HungarianOrderMatcher(mol1)
1✔
366
        d_source = mm_source.as_dict()
1✔
367

368
        mm_target = HungarianOrderMatcher.from_dict(d_source)
1✔
369
        assert d_source == mm_target.as_dict()
1✔
370

371
    def test_rotated_molecule(self):
1✔
372
        coords = [
1✔
373
            [0.000000, 0.000000, 0.000000],
374
            [0.000000, 0.000000, 1.089000],
375
            [1.026719, 0.000000, -0.363000],
376
            [-0.513360, -0.889165, -0.363000],
377
            [-0.513360, 0.889165, -0.363000],
378
        ]
379

380
        op = SymmOp.from_origin_axis_angle([0, 0, 0], [0.1, 0.2, 0.3], 60)
1✔
381
        rotcoords = [op.operate(c) for c in coords]
1✔
382

383
        mol1 = Molecule(["C", "H", "H", "H", "H"], coords)
1✔
384
        mol2 = Molecule(["C", "H", "H", "H", "H"], rotcoords)
1✔
385

386
        mm = HungarianOrderMatcher(mol1)
1✔
387
        _, rmsd = mm.fit(mol2)
1✔
388
        assert rmsd == approx(0, abs=6)
1✔
389

390
    def test_mismatched_atom_composition(self):
1✔
391
        mol1 = Molecule.from_file(os.path.join(test_dir, "benzene1.xyz"))
1✔
392
        mol2 = Molecule.from_file(os.path.join(test_dir, "t2.xyz"))
1✔
393
        mm = HungarianOrderMatcher(mol1)
1✔
394

395
        with pytest.raises(ValueError):
1✔
396
            _, rmsd = mm.fit(mol2)
1✔
397

398
    def test_fit(self):
1✔
399
        mol1 = Molecule.from_file(os.path.join(test_dir, "benzene1.xyz"))
1✔
400
        mol2 = Molecule.from_file(os.path.join(test_dir, "benzene2.xyz"))
1✔
401

402
        mm = HungarianOrderMatcher(mol1)
1✔
403

404
        _, rmsd = mm.fit(mol2)
1✔
405
        assert rmsd == approx(1.4171601659148593e-05, abs=1e-6)
1✔
406

407
        mol1 = Molecule.from_file(os.path.join(test_dir, "c1.xyz"))
1✔
408
        mol2 = Molecule.from_file(os.path.join(test_dir, "c2.xyz"))
1✔
409
        mm = HungarianOrderMatcher(mol1)
1✔
410

411
        _, rmsd = mm.fit(mol2)
1✔
412
        assert rmsd == approx(9.479012116064961e-05, abs=1e-6)
1✔
413

414
        mol1 = Molecule.from_file(os.path.join(test_dir, "t3.xyz"))
1✔
415
        mol2 = Molecule.from_file(os.path.join(test_dir, "t4.xyz"))
1✔
416
        mm = HungarianOrderMatcher(mol1)
1✔
417

418
        _, rmsd = mm.fit(mol2)
1✔
419
        assert rmsd == approx(0.002825344731118855, abs=1e-6)
1✔
420

421
        mol1 = Molecule.from_file(os.path.join(test_dir, "j1.xyz"))
1✔
422
        mol2 = Molecule.from_file(os.path.join(test_dir, "j2.xyz"))
1✔
423
        mm = HungarianOrderMatcher(mol1)
1✔
424

425
        _, rmsd = mm.fit(mol2)
1✔
426
        assert rmsd == approx(9.28245597473488e-05, abs=1e-6)
1✔
427

428
        mol1 = Molecule.from_file(os.path.join(test_dir, "ethene1.xyz"))
1✔
429
        mol2 = Molecule.from_file(os.path.join(test_dir, "ethene2.xyz"))
1✔
430
        mm = HungarianOrderMatcher(mol1)
1✔
431

432
        _, rmsd = mm.fit(mol2)
1✔
433
        assert rmsd == approx(0.00021150729609276233, abs=1e-6)
1✔
434

435
        mol1 = Molecule.from_file(os.path.join(test_dir, "toluene1.xyz"))
1✔
436
        mol2 = Molecule.from_file(os.path.join(test_dir, "toluene2.xyz"))
1✔
437
        mm = HungarianOrderMatcher(mol1)
1✔
438

439
        _, rmsd = mm.fit(mol2)
1✔
440
        assert rmsd == approx(0.0001445787263551832, abs=1e-6)
1✔
441

442
        mol1 = Molecule.from_file(os.path.join(test_dir, "cyclohexane1.xyz"))
1✔
443
        mol2 = Molecule.from_file(os.path.join(test_dir, "cyclohexane2.xyz"))
1✔
444
        mm = HungarianOrderMatcher(mol1)
1✔
445

446
        _, rmsd = mm.fit(mol2)
1✔
447
        assert rmsd == approx(0.00012447269440740117, abs=1e-6)
1✔
448

449
        mol1 = Molecule.from_file(os.path.join(test_dir, "oxygen1.xyz"))
1✔
450
        mol2 = Molecule.from_file(os.path.join(test_dir, "oxygen2.xyz"))
1✔
451
        mm = HungarianOrderMatcher(mol1)
1✔
452

453
        _, rmsd = mm.fit(mol2)
1✔
454
        assert rmsd == approx(0, abs=6)
1✔
455

456

457
class GeneticOrderMatcherTest(unittest.TestCase):
1✔
458
    def test_get_rmsd(self):
1✔
459
        mol1 = Molecule.from_file(os.path.join(test_dir, "t3.xyz"))
1✔
460
        mol2 = Molecule.from_file(os.path.join(test_dir, "t4.xyz"))
1✔
461

462
        mm = GeneticOrderMatcher(mol1, threshold=0.3)
1✔
463

464
        _, rmsd = mm.fit(mol2)[0]
1✔
465
        assert rmsd == approx(0.0028172956033734615, abs=1e-6)
1✔
466

467
    def test_to_and_from_dict(self):
1✔
468
        mol1 = Molecule.from_file(os.path.join(test_dir, "t3.xyz"))
1✔
469

470
        mm_source = GeneticOrderMatcher(mol1, threshold=0.3)
1✔
471
        d_source = mm_source.as_dict()
1✔
472

473
        mm_target = GeneticOrderMatcher.from_dict(d_source)
1✔
474
        assert d_source == mm_target.as_dict()
1✔
475

476
    def test_rotated_molecule(self):
1✔
477
        coords = [
1✔
478
            [0.000000, 0.000000, 0.000000],
479
            [0.000000, 0.000000, 1.089000],
480
            [1.026719, 0.000000, -0.363000],
481
            [-0.513360, -0.889165, -0.363000],
482
            [-0.513360, 0.889165, -0.363000],
483
        ]
484

485
        op = SymmOp.from_origin_axis_angle([0, 0, 0], [0.1, 0.2, 0.3], 60)
1✔
486
        rotcoords = [op.operate(c) for c in coords]
1✔
487

488
        mol1 = Molecule(["C", "H", "H", "H", "H"], coords)
1✔
489
        mol2 = Molecule(["C", "H", "H", "H", "H"], rotcoords)
1✔
490

491
        mm = GeneticOrderMatcher(mol1, threshold=0.3)
1✔
492
        _, rmsd = mm.fit(mol2)[0]
1✔
493
        assert rmsd == approx(0, abs=6)
1✔
494

495
    def test_mismatched_atom_composition(self):
1✔
496
        mol1 = Molecule.from_file(os.path.join(test_dir, "benzene1.xyz"))
1✔
497
        mol2 = Molecule.from_file(os.path.join(test_dir, "t2.xyz"))
1✔
498
        mm = GeneticOrderMatcher(mol1, threshold=0.3)
1✔
499

500
        with pytest.raises(ValueError):
1✔
501
            _, rmsd = mm.fit(mol2)[0]
1✔
502

503
    def test_fit(self):
1✔
504
        mol1 = Molecule.from_file(os.path.join(test_dir, "benzene1.xyz"))
1✔
505
        mol2 = Molecule.from_file(os.path.join(test_dir, "benzene2.xyz"))
1✔
506

507
        mm = GeneticOrderMatcher(mol1, threshold=0.01)
1✔
508

509
        _, rmsd = mm.fit(mol2)[0]
1✔
510
        assert rmsd == approx(7.061017534055039e-05, abs=1e-6)
1✔
511

512
        mol1 = Molecule.from_file(os.path.join(test_dir, "c1.xyz"))
1✔
513
        mol2 = Molecule.from_file(os.path.join(test_dir, "c2.xyz"))
1✔
514
        mm = GeneticOrderMatcher(mol1, threshold=0.01)
1✔
515

516
        _, rmsd = mm.fit(mol2)[0]
1✔
517
        assert rmsd == approx(9.459575146593829e-05, abs=1e-6)
1✔
518

519
        mol1 = Molecule.from_file(os.path.join(test_dir, "t3.xyz"))
1✔
520
        mol2 = Molecule.from_file(os.path.join(test_dir, "t4.xyz"))
1✔
521
        mm = GeneticOrderMatcher(mol1, threshold=0.01)
1✔
522

523
        _, rmsd = mm.fit(mol2)[0]
1✔
524
        assert rmsd == approx(0.0028172956033734615, abs=1e-6)
1✔
525

526
        mol1 = Molecule.from_file(os.path.join(test_dir, "j1.xyz"))
1✔
527
        mol2 = Molecule.from_file(os.path.join(test_dir, "j2.xyz"))
1✔
528
        mm = GeneticOrderMatcher(mol1, threshold=0.01)
1✔
529

530
        _, rmsd = mm.fit(mol2)[0]
1✔
531
        assert rmsd == approx(9.28245597473488e-05, abs=1e-6)
1✔
532

533
        mol1 = Molecule.from_file(os.path.join(test_dir, "ethene1.xyz"))
1✔
534
        mol2 = Molecule.from_file(os.path.join(test_dir, "ethene2.xyz"))
1✔
535
        mm = GeneticOrderMatcher(mol1, threshold=0.01)
1✔
536

537
        _, rmsd = mm.fit(mol2)[0]
1✔
538
        assert rmsd == approx(0.00019757961816426042, abs=1e-6)
1✔
539

540
        mol1 = Molecule.from_file(os.path.join(test_dir, "toluene1.xyz"))
1✔
541
        mol2 = Molecule.from_file(os.path.join(test_dir, "toluene2.xyz"))
1✔
542
        mm = GeneticOrderMatcher(mol1, threshold=0.1)
1✔
543

544
        _, rmsd = mm.fit(mol2)[0]
1✔
545
        assert rmsd == approx(0.0001398867874149986, abs=1e-6)
1✔
546

547
        mol1 = Molecule.from_file(os.path.join(test_dir, "cyclohexane1.xyz"))
1✔
548
        mol2 = Molecule.from_file(os.path.join(test_dir, "cyclohexane2.xyz"))
1✔
549
        mm = GeneticOrderMatcher(mol1, threshold=0.01)
1✔
550

551
        _, rmsd = mm.fit(mol2)[0]
1✔
552
        assert rmsd == approx(0.00012190586696474853, abs=1e-6)
1✔
553

554
        mol1 = Molecule.from_file(os.path.join(test_dir, "oxygen1.xyz"))
1✔
555
        mol2 = Molecule.from_file(os.path.join(test_dir, "oxygen2.xyz"))
1✔
556
        mm = GeneticOrderMatcher(mol1, threshold=0.01)
1✔
557

558
        _, rmsd = mm.fit(mol2)[0]
1✔
559
        assert rmsd == approx(0, abs=6)
1✔
560

561

562
class KabschMatcherSiTest(unittest.TestCase):
1✔
563
    @classmethod
1✔
564
    def setUpClass(cls):
1✔
565
        cls.mol1 = Molecule.from_file(os.path.join(test_dir, "Si_cluster.xyz"))
1✔
566
        cls.mm = KabschMatcher(cls.mol1)
1✔
567

568
    def test_to_and_from_dict(self):
1✔
569
        d = self.mm.as_dict()
1✔
570
        mm = KabschMatcher.from_dict(d)
1✔
571
        assert d == mm.as_dict()
1✔
572

573
    def test_missmatched_atoms(self):
1✔
574
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si2O_cluster.xyz"))
1✔
575
        with pytest.raises(ValueError):
1✔
576
            _, rmsd = self.mm.fit(mol2)
1✔
577

578
    def test_rotated_molecule(self):
1✔
579
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si_cluster_rotated.xyz"))
1✔
580
        _, rmsd = self.mm.fit(mol2)
1✔
581
        assert rmsd == approx(0, abs=6)
1✔
582

583
    def test_perturbed_atom_position(self):
1✔
584
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si_cluster_perturbed.xyz"))
1✔
585
        _, rmsd = self.mm.fit(mol2)
1✔
586
        assert rmsd == approx(0.2232223954240079, abs=1e-6)
1✔
587

588
    def test_permuted_atoms_order(self):
1✔
589
        # This test shows very poor rmsd result, because the `KabschMatcher`
590
        # is not capable to handle arbitrary atom's order
591
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si_cluster_permuted.xyz"))
1✔
592
        _, rmsd = self.mm.fit(mol2)
1✔
593
        assert rmsd == approx(2.7962454578966454, abs=1e-6)
1✔
594

595

596
class BruteForceOrderMatcherSiTest(unittest.TestCase):
1✔
597
    @classmethod
1✔
598
    def setUpClass(cls):
1✔
599
        cls.mol1 = Molecule.from_file(os.path.join(test_dir, "Si_cluster.xyz"))
1✔
600
        cls.mm = BruteForceOrderMatcher(cls.mol1)
1✔
601

602
    def test_to_and_from_dict(self):
1✔
603
        d = self.mm.as_dict()
1✔
604
        mm = BruteForceOrderMatcher.from_dict(d)
1✔
605
        assert d == mm.as_dict()
1✔
606

607
    def test_random_match(self):
1✔
608
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si_cluster_2.xyz"))
1✔
609

610
        # ValueError: The number of all possible permuataions (20922789888000) is not feasible to run this method!
611
        with pytest.raises(ValueError):
1✔
612
            _, rmsd = self.mm.fit(mol2)
1✔
613

614

615
class HungarianOrderMatcherSiTest(unittest.TestCase):
1✔
616
    @classmethod
1✔
617
    def setUpClass(cls):
1✔
618
        cls.mol1 = Molecule.from_file(os.path.join(test_dir, "Si_cluster.xyz"))
1✔
619
        cls.mm = HungarianOrderMatcher(cls.mol1)
1✔
620

621
    def test_to_and_from_dict(self):
1✔
622
        d = self.mm.as_dict()
1✔
623
        mm = HungarianOrderMatcher.from_dict(d)
1✔
624
        assert d == mm.as_dict()
1✔
625

626
    def test_missmatched_atoms(self):
1✔
627
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si2O_cluster_rotated.xyz"))
1✔
628
        with pytest.raises(ValueError):
1✔
629
            _, rmsd = self.mm.fit(mol2)
1✔
630

631
    def test_rotated_molecule(self):
1✔
632
        # TODO: Checking the cause of the large deviation
633
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si_cluster_rotated.xyz"))
1✔
634
        _, rmsd = self.mm.fit(mol2)
1✔
635
        assert rmsd == approx(1.025066171481399, abs=1e-6)
1✔
636

637
    def test_perturbed_atom_position(self):
1✔
638
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si_cluster_perturbed.xyz"))
1✔
639
        _, rmsd = self.mm.fit(mol2)
1✔
640
        assert rmsd == approx(0.2232223954240077, abs=1e-6)
1✔
641

642
    def test_permuted_atoms_order(self):
1✔
643
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si_cluster_permuted.xyz"))
1✔
644
        _, rmsd = self.mm.fit(mol2)
1✔
645
        assert rmsd == approx(0, abs=6)
1✔
646

647
    def test_random_match(self):
1✔
648
        # TODO: Checking the cause of the large deviation
649
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si_cluster_2.xyz"))
1✔
650
        _, rmsd = self.mm.fit(mol2)
1✔
651
        assert rmsd == approx(1.0177241485450828, abs=1e-6)
1✔
652

653

654
class GeneticOrderMatcherSiTest(unittest.TestCase):
1✔
655
    @classmethod
1✔
656
    def setUpClass(cls):
1✔
657
        cls.mol1 = Molecule.from_file(os.path.join(test_dir, "Si_cluster.xyz"))
1✔
658
        cls.mm = GeneticOrderMatcher(cls.mol1, threshold=0.3)
1✔
659

660
    def test_to_and_from_dict(self):
1✔
661
        d = self.mm.as_dict()
1✔
662
        mm = GeneticOrderMatcher.from_dict(d)
1✔
663
        assert d == mm.as_dict()
1✔
664

665
    def test_missmatched_atoms(self):
1✔
666
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si2O_cluster.xyz"))
1✔
667
        with pytest.raises(ValueError):
1✔
668
            self.mm.fit(mol2)
1✔
669

670
    def test_rotated_molecule(self):
1✔
671
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si_cluster_rotated.xyz"))
1✔
672
        res = self.mm.fit(mol2)
1✔
673
        assert res[0][-1] == approx(0, abs=6)
1✔
674

675
    def test_perturbed_atom_position(self):
1✔
676
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si_cluster_perturbed.xyz"))
1✔
677
        res = self.mm.fit(mol2)
1✔
678
        assert res[0][-1] == approx(0.2232223954240079, abs=1e-6)
1✔
679

680
    def test_permuted_atoms_order(self):
1✔
681
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si_cluster_permuted.xyz"))
1✔
682
        res = self.mm.fit(mol2)
1✔
683
        assert res[0][-1] == approx(0, abs=6)
1✔
684

685
    def test_random_match(self):
1✔
686
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si_cluster_2.xyz"))
1✔
687
        res = self.mm.fit(mol2)
1✔
688
        assert res[0][-1] == approx(0.22163169511782, abs=1e-6)
1✔
689

690

691
class KabschMatcherSi2OTest(unittest.TestCase):
1✔
692
    @classmethod
1✔
693
    def setUpClass(cls):
1✔
694
        cls.mol1 = Molecule.from_file(os.path.join(test_dir, "Si2O_cluster.xyz"))
1✔
695
        cls.mm = KabschMatcher(cls.mol1)
1✔
696

697
    def test_missmatched_atoms(self):
1✔
698
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si_cluster_rotated.xyz"))
1✔
699
        with pytest.raises(ValueError):
1✔
700
            _, rmsd = self.mm.fit(mol2)
1✔
701

702
    def test_rotated_molecule(self):
1✔
703
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si2O_cluster_rotated.xyz"))
1✔
704
        _, rmsd = self.mm.fit(mol2)
1✔
705
        assert rmsd == approx(0, abs=6)
1✔
706

707
    def test_perturbed_atom_position(self):
1✔
708
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si2O_cluster_perturbed.xyz"))
1✔
709
        _, rmsd = self.mm.fit(mol2)
1✔
710
        assert rmsd == approx(0.24340452336622473, abs=1e-6)
1✔
711

712
    def test_permuted_atoms_order(self):
1✔
713
        # This task should fail, because `KabschMatcher` is not capable
714
        # to handle arbitrary atom's order
715
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si2O_cluster_permuted.xyz"))
1✔
716
        with pytest.raises(ValueError):
1✔
717
            _, rmsd = self.mm.fit(mol2)
1✔
718

719

720
class BruteForceOrderMatcherSi2OTest(unittest.TestCase):
1✔
721
    @classmethod
1✔
722
    def setUpClass(cls):
1✔
723
        cls.mol1 = Molecule.from_file(os.path.join(test_dir, "Si2O_cluster.xyz"))
1✔
724
        cls.mm = BruteForceOrderMatcher(cls.mol1)
1✔
725

726
    def test_missmatched_atoms(self):
1✔
727
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si_cluster_rotated.xyz"))
1✔
728
        with pytest.raises(ValueError):
1✔
729
            _, rmsd = self.mm.fit(mol2)
1✔
730

731
    def test_rotated_molecule(self):
1✔
732
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si2O_cluster_rotated.xyz"))
1✔
733
        _, rmsd = self.mm.fit(mol2)
1✔
734
        assert rmsd == approx(0, abs=6)
1✔
735

736
    def test_perturbed_atom_position(self):
1✔
737
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si2O_cluster_perturbed.xyz"))
1✔
738
        _, rmsd = self.mm.fit(mol2)
1✔
739
        assert rmsd == approx(0.2434045087608993, abs=1e-6)
1✔
740

741
    def test_permuted_atoms_order(self):
1✔
742
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si2O_cluster_permuted.xyz"))
1✔
743
        _, rmsd = self.mm.fit(mol2)
1✔
744
        assert rmsd == approx(0, abs=6)
1✔
745

746
    def test_random_match(self):
1✔
747
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si2O_cluster_2.xyz"))
1✔
748
        _, rmsd = self.mm.fit(mol2)
1✔
749
        assert rmsd == approx(0.23051587697194997, abs=1e-6)
1✔
750

751

752
class HungarianOrderMatcherSi2OTest(unittest.TestCase):
1✔
753
    @classmethod
1✔
754
    def setUpClass(cls):
1✔
755
        cls.mol1 = Molecule.from_file(os.path.join(test_dir, "Si2O_cluster.xyz"))
1✔
756
        cls.mm = HungarianOrderMatcher(cls.mol1)
1✔
757

758
    def test_missmatched_atoms(self):
1✔
759
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si_cluster_rotated.xyz"))
1✔
760
        with pytest.raises(ValueError):
1✔
761
            _, rmsd = self.mm.fit(mol2)
1✔
762

763
    def test_rotated_molecule(self):
1✔
764
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si2O_cluster_rotated.xyz"))
1✔
765
        _, rmsd = self.mm.fit(mol2)
1✔
766
        assert rmsd == approx(0, abs=6)
1✔
767

768
    def test_perturbed_atom_position(self):
1✔
769
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si2O_cluster_perturbed.xyz"))
1✔
770
        _, rmsd = self.mm.fit(mol2)
1✔
771
        assert rmsd == approx(0.24474957657894614, abs=1e-6)
1✔
772

773
    def test_permuted_atoms_order(self):
1✔
774
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si2O_cluster_permuted.xyz"))
1✔
775
        _, rmsd = self.mm.fit(mol2)
1✔
776
        assert rmsd == approx(0, abs=6)
1✔
777

778
    def test_random_match(self):
1✔
779
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si2O_cluster_2.xyz"))
1✔
780
        _, rmsd = self.mm.fit(mol2)
1✔
781
        assert rmsd == approx(0.23231038877573124, abs=1e-6)
1✔
782

783

784
class GeneticOrderMatcherSi2OTest(unittest.TestCase):
1✔
785
    @classmethod
1✔
786
    def setUpClass(cls):
1✔
787
        cls.mol1 = Molecule.from_file(os.path.join(test_dir, "Si2O_cluster.xyz"))
1✔
788
        cls.mm = GeneticOrderMatcher(cls.mol1, threshold=0.3)
1✔
789

790
    def test_missmatched_atoms(self):
1✔
791
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si_cluster.xyz"))
1✔
792
        with pytest.raises(ValueError):
1✔
793
            self.mm.fit(mol2)
1✔
794

795
    def test_rotated_molecule(self):
1✔
796
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si2O_cluster_rotated.xyz"))
1✔
797
        res = self.mm.fit(mol2)
1✔
798
        assert res[0][1] == approx(0, abs=6)
1✔
799

800
    def test_perturbed_atom_position(self):
1✔
801
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si2O_cluster_perturbed.xyz"))
1✔
802
        res = self.mm.fit(mol2)
1✔
803
        assert len(res) == 3
1✔
804
        assert res[0][1] == approx(0.24340452336622473, abs=1e-6)
1✔
805

806
    def test_permuted_atoms_order(self):
1✔
807
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si2O_cluster_permuted.xyz"))
1✔
808
        res = self.mm.fit(mol2)
1✔
809
        assert len(res) == 3
1✔
810
        assert res[0][1] == approx(0, abs=6)
1✔
811

812
    def test_random_match(self):
1✔
813
        mol2 = Molecule.from_file(os.path.join(test_dir, "Si2O_cluster_2.xyz"))
1✔
814
        res = self.mm.match(mol2)
1✔
815
        assert len(res) == 3
1✔
816
        assert res[0][0] == [5, 0, 4, 1, 3, 2]
1✔
817
        assert res[0][-1] == approx(0.2305159973457393, abs=1e-6)
1✔
818

819

820
if __name__ == "__main__":
1✔
821
    # Run the following code to generate test cases:
822
    # generate_Si_cluster()
823
    # generate_Si2O_cluster()
824

825
    unittest.main()
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc