File: test_fixinternals.py

package info (click to toggle)
python-ase 3.21.1-2
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 13,936 kB
  • sloc: python: 122,428; xml: 946; makefile: 111; javascript: 47
file content (198 lines) | stat: -rw-r--r-- 7,030 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
from ase.calculators.emt import EMT
from ase.constraints import FixInternals
from ase.optimize.bfgs import BFGS
from ase.build import molecule
import copy
import pytest


# Convenience functions to compute linear combinations of internal coordinates
def get_bondcombo(atoms, bondcombo_def):
    return sum([defin[2] * atoms.get_distance(*defin[0:2]) for
                defin in bondcombo_def])


def get_anglecombo(atoms, anglecombo_def):
    return sum([defin[3] * atoms.get_angle(*defin[0:3]) for
                defin in anglecombo_def])


def get_dihedralcombo(atoms, dihedralcombo_def):
    return sum([defin[4] * atoms.get_dihedral(*defin[0:4]) for
                defin in dihedralcombo_def])


def setup_atoms():
    atoms = molecule('CH3CH2OH', vacuum=5.0)
    atoms.rattle(stdev=0.3)
    return atoms

def setup_fixinternals():
    atoms = setup_atoms()

    # Angles, Bonds, Dihedrals are built up with pairs of constraint
    # value and indices defining the constraint
    # Linear combinations of bond lengths are built up similarly with the
    # coefficients appended to the indices defining the constraint

    # Fix bond between atoms 1 and 2 to 1.4
    bond_def = [1, 2]
    target_bond = 1.4

    # Fix angle to whatever it was from the start
    angle_def = [6, 0, 1]
    target_angle = atoms.get_angle(*angle_def)

    # Fix this dihedral angle to whatever it was from the start
    dihedral_def = [6, 0, 1, 2]
    target_dihedral = atoms.get_dihedral(*dihedral_def)

    # Initialize constraint
    constr = FixInternals(bonds=[(target_bond, bond_def)],
                              angles_deg=[(target_angle, angle_def)],
                              dihedrals_deg=[(target_dihedral, dihedral_def)],
                              epsilon=1e-10)
    print(constr)
    return (atoms, constr, bond_def, target_bond, angle_def, target_angle,
            dihedral_def, target_dihedral)


def test_fixinternals():
    (atoms, constr, bond_def, target_bond, angle_def, target_angle,
     dihedral_def, target_dihedral) = setup_fixinternals()

    calc = EMT()

    opt = BFGS(atoms)

    previous_angle = atoms.get_angle(*angle_def)
    previous_dihedral = atoms.get_dihedral(*dihedral_def)

    print('angle before', previous_angle)
    print('dihedral before', previous_dihedral)
    print('bond length before', atoms.get_distance(*bond_def))
    print('target bondlength', target_bond)

    atoms.calc = calc
    atoms.set_constraint(constr)
    print('-----Optimization-----')
    opt.run(fmax=0.01)

    new_angle = atoms.get_angle(*angle_def)
    new_dihedral = atoms.get_dihedral(*dihedral_def)
    new_bondlength = atoms.get_distance(*bond_def)

    print('angle after', new_angle)
    print('dihedral after', new_dihedral)
    print('bondlength after', new_bondlength)

    err1 = new_angle - previous_angle
    err2 = new_dihedral - previous_dihedral
    err3 = new_bondlength - target_bond

    print('error in angle', repr(err1))
    print('error in dihedral', repr(err2))
    print('error in bondlength', repr(err3))

    assert err1 < 1e-11
    assert err2 < 1e-12
    assert err3 < 1e-12


def setup_combos():
    atoms = setup_atoms()

    # Fix linear combination of two bond lengths with atom indices 0-8 and
    # 0-6 with weighting coefficients 1.0 and -1.0 to the current value.
    # In other words, fulfil the following constraint:
    # 1.0 * atoms.get_distance(2, 1) + -1.0 * atoms.get_distance(2, 3) = const.
    bondcombo_def = [[2, 1, 1.0], [2, 3, -1.0]]
    target_bondcombo = get_bondcombo(atoms, bondcombo_def)

    # Fix linear combination of two angles
    # 1. * atoms.get_angle(7, 0, 8) + 1. * atoms.get_angle(7, 0, 6) = const.
    anglecombo_def = [[7, 0, 8, 1.], [7, 0, 6, 1]]
    target_anglecombo = get_anglecombo(atoms, anglecombo_def)

    # Fix linear combination of two dihedrals
    dihedralcombo_def = [[3, 2, 1, 4, 1.0], [2, 1, 0, 7, 1.0]]
    target_dihedralcombo = get_dihedralcombo(atoms, dihedralcombo_def)

    # Initialize constraint
    constr = FixInternals(bondcombos=[(target_bondcombo, bondcombo_def)],
                          anglecombos=[(target_anglecombo, anglecombo_def)],
                          dihedralcombos=[(target_dihedralcombo,
                          dihedralcombo_def)], epsilon=1e-10)
    print(constr)
    return (atoms, constr, bondcombo_def, target_bondcombo, anglecombo_def,
            target_anglecombo, dihedralcombo_def, target_dihedralcombo)


def test_combos():
    (atoms, constr, bondcombo_def, target_bondcombo, anglecombo_def,
     target_anglecombo, dihedralcombo_def,
     target_dihedralcombo) = setup_combos()

    ref_bondcombo = get_bondcombo(atoms, bondcombo_def)
    ref_anglecombo = get_anglecombo(atoms, anglecombo_def)
    ref_dihedralcombo = get_dihedralcombo(atoms, dihedralcombo_def)

    atoms.calc = EMT()
    atoms.set_constraint(constr)

    opt = BFGS(atoms)
    opt.run(fmax=0.01)

    new_bondcombo = get_bondcombo(atoms, bondcombo_def)
    new_anglecombo = get_anglecombo(atoms, anglecombo_def)
    new_dihedralcombo = get_dihedralcombo(atoms, dihedralcombo_def)

    err_bondcombo = new_bondcombo - ref_bondcombo
    err_anglecombo = new_anglecombo - ref_anglecombo
    err_dihedralcombo = new_dihedralcombo - ref_dihedralcombo

    print('error in bondcombo:', repr(err_bondcombo))
    print('error in anglecombo:', repr(err_anglecombo))
    print('error in dihedralcombo:', repr(err_dihedralcombo))

    for err in [err_bondcombo, err_anglecombo, err_dihedralcombo]:
        assert err < 2e-12


def test_index_shuffle():
    (atoms, constr, bond_def, target_bond, angle_def, target_angle,
     dihedral_def, target_dihedral) = setup_fixinternals()

    constr2 = copy.deepcopy(constr)

    # test no change, test constr.get_indices()
    assert all(a == b for a, b in zip(constr.get_indices(), (0, 1, 2, 6, 8)))
    constr.index_shuffle(atoms, range(len(atoms)))
    assert all(a == b for a, b in zip(constr.get_indices(), (0, 1, 2, 6, 8)))

    # test full constraint is not part of new slice
    with pytest.raises(IndexError):
        constr.index_shuffle(atoms, [0])

    # test correct shuffling
    constr2.index_shuffle(atoms, [1, 2, 0, 6])
    assert constr2.bonds[0][1] == [0, 1]
    assert constr2.angles[0][1] == [3, 2, 0]
    assert constr2.dihedrals[0][1] == [3, 2, 0, 1]


def test_combo_index_shuffle():
    (atoms, constr, bondcombo_def, target_bondcombo, anglecombo_def,
     target_anglecombo, dihedralcombo_def,
     target_dihedralcombo) = setup_combos()

    # test no change, test constr.get_indices()
    answer = (0, 1, 2, 3, 4, 6, 7, 8)
    assert all(a == b for a, b in zip(constr.get_indices(), answer))
    constr.index_shuffle(atoms, range(len(atoms)))
    assert all(a == b for a, b in zip(constr.get_indices(), answer))

    # test anglecombo not part of slice
    constr.index_shuffle(atoms, [1, 2, 3, 4, 0, 7])
    assert constr.bondcombos[0][1] == [[1, 0, 1.0], [1, 2, -1.0]]
    assert constr.dihedralcombos[0][1] == [[2, 1, 0, 3, 1.0], [1, 0, 4, 5, 1.0]]