Custom Random Walk
Click to focus. When focused, press m to open the options menu.
Refresh the page to restart the simulation.
Overview
hoomd-rs allows you to customize your simulation model at all levels. This tutorial you how to implement a custom boundary condition and a custom trial move. It sets a closed boundary condition that confines bodies inside a circle, and applies trial moves that take discrete steps left, right, down, or up.
- Objective: Demonstrate the customization of a MC simulation.
- File:
hoomd-rs/examples/mc-tutorial/custom-random-walk.rs - Run (interactively):
cargo run --release --features "bevy" --example custom-random-walk - Run (in batch mode):
cargo run --release --example custom-random-walk
Use Declarations
use rand::{Rng, seq::IndexedRandom};
use std::iter;
use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};
/// Closed circular boundary condition.
struct Circle {
radius: f64,
}
impl IsPointInside<Cartesian<2>> for Circle {
fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
point.distance(&[0.0, 0.0].into()) < self.radius
}
}
/// Take fixed steps left, right, down, or up.
struct Discrete;
impl LocalTrial<Point<Cartesian<2>>> for Discrete {
fn propose<R: Rng>(
&self,
rng: &mut R,
body_properties: Point<Cartesian<2>>,
) -> Point<Cartesian<2>> {
let steps = [
[0.0, -1.0].into(),
[0.0, 1.0].into(),
[-1.0, 0.0].into(),
[1.0, 0.0].into(),
];
let mut trial = body_properties;
trial.position += *steps
.choose(rng)
.expect("steps should have at least 1 element");
trial
}
}
// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
/// Positions of all the bodies in the simulation.
microstate: Microstate<
Point<Cartesian<2>>,
Point<Cartesian<2>>,
AllPairs<SiteKey>,
Closed<Circle>,
>,
/// How sites interact with other sites and fields.
hamiltonian: Zero,
/// Trial moves to apply.
translate_sweep: Sweep<Discrete>,
/// Temperature set point.
macrostate: Isothermal,
}
impl CustomRandomWalk {
/// Construct a new random walk simulation.
fn new() -> anyhow::Result<CustomRandomWalk> {
let n = 1000;
let radius = 50.0;
let macrostate = Isothermal { temperature: 1.0 };
let circle = Circle { radius };
let microstate = Microstate::builder()
.boundary(Closed(circle))
.bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
.try_build()?;
let translate_sweep = Sweep(Discrete);
let hamiltonian = Zero;
Ok(CustomRandomWalk {
microstate,
hamiltonian,
translate_sweep,
macrostate,
})
}
}
impl Simulation for CustomRandomWalk {
/// Advance the simulation forward one step.
fn advance(&mut self) -> anyhow::Result<()> {
self.translate_sweep.apply(
&mut self.microstate,
&self.hamiltonian,
&self.macrostate,
);
self.microstate.increment_step();
Ok(())
}
/// Get the current simulation step.
fn step(&self) -> u64 {
self.microstate.step()
}
}
// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
let mut simulation = CustomRandomWalk::new()?;
for _ in 0..1_000_000 {
simulation.advance()?;
}
Ok(())
}
#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;
hoomd-rs uses rand crate to generate pseudorandom numbers.
Custom Boundary Condition
The Random Walk tutorial used the default open boundary condition. This tutorial shows how you can create a custom closed boundary condition.
Define the Circle Type
First, define a type that describes the boundary. In this case, the boundary is a
Circle that has a radius:
use rand::{Rng, seq::IndexedRandom};
use std::iter;
use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};
/// Closed circular boundary condition.
struct Circle {
radius: f64,
}
impl IsPointInside<Cartesian<2>> for Circle {
fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
point.distance(&[0.0, 0.0].into()) < self.radius
}
}
/// Take fixed steps left, right, down, or up.
struct Discrete;
impl LocalTrial<Point<Cartesian<2>>> for Discrete {
fn propose<R: Rng>(
&self,
rng: &mut R,
body_properties: Point<Cartesian<2>>,
) -> Point<Cartesian<2>> {
let steps = [
[0.0, -1.0].into(),
[0.0, 1.0].into(),
[-1.0, 0.0].into(),
[1.0, 0.0].into(),
];
let mut trial = body_properties;
trial.position += *steps
.choose(rng)
.expect("steps should have at least 1 element");
trial
}
}
// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
/// Positions of all the bodies in the simulation.
microstate: Microstate<
Point<Cartesian<2>>,
Point<Cartesian<2>>,
AllPairs<SiteKey>,
Closed<Circle>,
>,
/// How sites interact with other sites and fields.
hamiltonian: Zero,
/// Trial moves to apply.
translate_sweep: Sweep<Discrete>,
/// Temperature set point.
macrostate: Isothermal,
}
impl CustomRandomWalk {
/// Construct a new random walk simulation.
fn new() -> anyhow::Result<CustomRandomWalk> {
let n = 1000;
let radius = 50.0;
let macrostate = Isothermal { temperature: 1.0 };
let circle = Circle { radius };
let microstate = Microstate::builder()
.boundary(Closed(circle))
.bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
.try_build()?;
let translate_sweep = Sweep(Discrete);
let hamiltonian = Zero;
Ok(CustomRandomWalk {
microstate,
hamiltonian,
translate_sweep,
macrostate,
})
}
}
impl Simulation for CustomRandomWalk {
/// Advance the simulation forward one step.
fn advance(&mut self) -> anyhow::Result<()> {
self.translate_sweep.apply(
&mut self.microstate,
&self.hamiltonian,
&self.macrostate,
);
self.microstate.increment_step();
Ok(())
}
/// Get the current simulation step.
fn step(&self) -> u64 {
self.microstate.step()
}
}
// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
let mut simulation = CustomRandomWalk::new()?;
for _ in 0..1_000_000 {
simulation.advance()?;
}
Ok(())
}
#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;
Implement IsPointInside
Then, implement the IsPointInside trait for Circle. The only required method
is is_point_inside() which should return true for points inside the boundary
and false for points outside the boundary:
use rand::{Rng, seq::IndexedRandom};
use std::iter;
use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};
/// Closed circular boundary condition.
struct Circle {
radius: f64,
}
impl IsPointInside<Cartesian<2>> for Circle {
fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
point.distance(&[0.0, 0.0].into()) < self.radius
}
}
/// Take fixed steps left, right, down, or up.
struct Discrete;
impl LocalTrial<Point<Cartesian<2>>> for Discrete {
fn propose<R: Rng>(
&self,
rng: &mut R,
body_properties: Point<Cartesian<2>>,
) -> Point<Cartesian<2>> {
let steps = [
[0.0, -1.0].into(),
[0.0, 1.0].into(),
[-1.0, 0.0].into(),
[1.0, 0.0].into(),
];
let mut trial = body_properties;
trial.position += *steps
.choose(rng)
.expect("steps should have at least 1 element");
trial
}
}
// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
/// Positions of all the bodies in the simulation.
microstate: Microstate<
Point<Cartesian<2>>,
Point<Cartesian<2>>,
AllPairs<SiteKey>,
Closed<Circle>,
>,
/// How sites interact with other sites and fields.
hamiltonian: Zero,
/// Trial moves to apply.
translate_sweep: Sweep<Discrete>,
/// Temperature set point.
macrostate: Isothermal,
}
impl CustomRandomWalk {
/// Construct a new random walk simulation.
fn new() -> anyhow::Result<CustomRandomWalk> {
let n = 1000;
let radius = 50.0;
let macrostate = Isothermal { temperature: 1.0 };
let circle = Circle { radius };
let microstate = Microstate::builder()
.boundary(Closed(circle))
.bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
.try_build()?;
let translate_sweep = Sweep(Discrete);
let hamiltonian = Zero;
Ok(CustomRandomWalk {
microstate,
hamiltonian,
translate_sweep,
macrostate,
})
}
}
impl Simulation for CustomRandomWalk {
/// Advance the simulation forward one step.
fn advance(&mut self) -> anyhow::Result<()> {
self.translate_sweep.apply(
&mut self.microstate,
&self.hamiltonian,
&self.macrostate,
);
self.microstate.increment_step();
Ok(())
}
/// Get the current simulation step.
fn step(&self) -> u64 {
self.microstate.step()
}
}
// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
let mut simulation = CustomRandomWalk::new()?;
for _ in 0..1_000_000 {
simulation.advance()?;
}
Ok(())
}
#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;
This implementation of IsPointInside is only for the Cartesian<2> vector
type (V in the IsPointInside definition). You can read The Rust Programming
Language to learn more about generic types and traits.
Tip
Many shapes in
hoomd_geometryimplementIsPointInsideand can be used for closed boundary conditions. This tutorial creates a newCircletype to teach you about customization, buthoomd_geometry::shape::Circlewould work just as well.
Custom Trial Move
The Random Walk tutorial moved points by a random distance (up to a maximum) and in a random direction. Look up “random walk” in a text book and you find will a model that makes random hops on a square lattice. You can implement that in hoomd-rs with a custom trial move.
Define the Discrete Type
Similar to the custom boundary, you need to implement the trait
LocalTrial for a type. Here is the type:
use rand::{Rng, seq::IndexedRandom};
use std::iter;
use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};
/// Closed circular boundary condition.
struct Circle {
radius: f64,
}
impl IsPointInside<Cartesian<2>> for Circle {
fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
point.distance(&[0.0, 0.0].into()) < self.radius
}
}
/// Take fixed steps left, right, down, or up.
struct Discrete;
impl LocalTrial<Point<Cartesian<2>>> for Discrete {
fn propose<R: Rng>(
&self,
rng: &mut R,
body_properties: Point<Cartesian<2>>,
) -> Point<Cartesian<2>> {
let steps = [
[0.0, -1.0].into(),
[0.0, 1.0].into(),
[-1.0, 0.0].into(),
[1.0, 0.0].into(),
];
let mut trial = body_properties;
trial.position += *steps
.choose(rng)
.expect("steps should have at least 1 element");
trial
}
}
// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
/// Positions of all the bodies in the simulation.
microstate: Microstate<
Point<Cartesian<2>>,
Point<Cartesian<2>>,
AllPairs<SiteKey>,
Closed<Circle>,
>,
/// How sites interact with other sites and fields.
hamiltonian: Zero,
/// Trial moves to apply.
translate_sweep: Sweep<Discrete>,
/// Temperature set point.
macrostate: Isothermal,
}
impl CustomRandomWalk {
/// Construct a new random walk simulation.
fn new() -> anyhow::Result<CustomRandomWalk> {
let n = 1000;
let radius = 50.0;
let macrostate = Isothermal { temperature: 1.0 };
let circle = Circle { radius };
let microstate = Microstate::builder()
.boundary(Closed(circle))
.bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
.try_build()?;
let translate_sweep = Sweep(Discrete);
let hamiltonian = Zero;
Ok(CustomRandomWalk {
microstate,
hamiltonian,
translate_sweep,
macrostate,
})
}
}
impl Simulation for CustomRandomWalk {
/// Advance the simulation forward one step.
fn advance(&mut self) -> anyhow::Result<()> {
self.translate_sweep.apply(
&mut self.microstate,
&self.hamiltonian,
&self.macrostate,
);
self.microstate.increment_step();
Ok(())
}
/// Get the current simulation step.
fn step(&self) -> u64 {
self.microstate.step()
}
}
// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
let mut simulation = CustomRandomWalk::new()?;
for _ in 0..1_000_000 {
simulation.advance()?;
}
Ok(())
}
#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;
Discrete trials always take one step in one direction. Therefore, the
Discrete struct needs no fields.
Implement LocalTrial
LocalTrial has one method, propose():
use rand::{Rng, seq::IndexedRandom};
use std::iter;
use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};
/// Closed circular boundary condition.
struct Circle {
radius: f64,
}
impl IsPointInside<Cartesian<2>> for Circle {
fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
point.distance(&[0.0, 0.0].into()) < self.radius
}
}
/// Take fixed steps left, right, down, or up.
struct Discrete;
impl LocalTrial<Point<Cartesian<2>>> for Discrete {
fn propose<R: Rng>(
&self,
rng: &mut R,
body_properties: Point<Cartesian<2>>,
) -> Point<Cartesian<2>> {
let steps = [
[0.0, -1.0].into(),
[0.0, 1.0].into(),
[-1.0, 0.0].into(),
[1.0, 0.0].into(),
];
let mut trial = body_properties;
trial.position += *steps
.choose(rng)
.expect("steps should have at least 1 element");
trial
}
}
// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
/// Positions of all the bodies in the simulation.
microstate: Microstate<
Point<Cartesian<2>>,
Point<Cartesian<2>>,
AllPairs<SiteKey>,
Closed<Circle>,
>,
/// How sites interact with other sites and fields.
hamiltonian: Zero,
/// Trial moves to apply.
translate_sweep: Sweep<Discrete>,
/// Temperature set point.
macrostate: Isothermal,
}
impl CustomRandomWalk {
/// Construct a new random walk simulation.
fn new() -> anyhow::Result<CustomRandomWalk> {
let n = 1000;
let radius = 50.0;
let macrostate = Isothermal { temperature: 1.0 };
let circle = Circle { radius };
let microstate = Microstate::builder()
.boundary(Closed(circle))
.bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
.try_build()?;
let translate_sweep = Sweep(Discrete);
let hamiltonian = Zero;
Ok(CustomRandomWalk {
microstate,
hamiltonian,
translate_sweep,
macrostate,
})
}
}
impl Simulation for CustomRandomWalk {
/// Advance the simulation forward one step.
fn advance(&mut self) -> anyhow::Result<()> {
self.translate_sweep.apply(
&mut self.microstate,
&self.hamiltonian,
&self.macrostate,
);
self.microstate.increment_step();
Ok(())
}
/// Get the current simulation step.
fn step(&self) -> u64 {
self.microstate.step()
}
}
// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
let mut simulation = CustomRandomWalk::new()?;
for _ in 0..1_000_000 {
simulation.advance()?;
}
Ok(())
}
#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;
propose() takes in the properties (body_properties) of a body in the current
microstate of the system. It returns the trial body properties randomly
generated using the given rng. In this tutorial, the only body property is
position.
Enumerate Possible Steps
First, place the possible moves (down, up, left, right) in an array:
use rand::{Rng, seq::IndexedRandom};
use std::iter;
use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};
/// Closed circular boundary condition.
struct Circle {
radius: f64,
}
impl IsPointInside<Cartesian<2>> for Circle {
fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
point.distance(&[0.0, 0.0].into()) < self.radius
}
}
/// Take fixed steps left, right, down, or up.
struct Discrete;
impl LocalTrial<Point<Cartesian<2>>> for Discrete {
fn propose<R: Rng>(
&self,
rng: &mut R,
body_properties: Point<Cartesian<2>>,
) -> Point<Cartesian<2>> {
let steps = [
[0.0, -1.0].into(),
[0.0, 1.0].into(),
[-1.0, 0.0].into(),
[1.0, 0.0].into(),
];
let mut trial = body_properties;
trial.position += *steps
.choose(rng)
.expect("steps should have at least 1 element");
trial
}
}
// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
/// Positions of all the bodies in the simulation.
microstate: Microstate<
Point<Cartesian<2>>,
Point<Cartesian<2>>,
AllPairs<SiteKey>,
Closed<Circle>,
>,
/// How sites interact with other sites and fields.
hamiltonian: Zero,
/// Trial moves to apply.
translate_sweep: Sweep<Discrete>,
/// Temperature set point.
macrostate: Isothermal,
}
impl CustomRandomWalk {
/// Construct a new random walk simulation.
fn new() -> anyhow::Result<CustomRandomWalk> {
let n = 1000;
let radius = 50.0;
let macrostate = Isothermal { temperature: 1.0 };
let circle = Circle { radius };
let microstate = Microstate::builder()
.boundary(Closed(circle))
.bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
.try_build()?;
let translate_sweep = Sweep(Discrete);
let hamiltonian = Zero;
Ok(CustomRandomWalk {
microstate,
hamiltonian,
translate_sweep,
macrostate,
})
}
}
impl Simulation for CustomRandomWalk {
/// Advance the simulation forward one step.
fn advance(&mut self) -> anyhow::Result<()> {
self.translate_sweep.apply(
&mut self.microstate,
&self.hamiltonian,
&self.macrostate,
);
self.microstate.increment_step();
Ok(())
}
/// Get the current simulation step.
fn step(&self) -> u64 {
self.microstate.step()
}
}
// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
let mut simulation = CustomRandomWalk::new()?;
for _ in 0..1_000_000 {
simulation.advance()?;
}
Ok(())
}
#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;
Randomly Select a Step
Then choose one of the steps randomly and add it to the body’s position:
use rand::{Rng, seq::IndexedRandom};
use std::iter;
use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};
/// Closed circular boundary condition.
struct Circle {
radius: f64,
}
impl IsPointInside<Cartesian<2>> for Circle {
fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
point.distance(&[0.0, 0.0].into()) < self.radius
}
}
/// Take fixed steps left, right, down, or up.
struct Discrete;
impl LocalTrial<Point<Cartesian<2>>> for Discrete {
fn propose<R: Rng>(
&self,
rng: &mut R,
body_properties: Point<Cartesian<2>>,
) -> Point<Cartesian<2>> {
let steps = [
[0.0, -1.0].into(),
[0.0, 1.0].into(),
[-1.0, 0.0].into(),
[1.0, 0.0].into(),
];
let mut trial = body_properties;
trial.position += *steps
.choose(rng)
.expect("steps should have at least 1 element");
trial
}
}
// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
/// Positions of all the bodies in the simulation.
microstate: Microstate<
Point<Cartesian<2>>,
Point<Cartesian<2>>,
AllPairs<SiteKey>,
Closed<Circle>,
>,
/// How sites interact with other sites and fields.
hamiltonian: Zero,
/// Trial moves to apply.
translate_sweep: Sweep<Discrete>,
/// Temperature set point.
macrostate: Isothermal,
}
impl CustomRandomWalk {
/// Construct a new random walk simulation.
fn new() -> anyhow::Result<CustomRandomWalk> {
let n = 1000;
let radius = 50.0;
let macrostate = Isothermal { temperature: 1.0 };
let circle = Circle { radius };
let microstate = Microstate::builder()
.boundary(Closed(circle))
.bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
.try_build()?;
let translate_sweep = Sweep(Discrete);
let hamiltonian = Zero;
Ok(CustomRandomWalk {
microstate,
hamiltonian,
translate_sweep,
macrostate,
})
}
}
impl Simulation for CustomRandomWalk {
/// Advance the simulation forward one step.
fn advance(&mut self) -> anyhow::Result<()> {
self.translate_sweep.apply(
&mut self.microstate,
&self.hamiltonian,
&self.macrostate,
);
self.microstate.increment_step();
Ok(())
}
/// Get the current simulation step.
fn step(&self) -> u64 {
self.microstate.step()
}
}
// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
let mut simulation = CustomRandomWalk::new()?;
for _ in 0..1_000_000 {
simulation.advance()?;
}
Ok(())
}
#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;
steps.choose(rng) chooses one of the elements of the array at random (with
uniform probability). Arrays in Rust do not have the choose() method by
default, it is provided by the IndexedRandom trait in the rand
crate. choose() will return None when the input array is empty. That can
never happen in this code, so you can safely unpack the option’s value with
expect().
Important
Local trial moves MUST satisfy local detailed balance, as defined in Manousiouthakis & Deem.
The Simulation Model
The Random Walk tutorial used local variables in the main() function to
store all the elements of the simulation model. You can certainly continue that
practice in your applications. As you build more complex codes, however, you
will need to move those elements to a struct so that the whole simulation model
can be accessed in different modules.
The custom random walk model consists of the microstate, the Hamiltonian, the translation trial moves, and the temperature set point (in units of energy ):
use rand::{Rng, seq::IndexedRandom};
use std::iter;
use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};
/// Closed circular boundary condition.
struct Circle {
radius: f64,
}
impl IsPointInside<Cartesian<2>> for Circle {
fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
point.distance(&[0.0, 0.0].into()) < self.radius
}
}
/// Take fixed steps left, right, down, or up.
struct Discrete;
impl LocalTrial<Point<Cartesian<2>>> for Discrete {
fn propose<R: Rng>(
&self,
rng: &mut R,
body_properties: Point<Cartesian<2>>,
) -> Point<Cartesian<2>> {
let steps = [
[0.0, -1.0].into(),
[0.0, 1.0].into(),
[-1.0, 0.0].into(),
[1.0, 0.0].into(),
];
let mut trial = body_properties;
trial.position += *steps
.choose(rng)
.expect("steps should have at least 1 element");
trial
}
}
// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
/// Positions of all the bodies in the simulation.
microstate: Microstate<
Point<Cartesian<2>>,
Point<Cartesian<2>>,
AllPairs<SiteKey>,
Closed<Circle>,
>,
/// How sites interact with other sites and fields.
hamiltonian: Zero,
/// Trial moves to apply.
translate_sweep: Sweep<Discrete>,
/// Temperature set point.
macrostate: Isothermal,
}
impl CustomRandomWalk {
/// Construct a new random walk simulation.
fn new() -> anyhow::Result<CustomRandomWalk> {
let n = 1000;
let radius = 50.0;
let macrostate = Isothermal { temperature: 1.0 };
let circle = Circle { radius };
let microstate = Microstate::builder()
.boundary(Closed(circle))
.bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
.try_build()?;
let translate_sweep = Sweep(Discrete);
let hamiltonian = Zero;
Ok(CustomRandomWalk {
microstate,
hamiltonian,
translate_sweep,
macrostate,
})
}
}
impl Simulation for CustomRandomWalk {
/// Advance the simulation forward one step.
fn advance(&mut self) -> anyhow::Result<()> {
self.translate_sweep.apply(
&mut self.microstate,
&self.hamiltonian,
&self.macrostate,
);
self.microstate.increment_step();
Ok(())
}
/// Get the current simulation step.
fn step(&self) -> u64 {
self.microstate.step()
}
}
// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
let mut simulation = CustomRandomWalk::new()?;
for _ in 0..1_000_000 {
simulation.advance()?;
}
Ok(())
}
#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;
Notice that this definition explicitly names the generic types of each of these fields. The Random Walk tutorial did not do this because the Rust compiler automatically determined which generic types were used. Structs can be made generic as well, but the details are beyond the scope of this tutorial. Consult the The Rust Programming Language to learn more.
Construct the Simulation Model
The new() method that constructs the CustomRandomWalk simulation model:
use rand::{Rng, seq::IndexedRandom};
use std::iter;
use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};
/// Closed circular boundary condition.
struct Circle {
radius: f64,
}
impl IsPointInside<Cartesian<2>> for Circle {
fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
point.distance(&[0.0, 0.0].into()) < self.radius
}
}
/// Take fixed steps left, right, down, or up.
struct Discrete;
impl LocalTrial<Point<Cartesian<2>>> for Discrete {
fn propose<R: Rng>(
&self,
rng: &mut R,
body_properties: Point<Cartesian<2>>,
) -> Point<Cartesian<2>> {
let steps = [
[0.0, -1.0].into(),
[0.0, 1.0].into(),
[-1.0, 0.0].into(),
[1.0, 0.0].into(),
];
let mut trial = body_properties;
trial.position += *steps
.choose(rng)
.expect("steps should have at least 1 element");
trial
}
}
// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
/// Positions of all the bodies in the simulation.
microstate: Microstate<
Point<Cartesian<2>>,
Point<Cartesian<2>>,
AllPairs<SiteKey>,
Closed<Circle>,
>,
/// How sites interact with other sites and fields.
hamiltonian: Zero,
/// Trial moves to apply.
translate_sweep: Sweep<Discrete>,
/// Temperature set point.
macrostate: Isothermal,
}
impl CustomRandomWalk {
/// Construct a new random walk simulation.
fn new() -> anyhow::Result<CustomRandomWalk> {
let n = 1000;
let radius = 50.0;
let macrostate = Isothermal { temperature: 1.0 };
let circle = Circle { radius };
let microstate = Microstate::builder()
.boundary(Closed(circle))
.bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
.try_build()?;
let translate_sweep = Sweep(Discrete);
let hamiltonian = Zero;
Ok(CustomRandomWalk {
microstate,
hamiltonian,
translate_sweep,
macrostate,
})
}
}
impl Simulation for CustomRandomWalk {
/// Advance the simulation forward one step.
fn advance(&mut self) -> anyhow::Result<()> {
self.translate_sweep.apply(
&mut self.microstate,
&self.hamiltonian,
&self.macrostate,
);
self.microstate.increment_step();
Ok(())
}
/// Get the current simulation step.
fn step(&self) -> u64 {
self.microstate.step()
}
}
// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
let mut simulation = CustomRandomWalk::new()?;
for _ in 0..1_000_000 {
simulation.advance()?;
}
Ok(())
}
#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;
One or more steps might fail, so return a Result<CustomRandomWalk>.
Parameters
Assign all the model parameters in one code block so that they are easy to modify:
use rand::{Rng, seq::IndexedRandom};
use std::iter;
use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};
/// Closed circular boundary condition.
struct Circle {
radius: f64,
}
impl IsPointInside<Cartesian<2>> for Circle {
fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
point.distance(&[0.0, 0.0].into()) < self.radius
}
}
/// Take fixed steps left, right, down, or up.
struct Discrete;
impl LocalTrial<Point<Cartesian<2>>> for Discrete {
fn propose<R: Rng>(
&self,
rng: &mut R,
body_properties: Point<Cartesian<2>>,
) -> Point<Cartesian<2>> {
let steps = [
[0.0, -1.0].into(),
[0.0, 1.0].into(),
[-1.0, 0.0].into(),
[1.0, 0.0].into(),
];
let mut trial = body_properties;
trial.position += *steps
.choose(rng)
.expect("steps should have at least 1 element");
trial
}
}
// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
/// Positions of all the bodies in the simulation.
microstate: Microstate<
Point<Cartesian<2>>,
Point<Cartesian<2>>,
AllPairs<SiteKey>,
Closed<Circle>,
>,
/// How sites interact with other sites and fields.
hamiltonian: Zero,
/// Trial moves to apply.
translate_sweep: Sweep<Discrete>,
/// Temperature set point.
macrostate: Isothermal,
}
impl CustomRandomWalk {
/// Construct a new random walk simulation.
fn new() -> anyhow::Result<CustomRandomWalk> {
let n = 1000;
let radius = 50.0;
let macrostate = Isothermal { temperature: 1.0 };
let circle = Circle { radius };
let microstate = Microstate::builder()
.boundary(Closed(circle))
.bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
.try_build()?;
let translate_sweep = Sweep(Discrete);
let hamiltonian = Zero;
Ok(CustomRandomWalk {
microstate,
hamiltonian,
translate_sweep,
macrostate,
})
}
}
impl Simulation for CustomRandomWalk {
/// Advance the simulation forward one step.
fn advance(&mut self) -> anyhow::Result<()> {
self.translate_sweep.apply(
&mut self.microstate,
&self.hamiltonian,
&self.macrostate,
);
self.microstate.increment_step();
Ok(())
}
/// Get the current simulation step.
fn step(&self) -> u64 {
self.microstate.step()
}
}
// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
let mut simulation = CustomRandomWalk::new()?;
for _ in 0..1_000_000 {
simulation.advance()?;
}
Ok(())
}
#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;
Microstate
Construct the Microstate with the circle boundary condition and place n
bodies at the origin:
use rand::{Rng, seq::IndexedRandom};
use std::iter;
use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};
/// Closed circular boundary condition.
struct Circle {
radius: f64,
}
impl IsPointInside<Cartesian<2>> for Circle {
fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
point.distance(&[0.0, 0.0].into()) < self.radius
}
}
/// Take fixed steps left, right, down, or up.
struct Discrete;
impl LocalTrial<Point<Cartesian<2>>> for Discrete {
fn propose<R: Rng>(
&self,
rng: &mut R,
body_properties: Point<Cartesian<2>>,
) -> Point<Cartesian<2>> {
let steps = [
[0.0, -1.0].into(),
[0.0, 1.0].into(),
[-1.0, 0.0].into(),
[1.0, 0.0].into(),
];
let mut trial = body_properties;
trial.position += *steps
.choose(rng)
.expect("steps should have at least 1 element");
trial
}
}
// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
/// Positions of all the bodies in the simulation.
microstate: Microstate<
Point<Cartesian<2>>,
Point<Cartesian<2>>,
AllPairs<SiteKey>,
Closed<Circle>,
>,
/// How sites interact with other sites and fields.
hamiltonian: Zero,
/// Trial moves to apply.
translate_sweep: Sweep<Discrete>,
/// Temperature set point.
macrostate: Isothermal,
}
impl CustomRandomWalk {
/// Construct a new random walk simulation.
fn new() -> anyhow::Result<CustomRandomWalk> {
let n = 1000;
let radius = 50.0;
let macrostate = Isothermal { temperature: 1.0 };
let circle = Circle { radius };
let microstate = Microstate::builder()
.boundary(Closed(circle))
.bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
.try_build()?;
let translate_sweep = Sweep(Discrete);
let hamiltonian = Zero;
Ok(CustomRandomWalk {
microstate,
hamiltonian,
translate_sweep,
macrostate,
})
}
}
impl Simulation for CustomRandomWalk {
/// Advance the simulation forward one step.
fn advance(&mut self) -> anyhow::Result<()> {
self.translate_sweep.apply(
&mut self.microstate,
&self.hamiltonian,
&self.macrostate,
);
self.microstate.increment_step();
Ok(())
}
/// Get the current simulation step.
fn step(&self) -> u64 {
self.microstate.step()
}
}
// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
let mut simulation = CustomRandomWalk::new()?;
for _ in 0..1_000_000 {
simulation.advance()?;
}
Ok(())
}
#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;
The newtype Closed can wrap any type that implements IsPointInside
(like the custom Circle) so that it can be used as a boundary condition.
Trial Moves
Apply the custom Discrete trial move to all bodies in the microstate:
use rand::{Rng, seq::IndexedRandom};
use std::iter;
use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};
/// Closed circular boundary condition.
struct Circle {
radius: f64,
}
impl IsPointInside<Cartesian<2>> for Circle {
fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
point.distance(&[0.0, 0.0].into()) < self.radius
}
}
/// Take fixed steps left, right, down, or up.
struct Discrete;
impl LocalTrial<Point<Cartesian<2>>> for Discrete {
fn propose<R: Rng>(
&self,
rng: &mut R,
body_properties: Point<Cartesian<2>>,
) -> Point<Cartesian<2>> {
let steps = [
[0.0, -1.0].into(),
[0.0, 1.0].into(),
[-1.0, 0.0].into(),
[1.0, 0.0].into(),
];
let mut trial = body_properties;
trial.position += *steps
.choose(rng)
.expect("steps should have at least 1 element");
trial
}
}
// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
/// Positions of all the bodies in the simulation.
microstate: Microstate<
Point<Cartesian<2>>,
Point<Cartesian<2>>,
AllPairs<SiteKey>,
Closed<Circle>,
>,
/// How sites interact with other sites and fields.
hamiltonian: Zero,
/// Trial moves to apply.
translate_sweep: Sweep<Discrete>,
/// Temperature set point.
macrostate: Isothermal,
}
impl CustomRandomWalk {
/// Construct a new random walk simulation.
fn new() -> anyhow::Result<CustomRandomWalk> {
let n = 1000;
let radius = 50.0;
let macrostate = Isothermal { temperature: 1.0 };
let circle = Circle { radius };
let microstate = Microstate::builder()
.boundary(Closed(circle))
.bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
.try_build()?;
let translate_sweep = Sweep(Discrete);
let hamiltonian = Zero;
Ok(CustomRandomWalk {
microstate,
hamiltonian,
translate_sweep,
macrostate,
})
}
}
impl Simulation for CustomRandomWalk {
/// Advance the simulation forward one step.
fn advance(&mut self) -> anyhow::Result<()> {
self.translate_sweep.apply(
&mut self.microstate,
&self.hamiltonian,
&self.macrostate,
);
self.microstate.increment_step();
Ok(())
}
/// Get the current simulation step.
fn step(&self) -> u64 {
self.microstate.step()
}
}
// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
let mut simulation = CustomRandomWalk::new()?;
for _ in 0..1_000_000 {
simulation.advance()?;
}
Ok(())
}
#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;
Sweep can wrap any type that implements LocalTrial.
Hamiltonian
As in the Random Walk tutorial, set so that bodies do not interact:
use rand::{Rng, seq::IndexedRandom};
use std::iter;
use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};
/// Closed circular boundary condition.
struct Circle {
radius: f64,
}
impl IsPointInside<Cartesian<2>> for Circle {
fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
point.distance(&[0.0, 0.0].into()) < self.radius
}
}
/// Take fixed steps left, right, down, or up.
struct Discrete;
impl LocalTrial<Point<Cartesian<2>>> for Discrete {
fn propose<R: Rng>(
&self,
rng: &mut R,
body_properties: Point<Cartesian<2>>,
) -> Point<Cartesian<2>> {
let steps = [
[0.0, -1.0].into(),
[0.0, 1.0].into(),
[-1.0, 0.0].into(),
[1.0, 0.0].into(),
];
let mut trial = body_properties;
trial.position += *steps
.choose(rng)
.expect("steps should have at least 1 element");
trial
}
}
// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
/// Positions of all the bodies in the simulation.
microstate: Microstate<
Point<Cartesian<2>>,
Point<Cartesian<2>>,
AllPairs<SiteKey>,
Closed<Circle>,
>,
/// How sites interact with other sites and fields.
hamiltonian: Zero,
/// Trial moves to apply.
translate_sweep: Sweep<Discrete>,
/// Temperature set point.
macrostate: Isothermal,
}
impl CustomRandomWalk {
/// Construct a new random walk simulation.
fn new() -> anyhow::Result<CustomRandomWalk> {
let n = 1000;
let radius = 50.0;
let macrostate = Isothermal { temperature: 1.0 };
let circle = Circle { radius };
let microstate = Microstate::builder()
.boundary(Closed(circle))
.bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
.try_build()?;
let translate_sweep = Sweep(Discrete);
let hamiltonian = Zero;
Ok(CustomRandomWalk {
microstate,
hamiltonian,
translate_sweep,
macrostate,
})
}
}
impl Simulation for CustomRandomWalk {
/// Advance the simulation forward one step.
fn advance(&mut self) -> anyhow::Result<()> {
self.translate_sweep.apply(
&mut self.microstate,
&self.hamiltonian,
&self.macrostate,
);
self.microstate.increment_step();
Ok(())
}
/// Get the current simulation step.
fn step(&self) -> u64 {
self.microstate.step()
}
}
// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
let mut simulation = CustomRandomWalk::new()?;
for _ in 0..1_000_000 {
simulation.advance()?;
}
Ok(())
}
#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;
Initialize the Struct
Now that all of the elements of the simulation model have been constructed,
package them in a CustomRandomWalk struct:
use rand::{Rng, seq::IndexedRandom};
use std::iter;
use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};
/// Closed circular boundary condition.
struct Circle {
radius: f64,
}
impl IsPointInside<Cartesian<2>> for Circle {
fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
point.distance(&[0.0, 0.0].into()) < self.radius
}
}
/// Take fixed steps left, right, down, or up.
struct Discrete;
impl LocalTrial<Point<Cartesian<2>>> for Discrete {
fn propose<R: Rng>(
&self,
rng: &mut R,
body_properties: Point<Cartesian<2>>,
) -> Point<Cartesian<2>> {
let steps = [
[0.0, -1.0].into(),
[0.0, 1.0].into(),
[-1.0, 0.0].into(),
[1.0, 0.0].into(),
];
let mut trial = body_properties;
trial.position += *steps
.choose(rng)
.expect("steps should have at least 1 element");
trial
}
}
// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
/// Positions of all the bodies in the simulation.
microstate: Microstate<
Point<Cartesian<2>>,
Point<Cartesian<2>>,
AllPairs<SiteKey>,
Closed<Circle>,
>,
/// How sites interact with other sites and fields.
hamiltonian: Zero,
/// Trial moves to apply.
translate_sweep: Sweep<Discrete>,
/// Temperature set point.
macrostate: Isothermal,
}
impl CustomRandomWalk {
/// Construct a new random walk simulation.
fn new() -> anyhow::Result<CustomRandomWalk> {
let n = 1000;
let radius = 50.0;
let macrostate = Isothermal { temperature: 1.0 };
let circle = Circle { radius };
let microstate = Microstate::builder()
.boundary(Closed(circle))
.bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
.try_build()?;
let translate_sweep = Sweep(Discrete);
let hamiltonian = Zero;
Ok(CustomRandomWalk {
microstate,
hamiltonian,
translate_sweep,
macrostate,
})
}
}
impl Simulation for CustomRandomWalk {
/// Advance the simulation forward one step.
fn advance(&mut self) -> anyhow::Result<()> {
self.translate_sweep.apply(
&mut self.microstate,
&self.hamiltonian,
&self.macrostate,
);
self.microstate.increment_step();
Ok(())
}
/// Get the current simulation step.
fn step(&self) -> u64 {
self.microstate.step()
}
}
// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
let mut simulation = CustomRandomWalk::new()?;
for _ in 0..1_000_000 {
simulation.advance()?;
}
Ok(())
}
#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;
The struct is wrapped in Ok to indicate that the Result of this function is
not an error.
Implement Simulation
The Simulation trait provides a common interface that all simulation
models can follow:
use rand::{Rng, seq::IndexedRandom};
use std::iter;
use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};
/// Closed circular boundary condition.
struct Circle {
radius: f64,
}
impl IsPointInside<Cartesian<2>> for Circle {
fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
point.distance(&[0.0, 0.0].into()) < self.radius
}
}
/// Take fixed steps left, right, down, or up.
struct Discrete;
impl LocalTrial<Point<Cartesian<2>>> for Discrete {
fn propose<R: Rng>(
&self,
rng: &mut R,
body_properties: Point<Cartesian<2>>,
) -> Point<Cartesian<2>> {
let steps = [
[0.0, -1.0].into(),
[0.0, 1.0].into(),
[-1.0, 0.0].into(),
[1.0, 0.0].into(),
];
let mut trial = body_properties;
trial.position += *steps
.choose(rng)
.expect("steps should have at least 1 element");
trial
}
}
// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
/// Positions of all the bodies in the simulation.
microstate: Microstate<
Point<Cartesian<2>>,
Point<Cartesian<2>>,
AllPairs<SiteKey>,
Closed<Circle>,
>,
/// How sites interact with other sites and fields.
hamiltonian: Zero,
/// Trial moves to apply.
translate_sweep: Sweep<Discrete>,
/// Temperature set point.
macrostate: Isothermal,
}
impl CustomRandomWalk {
/// Construct a new random walk simulation.
fn new() -> anyhow::Result<CustomRandomWalk> {
let n = 1000;
let radius = 50.0;
let macrostate = Isothermal { temperature: 1.0 };
let circle = Circle { radius };
let microstate = Microstate::builder()
.boundary(Closed(circle))
.bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
.try_build()?;
let translate_sweep = Sweep(Discrete);
let hamiltonian = Zero;
Ok(CustomRandomWalk {
microstate,
hamiltonian,
translate_sweep,
macrostate,
})
}
}
impl Simulation for CustomRandomWalk {
/// Advance the simulation forward one step.
fn advance(&mut self) -> anyhow::Result<()> {
self.translate_sweep.apply(
&mut self.microstate,
&self.hamiltonian,
&self.macrostate,
);
self.microstate.increment_step();
Ok(())
}
/// Get the current simulation step.
fn step(&self) -> u64 {
self.microstate.step()
}
}
// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
let mut simulation = CustomRandomWalk::new()?;
for _ in 0..1_000_000 {
simulation.advance()?;
}
Ok(())
}
#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;
Advance the Simulation
The first method that all simulation models must have is an advance() method
that moves the model forward one step. The implementation is identical to that
in the Random Walk tutorial:
use rand::{Rng, seq::IndexedRandom};
use std::iter;
use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};
/// Closed circular boundary condition.
struct Circle {
radius: f64,
}
impl IsPointInside<Cartesian<2>> for Circle {
fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
point.distance(&[0.0, 0.0].into()) < self.radius
}
}
/// Take fixed steps left, right, down, or up.
struct Discrete;
impl LocalTrial<Point<Cartesian<2>>> for Discrete {
fn propose<R: Rng>(
&self,
rng: &mut R,
body_properties: Point<Cartesian<2>>,
) -> Point<Cartesian<2>> {
let steps = [
[0.0, -1.0].into(),
[0.0, 1.0].into(),
[-1.0, 0.0].into(),
[1.0, 0.0].into(),
];
let mut trial = body_properties;
trial.position += *steps
.choose(rng)
.expect("steps should have at least 1 element");
trial
}
}
// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
/// Positions of all the bodies in the simulation.
microstate: Microstate<
Point<Cartesian<2>>,
Point<Cartesian<2>>,
AllPairs<SiteKey>,
Closed<Circle>,
>,
/// How sites interact with other sites and fields.
hamiltonian: Zero,
/// Trial moves to apply.
translate_sweep: Sweep<Discrete>,
/// Temperature set point.
macrostate: Isothermal,
}
impl CustomRandomWalk {
/// Construct a new random walk simulation.
fn new() -> anyhow::Result<CustomRandomWalk> {
let n = 1000;
let radius = 50.0;
let macrostate = Isothermal { temperature: 1.0 };
let circle = Circle { radius };
let microstate = Microstate::builder()
.boundary(Closed(circle))
.bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
.try_build()?;
let translate_sweep = Sweep(Discrete);
let hamiltonian = Zero;
Ok(CustomRandomWalk {
microstate,
hamiltonian,
translate_sweep,
macrostate,
})
}
}
impl Simulation for CustomRandomWalk {
/// Advance the simulation forward one step.
fn advance(&mut self) -> anyhow::Result<()> {
self.translate_sweep.apply(
&mut self.microstate,
&self.hamiltonian,
&self.macrostate,
);
self.microstate.increment_step();
Ok(())
}
/// Get the current simulation step.
fn step(&self) -> u64 {
self.microstate.step()
}
}
// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
let mut simulation = CustomRandomWalk::new()?;
for _ in 0..1_000_000 {
simulation.advance()?;
}
Ok(())
}
#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;
All the complexity of the customizations is contained in the implementation
of IsPointInside for Circle and LocalTrial for Discrete.
Get the Simulation Step
All simulation models must also implement a method that provides the current simulation step:
use rand::{Rng, seq::IndexedRandom};
use std::iter;
use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};
/// Closed circular boundary condition.
struct Circle {
radius: f64,
}
impl IsPointInside<Cartesian<2>> for Circle {
fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
point.distance(&[0.0, 0.0].into()) < self.radius
}
}
/// Take fixed steps left, right, down, or up.
struct Discrete;
impl LocalTrial<Point<Cartesian<2>>> for Discrete {
fn propose<R: Rng>(
&self,
rng: &mut R,
body_properties: Point<Cartesian<2>>,
) -> Point<Cartesian<2>> {
let steps = [
[0.0, -1.0].into(),
[0.0, 1.0].into(),
[-1.0, 0.0].into(),
[1.0, 0.0].into(),
];
let mut trial = body_properties;
trial.position += *steps
.choose(rng)
.expect("steps should have at least 1 element");
trial
}
}
// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
/// Positions of all the bodies in the simulation.
microstate: Microstate<
Point<Cartesian<2>>,
Point<Cartesian<2>>,
AllPairs<SiteKey>,
Closed<Circle>,
>,
/// How sites interact with other sites and fields.
hamiltonian: Zero,
/// Trial moves to apply.
translate_sweep: Sweep<Discrete>,
/// Temperature set point.
macrostate: Isothermal,
}
impl CustomRandomWalk {
/// Construct a new random walk simulation.
fn new() -> anyhow::Result<CustomRandomWalk> {
let n = 1000;
let radius = 50.0;
let macrostate = Isothermal { temperature: 1.0 };
let circle = Circle { radius };
let microstate = Microstate::builder()
.boundary(Closed(circle))
.bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
.try_build()?;
let translate_sweep = Sweep(Discrete);
let hamiltonian = Zero;
Ok(CustomRandomWalk {
microstate,
hamiltonian,
translate_sweep,
macrostate,
})
}
}
impl Simulation for CustomRandomWalk {
/// Advance the simulation forward one step.
fn advance(&mut self) -> anyhow::Result<()> {
self.translate_sweep.apply(
&mut self.microstate,
&self.hamiltonian,
&self.macrostate,
);
self.microstate.increment_step();
Ok(())
}
/// Get the current simulation step.
fn step(&self) -> u64 {
self.microstate.step()
}
}
// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
let mut simulation = CustomRandomWalk::new()?;
for _ in 0..1_000_000 {
simulation.advance()?;
}
Ok(())
}
#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;
Implement main()
To run the simulation, construct the CustomRandomWalk simulation model.
Then call advance() many times:
use rand::{Rng, seq::IndexedRandom};
use std::iter;
use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};
/// Closed circular boundary condition.
struct Circle {
radius: f64,
}
impl IsPointInside<Cartesian<2>> for Circle {
fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
point.distance(&[0.0, 0.0].into()) < self.radius
}
}
/// Take fixed steps left, right, down, or up.
struct Discrete;
impl LocalTrial<Point<Cartesian<2>>> for Discrete {
fn propose<R: Rng>(
&self,
rng: &mut R,
body_properties: Point<Cartesian<2>>,
) -> Point<Cartesian<2>> {
let steps = [
[0.0, -1.0].into(),
[0.0, 1.0].into(),
[-1.0, 0.0].into(),
[1.0, 0.0].into(),
];
let mut trial = body_properties;
trial.position += *steps
.choose(rng)
.expect("steps should have at least 1 element");
trial
}
}
// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
/// Positions of all the bodies in the simulation.
microstate: Microstate<
Point<Cartesian<2>>,
Point<Cartesian<2>>,
AllPairs<SiteKey>,
Closed<Circle>,
>,
/// How sites interact with other sites and fields.
hamiltonian: Zero,
/// Trial moves to apply.
translate_sweep: Sweep<Discrete>,
/// Temperature set point.
macrostate: Isothermal,
}
impl CustomRandomWalk {
/// Construct a new random walk simulation.
fn new() -> anyhow::Result<CustomRandomWalk> {
let n = 1000;
let radius = 50.0;
let macrostate = Isothermal { temperature: 1.0 };
let circle = Circle { radius };
let microstate = Microstate::builder()
.boundary(Closed(circle))
.bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
.try_build()?;
let translate_sweep = Sweep(Discrete);
let hamiltonian = Zero;
Ok(CustomRandomWalk {
microstate,
hamiltonian,
translate_sweep,
macrostate,
})
}
}
impl Simulation for CustomRandomWalk {
/// Advance the simulation forward one step.
fn advance(&mut self) -> anyhow::Result<()> {
self.translate_sweep.apply(
&mut self.microstate,
&self.hamiltonian,
&self.macrostate,
);
self.microstate.increment_step();
Ok(())
}
/// Get the current simulation step.
fn step(&self) -> u64 {
self.microstate.step()
}
}
// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
let mut simulation = CustomRandomWalk::new()?;
for _ in 0..1_000_000 {
simulation.advance()?;
}
Ok(())
}
#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;
Later tutorials will show how you can write simulation trajectories and log properties of the simulation to files.
Note
This
main()function runs in batch mode. There is a differentmain()(not shown here) used in the interactive example.
Conclusion
This tutorial showed you how to customize the random walk simulation with new boundary conditions and apply your own trial moves to the points in it. Rust compiles your customizations into machine code and can inline them into the main simulation loop. This means that your custom simulations run just as fast as they do when using the built-in types.
Navigate to the top of the page to see the simulation in action. Notice that no points leave the boundary. Try pausing the simulation and advancing one step at a time. You should see that every body moves left, right, down, or up on every step.
You can also run the example in batch mode and then open
the generated trajectory.gsd in Ovito or another visualization tool:
cargo run --release --example custom-random-walk
Complete Code
use rand::{Rng, seq::IndexedRandom};
use std::iter;
use hoomd_geometry::IsPointInside;
use hoomd_interaction::Zero;
use hoomd_mc::{LocalTrial, Sweep, Trial};
use hoomd_microstate::{
Body, Microstate, SiteKey, boundary::Closed, property::Point,
};
use hoomd_simulation::{Simulation, macrostate::Isothermal};
use hoomd_spatial::AllPairs;
use hoomd_vector::{Cartesian, Metric};
/// Closed circular boundary condition.
struct Circle {
radius: f64,
}
impl IsPointInside<Cartesian<2>> for Circle {
fn is_point_inside(&self, point: &Cartesian<2>) -> bool {
point.distance(&[0.0, 0.0].into()) < self.radius
}
}
/// Take fixed steps left, right, down, or up.
struct Discrete;
impl LocalTrial<Point<Cartesian<2>>> for Discrete {
fn propose<R: Rng>(
&self,
rng: &mut R,
body_properties: Point<Cartesian<2>>,
) -> Point<Cartesian<2>> {
let steps = [
[0.0, -1.0].into(),
[0.0, 1.0].into(),
[-1.0, 0.0].into(),
[1.0, 0.0].into(),
];
let mut trial = body_properties;
trial.position += *steps
.choose(rng)
.expect("steps should have at least 1 element");
trial
}
}
// Remove the cfg_attr(...) line when using this code outside the hoomd-rs/examples directory.
#[cfg_attr(feature = "bevy", derive(Resource))]
struct CustomRandomWalk {
/// Positions of all the bodies in the simulation.
microstate: Microstate<
Point<Cartesian<2>>,
Point<Cartesian<2>>,
AllPairs<SiteKey>,
Closed<Circle>,
>,
/// How sites interact with other sites and fields.
hamiltonian: Zero,
/// Trial moves to apply.
translate_sweep: Sweep<Discrete>,
/// Temperature set point.
macrostate: Isothermal,
}
impl CustomRandomWalk {
/// Construct a new random walk simulation.
fn new() -> anyhow::Result<CustomRandomWalk> {
let n = 1000;
let radius = 50.0;
let macrostate = Isothermal { temperature: 1.0 };
let circle = Circle { radius };
let microstate = Microstate::builder()
.boundary(Closed(circle))
.bodies(iter::repeat_n(Body::point(Cartesian::default()), n))
.try_build()?;
let translate_sweep = Sweep(Discrete);
let hamiltonian = Zero;
Ok(CustomRandomWalk {
microstate,
hamiltonian,
translate_sweep,
macrostate,
})
}
}
impl Simulation for CustomRandomWalk {
/// Advance the simulation forward one step.
fn advance(&mut self) -> anyhow::Result<()> {
self.translate_sweep.apply(
&mut self.microstate,
&self.hamiltonian,
&self.macrostate,
);
self.microstate.increment_step();
Ok(())
}
/// Get the current simulation step.
fn step(&self) -> u64 {
self.microstate.step()
}
}
// Remove the cfg(not(...)) line when using this code outside the hoomd-rs/examples directory.
#[cfg(not(feature = "bevy"))]
fn main() -> anyhow::Result<()> {
let mut simulation = CustomRandomWalk::new()?;
for _ in 0..1_000_000 {
simulation.advance()?;
}
Ok(())
}
#[cfg(feature = "bevy")]
mod custom_random_walk_interactive;
#[cfg(feature = "bevy")]
use bevy::prelude::Resource;
#[cfg(feature = "bevy")]
use custom_random_walk_interactive::main;
Development of hoomd-rs is led by the Glotzer Group at the University of Michigan.
Copyright © 2024-2026 The Regents of the University of Michigan.