hillclimber 0.1.0a1__py3-none-any.whl → 0.1.0a3__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.
Potentially problematic release.
This version of hillclimber might be problematic. Click here for more details.
- hillclimber/__init__.py +20 -5
- hillclimber/actions.py +29 -3
- hillclimber/analysis.py +636 -0
- hillclimber/biases.py +293 -0
- hillclimber/cvs.py +691 -284
- hillclimber/interfaces.py +45 -5
- hillclimber/metadynamics.py +110 -34
- hillclimber/opes.py +357 -0
- hillclimber/selectors.py +127 -2
- hillclimber/virtual_atoms.py +335 -0
- {hillclimber-0.1.0a1.dist-info → hillclimber-0.1.0a3.dist-info}/METADATA +59 -1
- hillclimber-0.1.0a3.dist-info/RECORD +17 -0
- hillclimber-0.1.0a1.dist-info/RECORD +0 -13
- {hillclimber-0.1.0a1.dist-info → hillclimber-0.1.0a3.dist-info}/WHEEL +0 -0
- {hillclimber-0.1.0a1.dist-info → hillclimber-0.1.0a3.dist-info}/entry_points.txt +0 -0
- {hillclimber-0.1.0a1.dist-info → hillclimber-0.1.0a3.dist-info}/licenses/LICENSE +0 -0
hillclimber/selectors.py
CHANGED
|
@@ -7,6 +7,107 @@ import rdkit2ase
|
|
|
7
7
|
from hillclimber.interfaces import AtomSelector
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
# --- Indexable Selector Wrappers ---
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclasses.dataclass
|
|
14
|
+
class _GroupIndexedSelector(AtomSelector):
|
|
15
|
+
"""Selector with group-level indexing applied.
|
|
16
|
+
|
|
17
|
+
This is an internal class created when you index a selector at the group level.
|
|
18
|
+
For example: water_sel[0] or water_sel[0:2]
|
|
19
|
+
"""
|
|
20
|
+
selector: AtomSelector
|
|
21
|
+
group_index: int | slice | list[int]
|
|
22
|
+
|
|
23
|
+
def __getitem__(self, idx: int | slice | list[int]) -> AtomSelector:
|
|
24
|
+
"""Atom-level indexing.
|
|
25
|
+
|
|
26
|
+
After group indexing, this applies atom-level indexing.
|
|
27
|
+
For example: water_sel[0:2][1:3] selects atoms 1-2 from groups 0-1.
|
|
28
|
+
"""
|
|
29
|
+
return _AtomIndexedSelector(self, idx)
|
|
30
|
+
|
|
31
|
+
def __add__(self, other: AtomSelector) -> AtomSelector:
|
|
32
|
+
"""Combine two selectors."""
|
|
33
|
+
return _CombinedSelector([self, other])
|
|
34
|
+
|
|
35
|
+
def select(self, atoms: ase.Atoms) -> list[list[int]]:
|
|
36
|
+
"""Apply group indexing to the underlying selector."""
|
|
37
|
+
groups = self.selector.select(atoms)
|
|
38
|
+
|
|
39
|
+
# Apply group indexing (supports negative indices)
|
|
40
|
+
if isinstance(self.group_index, int):
|
|
41
|
+
return [groups[self.group_index]] # Python handles negative indices
|
|
42
|
+
elif isinstance(self.group_index, slice):
|
|
43
|
+
return groups[self.group_index] # Python handles negative indices in slices
|
|
44
|
+
else: # list[int]
|
|
45
|
+
return [groups[i] for i in self.group_index] # Negative indices work here too
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclasses.dataclass
|
|
49
|
+
class _AtomIndexedSelector(AtomSelector):
|
|
50
|
+
"""Selector with both group and atom-level indexing applied.
|
|
51
|
+
|
|
52
|
+
This is an internal class created when you apply two levels of indexing.
|
|
53
|
+
For example: water_sel[0][0] or water_sel[0:2][1:3]
|
|
54
|
+
"""
|
|
55
|
+
group_selector: _GroupIndexedSelector
|
|
56
|
+
atom_index: int | slice | list[int]
|
|
57
|
+
|
|
58
|
+
def __getitem__(self, idx) -> AtomSelector:
|
|
59
|
+
"""Prevent three-level indexing."""
|
|
60
|
+
raise ValueError("Cannot index beyond 2 levels (group, then atom)")
|
|
61
|
+
|
|
62
|
+
def __add__(self, other: AtomSelector) -> AtomSelector:
|
|
63
|
+
"""Combine two selectors."""
|
|
64
|
+
return _CombinedSelector([self, other])
|
|
65
|
+
|
|
66
|
+
def select(self, atoms: ase.Atoms) -> list[list[int]]:
|
|
67
|
+
"""Apply atom-level indexing to each group."""
|
|
68
|
+
groups = self.group_selector.select(atoms)
|
|
69
|
+
|
|
70
|
+
# Apply atom-level indexing to each group (supports negative indices)
|
|
71
|
+
result = []
|
|
72
|
+
for group in groups:
|
|
73
|
+
if isinstance(self.atom_index, int):
|
|
74
|
+
result.append([group[self.atom_index]]) # Negative indices work
|
|
75
|
+
elif isinstance(self.atom_index, slice):
|
|
76
|
+
result.append(group[self.atom_index]) # Negative indices in slices work
|
|
77
|
+
else: # list[int]
|
|
78
|
+
result.append([group[i] for i in self.atom_index]) # Negative indices work
|
|
79
|
+
|
|
80
|
+
return result
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclasses.dataclass
|
|
84
|
+
class _CombinedSelector(AtomSelector):
|
|
85
|
+
"""Selector that combines multiple selectors.
|
|
86
|
+
|
|
87
|
+
This is an internal class created when you combine selectors with +.
|
|
88
|
+
For example: water_sel + ethanol_sel
|
|
89
|
+
"""
|
|
90
|
+
selectors: list[AtomSelector]
|
|
91
|
+
|
|
92
|
+
def __getitem__(self, idx: int | slice | list[int]) -> AtomSelector:
|
|
93
|
+
"""Group-level indexing on combined result."""
|
|
94
|
+
return _GroupIndexedSelector(self, idx)
|
|
95
|
+
|
|
96
|
+
def __add__(self, other: AtomSelector) -> AtomSelector:
|
|
97
|
+
"""Combine with another selector."""
|
|
98
|
+
# Flatten if other is also a CombinedSelector
|
|
99
|
+
if isinstance(other, _CombinedSelector):
|
|
100
|
+
return _CombinedSelector(self.selectors + other.selectors)
|
|
101
|
+
return _CombinedSelector(self.selectors + [other])
|
|
102
|
+
|
|
103
|
+
def select(self, atoms: ase.Atoms) -> list[list[int]]:
|
|
104
|
+
"""Concatenate all groups from all selectors."""
|
|
105
|
+
result = []
|
|
106
|
+
for selector in self.selectors:
|
|
107
|
+
result.extend(selector.select(atoms))
|
|
108
|
+
return result
|
|
109
|
+
|
|
110
|
+
|
|
10
111
|
@dataclasses.dataclass
|
|
11
112
|
class IndexSelector(AtomSelector):
|
|
12
113
|
"""Select atoms based on grouped indices.
|
|
@@ -23,6 +124,14 @@ class IndexSelector(AtomSelector):
|
|
|
23
124
|
# mostly used for debugging
|
|
24
125
|
indices: list[list[int]]
|
|
25
126
|
|
|
127
|
+
def __getitem__(self, idx: int | slice | list[int]) -> AtomSelector:
|
|
128
|
+
"""Group-level indexing."""
|
|
129
|
+
return _GroupIndexedSelector(self, idx)
|
|
130
|
+
|
|
131
|
+
def __add__(self, other: AtomSelector) -> AtomSelector:
|
|
132
|
+
"""Combine two selectors."""
|
|
133
|
+
return _CombinedSelector([self, other])
|
|
134
|
+
|
|
26
135
|
def select(self, atoms: ase.Atoms) -> list[list[int]]:
|
|
27
136
|
return self.indices
|
|
28
137
|
|
|
@@ -39,6 +148,14 @@ class SMILESSelector(AtomSelector):
|
|
|
39
148
|
|
|
40
149
|
smiles: str
|
|
41
150
|
|
|
151
|
+
def __getitem__(self, idx: int | slice | list[int]) -> AtomSelector:
|
|
152
|
+
"""Group-level indexing."""
|
|
153
|
+
return _GroupIndexedSelector(self, idx)
|
|
154
|
+
|
|
155
|
+
def __add__(self, other: AtomSelector) -> AtomSelector:
|
|
156
|
+
"""Combine two selectors."""
|
|
157
|
+
return _CombinedSelector([self, other])
|
|
158
|
+
|
|
42
159
|
def select(self, atoms: ase.Atoms) -> list[list[int]]:
|
|
43
160
|
matches = rdkit2ase.match_substructure(atoms, smiles=self.smiles)
|
|
44
161
|
return [list(match) for match in matches]
|
|
@@ -55,8 +172,8 @@ class SMARTSSelector(AtomSelector):
|
|
|
55
172
|
|
|
56
173
|
Note
|
|
57
174
|
----
|
|
58
|
-
The selector is applied only to the first trajectory frame.
|
|
59
|
-
Since indices can change during e.g. proton transfer, biasing specific groups (e.g. `[OH-]`) may fail.
|
|
175
|
+
The selector is applied only to the first trajectory frame.
|
|
176
|
+
Since indices can change during e.g. proton transfer, biasing specific groups (e.g. `[OH-]`) may fail.
|
|
60
177
|
In such cases, select all `[OH2]` and `[OH-]` groups and use CoordinationNumber CVs.
|
|
61
178
|
Account for this method with all changes in chemical structure.
|
|
62
179
|
|
|
@@ -90,6 +207,14 @@ class SMARTSSelector(AtomSelector):
|
|
|
90
207
|
pattern: str
|
|
91
208
|
hydrogens: tp.Literal["include", "exclude", "isolated"] = "exclude"
|
|
92
209
|
|
|
210
|
+
def __getitem__(self, idx: int | slice | list[int]) -> AtomSelector:
|
|
211
|
+
"""Group-level indexing."""
|
|
212
|
+
return _GroupIndexedSelector(self, idx)
|
|
213
|
+
|
|
214
|
+
def __add__(self, other: AtomSelector) -> AtomSelector:
|
|
215
|
+
"""Combine two selectors."""
|
|
216
|
+
return _CombinedSelector([self, other])
|
|
217
|
+
|
|
93
218
|
def select(self, atoms: ase.Atoms) -> list[list[int]]:
|
|
94
219
|
return rdkit2ase.select_atoms_grouped(
|
|
95
220
|
rdkit2ase.ase2rdkit(atoms), self.pattern, self.hydrogens
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
"""Virtual atom definitions for creating points in space from atom groups.
|
|
2
|
+
|
|
3
|
+
Virtual atoms reduce groups of atoms to single points (or multiple points, one per group)
|
|
4
|
+
using strategies like center of mass (COM), center of geometry (COG), or first atom.
|
|
5
|
+
|
|
6
|
+
Resources
|
|
7
|
+
---------
|
|
8
|
+
- https://www.plumed.org/doc-master/user-doc/html/COM
|
|
9
|
+
- https://www.plumed.org/doc-master/user-doc/html/CENTER
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import dataclasses
|
|
15
|
+
import typing as tp
|
|
16
|
+
|
|
17
|
+
import ase
|
|
18
|
+
|
|
19
|
+
from hillclimber.interfaces import AtomSelector
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclasses.dataclass
|
|
23
|
+
class VirtualAtom:
|
|
24
|
+
"""Creates virtual atom(s) from atom groups.
|
|
25
|
+
|
|
26
|
+
Creates ONE virtual atom for EACH group returned by the selector.
|
|
27
|
+
To select specific groups, use selector indexing before creating the VirtualAtom.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
atoms : AtomSelector | VirtualAtom
|
|
32
|
+
Source atoms or nested virtual atoms.
|
|
33
|
+
Use selector indexing to choose specific groups: VirtualAtom(selector[0], "com")
|
|
34
|
+
reduction : {"com", "cog", "first", "flatten"}, default="com"
|
|
35
|
+
How to reduce atoms to points:
|
|
36
|
+
|
|
37
|
+
- "com": Center of mass for each group (creates virtual site)
|
|
38
|
+
- "cog": Center of geometry for each group (creates virtual site)
|
|
39
|
+
- "first": First atom of each group (no virtual site needed)
|
|
40
|
+
- "flatten": Combine all groups into one, then apply reduction
|
|
41
|
+
label : str | None, default=None
|
|
42
|
+
Optional label for PLUMED virtual site commands.
|
|
43
|
+
|
|
44
|
+
Examples
|
|
45
|
+
--------
|
|
46
|
+
Create COM for each molecule:
|
|
47
|
+
|
|
48
|
+
>>> import hillclimber as hc
|
|
49
|
+
>>> # Two ethanols → two virtual atoms (COMs)
|
|
50
|
+
>>> selector = hc.SMARTSSelector("CCO")
|
|
51
|
+
>>> ethanols = hc.VirtualAtom(selector, reduction="com")
|
|
52
|
+
|
|
53
|
+
Select specific virtual atom using selector indexing:
|
|
54
|
+
|
|
55
|
+
>>> # Select first ethanol's COM
|
|
56
|
+
>>> ethanol_0 = hc.VirtualAtom(selector[0], "com")
|
|
57
|
+
|
|
58
|
+
Select multiple virtual atoms using selector slicing:
|
|
59
|
+
|
|
60
|
+
>>> # Select first two ethanols
|
|
61
|
+
>>> first_two = hc.VirtualAtom(selector[0:2], "com")
|
|
62
|
+
>>> # Select every other water
|
|
63
|
+
>>> water_sel = hc.SMARTSSelector("O")
|
|
64
|
+
>>> every_other = hc.VirtualAtom(water_sel[::2], "com")
|
|
65
|
+
|
|
66
|
+
Select specific virtual atoms using selector list indexing:
|
|
67
|
+
|
|
68
|
+
>>> # Select specific ethanols by indices
|
|
69
|
+
>>> selected = hc.VirtualAtom(selector[[0, 2, 4]], "com")
|
|
70
|
+
|
|
71
|
+
Flatten all groups into one virtual atom:
|
|
72
|
+
|
|
73
|
+
>>> # All water oxygens → one COM
|
|
74
|
+
>>> all_waters = hc.VirtualAtom(hc.SMARTSSelector("O"), reduction="flatten")
|
|
75
|
+
|
|
76
|
+
Nested virtual atoms (COM of COMs):
|
|
77
|
+
|
|
78
|
+
>>> # First: create COM for each water
|
|
79
|
+
>>> water_coms = hc.VirtualAtom(hc.SMARTSSelector("O"), reduction="com")
|
|
80
|
+
>>> # Then: create COM of all those COMs
|
|
81
|
+
>>> center = hc.VirtualAtom(water_coms, reduction="flatten")
|
|
82
|
+
|
|
83
|
+
Use in distance CVs:
|
|
84
|
+
|
|
85
|
+
>>> # Single distance (using selector indexing)
|
|
86
|
+
>>> ethanol_sel = hc.SMARTSSelector("CCO")
|
|
87
|
+
>>> water_sel = hc.SMARTSSelector("O")
|
|
88
|
+
>>> dist = hc.DistanceCV(
|
|
89
|
+
... x1=hc.VirtualAtom(ethanol_sel[0], "com"),
|
|
90
|
+
... x2=hc.VirtualAtom(water_sel[0], "com"),
|
|
91
|
+
... prefix="d"
|
|
92
|
+
... )
|
|
93
|
+
>>>
|
|
94
|
+
>>> # Multiple distances (first ethanol to all waters)
|
|
95
|
+
>>> dist = hc.DistanceCV(
|
|
96
|
+
... x1=hc.VirtualAtom(ethanol_sel[0], "com"),
|
|
97
|
+
... x2=hc.VirtualAtom(water_sel, "com"),
|
|
98
|
+
... prefix="d"
|
|
99
|
+
... )
|
|
100
|
+
>>>
|
|
101
|
+
>>> # Multiple distances (first two ethanols to first three waters)
|
|
102
|
+
>>> dist = hc.DistanceCV(
|
|
103
|
+
... x1=hc.VirtualAtom(ethanol_sel[0:2], "com"),
|
|
104
|
+
... x2=hc.VirtualAtom(water_sel[0:3], "com"),
|
|
105
|
+
... prefix="d"
|
|
106
|
+
... )
|
|
107
|
+
>>>
|
|
108
|
+
>>> # All pairwise distances
|
|
109
|
+
>>> dist = hc.DistanceCV(
|
|
110
|
+
... x1=hc.VirtualAtom(water_sel, "com"),
|
|
111
|
+
... x2=hc.VirtualAtom(water_sel, "com"),
|
|
112
|
+
... prefix="d"
|
|
113
|
+
... )
|
|
114
|
+
|
|
115
|
+
Resources
|
|
116
|
+
---------
|
|
117
|
+
- https://www.plumed.org/doc-master/user-doc/html/COM
|
|
118
|
+
- https://www.plumed.org/doc-master/user-doc/html/CENTER
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
atoms: AtomSelector | "VirtualAtom"
|
|
122
|
+
reduction: tp.Literal["com", "cog", "first", "flatten"] = "com"
|
|
123
|
+
label: str | None = None
|
|
124
|
+
|
|
125
|
+
def __add__(self, other: "VirtualAtom") -> "VirtualAtom":
|
|
126
|
+
"""Combine two VirtualAtoms.
|
|
127
|
+
|
|
128
|
+
Returns a new VirtualAtom that represents both sets of virtual sites.
|
|
129
|
+
The underlying selectors are combined using the selector's __add__ method.
|
|
130
|
+
|
|
131
|
+
Parameters
|
|
132
|
+
----------
|
|
133
|
+
other : VirtualAtom
|
|
134
|
+
Another VirtualAtom to combine with this one.
|
|
135
|
+
|
|
136
|
+
Returns
|
|
137
|
+
-------
|
|
138
|
+
VirtualAtom
|
|
139
|
+
New VirtualAtom with combined underlying selectors.
|
|
140
|
+
|
|
141
|
+
Raises
|
|
142
|
+
------
|
|
143
|
+
ValueError
|
|
144
|
+
If the two VirtualAtoms have different reduction strategies.
|
|
145
|
+
|
|
146
|
+
Examples
|
|
147
|
+
--------
|
|
148
|
+
>>> water_coms = hc.VirtualAtom(water_sel, "com")
|
|
149
|
+
>>> ethanol_coms = hc.VirtualAtom(ethanol_sel, "com")
|
|
150
|
+
>>> all_coms = water_coms + ethanol_coms
|
|
151
|
+
>>>
|
|
152
|
+
>>> # Combine with indexed selectors
|
|
153
|
+
>>> first_water = hc.VirtualAtom(water_sel[0], "com")
|
|
154
|
+
>>> first_ethanol = hc.VirtualAtom(ethanol_sel[0], "com")
|
|
155
|
+
>>> combined = first_water + first_ethanol # 2 COMs
|
|
156
|
+
"""
|
|
157
|
+
# Reduction must be the same
|
|
158
|
+
if self.reduction != other.reduction:
|
|
159
|
+
raise ValueError(
|
|
160
|
+
f"Cannot combine VirtualAtoms with different reductions: "
|
|
161
|
+
f"{self.reduction} vs {other.reduction}"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# Combine the underlying selectors
|
|
165
|
+
combined_atoms = self.atoms + other.atoms
|
|
166
|
+
|
|
167
|
+
return VirtualAtom(
|
|
168
|
+
atoms=combined_atoms,
|
|
169
|
+
reduction=self.reduction,
|
|
170
|
+
label=None # Reset label for combined VirtualAtom
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
def select(self, atoms: ase.Atoms) -> list[list[int]]:
|
|
174
|
+
"""Select atom groups for virtual atom creation.
|
|
175
|
+
|
|
176
|
+
Returns list of atom groups. Each group represents the atoms for
|
|
177
|
+
one virtual site.
|
|
178
|
+
|
|
179
|
+
Parameters
|
|
180
|
+
----------
|
|
181
|
+
atoms : ase.Atoms
|
|
182
|
+
The atomic structure to select from.
|
|
183
|
+
|
|
184
|
+
Returns
|
|
185
|
+
-------
|
|
186
|
+
list[list[int]]
|
|
187
|
+
Groups of atom indices. Each inner list is one group.
|
|
188
|
+
"""
|
|
189
|
+
# Get groups from source
|
|
190
|
+
if isinstance(self.atoms, VirtualAtom):
|
|
191
|
+
# Nested: inner VirtualAtom returns groups
|
|
192
|
+
groups = self.atoms.select(atoms)
|
|
193
|
+
else:
|
|
194
|
+
groups = self.atoms.select(atoms)
|
|
195
|
+
|
|
196
|
+
# Apply reduction strategy
|
|
197
|
+
if self.reduction == "flatten":
|
|
198
|
+
# Combine all groups into one
|
|
199
|
+
flat = [idx for group in groups for idx in group]
|
|
200
|
+
return [flat]
|
|
201
|
+
else:
|
|
202
|
+
# com, cog, first: keep groups separate
|
|
203
|
+
# Actual point creation happens in to_plumed()
|
|
204
|
+
return groups
|
|
205
|
+
|
|
206
|
+
def to_plumed(self, atoms: ase.Atoms) -> tuple[list[str], list[str]]:
|
|
207
|
+
"""Generate PLUMED virtual site commands.
|
|
208
|
+
|
|
209
|
+
Handles nested VirtualAtoms by first generating commands for the inner
|
|
210
|
+
VirtualAtom, then using those labels to create the outer virtual site.
|
|
211
|
+
|
|
212
|
+
Parameters
|
|
213
|
+
----------
|
|
214
|
+
atoms : ase.Atoms
|
|
215
|
+
The atomic structure to use for generating commands.
|
|
216
|
+
|
|
217
|
+
Returns
|
|
218
|
+
-------
|
|
219
|
+
labels : list[str]
|
|
220
|
+
Labels for the virtual sites/atoms created. These can be used
|
|
221
|
+
in subsequent PLUMED commands.
|
|
222
|
+
commands : list[str]
|
|
223
|
+
PLUMED command strings for creating virtual sites.
|
|
224
|
+
Empty if reduction="first" (no virtual sites needed).
|
|
225
|
+
|
|
226
|
+
Examples
|
|
227
|
+
--------
|
|
228
|
+
>>> va = hc.VirtualAtom(hc.SMARTSSelector("O"), reduction="com")
|
|
229
|
+
>>> labels, commands = va.to_plumed(atoms)
|
|
230
|
+
>>> print(labels)
|
|
231
|
+
['vsite_123_0', 'vsite_123_1']
|
|
232
|
+
>>> print(commands)
|
|
233
|
+
['vsite_123_0: COM ATOMS=1,2,3', 'vsite_123_1: COM ATOMS=4,5,6']
|
|
234
|
+
|
|
235
|
+
>>> # Nested VirtualAtom (COM of COMs)
|
|
236
|
+
>>> water_coms = hc.VirtualAtom(water_sel, "com")
|
|
237
|
+
>>> center = hc.VirtualAtom(water_coms, "com")
|
|
238
|
+
>>> labels, commands = center.to_plumed(atoms)
|
|
239
|
+
>>> # Commands will include both the individual COMs and the center COM
|
|
240
|
+
"""
|
|
241
|
+
# Check if this is a nested VirtualAtom
|
|
242
|
+
if isinstance(self.atoms, VirtualAtom):
|
|
243
|
+
# First, generate commands for the inner VirtualAtom
|
|
244
|
+
inner_labels, inner_commands = self.atoms.to_plumed(atoms)
|
|
245
|
+
|
|
246
|
+
# Now create virtual site using the inner labels
|
|
247
|
+
commands = list(inner_commands) # Copy inner commands
|
|
248
|
+
labels = []
|
|
249
|
+
|
|
250
|
+
if self.reduction == "first":
|
|
251
|
+
# Just use the first inner label
|
|
252
|
+
labels = [inner_labels[0]]
|
|
253
|
+
elif self.reduction == "flatten":
|
|
254
|
+
# Create COM/COG of all inner virtual sites
|
|
255
|
+
base_label = self.label or f"vsite_{id(self)}"
|
|
256
|
+
cmd_type = "COM" if self.reduction == "com" else "CENTER"
|
|
257
|
+
atom_list = ",".join(inner_labels)
|
|
258
|
+
commands.append(f"{base_label}: {cmd_type} ATOMS={atom_list}")
|
|
259
|
+
labels = [base_label]
|
|
260
|
+
elif self.reduction in ["com", "cog"]:
|
|
261
|
+
# For COM/COG with nested VirtualAtoms, we create a single
|
|
262
|
+
# virtual site from all inner labels (similar to flatten)
|
|
263
|
+
base_label = self.label or f"vsite_{id(self)}"
|
|
264
|
+
cmd_type = "COM" if self.reduction == "com" else "CENTER"
|
|
265
|
+
atom_list = ",".join(inner_labels)
|
|
266
|
+
commands.append(f"{base_label}: {cmd_type} ATOMS={atom_list}")
|
|
267
|
+
labels = [base_label]
|
|
268
|
+
else:
|
|
269
|
+
# This shouldn't typically happen, but handle it
|
|
270
|
+
# Just pass through the inner labels
|
|
271
|
+
labels = inner_labels
|
|
272
|
+
|
|
273
|
+
return labels, commands
|
|
274
|
+
|
|
275
|
+
# Non-nested case: regular selector
|
|
276
|
+
groups = self.select(atoms)
|
|
277
|
+
labels = []
|
|
278
|
+
commands = []
|
|
279
|
+
|
|
280
|
+
for i, group in enumerate(groups):
|
|
281
|
+
if self.reduction == "first" or (self.reduction == "flatten" and len(group) == 1):
|
|
282
|
+
# No virtual site needed, use atom index directly
|
|
283
|
+
labels.append(str(group[0] + 1))
|
|
284
|
+
else:
|
|
285
|
+
# Create virtual site (COM or CENTER)
|
|
286
|
+
base_label = self.label or f"vsite_{id(self)}"
|
|
287
|
+
label = base_label if len(groups) == 1 else f"{base_label}_{i}"
|
|
288
|
+
|
|
289
|
+
cmd_type = "COM" if self.reduction == "com" else "CENTER"
|
|
290
|
+
atom_list = ",".join(str(idx + 1) for idx in group)
|
|
291
|
+
commands.append(f"{label}: {cmd_type} ATOMS={atom_list}")
|
|
292
|
+
labels.append(label)
|
|
293
|
+
|
|
294
|
+
return labels, commands
|
|
295
|
+
|
|
296
|
+
def count(self, atoms: ase.Atoms) -> int:
|
|
297
|
+
"""Return number of virtual atoms this represents.
|
|
298
|
+
|
|
299
|
+
Parameters
|
|
300
|
+
----------
|
|
301
|
+
atoms : ase.Atoms
|
|
302
|
+
The atomic structure to count groups in.
|
|
303
|
+
|
|
304
|
+
Returns
|
|
305
|
+
-------
|
|
306
|
+
int
|
|
307
|
+
Number of virtual atoms (groups) this will create.
|
|
308
|
+
|
|
309
|
+
Examples
|
|
310
|
+
--------
|
|
311
|
+
>>> water_sel = hc.SMARTSSelector("O")
|
|
312
|
+
>>> va = hc.VirtualAtom(water_sel, reduction="com")
|
|
313
|
+
>>> va.count(atoms) # Number of water molecules
|
|
314
|
+
3
|
|
315
|
+
>>> # Use selector indexing to get single group
|
|
316
|
+
>>> va_single = hc.VirtualAtom(water_sel[0], reduction="com")
|
|
317
|
+
>>> va_single.count(atoms) # Single water - always 1
|
|
318
|
+
1
|
|
319
|
+
>>> va_flat = hc.VirtualAtom(water_sel, reduction="flatten")
|
|
320
|
+
>>> va_flat.count(atoms) # Flattened - always 1
|
|
321
|
+
1
|
|
322
|
+
>>> # Nested VirtualAtom
|
|
323
|
+
>>> water_coms = hc.VirtualAtom(water_sel, "com") # 3 COMs
|
|
324
|
+
>>> center = hc.VirtualAtom(water_coms, "com") # 1 COM of 3 COMs
|
|
325
|
+
>>> center.count(atoms)
|
|
326
|
+
1
|
|
327
|
+
"""
|
|
328
|
+
# For nested VirtualAtoms with com/cog/flatten reduction, we create a single virtual site
|
|
329
|
+
if isinstance(self.atoms, VirtualAtom) and self.reduction in ["com", "cog", "flatten"]:
|
|
330
|
+
return 1
|
|
331
|
+
# For nested VirtualAtoms with "first" reduction, pass through
|
|
332
|
+
elif isinstance(self.atoms, VirtualAtom) and self.reduction == "first":
|
|
333
|
+
return 1
|
|
334
|
+
# For non-nested or other cases, count the groups
|
|
335
|
+
return len(self.select(atoms))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hillclimber
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.0a3
|
|
4
4
|
Summary: Python interfaces for the plumed library with enhanced sampling.
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -124,6 +124,64 @@ with project:
|
|
|
124
124
|
project.build()
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
+
## Units
|
|
128
|
+
|
|
129
|
+
hillclimber uses **ASE units** throughout the package:
|
|
130
|
+
|
|
131
|
+
- **Distances**: Ångström (Å)
|
|
132
|
+
- **Energies**: electronvolt (eV)
|
|
133
|
+
- **Time**: femtoseconds (fs)
|
|
134
|
+
- **Temperature**: Kelvin (K)
|
|
135
|
+
- **Mass**: atomic mass units (amu)
|
|
136
|
+
|
|
137
|
+
### Unit Conversion with PLUMED
|
|
138
|
+
|
|
139
|
+
PLUMED internally uses different units (nm, kJ/mol, ps). hillclimber automatically handles the conversion by adding a `UNITS` line to the PLUMED input:
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
UNITS LENGTH=A TIME=0.001 ENERGY=96.48533288249877
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
This tells PLUMED to interpret all input parameters in ASE units:
|
|
146
|
+
- `LENGTH=A`: Distances are in Ångström
|
|
147
|
+
- `TIME=0.001`: Time values are in fs (1 fs = 0.001 ps)
|
|
148
|
+
- `ENERGY=96.485`: Energies are in eV (1 eV = 96.485 kJ/mol)
|
|
149
|
+
|
|
150
|
+
**All PLUMED parameters (HEIGHT, BARRIER, KAPPA, SIGMA, etc.) should be specified in ASE units (eV, Å, fs).** The UNITS line ensures PLUMED interprets them correctly.
|
|
151
|
+
|
|
152
|
+
### Example with Units
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
config = hc.MetaDynamicsConfig(
|
|
156
|
+
height=0.5, # eV - Gaussian hill height
|
|
157
|
+
pace=150, # MD steps - deposition frequency
|
|
158
|
+
biasfactor=10.0, # Dimensionless - well-tempered factor
|
|
159
|
+
temp=300.0 # Kelvin - system temperature
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
bias = hc.MetadBias(
|
|
163
|
+
cv=distance_cv,
|
|
164
|
+
sigma=0.1, # Å - Gaussian width for distance CV
|
|
165
|
+
grid_min=0.0, # Å - minimum grid value
|
|
166
|
+
grid_max=5.0, # Å - maximum grid value
|
|
167
|
+
grid_bin=100 # Number of bins
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# Restraint example
|
|
171
|
+
restraint = hc.RestraintBias(
|
|
172
|
+
cv=distance_cv,
|
|
173
|
+
kappa=200.0, # eV/Ų - force constant
|
|
174
|
+
at=2.5 # Å - restraint center
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
metad_model = hc.MetaDynamicsModel(
|
|
178
|
+
config=config,
|
|
179
|
+
bias_cvs=[bias],
|
|
180
|
+
actions=[restraint],
|
|
181
|
+
timestep=0.5 # fs - MD timestep
|
|
182
|
+
)
|
|
183
|
+
```
|
|
184
|
+
|
|
127
185
|
## Collective Variables
|
|
128
186
|
|
|
129
187
|
hillclimber supports multiple types of collective variables:
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
hillclimber/__init__.py,sha256=OoxVZ1pPvRVHY1VVShDBaEGIF06O5fDFxCSGUNGW3m4,1041
|
|
2
|
+
hillclimber/actions.py,sha256=7s78RWP-YnBbbxZyA65OwnyL60v8LnKHyNalui1ypAo,1514
|
|
3
|
+
hillclimber/analysis.py,sha256=2KrlSVRNdajZBFCm8AZBjTixTeZczbu5ya4laLXEZP0,20674
|
|
4
|
+
hillclimber/biases.py,sha256=TbEM19NUOuaTUchzEGNIM8M4TDmtlE5Ss8e9VBbHr5s,9448
|
|
5
|
+
hillclimber/calc.py,sha256=dqanaBF6BJwP6lHQqFqEIng-3bTN_DcddRV-gceboKs,665
|
|
6
|
+
hillclimber/cvs.py,sha256=AWC7Z3h0CyAO7_wq-WdLhNi3spH456Blik60_M99MDA,39934
|
|
7
|
+
hillclimber/interfaces.py,sha256=H4HKN1HldhNJeooqtS-HpJLrCFqpMPr80aPER4SKiao,3807
|
|
8
|
+
hillclimber/metadynamics.py,sha256=1Kn5VhlrGlcVY9g41dNCEbJX-7j5ZEStcM8UGgTkCfI,12636
|
|
9
|
+
hillclimber/nodes.py,sha256=XL9uEXd2HdW2mlbwriG_fMCkZAaz4uZBOI5edO42YDA,145
|
|
10
|
+
hillclimber/opes.py,sha256=F_1daa9xB-wsNyow6sIfxJG2nyOSyCc-vVRuP5ADWdE,13310
|
|
11
|
+
hillclimber/selectors.py,sha256=VoWMvTnKU9vr0dphqxGk1OdhrabbDkzq4GQkeprd6RQ,7931
|
|
12
|
+
hillclimber/virtual_atoms.py,sha256=GVXCJZlbx1cY_ST2G5NHQsGpdMkBLUz04aFm-cD--OA,12270
|
|
13
|
+
hillclimber-0.1.0a3.dist-info/METADATA,sha256=UOw07yd2AJiq8CY2a3myuqS9Udy8y_1nb6gMcHNd0QA,12980
|
|
14
|
+
hillclimber-0.1.0a3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
15
|
+
hillclimber-0.1.0a3.dist-info/entry_points.txt,sha256=RsCL3TDKfieatIWP9JHjmTzMtgWERqwpuuuDPdQ4t5g,124
|
|
16
|
+
hillclimber-0.1.0a3.dist-info/licenses/LICENSE,sha256=FKf4VPZYbuyRVMVSrl6HO48bnw6ih8Uur5y-h_MJAcA,13576
|
|
17
|
+
hillclimber-0.1.0a3.dist-info/RECORD,,
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
hillclimber/__init__.py,sha256=M0w80B5FDzRY8-JIH_oD-RWJf5TKGRRaTfEvaWBYvtU,566
|
|
2
|
-
hillclimber/actions.py,sha256=A4pFpeFnluQT8VpmcP_3mpA6Xc58fjhjdcIJzv5fV90,764
|
|
3
|
-
hillclimber/calc.py,sha256=dqanaBF6BJwP6lHQqFqEIng-3bTN_DcddRV-gceboKs,665
|
|
4
|
-
hillclimber/cvs.py,sha256=7rWKtlbYZX2jQNee5n84rn_eIh4JuIQbUjyK52k_NR8,24844
|
|
5
|
-
hillclimber/interfaces.py,sha256=X4LItHGx7h74WzG8ESpZ7htMr_-BEkBzbJtPIRg0Upw,2471
|
|
6
|
-
hillclimber/metadynamics.py,sha256=qn_41I-c4TilWH_aAMzboTo_SI3hsRNjm0S6OAoVKyI,8795
|
|
7
|
-
hillclimber/nodes.py,sha256=XL9uEXd2HdW2mlbwriG_fMCkZAaz4uZBOI5edO42YDA,145
|
|
8
|
-
hillclimber/selectors.py,sha256=x9Dyqpl9KtZkLWuMsN20AGcaUv6_bG_vSLPNKaB2iyk,3172
|
|
9
|
-
hillclimber-0.1.0a1.dist-info/METADATA,sha256=P28Sx55LnPOi2c5LEnvzXNdlPTtkFpEGrSZDqhvyVjI,11254
|
|
10
|
-
hillclimber-0.1.0a1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
11
|
-
hillclimber-0.1.0a1.dist-info/entry_points.txt,sha256=RsCL3TDKfieatIWP9JHjmTzMtgWERqwpuuuDPdQ4t5g,124
|
|
12
|
-
hillclimber-0.1.0a1.dist-info/licenses/LICENSE,sha256=FKf4VPZYbuyRVMVSrl6HO48bnw6ih8Uur5y-h_MJAcA,13576
|
|
13
|
-
hillclimber-0.1.0a1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|