ipi 2.6.0__py3-none-any.whl
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.
- ipi/__init__.py +7 -0
- ipi/_driver/__init__.py +0 -0
- ipi/_driver/driver.py +218 -0
- ipi/_driver/pes/__init__.py +14 -0
- ipi/_driver/pes/dummy.py +17 -0
- ipi/_driver/pes/harmonic.py +30 -0
- ipi/_driver/pes/rascal.py +57 -0
- ipi/engine/__init__.py +25 -0
- ipi/engine/atoms.py +273 -0
- ipi/engine/barostats.py +1408 -0
- ipi/engine/beads.py +348 -0
- ipi/engine/cell.py +132 -0
- ipi/engine/ensembles.py +280 -0
- ipi/engine/forcefields.py +1784 -0
- ipi/engine/forces.py +1487 -0
- ipi/engine/initializer.py +663 -0
- ipi/engine/motion/__init__.py +26 -0
- ipi/engine/motion/al6xxx_kmc.py +678 -0
- ipi/engine/motion/alchemy.py +160 -0
- ipi/engine/motion/atomswap.py +135 -0
- ipi/engine/motion/constrained_dynamics.py +541 -0
- ipi/engine/motion/dynamics.py +809 -0
- ipi/engine/motion/geop.py +1005 -0
- ipi/engine/motion/instanton.py +1549 -0
- ipi/engine/motion/motion.py +87 -0
- ipi/engine/motion/multi.py +72 -0
- ipi/engine/motion/neb.py +984 -0
- ipi/engine/motion/phonons.py +448 -0
- ipi/engine/motion/planetary.py +281 -0
- ipi/engine/motion/ramp.py +116 -0
- ipi/engine/motion/replay.py +177 -0
- ipi/engine/motion/scphonons.py +901 -0
- ipi/engine/motion/stringmep.py +1511 -0
- ipi/engine/motion/vscf.py +1473 -0
- ipi/engine/normalmodes.py +905 -0
- ipi/engine/outputs.py +665 -0
- ipi/engine/properties.py +2995 -0
- ipi/engine/simulation.py +402 -0
- ipi/engine/smotion/__init__.py +14 -0
- ipi/engine/smotion/dmd.py +56 -0
- ipi/engine/smotion/metad.py +97 -0
- ipi/engine/smotion/multi.py +62 -0
- ipi/engine/smotion/remd.py +227 -0
- ipi/engine/smotion/smotion.py +58 -0
- ipi/engine/system.py +127 -0
- ipi/engine/thermostats.py +1276 -0
- ipi/external/__init__.py +0 -0
- ipi/external/importlib/__init__.py +5 -0
- ipi/external/importlib/bundledimportlib.py +38 -0
- ipi/inputs/__init__.py +25 -0
- ipi/inputs/atoms.py +133 -0
- ipi/inputs/barostats.py +180 -0
- ipi/inputs/beads.py +152 -0
- ipi/inputs/cell.py +62 -0
- ipi/inputs/ensembles.py +152 -0
- ipi/inputs/forcefields.py +964 -0
- ipi/inputs/forces.py +196 -0
- ipi/inputs/initializer.py +535 -0
- ipi/inputs/interface.py +160 -0
- ipi/inputs/motion/__init__.py +10 -0
- ipi/inputs/motion/al6xxx_kmc.py +269 -0
- ipi/inputs/motion/alchemy.py +98 -0
- ipi/inputs/motion/atomswap.py +101 -0
- ipi/inputs/motion/constrained_dynamics.py +354 -0
- ipi/inputs/motion/dynamics.py +135 -0
- ipi/inputs/motion/geop.py +241 -0
- ipi/inputs/motion/instanton.py +338 -0
- ipi/inputs/motion/motion.py +470 -0
- ipi/inputs/motion/neb.py +326 -0
- ipi/inputs/motion/phonons.py +130 -0
- ipi/inputs/motion/planetary.py +149 -0
- ipi/inputs/motion/ramp.py +157 -0
- ipi/inputs/motion/scphonons.py +189 -0
- ipi/inputs/motion/stringmep.py +317 -0
- ipi/inputs/motion/vscf.py +276 -0
- ipi/inputs/normalmodes.py +169 -0
- ipi/inputs/outputs.py +468 -0
- ipi/inputs/prng.py +123 -0
- ipi/inputs/simulation.py +336 -0
- ipi/inputs/smotion/__init__.py +10 -0
- ipi/inputs/smotion/dmd.py +59 -0
- ipi/inputs/smotion/metad.py +71 -0
- ipi/inputs/smotion/remd.py +85 -0
- ipi/inputs/smotion/smotion.py +154 -0
- ipi/inputs/system.py +231 -0
- ipi/inputs/thermostats.py +389 -0
- ipi/interfaces/__init__.py +8 -0
- ipi/interfaces/sockets.py +915 -0
- ipi/tests/__init__.py +3 -0
- ipi/tests/regression_tests/__init__.py +0 -0
- ipi/tests/regression_tests/runstools.py +245 -0
- ipi/tests/regression_tests/test_run.py +80 -0
- ipi/tests/regression_tests/tests/NVE/NVE_1/harmonic_python/files_to_check.txt +7 -0
- ipi/tests/regression_tests/tests/NVE/NVE_1/harmonic_python/init.xyz +3 -0
- ipi/tests/regression_tests/tests/NVE/NVE_1/harmonic_python/input.xml +40 -0
- ipi/tests/regression_tests/tests/NVE/NVE_1/harmonic_python/ref_simulation.frc_c.xyz +33 -0
- ipi/tests/regression_tests/tests/NVE/NVE_1/harmonic_python/ref_simulation.mom_c.xyz +33 -0
- ipi/tests/regression_tests/tests/NVE/NVE_1/harmonic_python/ref_simulation.out +16 -0
- ipi/tests/regression_tests/tests/NVE/NVE_1/harmonic_python/ref_simulation.pos_c.xyz +33 -0
- ipi/tests/regression_tests/tests/NVE/NVE_1/harmonic_python/ref_simulation.vel_c.xyz +33 -0
- ipi/tests/regression_tests/tests/NVE/NVE_1/harmonic_python/test_settings.dat +6 -0
- ipi/tests/test_tools.py +361 -0
- ipi/utils/__init__.py +19 -0
- ipi/utils/constrtools.py +530 -0
- ipi/utils/decorators.py +20 -0
- ipi/utils/depend.py +922 -0
- ipi/utils/distance.py +42 -0
- ipi/utils/exchange.py +183 -0
- ipi/utils/hesstools.py +250 -0
- ipi/utils/inputvalue.py +1323 -0
- ipi/utils/instools.py +300 -0
- ipi/utils/io/__init__.py +455 -0
- ipi/utils/io/backends/__init__.py +10 -0
- ipi/utils/io/backends/io_ase.py +109 -0
- ipi/utils/io/backends/io_binary.py +59 -0
- ipi/utils/io/backends/io_json.py +113 -0
- ipi/utils/io/backends/io_pdb.py +201 -0
- ipi/utils/io/backends/io_xyz.py +158 -0
- ipi/utils/io/inputs/__init__.py +10 -0
- ipi/utils/io/inputs/io_xml.py +599 -0
- ipi/utils/io/io_units.py +131 -0
- ipi/utils/mathtools.py +494 -0
- ipi/utils/messages.py +154 -0
- ipi/utils/mintools.py +1629 -0
- ipi/utils/nmtransform.py +489 -0
- ipi/utils/phonontools.py +69 -0
- ipi/utils/prng.py +117 -0
- ipi/utils/softexit.py +189 -0
- ipi/utils/sparse.py +160 -0
- ipi/utils/units.py +374 -0
- ipi-2.6.0.data/scripts/i-pi +112 -0
- ipi-2.6.0.data/scripts/i-pi-committee-reweight +267 -0
- ipi-2.6.0.data/scripts/i-pi-contract-trajectory +175 -0
- ipi-2.6.0.data/scripts/i-pi-get_Ascp +215 -0
- ipi-2.6.0.data/scripts/i-pi-getacf +258 -0
- ipi-2.6.0.data/scripts/i-pi-getproperty +63 -0
- ipi-2.6.0.data/scripts/i-pi-gleacf +362 -0
- ipi-2.6.0.data/scripts/i-pi-kinetic2tag +101 -0
- ipi-2.6.0.data/scripts/i-pi-mergebeadspdb +54 -0
- ipi-2.6.0.data/scripts/i-pi-mux-positions +172 -0
- ipi-2.6.0.data/scripts/i-pi-paraweights +226 -0
- ipi-2.6.0.data/scripts/i-pi-planetary +569 -0
- ipi-2.6.0.data/scripts/i-pi-posforce2kinetic +145 -0
- ipi-2.6.0.data/scripts/i-pi-py_driver +218 -0
- ipi-2.6.0.data/scripts/i-pi-remdsort +242 -0
- ipi-2.6.0.data/scripts/i-pi-style +109 -0
- ipi-2.6.0.data/scripts/i-pi-tests +66 -0
- ipi-2.6.0.data/scripts/i-pi-trimsim +228 -0
- ipi-2.6.0.dist-info/METADATA +138 -0
- ipi-2.6.0.dist-info/RECORD +154 -0
- ipi-2.6.0.dist-info/WHEEL +5 -0
- ipi-2.6.0.dist-info/license_GPL.txt +675 -0
- ipi-2.6.0.dist-info/license_MIT.txt +21 -0
- ipi-2.6.0.dist-info/top_level.txt +1 -0
ipi/__init__.py
ADDED
ipi/_driver/__init__.py
ADDED
|
File without changes
|
ipi/_driver/driver.py
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import socket
|
|
3
|
+
import argparse
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
from pes import *
|
|
8
|
+
except ImportError:
|
|
9
|
+
# when in an installed i-PI package
|
|
10
|
+
from ipi._driver.pes import *
|
|
11
|
+
|
|
12
|
+
description = """
|
|
13
|
+
Minimal example of a Python driver connecting to i-PI and exchanging energy, forces, etc.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def recv_data(sock, data):
|
|
18
|
+
"""Fetches binary data from i-PI socket."""
|
|
19
|
+
blen = data.itemsize * data.size
|
|
20
|
+
buf = np.zeros(blen, np.byte)
|
|
21
|
+
|
|
22
|
+
bpos = 0
|
|
23
|
+
while bpos < blen:
|
|
24
|
+
timeout = False
|
|
25
|
+
try:
|
|
26
|
+
bpart = 1
|
|
27
|
+
bpart = sock.recv_into(buf[bpos:], blen - bpos)
|
|
28
|
+
except socket.timeout:
|
|
29
|
+
print(" @SOCKET: Timeout in status recvall, trying again!")
|
|
30
|
+
timeout = True
|
|
31
|
+
pass
|
|
32
|
+
if not timeout and bpart == 0:
|
|
33
|
+
raise RuntimeError("Socket disconnected!")
|
|
34
|
+
bpos += bpart
|
|
35
|
+
if np.isscalar(data):
|
|
36
|
+
return np.frombuffer(buf[0:blen], data.dtype)[0]
|
|
37
|
+
else:
|
|
38
|
+
return np.frombuffer(buf[0:blen], data.dtype).reshape(data.shape)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def send_data(sock, data):
|
|
42
|
+
"""Sends binary data to i-PI socket."""
|
|
43
|
+
|
|
44
|
+
if np.isscalar(data):
|
|
45
|
+
data = np.array([data], data.dtype)
|
|
46
|
+
buf = data.tobytes()
|
|
47
|
+
sock.send(buf)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
HDRLEN = 12 # number of characters of the default message strings
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def Message(mystr):
|
|
54
|
+
"""Returns a header of standard length HDRLEN."""
|
|
55
|
+
|
|
56
|
+
# convert to bytestream since we'll be sending this over a socket
|
|
57
|
+
return str.ljust(str.upper(mystr), HDRLEN).encode()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def run_driver(unix=False, address="", port=12345, driver=Dummy_driver()):
|
|
61
|
+
"""Minimal socket client for i-PI."""
|
|
62
|
+
|
|
63
|
+
# Opens a socket to i-PI
|
|
64
|
+
if unix:
|
|
65
|
+
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
66
|
+
sock.connect("/tmp/ipi_" + address)
|
|
67
|
+
else:
|
|
68
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
69
|
+
sock.connect((address, port))
|
|
70
|
+
|
|
71
|
+
f_init = False
|
|
72
|
+
f_data = False
|
|
73
|
+
f_verbose = False
|
|
74
|
+
|
|
75
|
+
# initializes structure arrays
|
|
76
|
+
cell = np.zeros((3, 3), float)
|
|
77
|
+
icell = np.zeros((3, 3), float)
|
|
78
|
+
pos = np.zeros(0, float)
|
|
79
|
+
|
|
80
|
+
# initializes return arrays
|
|
81
|
+
pot = 0.0
|
|
82
|
+
force = np.zeros(0, float)
|
|
83
|
+
vir = np.zeros((3, 3), float)
|
|
84
|
+
while True: # ah the infinite loop!
|
|
85
|
+
header = sock.recv(HDRLEN)
|
|
86
|
+
|
|
87
|
+
if header == Message("STATUS"):
|
|
88
|
+
# responds to a status request
|
|
89
|
+
if not f_init:
|
|
90
|
+
sock.sendall(Message("NEEDINIT"))
|
|
91
|
+
elif f_data:
|
|
92
|
+
sock.sendall(Message("HAVEDATA"))
|
|
93
|
+
else:
|
|
94
|
+
sock.sendall(Message("READY"))
|
|
95
|
+
elif header == Message("INIT"):
|
|
96
|
+
# initialization
|
|
97
|
+
rid = recv_data(sock, np.int32())
|
|
98
|
+
initlen = recv_data(sock, np.int32())
|
|
99
|
+
initstr = recv_data(sock, np.chararray(initlen))
|
|
100
|
+
if f_verbose:
|
|
101
|
+
print(rid, initstr)
|
|
102
|
+
f_init = True # we are initialized now
|
|
103
|
+
elif header == Message("POSDATA"):
|
|
104
|
+
# receives structural information
|
|
105
|
+
cell = recv_data(sock, cell)
|
|
106
|
+
icell = recv_data(
|
|
107
|
+
sock, icell
|
|
108
|
+
) # inverse of the cell. mostly useless legacy stuff
|
|
109
|
+
nat = recv_data(sock, np.int32())
|
|
110
|
+
if len(pos) == 0:
|
|
111
|
+
# shapes up the position array
|
|
112
|
+
pos.resize((nat, 3))
|
|
113
|
+
force.resize((nat, 3))
|
|
114
|
+
else:
|
|
115
|
+
if len(pos) != nat:
|
|
116
|
+
raise RuntimeError("Atom number changed during i-PI run")
|
|
117
|
+
pos = recv_data(sock, pos)
|
|
118
|
+
|
|
119
|
+
##### THIS IS THE TIME TO DO SOMETHING WITH THE POSITIONS!
|
|
120
|
+
pot, force, vir, extras = driver(cell, pos)
|
|
121
|
+
f_data = True
|
|
122
|
+
elif header == Message("GETFORCE"):
|
|
123
|
+
sock.sendall(Message("FORCEREADY"))
|
|
124
|
+
|
|
125
|
+
# sanity check in the returned values (catches bugs and inconsistencies in the implementation)
|
|
126
|
+
if not isinstance(force, np.ndarray) and force.dtype == np.float64:
|
|
127
|
+
raise ValueError(
|
|
128
|
+
"driver returned forces with the wrong type: we need a "
|
|
129
|
+
"numpy.ndarray containing 64-bit floating points values"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
if not isinstance(vir, np.ndarray) and vir.dtype == np.float64:
|
|
133
|
+
raise ValueError(
|
|
134
|
+
"driver returned virial with the wrong type: we need a "
|
|
135
|
+
"numpy.ndarray containing 64-bit floating points values"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
if len(force.flatten()) != len(pos.flatten()):
|
|
139
|
+
raise ValueError(
|
|
140
|
+
"driver returned forces with the wrong size: number of "
|
|
141
|
+
"atoms and dimensions must match positions"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
if len(vir.flatten()) != 9:
|
|
145
|
+
raise ValueError(
|
|
146
|
+
"driver returned a virial tensor which does not have 9 components"
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
send_data(sock, np.float64(pot))
|
|
150
|
+
send_data(sock, np.int32(nat))
|
|
151
|
+
send_data(sock, force)
|
|
152
|
+
send_data(sock, vir)
|
|
153
|
+
send_data(sock, np.int32(len(extras)))
|
|
154
|
+
sock.sendall(extras.encode("utf-8"))
|
|
155
|
+
|
|
156
|
+
f_data = False
|
|
157
|
+
elif header == Message("EXIT"):
|
|
158
|
+
print("Received exit message from i-PI. Bye bye!")
|
|
159
|
+
return
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
if __name__ == "__main__":
|
|
163
|
+
parser = argparse.ArgumentParser(description=description)
|
|
164
|
+
|
|
165
|
+
parser.add_argument(
|
|
166
|
+
"-u",
|
|
167
|
+
"--unix",
|
|
168
|
+
action="store_true",
|
|
169
|
+
default=False,
|
|
170
|
+
help="Use a UNIX domain socket.",
|
|
171
|
+
)
|
|
172
|
+
parser.add_argument(
|
|
173
|
+
"-a",
|
|
174
|
+
"--address",
|
|
175
|
+
type=str,
|
|
176
|
+
default="localhost",
|
|
177
|
+
help="Host name (for INET sockets) or name of the UNIX domain socket to connect to.",
|
|
178
|
+
)
|
|
179
|
+
parser.add_argument(
|
|
180
|
+
"-p",
|
|
181
|
+
"--port",
|
|
182
|
+
type=int,
|
|
183
|
+
default=12345,
|
|
184
|
+
help="TCP/IP port number. Ignored when using UNIX domain sockets.",
|
|
185
|
+
)
|
|
186
|
+
parser.add_argument(
|
|
187
|
+
"-m",
|
|
188
|
+
"--mode",
|
|
189
|
+
type=str,
|
|
190
|
+
default="dummy",
|
|
191
|
+
help="""Type of potential to be used to compute the potential and its derivatives.
|
|
192
|
+
Currently implemented: [dummy, harmonic]
|
|
193
|
+
""",
|
|
194
|
+
)
|
|
195
|
+
parser.add_argument(
|
|
196
|
+
"-o",
|
|
197
|
+
"--param",
|
|
198
|
+
type=str,
|
|
199
|
+
default="",
|
|
200
|
+
help="""Parameters required to run the driver. Comma-separated list of values
|
|
201
|
+
""",
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
args = parser.parse_args()
|
|
205
|
+
|
|
206
|
+
if args.mode in __drivers__:
|
|
207
|
+
d_f = __drivers__[args.mode](args.param)
|
|
208
|
+
elif args.mode == "dummy":
|
|
209
|
+
d_f = Dummy_driver(args.param)
|
|
210
|
+
else:
|
|
211
|
+
raise ValueError("Unsupported driver mode ", args.mode)
|
|
212
|
+
|
|
213
|
+
run_driver(
|
|
214
|
+
unix=args.unix,
|
|
215
|
+
address=args.address,
|
|
216
|
+
port=args.port,
|
|
217
|
+
driver=d_f,
|
|
218
|
+
)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
""" Small functions/classes providing access to driver PES to be called from driver.py """
|
|
2
|
+
|
|
3
|
+
from .dummy import Dummy_driver
|
|
4
|
+
from .harmonic import Harmonic_driver
|
|
5
|
+
from .rascal import Rascal_driver
|
|
6
|
+
|
|
7
|
+
__all__ = ["__drivers__", "Dummy_driver", "Harmonic_driver", "Rascal_driver"]
|
|
8
|
+
|
|
9
|
+
# dictionary linking strings
|
|
10
|
+
__drivers__ = {
|
|
11
|
+
"dummy": Dummy_driver,
|
|
12
|
+
"harmonic": Harmonic_driver,
|
|
13
|
+
"rascal": Rascal_driver,
|
|
14
|
+
}
|
ipi/_driver/pes/dummy.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class Dummy_driver(object):
|
|
2
|
+
def __init__(self, args=None):
|
|
3
|
+
"""Initialized dummy drivers"""
|
|
4
|
+
self.args = args
|
|
5
|
+
self.check_arguments()
|
|
6
|
+
|
|
7
|
+
def check_arguments(self):
|
|
8
|
+
"""Dummy function that checks the arguments required to run the driver"""
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
def __call__(self, cell, pos):
|
|
12
|
+
"""Does nothing, but returns properties that can be used by the driver loop."""
|
|
13
|
+
pot = 0.0
|
|
14
|
+
force = pos * 0.0 # makes a zero force with same shape as pos
|
|
15
|
+
vir = cell * 0.0 # makes a zero virial with same shape as cell
|
|
16
|
+
extras = "nada"
|
|
17
|
+
return pot, force, vir, extras
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
""" Harmonic potential """
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from .dummy import Dummy_driver
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Harmonic_driver(Dummy_driver):
|
|
8
|
+
def __init__(self, args=None):
|
|
9
|
+
self.error_msg = """\nHarmonic driver requires specification of force constant.\nExample: python driver.py -m harmonic -u -o 1.3\n"""
|
|
10
|
+
super(Harmonic_driver, self).__init__(args)
|
|
11
|
+
|
|
12
|
+
def check_arguments(self):
|
|
13
|
+
"""Function that checks the arguments required to run the driver"""
|
|
14
|
+
try:
|
|
15
|
+
k = list(map(float, self.args.split(",")))
|
|
16
|
+
except ValueError:
|
|
17
|
+
sys.exit(self.error_msg)
|
|
18
|
+
|
|
19
|
+
if len(k) == 1:
|
|
20
|
+
self.k = k[0]
|
|
21
|
+
else:
|
|
22
|
+
sys.exit(self.error_msg)
|
|
23
|
+
|
|
24
|
+
def __call__(self, cell, pos):
|
|
25
|
+
"""Silly harmonic potential"""
|
|
26
|
+
pot = 0.5 * self.k * (pos**2).sum()
|
|
27
|
+
force = -self.k * pos # makes a zero force with same shape as pos
|
|
28
|
+
vir = cell * 0.0 # makes a zero virial with same shape as cell
|
|
29
|
+
extras = "nada"
|
|
30
|
+
return pot, force, vir, extras
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Interface with librascal to run machine learning potentials"""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from .dummy import Dummy_driver
|
|
5
|
+
|
|
6
|
+
from ipi.utils.mathtools import det_ut3x3
|
|
7
|
+
from ipi.utils.units import unit_to_internal, unit_to_user
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
from rascal.models.genericmd import GenericMDCalculator as RascalCalc
|
|
11
|
+
except:
|
|
12
|
+
RascalCalc = None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Rascal_driver(Dummy_driver):
|
|
16
|
+
def __init__(self, args=None):
|
|
17
|
+
self.error_msg = """Rascal driver requires specification of a .json model file fitted with librascal,
|
|
18
|
+
and a template file that describes the chemical makeup of the structure.
|
|
19
|
+
Example: python driver.py -m rascal -u -o model.json,template.xyz"""
|
|
20
|
+
|
|
21
|
+
super().__init__(args)
|
|
22
|
+
|
|
23
|
+
if RascalCalc is None:
|
|
24
|
+
raise ImportError("Couldn't load librascal bindings")
|
|
25
|
+
|
|
26
|
+
def check_arguments(self):
|
|
27
|
+
"""Check the arguments required to run the driver
|
|
28
|
+
|
|
29
|
+
This loads the potential and atoms template in librascal
|
|
30
|
+
"""
|
|
31
|
+
try:
|
|
32
|
+
arglist = self.args.split(",")
|
|
33
|
+
except ValueError:
|
|
34
|
+
sys.exit(self.error_msg)
|
|
35
|
+
|
|
36
|
+
if len(arglist) == 2:
|
|
37
|
+
self.model = arglist[0]
|
|
38
|
+
self.template = arglist[1]
|
|
39
|
+
else:
|
|
40
|
+
sys.exit(self.error_msg)
|
|
41
|
+
|
|
42
|
+
self.rascal_calc = RascalCalc(self.model, True, self.template)
|
|
43
|
+
|
|
44
|
+
def __call__(self, cell, pos):
|
|
45
|
+
"""Get energies, forces, and stresses from the librascal model"""
|
|
46
|
+
pos_rascal = unit_to_user("length", "angstrom", pos)
|
|
47
|
+
# librascal expects ASE-format, cell-vectors-as-rows
|
|
48
|
+
cell_rascal = unit_to_user("length", "angstrom", cell.T)
|
|
49
|
+
# Do the actual calculation
|
|
50
|
+
pot, force, stress = self.rascal_calc.calculate(pos_rascal, cell_rascal)
|
|
51
|
+
pot_ipi = unit_to_internal("energy", "electronvolt", pot)
|
|
52
|
+
force_ipi = unit_to_internal("force", "ev/ang", force)
|
|
53
|
+
# The rascal stress is normalized by the cell volume (in rascal units)
|
|
54
|
+
vir_rascal = -1 * stress * det_ut3x3(cell_rascal)
|
|
55
|
+
vir_ipi = unit_to_internal("energy", "electronvolt", vir_rascal.T)
|
|
56
|
+
extras = ""
|
|
57
|
+
return pot_ipi, force_ipi, vir_ipi, extras
|
ipi/engine/__init__.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Modules used to run simulations.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# This file is part of i-PI.
|
|
6
|
+
# i-PI Copyright (C) 2014-2015 i-PI developers
|
|
7
|
+
# See the "licenses" directory for full license information.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"atoms",
|
|
12
|
+
"cell",
|
|
13
|
+
"simulation",
|
|
14
|
+
"forces",
|
|
15
|
+
"ensembles",
|
|
16
|
+
"properties",
|
|
17
|
+
"thermostats",
|
|
18
|
+
"barostats",
|
|
19
|
+
"beads",
|
|
20
|
+
"outputs",
|
|
21
|
+
"normalmodes",
|
|
22
|
+
"initializer",
|
|
23
|
+
"system",
|
|
24
|
+
"paratemp",
|
|
25
|
+
]
|
ipi/engine/atoms.py
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""Classes which deal with classical atoms.
|
|
2
|
+
|
|
3
|
+
Used for holding information about the atoms, including their positions, masses
|
|
4
|
+
momenta and kinetic energy. Has separate classes for accessing the global
|
|
5
|
+
arrays of atoms and for individual atoms.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# This file is part of i-PI.
|
|
9
|
+
# i-PI Copyright (C) 2014-2015 i-PI developers
|
|
10
|
+
# See the "licenses" directory for full license information.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
from ipi.utils.depend import *
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
__all__ = ["Atoms", "Atom"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Atom(dobject):
|
|
22
|
+
|
|
23
|
+
"""Represent an atom, with position, velocity, mass and related properties.
|
|
24
|
+
|
|
25
|
+
This is actually only an interface to the Atoms class, i.e. only stores
|
|
26
|
+
views of the large arrays which contain all the coordinates.
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
kin: The kinetic energy of the atom.
|
|
30
|
+
kstress: The contribution of the atom to the kinetic stress tensor.
|
|
31
|
+
|
|
32
|
+
Depend objects:
|
|
33
|
+
p: The three components of the momentum of the atom.
|
|
34
|
+
q: The three components of the position of the atom.
|
|
35
|
+
m: The mass of the atom.
|
|
36
|
+
name: The name of the atom.
|
|
37
|
+
m3: An array of 3 elements with each element being the mass of the atom.
|
|
38
|
+
Used when each degree of freedom needs to be divided by the mass.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, system, index):
|
|
42
|
+
"""Initialises Atom.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
system: An Atoms object containing the required atom.
|
|
46
|
+
index: An integer giving the index of the required atom in the atoms
|
|
47
|
+
list. Note that indices start from 0.
|
|
48
|
+
"""
|
|
49
|
+
dself = dd(self) # direct access
|
|
50
|
+
|
|
51
|
+
dself.p = system.p[3 * index : 3 * index + 3]
|
|
52
|
+
dself.q = system.q[3 * index : 3 * index + 3]
|
|
53
|
+
dself.m = system.m[index : index + 1]
|
|
54
|
+
dself.name = system.names[index : index + 1]
|
|
55
|
+
dself.m3 = system.m3[3 * index : 3 * index + 3]
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def kin(self):
|
|
59
|
+
"""Calculates the contribution of the atom to the kinetic energy."""
|
|
60
|
+
|
|
61
|
+
return np.dot(self.p, self.p) / (2.0 * self.m)
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def kstress(self):
|
|
65
|
+
"""Calculates the contribution of the atom to the kinetic stress
|
|
66
|
+
tensor.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
p = dstrip(self.p)
|
|
70
|
+
ks = np.zeros((3, 3), float)
|
|
71
|
+
for i in range(3):
|
|
72
|
+
for j in range(i, 3):
|
|
73
|
+
ks[i, j] = p[i] * p[j]
|
|
74
|
+
return ks / self.m
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class Atoms(dobject):
|
|
78
|
+
|
|
79
|
+
"""Storage for the atoms' positions, masses and velocities.
|
|
80
|
+
|
|
81
|
+
Everything is stored as 3*n sized contiguous arrays,
|
|
82
|
+
and a convenience-access is provided through a list of Atom objects.
|
|
83
|
+
|
|
84
|
+
Attributes:
|
|
85
|
+
natoms: The number of atoms.
|
|
86
|
+
|
|
87
|
+
Depend objects:
|
|
88
|
+
p: An array giving the components of the atom positions.
|
|
89
|
+
q: An array giving the components of the atom momenta.
|
|
90
|
+
m: An array giving the atom masses.
|
|
91
|
+
names: An array giving the atom names.
|
|
92
|
+
m3: An array of 3*n elements where each element of m has been copied
|
|
93
|
+
three times. Used when each degree of freedom needs to be divided
|
|
94
|
+
by the mass.
|
|
95
|
+
M: The total mass of all the atoms.
|
|
96
|
+
kin: The total kinetic energy of the atoms. Depends on p and m3.
|
|
97
|
+
kstress: The contribution of the atoms to the kinetic stress tensor.
|
|
98
|
+
Depends on px, py, pz and m.
|
|
99
|
+
qx: An array giving the x components of the positions.
|
|
100
|
+
qy: An array giving the y components of the positions.
|
|
101
|
+
qz: An array giving the z components of the positions.
|
|
102
|
+
px: An array giving the x components of the momenta.
|
|
103
|
+
py: An array giving the y components of the momenta.
|
|
104
|
+
pz: An array giving the z components of the momenta.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
def __init__(self, natoms, _prebind=None):
|
|
108
|
+
"""Initialises Atoms.
|
|
109
|
+
|
|
110
|
+
Each replica and the centroid coordinate are all held as Atoms objects,
|
|
111
|
+
and so slices of the global position and momentum arrays must be used in
|
|
112
|
+
the initialisation so that they always agree with each other.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
natoms: An integer giving the number of atoms.
|
|
116
|
+
_prebind: An optional tuple of four elements; a depend_array of length
|
|
117
|
+
3*natoms for the positions, another for the momenta, a depend_array
|
|
118
|
+
of length natoms for the masses and another for the names.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
self.natoms = natoms
|
|
122
|
+
|
|
123
|
+
dself = dd(self) # direct access
|
|
124
|
+
|
|
125
|
+
if _prebind is None:
|
|
126
|
+
dself.q = depend_array(name="q", value=np.zeros(3 * natoms, float))
|
|
127
|
+
dself.p = depend_array(name="p", value=np.zeros(3 * natoms, float))
|
|
128
|
+
dself.m = depend_array(name="m", value=np.zeros(natoms, float))
|
|
129
|
+
dself.names = depend_array(
|
|
130
|
+
name="names", value=np.zeros(natoms, np.dtype("|U6"))
|
|
131
|
+
)
|
|
132
|
+
else:
|
|
133
|
+
dself.q = _prebind[0]
|
|
134
|
+
dself.p = _prebind[1]
|
|
135
|
+
dself.m = _prebind[2]
|
|
136
|
+
dself.names = _prebind[3]
|
|
137
|
+
|
|
138
|
+
dself.m3 = depend_array(
|
|
139
|
+
name="m3",
|
|
140
|
+
value=np.zeros(3 * natoms, float),
|
|
141
|
+
func=self.mtom3,
|
|
142
|
+
dependencies=[dself.m],
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
dself.M = depend_value(name="M", func=self.get_msum, dependencies=[dself.m])
|
|
146
|
+
dself.kin = depend_value(
|
|
147
|
+
name="kin", func=self.get_kin, dependencies=[dself.p, dself.m3]
|
|
148
|
+
)
|
|
149
|
+
dself.kstress = depend_value(
|
|
150
|
+
name="kstress", func=self.get_kstress, dependencies=[dself.p, dself.m]
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
def copy(self):
|
|
154
|
+
"""Creates a new Atoms object.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
An Atoms object with the same q, p, m and names arrays as the original.
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
newat = Atoms(self.natoms)
|
|
161
|
+
newat.q[:] = self.q
|
|
162
|
+
newat.p[:] = self.p
|
|
163
|
+
newat.m[:] = self.m
|
|
164
|
+
newat.names[:] = self.names
|
|
165
|
+
return newat
|
|
166
|
+
|
|
167
|
+
def __len__(self):
|
|
168
|
+
"""Length function.
|
|
169
|
+
|
|
170
|
+
This is called whenever the standard function len(atoms) is used.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
The number of atoms.
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
return self.natoms
|
|
177
|
+
|
|
178
|
+
def __iter__(self):
|
|
179
|
+
"""Iterator.
|
|
180
|
+
|
|
181
|
+
This is called whenever one iterates over an Atoms object.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Itertor over all atoms in this Atoms object.
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
for index in range(len(self)):
|
|
188
|
+
yield Atom(self, index)
|
|
189
|
+
|
|
190
|
+
def __getitem__(self, index):
|
|
191
|
+
"""Overwrites standard getting function.
|
|
192
|
+
|
|
193
|
+
This is called whenever the standard function atoms[index] is used.
|
|
194
|
+
Returns an Atom object with the appropriate position and momenta arrays.
|
|
195
|
+
Note that they are dynamically generated each time an Atom needs to be
|
|
196
|
+
accessed, as this reduces the number of depend objects that need to be
|
|
197
|
+
held at any one time.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
index: The index of the atom to be accessed.
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
The atom given by the index.
|
|
204
|
+
"""
|
|
205
|
+
|
|
206
|
+
return Atom(self, index)
|
|
207
|
+
|
|
208
|
+
def __setitem__(self, index, value):
|
|
209
|
+
"""Overwrites standard setting function.
|
|
210
|
+
|
|
211
|
+
This is called whenever the standard function atoms[index]=value is used.
|
|
212
|
+
Changes the position and momenta of the appropriate slice of the global
|
|
213
|
+
position and momentum arrays to those given by value.
|
|
214
|
+
Note that they are dynamically generated each time an Atom needs to be
|
|
215
|
+
accessed, as this reduces the number of depend objects that need to be
|
|
216
|
+
held at any one time.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
index: The atom to be changed.
|
|
220
|
+
value: The Atom object that holds the new values.
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
pat = Atom(self, index)
|
|
224
|
+
pat.p = value.p
|
|
225
|
+
pat.q = value.q
|
|
226
|
+
pat.m = value.m
|
|
227
|
+
pat.name = value.name
|
|
228
|
+
|
|
229
|
+
def get_msum(self):
|
|
230
|
+
"""Calculates the total mass."""
|
|
231
|
+
|
|
232
|
+
return self.m.sum()
|
|
233
|
+
|
|
234
|
+
def mtom3(self):
|
|
235
|
+
"""Returns a 3*n mass array.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
An array of 3*n elements where each element of m has been copied
|
|
239
|
+
three times. Used when each degree of freedom needs to be divided
|
|
240
|
+
by the mass.
|
|
241
|
+
"""
|
|
242
|
+
|
|
243
|
+
m3 = np.zeros(3 * self.natoms, float)
|
|
244
|
+
m3[0 : 3 * self.natoms : 3] = self.m
|
|
245
|
+
m3[1 : 3 * self.natoms : 3] = m3[0 : 3 * self.natoms : 3]
|
|
246
|
+
m3[2 : 3 * self.natoms : 3] = m3[0 : 3 * self.natoms : 3]
|
|
247
|
+
return m3
|
|
248
|
+
|
|
249
|
+
def get_kin(self):
|
|
250
|
+
"""Calculates the total kinetic energy of the system."""
|
|
251
|
+
|
|
252
|
+
p = dstrip(self.p)
|
|
253
|
+
return 0.5 * np.dot(p, p / dstrip(self.m3))
|
|
254
|
+
|
|
255
|
+
def get_kstress(self):
|
|
256
|
+
"""Calculates the total contribution of the atoms to the kinetic stress
|
|
257
|
+
tensor -- not volume-scaled
|
|
258
|
+
"""
|
|
259
|
+
|
|
260
|
+
p = dstrip(self.p)
|
|
261
|
+
m = dstrip(self.m)
|
|
262
|
+
px = p[0::3]
|
|
263
|
+
py = p[1::3]
|
|
264
|
+
pz = p[2::3]
|
|
265
|
+
|
|
266
|
+
ks = np.zeros((3, 3), float)
|
|
267
|
+
ks[0, 0] = np.dot(px, px / m)
|
|
268
|
+
ks[1, 1] = np.dot(py, py / m)
|
|
269
|
+
ks[2, 2] = np.dot(pz, pz / m)
|
|
270
|
+
ks[0, 1] = np.dot(px, py / m)
|
|
271
|
+
ks[0, 2] = np.dot(px, pz / m)
|
|
272
|
+
ks[1, 2] = np.dot(py, pz / m)
|
|
273
|
+
return ks
|