Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5d8afe2
Create criteria and termination data structure
chrisjonesBSU Dec 19, 2025
5fa3d1d
Merge branch 'develop' of github.com:mosdef-hub/mbuild into criteria
chrisjonesBSU Dec 19, 2025
6ec08c3
Change start_time attr
chrisjonesBSU Dec 19, 2025
8023518
Rename variables and classes for improved clarity
Dec 22, 2025
6ed931e
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 22, 2025
ba391a0
Use Termination data structure in random walks, update tests
Dec 22, 2025
0e1bbe4
Merge branch 'criteria' of github.com:chrisjonesBSU/mbuild into criteria
Dec 22, 2025
67586f4
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 22, 2025
04e1a23
fix typo
Dec 22, 2025
bd73ab6
Use NumAttempts in tests
Dec 22, 2025
5da3ff2
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 22, 2025
d81f56c
fix some other tests
Dec 22, 2025
3201172
refactor random walk to build up coordinate array in chunks, then tri…
Dec 23, 2025
fb7ea10
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 23, 2025
b21b4d0
Fix extend chunk logic, add more tests
Dec 23, 2025
a3a4248
Move termination tests to its own file
Dec 23, 2025
55a57c5
new test for num attempts termination
Dec 23, 2025
6dfc34b
Use logging instead of raising error on failed walk
Dec 23, 2025
4c36443
use warning level for failed RW
Dec 23, 2025
6028701
remove max_attempts param, update tests
Dec 23, 2025
bd81175
Make Re and Rg termination check more robust, move termination tests …
chrisjonesBSU Jan 5, 2026
1ea00bd
Add doc strings, change parameter name for required_to_end to is_target
chrisjonesBSU Jan 6, 2026
9bd074f
Update units and docs for RadiusOfGyration to use Rg^2
chrisjonesBSU Jan 6, 2026
943f866
add one more unit test
chrisjonesBSU Jan 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions mbuild/path/bias.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class TargetType(Bias):
def __init__(self, target_type, weight, r_cut):
self.target_type = target_type
self.r_cut = r_cut
super(TargetType, self).__init__(weight=weight)
super().__init__(weight=weight)

def __call__(self, candidates):
types = np.array(
Expand All @@ -91,7 +91,7 @@ class AvoidType(Bias):
def __init__(self, avoid_type, weight, r_cut):
self.avoid_type = avoid_type
self.r_cut = r_cut
super(AvoidType, self).__init__(weight=weight)
super().__init__(weight=weight)

def __call__(self, candidates):
types = np.array(
Expand Down Expand Up @@ -121,7 +121,7 @@ def __init__(self, direction, weight):
if norm < 1e-8:
raise ValueError("Direction vector must be non-zero.")
self.direction = direction / norm
super(TargetDirection, self).__init__(weight=weight)
super().__init__(weight=weight)

def __call__(self, candidates):
last_step_pos = self.path.coordinates[self.path.count]
Expand All @@ -144,7 +144,7 @@ class AvoidDirection(Bias):

def __init__(self, direction, weight):
self.direction = direction
super(AvoidDirection, self).__init__(weight=weight)
super().__init__(weight=weight)

def __call__(self, candidates):
last_step_pos = self.path.coordinates[self.path.count]
Expand All @@ -166,7 +166,7 @@ class TargetEdge(Bias):
"""Bias next-moves so that ones moving towards a surface are more likely to be accepted."""

def __init__(self, weight):
super(TargetEdge, self).__init__(weight=weight)
super().__init__(weight=weight)

def __call__(self):
raise NotImplementedError(
Expand All @@ -178,7 +178,7 @@ class AvoidEdge(Bias):
"""Bias next-moves so that ones away from a surface are more likely to be accepted."""

def __init__(self, weight):
super(AvoidEdge, self).__init__(weight=weight)
super().__init__(weight=weight)

def __call__(self):
raise NotImplementedError(
Expand All @@ -191,7 +191,7 @@ class TargetPath(Bias):

def __init__(self, target_path, weight):
self.target_path = target_path
super(TargetPath, self).__init__(weight=weight)
super().__init__(weight=weight)

def __call__(self):
raise NotImplementedError(
Expand Down
82 changes: 50 additions & 32 deletions mbuild/path/path.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""Classes to generate intra-molecular paths and configurations."""

import logging
import math
import time
from abc import abstractmethod
from copy import deepcopy

Expand All @@ -12,6 +14,8 @@
from mbuild.path.path_utils import check_path, random_coordinate
from mbuild.utils.volumes import CuboidConstraint, CylinderConstraint

logger = logging.getLogger(__name__)


class Path:
"""Creates a path from a given set of coordinates and a bond graph.
Expand Down Expand Up @@ -87,8 +91,8 @@ def from_compound(cls, compound):
return path

def _extend_coordinates(self, N):
zeros = np.zeros((N, 3))
new_array = np.concatenate(self.coordinates, zeros)
zeros = np.zeros((N, 3), dtype=self.coordinates.dtype)
new_array = np.concatenate([self.coordinates, zeros])
self.coordinates = new_array
self.N += N

Expand Down Expand Up @@ -196,11 +200,11 @@ def _path_history(self):
class HardSphereRandomWalk(Path):
def __init__(
self,
N,
bond_length,
radius,
min_angle,
max_angle,
termination,
bead_name="_A",
volume_constraint=None,
bias=None,
Expand All @@ -209,10 +213,10 @@ def __init__(
attach_paths=False,
initial_point=None,
include_compound=None,
max_attempts=1e5,
seed=42,
trial_batch_size=20,
tolerance=1e-5,
chunk_size=512,
):
"""Generates coordinates from a self avoiding random walk using
fixed bond lengths, hard spheres, and minimum and maximum angles
Expand Down Expand Up @@ -256,9 +260,6 @@ def __init__(
The number of trial moves to attempt in parallel for each step.
Using larger values can improve success rates for more dense
random walks.
max_attempts : int, default = 1e5
The maximum number of trial moves to attempt before quiting.
for the random walk.
tolerance : float, default = 1e-4
Tolerance used for rounding and checking for overlaps.

Expand Down Expand Up @@ -290,12 +291,12 @@ def __init__(
self.volume_constraint = volume_constraint
self.tolerance = tolerance
self.trial_batch_size = int(trial_batch_size)
self.max_attempts = int(max_attempts)
self.attempts = 0
self.start_from_path_index = start_from_path_index
self.start_from_path = start_from_path
self.attach_paths = attach_paths
self._particle_pairs = {}
self.chunk_size = chunk_size

# Create RNG state.
self.rng = np.random.default_rng(seed)
Expand All @@ -317,23 +318,28 @@ def __init__(
self.pbc = (None, None, None)
self.box_lengths = (None, None, None)

# Set up and attach path to bias
# Set up bias conditions
self.bias = bias
if self.bias:
self.bias._attach_path(self)

# Set up termination conditions
if termination is None:
raise RuntimeError("No terminaiton conditions have been passed in.")
self.termination = termination
self.termination._attach_path(self)

# This random walk is including a previous path
# Inherit coordinates, bond graph, and count from previous path
if start_from_path:
coordinates = np.concatenate(
(
start_from_path.get_coordinates().astype(np.float32),
np.zeros((N, 3), dtype=np.float32),
np.zeros((self.chunk_size, 3), dtype=np.float32),
),
axis=0,
)
self.count = len(start_from_path.coordinates)
N = None
bond_graph = deepcopy(start_from_path.bond_graph)
if start_from_path_index is not None and start_from_path_index < 0:
self.start_from_path_index = self.count + start_from_path_index
Expand All @@ -342,8 +348,7 @@ def __init__(
# Set default values for coordinates, bond graph and count
else:
bond_graph = nx.Graph()
coordinates = np.zeros((N, 3), dtype=np.float32)
N = None
coordinates = np.zeros((self.chunk_size, 3), dtype=np.float32)
self.count = 0
self.start_index = 0

Expand All @@ -355,11 +360,16 @@ def __init__(
self.next_step = random_coordinate
self.check_path = check_path

super(HardSphereRandomWalk, self).__init__(
coordinates=coordinates, N=N, bond_graph=bond_graph, bead_name=bead_name
# Needed for WallTime stop criterion
self.start_time = None

super().__init__(
coordinates=coordinates, bond_graph=bond_graph, bead_name=bead_name
)

def generate(self):
# start time is needed for path.termination.WallTime
self.start_time = time.time()
# Set the first coordinate using method _initial_points()
initial_xyz = self._initial_points()
self.coordinates[self.count] = initial_xyz
Expand Down Expand Up @@ -424,13 +434,10 @@ def generate(self):
next_point_found = True
# 2nd point failed, continue while loop
self.attempts += 1

if self.attempts == self.max_attempts and self.count < self.N:
raise RuntimeError(
"The maximum number attempts allowed have passed, and only ",
f"{self.count - self._init_count} sucsessful attempts were completed.",
"Try changing the parameters or seed and running again.",
)
# TODO Use termination here
if self.termination.is_met() and not self.termination.sucsessful:
logger.warning("Random walk not successful.")
logger.warning(self.termination.summarize())

# Starting random walk from a previous set of coordinates (another path)
# First point was accepted in self._initial_point with these conditions
Expand All @@ -441,7 +448,8 @@ def generate(self):
self.attempts += 1

#### Initial conditions set, now start RW ####
while self.count < self.N - 1:
walk_finished = False
while not walk_finished:
# Generate a batch of angles and vectors to create a set of candidate next coordinates
batch_angles, batch_vectors = self._generate_random_trials()
candidates = self.next_step(
Expand Down Expand Up @@ -496,13 +504,24 @@ def generate(self):
break
# candidates didn't produce a single valid next point
self.attempts += 1
# Check if we've filled up the current chunk size, if so, extend.
if (self.count - self._init_count + 1) % self.chunk_size == 0:
self._extend_coordinates(N=self.chunk_size)
walk_finished = self.termination.is_met()

# Trim the final coordinates, removing any used in last chunk
self.coordinates = self.coordinates[: self.count + 1]
if self.termination.success:
logger.info("Random walk successful.")
else:
logger.warning("Random walk not successful.")
logger.warning(self.termination.summarize())

if self.attempts == self.max_attempts and self.count < self.N:
raise RuntimeError(
"The maximum number attempts allowed have passed, and only ",
f"{self.count - self._init_count} sucsessful attempts were completed.",
"Try changing the parameters or seed and running again.",
)
def current_walk_coordinates(self):
"""Return the coordinates from the current random walk only.
This ignores any coordinates from previous paths used to start this path.
"""
return self.coordinates[self._init_count : self.count + 1]

def _generate_random_trials(self):
"""Generate a batch of random angles and vectors using the RNG state."""
Expand Down Expand Up @@ -531,7 +550,7 @@ def _initial_points(self):
self.include_compound,
]
):
max_dist = (self.N * self.radius) - self.radius
max_dist = (5 * self.radius) - self.radius
xyz = self.rng.uniform(low=-max_dist / 2, high=max_dist / 2, size=3)
return xyz

Expand Down Expand Up @@ -602,8 +621,7 @@ def _initial_points(self):
):
return xyz
self.attempts += 1

if self.attempts == self.max_attempts and self.count < self.N:
if self.termination.is_met():
raise RuntimeError(
"The maximum number attempts allowed have passed, and only ",
f"{self.count - self._init_count} sucsessful attempts were completed.",
Expand Down
Loading
Loading