mdinterface 1.0.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.
@@ -0,0 +1,220 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created on Fri Apr 19 14:05:39 2024
5
+
6
+ @author: roncofaber
7
+ """
8
+
9
+ import copy
10
+ #%%
11
+
12
+ class Topology(object):
13
+
14
+ def __init__(self, resname=None, formula=None):
15
+
16
+ self._id = None
17
+ self._formula = formula
18
+ self._resname = resname
19
+
20
+ return
21
+
22
+ @property
23
+ def id(self):
24
+ return self._id
25
+
26
+ @property
27
+ def formula(self):
28
+ return self._formula
29
+
30
+ @property
31
+ def resname(self):
32
+ return self._resname
33
+
34
+ def set_id(self, value):
35
+ self._id = value
36
+ return
37
+
38
+ def set_formula(self, value):
39
+ self._formula = value
40
+
41
+ def set_resname(self, value):
42
+ self._resname = value
43
+
44
+ def copy(self):
45
+ return copy.deepcopy(self)
46
+
47
+ #%%
48
+
49
+ class Atom(Topology):
50
+ def __init__(self, symbol, label=None, eps=None, sig=None):
51
+
52
+ super(Atom, self).__init__()
53
+
54
+ self.symbol = symbol
55
+
56
+ self.eps = eps
57
+ self.sig = sig
58
+
59
+ if label is None:
60
+ label = symbol
61
+
62
+ self.set_label(label)
63
+
64
+ return
65
+
66
+ def set_label(self, value):
67
+ self._label = value
68
+ return
69
+
70
+ @property
71
+ def label(self):
72
+ return self._label
73
+
74
+ @property
75
+ def extended_label(self):
76
+ return self._label + "_" + self.resname
77
+
78
+ def __repr__(self):
79
+ return f"{self.__class__.__name__}({self.label}, eps={self.eps}, sig={self.sig})"
80
+
81
+ # TODO CHANGE BACK IF DOESN'T WORK
82
+ def __eq__(self, other):
83
+ return self.eps == other.eps and self.sig == other.sig and self.symbol == other.symbol
84
+
85
+ # def __eq__(self, other):
86
+ # return self.eps == other.eps and self.sig == other.sig and self.label == other.label
87
+
88
+ #%%
89
+
90
+ class Bond(Topology):
91
+ def __init__(self, a1, a2, kr=None, r0=None):
92
+ super(Bond, self).__init__()
93
+ self._a1 = a1
94
+ self._a2 = a2
95
+ self.kr = kr
96
+ self.r0 = r0
97
+ return
98
+
99
+ @property
100
+ def symbols(self):
101
+ return self._a1, self._a2
102
+
103
+ def __repr__(self):
104
+ return f"{self.__class__.__name__}({'-'.join(self.symbols)},kr={self.kr}, r0={self.r0})"
105
+
106
+ @property
107
+ def elements(self):
108
+ return self._a1.split('_')[0], self._a2.split('_')[0]
109
+
110
+ def __eq__(self, other):
111
+ return (self.kr == other.kr and self.r0 == other.r0 and
112
+ (self.elements == other.elements or self.elements == other.elements[::-1]))
113
+
114
+
115
+ class Angle(Topology):
116
+ def __init__(self, a1, a2, a3, kr=None, theta0=None):
117
+ super(Angle, self).__init__()
118
+ self._a1 = a1
119
+ self._a2 = a2
120
+ self._a3 = a3
121
+ self.kr = kr
122
+ self.theta0 = theta0
123
+ return
124
+
125
+ @property
126
+ def symbols(self):
127
+ return self._a1, self._a2, self._a3
128
+
129
+ @property
130
+ def elements(self):
131
+ return self._a1.split('_')[0], self._a2.split('_')[0], self._a3.split('_')[0]
132
+
133
+ def __repr__(self):
134
+ return f"{self.__class__.__name__}({'-'.join(self.symbols)}, kr={self.kr}, theta0={self.theta0})"
135
+
136
+ def __eq__(self, other):
137
+ return (self.kr == other.kr and self.theta0 == other.theta0 and
138
+ (self.elements == other.elements or self.elements == other.elements[::-1]))
139
+
140
+
141
+ class Dihedral(Topology):
142
+ def __init__(self, a1, a2, a3, a4, A1=None, A2=None, A3=None, A4=None, A5=None):
143
+ super(Dihedral, self).__init__()
144
+ self._a1 = a1
145
+ self._a2 = a2
146
+ self._a3 = a3
147
+ self._a4 = a4
148
+ self._values = [A1, A2, A3, A4, A5]
149
+ return
150
+
151
+ @property
152
+ def symbols(self):
153
+ return self._a1, self._a2, self._a3, self._a4
154
+
155
+ @property
156
+ def values(self):
157
+ return self._values
158
+
159
+ @property
160
+ def elements(self):
161
+ return self._a1.split('_')[0], self._a2.split('_')[0],\
162
+ self._a3.split('_')[0], self._a4.split('_')[0]
163
+
164
+ def __repr__(self):
165
+ symbols = '-'.join(self.symbols)
166
+ values = '|'.join([str(ii) for ii in self._values])
167
+ return f"{self.__class__.__name__}({symbols}, A={values})"
168
+
169
+ def __eq__(self, other):
170
+ return (self.values == other.values and
171
+ (self.elements == other.elements or self.elements == other.elements[::-1]))
172
+
173
+ def write(self, fout):
174
+ atype = "{}-{}-{}-{}".format(*self.symbols)
175
+ if self._values[-1] is not None:
176
+ value = "{:>7.4f} {:>7.4f} {:>7.4f} {:>7.4f} {:>7.4f}".format(*self.values)
177
+ else:
178
+ value = "{:>7.4f} {:>7.4f} {:>7.4f} {:>7.4f}".format(*self.values[:-1])
179
+ fout.write("{:>5} {} # {:<8} | {}\n".format(self.id, value, atype, self.resname))
180
+
181
+
182
+ class Improper(Topology): # cvff improper style
183
+ def __init__(self, a1, a2="x", a3="x", a4="x", K=None, d=None, n=None):
184
+ super(Improper, self).__init__()
185
+
186
+ self._a1 = a1
187
+ self._a2 = a2
188
+ self._a3 = a3
189
+ self._a4 = a4
190
+
191
+ self._K = K
192
+
193
+ assert d in [1, -1], "Wrong 'd' value"
194
+ self._d = d
195
+
196
+ assert n in [0,1,2,3,4,5,6], "Wrong 'n' value"
197
+ self._n = n
198
+
199
+ return
200
+
201
+ @property
202
+ def symbols(self):
203
+ return self._a1, self._a2, self._a3, self._a4
204
+
205
+ @property
206
+ def values(self):
207
+ return self._K, self._d, self._n
208
+
209
+ def __repr__(self):
210
+ symbols = '-'.join(self.symbols)
211
+ values = '|'.join([str(ii) for ii in self.values])
212
+ return f"{self.__class__.__name__}({symbols}, K,d,n={values})"
213
+
214
+ def __eq__(self, other):
215
+ return (self.values == other.values and
216
+ self._a1.split('_')[0] == other._a1.split('_')[0])
217
+
218
+
219
+
220
+
@@ -0,0 +1,8 @@
1
+ # io/__init__.py
2
+
3
+ """
4
+ io: Provides input/output functionalities for various file formats.
5
+ """
6
+
7
+ from .lammpswriter import *
8
+ from .packmol import header, box_place, fix_place
@@ -0,0 +1,261 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created on Wed Oct 25 15:03:47 2023
5
+
6
+ @author: roncoroni
7
+ """
8
+
9
+ import numpy as np
10
+
11
+ from MDAnalysis.lib import util, mdamath
12
+ from MDAnalysis.core.groups import requires
13
+ from MDAnalysis.exceptions import NoDataError
14
+ from MDAnalysis.coordinates import base
15
+
16
+
17
+ btype_sections = {'bond':'Bonds', 'angle':'Angles',
18
+ 'dihedral':'Dihedrals', 'improper':'Impropers'}
19
+
20
+
21
+ class DATAWriter(base.WriterBase):
22
+ """Write out the current time step as a LAMMPS DATA file.
23
+
24
+ This writer supports the sections Atoms, Masses, Velocities, Bonds,
25
+ Angles, Dihedrals, and Impropers. This writer will write the header
26
+ and these sections (if applicable). Atoms section is written in the
27
+ "full" sub-style if charges are available or "molecular" sub-style
28
+ if they are not. Molecule id is set to 0 for all atoms.
29
+
30
+ Note
31
+ ----
32
+ This writer assumes "conventional" or "real" LAMMPS units where length
33
+ is measured in Angstroms and velocity is measured in Angstroms per
34
+ femtosecond. To write in different units, specify `lengthunit`
35
+
36
+ If atom types are not already positive integers, the user must set them
37
+ to be positive integers, because the writer will not automatically
38
+ assign new types.
39
+
40
+ To preserve numerical atom types when writing a selection, the Masses
41
+ section will have entries for each atom type up to the maximum atom type.
42
+ If the universe does not contain atoms of some type in
43
+ {1, ... max(atom_types)}, then the mass for that type will be set to 1.
44
+
45
+ In order to write bonds, each selected bond type must be explicitly set to
46
+ an integer >= 1.
47
+
48
+ """
49
+ format = 'DATA'
50
+
51
+ def __init__(self, filename, convert_units=True, **kwargs):
52
+ """Set up a DATAWriter
53
+
54
+ Parameters
55
+ ----------
56
+ filename : str
57
+ output filename
58
+ convert_units : bool, optional
59
+ units are converted to the MDAnalysis base format; [``True``]
60
+ """
61
+ self.filename = util.filename(filename, ext='data', keep=True)
62
+
63
+ self.convert_units = convert_units
64
+
65
+ self.units = {'time': 'fs', 'length': 'Angstrom'}
66
+ self.units['length'] = kwargs.pop('lengthunit', self.units['length'])
67
+ self.units['time'] = kwargs.pop('timeunit', self.units['time'])
68
+ self.units['velocity'] = kwargs.pop('velocityunit',
69
+ self.units['length']+'/'+self.units['time'])
70
+
71
+ def _write_atoms(self, atoms, data):
72
+ self.f.write('\n')
73
+ self.f.write('Atoms\n')
74
+ self.f.write('\n')
75
+
76
+ try:
77
+ charges = atoms.charges
78
+ except (NoDataError, AttributeError):
79
+ has_charges = False
80
+ else:
81
+ has_charges = True
82
+
83
+ indices = atoms.indices + 1
84
+ i_l = len(str(indices.max()))
85
+
86
+ unique_types, types = np.unique(atoms.types, return_inverse=True)
87
+ types += 1
88
+ t_l = len(str(len(unique_types)))
89
+
90
+ moltags = atoms.resindices
91
+ m_l = len(str(moltags.max()))+1
92
+
93
+ if self.convert_units:
94
+ coordinates = self.convert_pos_to_native(atoms.positions, inplace=False)
95
+
96
+ b_l = len(str(int(coordinates.max()))) + 1
97
+ b_t = b_l + 6
98
+
99
+ if has_charges:
100
+ for index, moltag, atype, charge, coords in zip(indices, moltags,
101
+ types, charges, coordinates):
102
+ x, y, z = coords
103
+ self.f.write(f"{index:{i_l}d} {moltag:{m_l}d} {atype:{t_l}d} {charge: .7f}"
104
+ f" {x:> {b_t}.5f} {y:> {b_t}.5f} {z:> {b_t}.5f}\n")
105
+ else:
106
+ for index, moltag, atype, coords in zip(indices, moltags, types,
107
+ coordinates):
108
+ x, y, z = coords
109
+ self.f.write(f"{index:{i_l}d} {moltag:{m_l}d} {atype:{t_l}d}"
110
+ f" {x:> {b_t}.5f} {y:> {b_t}.5f} {z:> {b_t}.5f}\n")
111
+
112
+ def _write_velocities(self, atoms):
113
+ self.f.write('\n')
114
+ self.f.write('Velocities\n')
115
+ self.f.write('\n')
116
+ indices = atoms.indices + 1
117
+ velocities = self.convert_velocities_to_native(atoms.velocities,
118
+ inplace=False)
119
+ for index, vel in zip(indices, velocities):
120
+ self.f.write('{i:d} {x:f} {y:f} {z:f}\n'.format(i=index, x=vel[0],
121
+ y=vel[1], z=vel[2]))
122
+
123
+ def _write_masses(self, atoms):
124
+ # self.f.write('\n')
125
+ self.f.write('Masses\n')
126
+ self.f.write('\n')
127
+
128
+ for cc, atype in enumerate(np.unique(atoms.types)):
129
+
130
+ # search entire universe for mass info, not just writing selection
131
+ masses = set(atoms.select_atoms('type {}'.format(atype)).masses)
132
+
133
+ if len(masses) == 0:
134
+ mass = 1.0
135
+ else:
136
+ mass = masses.pop()
137
+
138
+ if masses:
139
+ raise ValueError('LAMMPS DATAWriter: to write data file, '+
140
+ 'atoms with same type must have same mass')
141
+
142
+
143
+ self.f.write('{:d} {:> 8.3f} # {}\n'.format(cc+1, mass, atype))
144
+
145
+ def _write_bonds(self, bonds):
146
+ self.f.write('\n')
147
+ self.f.write('{}\n'.format(btype_sections[bonds.btype]))
148
+ self.f.write('\n')
149
+ for bond, i in zip(bonds, range(1, len(bonds)+1)):
150
+ try:
151
+ self.f.write('{:d} {:d} '.format(i, int(bond.type))+\
152
+ ' '.join((bond.atoms.indices + 1).astype(str))+'\n')
153
+ except TypeError:
154
+ errmsg = (f"LAMMPS DATAWriter: Trying to write bond, but bond "
155
+ f"type {bond.type} is not numerical.")
156
+ raise TypeError(errmsg) from None
157
+
158
+ def _write_dimensions(self, dimensions):
159
+ """Convert dimensions to triclinic vectors, convert lengths to native
160
+ units and then write the dimensions section
161
+ """
162
+ if self.convert_units:
163
+ triv = self.convert_pos_to_native(mdamath.triclinic_vectors(
164
+ dimensions),inplace=False)
165
+ self.f.write('\n')
166
+ self.f.write('{:f} {:f} xlo xhi\n'.format(0., triv[0][0]))
167
+ self.f.write('{:f} {:f} ylo yhi\n'.format(0., triv[1][1]))
168
+ self.f.write('{:f} {:f} zlo zhi\n'.format(0., triv[2][2]))
169
+ if any([triv[1][0], triv[2][0], triv[2][1]]):
170
+ self.f.write('{xy:f} {xz:f} {yz:f} xy xz yz\n'.format(
171
+ xy=triv[1][0], xz=triv[2][0], yz=triv[2][1]))
172
+ self.f.write('\n')
173
+
174
+ @requires('types', 'masses')
175
+ def write(self, selection, frame=None):
176
+ """Write selection at current trajectory frame to file.
177
+
178
+ The sections for Atoms, Masses, Velocities, Bonds, Angles,
179
+ Dihedrals, and Impropers (if these are defined) are
180
+ written. The Atoms section is written in the "full" sub-style
181
+ if charges are available or "molecular" sub-style if they are
182
+ not. Molecule id in atoms section is set to to 0.
183
+
184
+ No other sections are written to the DATA file.
185
+ As of this writing, other sections are not parsed into the topology
186
+ by the :class:`DATAReader`.
187
+
188
+ Note
189
+ ----
190
+ If the selection includes a partial fragment, then only the bonds,
191
+ angles, etc. whose atoms are contained within the selection will be
192
+ included.
193
+
194
+ Parameters
195
+ ----------
196
+ selection : AtomGroup or Universe
197
+ MDAnalysis AtomGroup (selection or Universe.atoms) or also Universe
198
+ frame : int (optional)
199
+ optionally move to frame number `frame`
200
+
201
+ """
202
+ u = selection.universe
203
+ if frame is not None:
204
+ u.trajectory[frame]
205
+ else:
206
+ frame = u.trajectory.ts.frame
207
+
208
+ # make sure to use atoms (Issue 46)
209
+ atoms = selection.atoms
210
+
211
+ # check that types can be converted to ints if they aren't ints already
212
+ # try:
213
+ # atoms.types.astype(np.int32)
214
+ # except ValueError:
215
+ # errmsg = ("LAMMPS.DATAWriter: atom types must be convertible to "
216
+ # "integers")
217
+ # raise ValueError(errmsg) from None
218
+
219
+ try:
220
+ velocities = atoms.velocities
221
+ except (NoDataError, AttributeError):
222
+ has_velocities = False
223
+ else:
224
+ has_velocities = True
225
+
226
+ features = {}
227
+ with util.openany(self.filename, 'wt') as self.f:
228
+ self.f.write('LAMMPS data file via MDAnalysis\n')
229
+ self.f.write('\n')
230
+ self.f.write('{:>12d} atoms\n'.format(len(atoms)))
231
+
232
+ attrs = [('bond', 'bonds'), ('angle', 'angles'),
233
+ ('dihedral', 'dihedrals'), ('improper', 'impropers')]
234
+
235
+ for btype, attr_name in attrs:
236
+ features[btype] = atoms.__getattribute__(attr_name)
237
+ self.f.write('{:>12d} {}\n'.format(len(features[btype]),
238
+ attr_name))
239
+ features[btype] = features[btype].atomgroup_intersection(
240
+ atoms, strict=True)
241
+
242
+ self.f.write('\n')
243
+
244
+ ntypes = len(set(atoms.types))
245
+ self.f.write('{:>12d} atom types\n'.format(ntypes))
246
+
247
+ for btype, attr in features.items():
248
+ self.f.write('{:>12d} {} types\n'.format(len(attr.types()),
249
+ btype))
250
+
251
+ self._write_dimensions(atoms.dimensions)
252
+
253
+ self._write_masses(atoms)
254
+ self._write_atoms(atoms, u.trajectory.ts.data)
255
+ for attr in features.values():
256
+ if attr is None or len(attr) == 0:
257
+ continue
258
+ self._write_bonds(attr)
259
+
260
+ if has_velocities:
261
+ self._write_velocities(atoms)
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created on Wed Nov 22 13:57:02 2023
5
+
6
+ @author: roncoroni
7
+ """
8
+
9
+
10
+ header = """\
11
+ tolerance 2.0
12
+ output {}
13
+ nloop 100
14
+ seed {}
15
+
16
+ filetype pdb
17
+ # movebadrandom
18
+ """
19
+
20
+ box_place = """
21
+ structure mol_{}.pdb
22
+ number {}
23
+ inside box {}
24
+ resnumbers 2
25
+ end structure
26
+ """
27
+
28
+ fix_place = """
29
+ structure mol_{}.pdb
30
+ number 1
31
+ center
32
+ fixed {} {} {} 0. 0. 0.
33
+ resnumbers 2
34
+ end structure
35
+ """
mdinterface/io/read.py ADDED
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created on Thu Oct 24 18:33:06 2024
5
+
6
+ @author: roncofaber
7
+ """
8
+
9
+ import numpy as np
10
+
11
+ import ase
12
+ import ase.visualize
13
+ import ase.io.lammpsdata
14
+ from mdinterface.core.topology import Atom, Bond, Angle, Dihedral, Improper
15
+
16
+ #%%
17
+
18
+ def read_lammps_data_file(filename):
19
+
20
+ system = ase.io.lammpsdata.read_lammps_data(filename)
21
+ system.set_pbc(False)
22
+ system.set_cell(None)
23
+
24
+ symbols = system.get_chemical_symbols()
25
+ # Initialize data containers
26
+ bond_coeff = []
27
+ angle_coeff = []
28
+ dihedral_coeff = []
29
+ improper_coeff = []
30
+
31
+ atoms = []
32
+ bonds = []
33
+ angles = []
34
+ dihedrals = []
35
+ impropers = []
36
+
37
+ # Define a dictionary to map section names to their corresponding data lists
38
+ section_map = {
39
+ 'pair coeffs' : 'pair_coeff',
40
+ 'bond coeffs' : 'bond_coeff',
41
+ 'angle coeffs' : 'angle_coeff',
42
+ 'dihedral coeffs' : 'dihedral_coeff',
43
+ 'improper coeffs' : 'improper_coeff',
44
+ 'atoms' : 'atoms',
45
+ 'bonds' : 'bonds',
46
+ 'angles' : 'angles',
47
+ 'dihedrals' : 'dihedrals',
48
+ 'impropers' : 'impropers'
49
+ }
50
+
51
+ # Read the file and process lines
52
+ with open(filename, "r") as file:
53
+ to_read = None
54
+
55
+ for line in file:
56
+ line = line.strip()
57
+
58
+ if not line:
59
+ continue
60
+
61
+ section_key = line.lower()
62
+
63
+ if section_key in section_map:
64
+ to_read = section_map[section_key]
65
+ continue
66
+
67
+ line = line.split()
68
+
69
+ if to_read == "pair_coeff":
70
+ idx = int(line[0]) - 1
71
+ eps = float(line[1])
72
+ sig = float(line[2])
73
+ symbol = symbols[idx]
74
+ atom = Atom(symbol, f"{symbol}_{str(idx).zfill(3)}", eps, sig)
75
+ atoms.append(atom)
76
+
77
+ elif to_read == "bond_coeff":
78
+ kr = float(line[1])
79
+ r0 = float(line[2])
80
+ bond_coeff.append([kr, r0])
81
+
82
+ elif to_read == "angle_coeff":
83
+ kr = float(line[1])
84
+ theta0 = float(line[2])
85
+ angle_coeff.append([kr, theta0])
86
+
87
+ elif to_read == "dihedral_coeff":
88
+ values = [float(ii) for ii in line[1:]]
89
+ dihedral_coeff.append(values)
90
+
91
+ elif to_read == "improper_coeff":
92
+ values = [float(line[1]), int(line[2]), int(line[3])]
93
+ improper_coeff.append(values)
94
+
95
+ elif to_read == "bonds":
96
+ idx1 = int(line[2]) - 1
97
+ idx2 = int(line[3]) - 1
98
+ kr, r0 = bond_coeff[int(line[1]) - 1]
99
+ bond = Bond(atoms[idx1].label, atoms[idx2].label, kr, r0)
100
+ bonds.append(bond)
101
+
102
+ elif to_read == "angles":
103
+ idx1 = int(line[2]) - 1
104
+ idx2 = int(line[3]) - 1
105
+ idx3 = int(line[4]) - 1
106
+ kr, theta0 = angle_coeff[int(line[1]) - 1]
107
+ angle = Angle(atoms[idx1].label, atoms[idx2].label, atoms[idx3].label, kr, theta0)
108
+ angles.append(angle)
109
+
110
+ elif to_read == "dihedrals":
111
+ idxs = [int(ii) - 1 for ii in line[2:]]
112
+ values = dihedral_coeff[int(line[1]) - 1]
113
+ dihedral = Dihedral(atoms[idxs[0]].label, atoms[idxs[1]].label,
114
+ atoms[idxs[2]].label, atoms[idxs[3]].label, *values)
115
+ dihedrals.append(dihedral)
116
+
117
+ elif to_read == "impropers":
118
+ idxs = [int(ii) - 1 for ii in line[2:]]
119
+ values = improper_coeff[int(line[1]) - 1]
120
+ improper = Improper(atoms[idxs[0]].label, atoms[idxs[1]].label,
121
+ atoms[idxs[2]].label, atoms[idxs[3]].label,
122
+ K=values[0], d=values[1], n=values[2])
123
+ impropers.append(improper)
124
+
125
+ system.new_array("stype", np.array(atoms))
126
+ return system, atoms, bonds, angles, dihedrals, impropers