hoomd_interaction/lib.rs
1// Copyright (c) 2024-2026 The Regents of the University of Michigan.
2// Part of hoomd-rs, released under the BSD 3-Clause License.
3
4#![doc(
5 html_favicon_url = "https://raw.githubusercontent.com/glotzerlab/hoomd-rs/7352214172a490cc716492e9724ff42720a0018a/doc/theme/favicon.svg"
6)]
7#![doc(
8 html_logo_url = "https://raw.githubusercontent.com/glotzerlab/hoomd-rs/7352214172a490cc716492e9724ff42720a0018a/doc/theme/favicon.svg"
9)]
10
11//! Particle interactions and physical models that apply to microstates.
12//!
13//! `hoomd-interaction` defines traits that describe site and body interactions
14//! needed to perform Monte Carlo and molecular dynamics simulations.
15//!
16//! # Hamiltonian
17//!
18//! A type that describes a Hamiltonian (or a single term in a multi-part Hamiltonian)
19//! implements one or more of the following traits: [`TotalEnergy`], [`DeltaEnergyOne`],
20//! [`DeltaEnergyInsert`], and [`DeltaEnergyRemove`]. Given a microstate, the
21//! [`total_energy`] method computes the total energy of the Hamiltonian. The various
22//! `delta_energy_*` methods compute the *change* in total energy when updating, inserting,
23//! or removing a body (often needed for Monte Carlo simulation). Total energy
24//! computations *typically* cost $` O(N) `$ while `delta_energy` methods typically cost
25//! $` O(1) `$. These costs may vary based on the specific interaction type and/or the
26//! microstate's spatial data structure.
27//!
28//! As a convenience, most Hamiltonian types also implement [`MaximumInteractionRange`],
29//! so that callers can easily determine the maximum site-site interaction range in a
30//! given model.
31//!
32//! All the Hamiltonian traits can be automatically derived using a `#[derive()]` macro
33//! of the same name. The derived implementation sums over all the fields in the struct.
34//!
35//! [`total_energy`]: TotalEnergy::total_energy
36//!
37//! # Force interaction models
38//!
39//! Molecular dynamics simulations are driven by the forces and torques that act
40//! on the *bodies* in the simulation ([`NetBodyForceAndVirial`], [`NetBodyForceVirialAndTorque`]).
41//! The *body* forces and torques result from forces and torques on the *sites*
42//! [`NetSiteForceAndVirial`], [`NetSiteForceVirialAndTorque`]). When an MD model is Hamiltonian,
43//! the same types that implement [`DeltaEnergyOne`] (and related traits) also
44//! implement [`NetSiteForceAndVirial`] and/or [`NetSiteForceVirialAndTorque`].
45//! Virials are always calculated alongside forces.
46//!
47//! Given a microstate and the index of a site in that microstate,
48//! [`NetSiteForceAndVirial`] and/or [`NetSiteForceVirialAndTorque`] compute the net force and virial
49//! (and torque) acting on that site. Thus, you can use your `hamiltonian`
50//! variable to compute both energy and force properties on the system.
51//! Use `Rigid(hamiltonian)` with MD integration methods. The [`Rigid`]
52//! type implements [`NetBodyForceAndVirial`] and/or [`NetBodyForceVirialAndTorque`] for types
53//! that implement [`NetSiteForceAndVirial`] or [`NetSiteForceVirialAndTorque`] respectively.
54//!
55//! Not all MD models are Hamiltonian, so types may implement [`NetSiteForceAndVirial`]
56//! but but not [`TotalEnergy`]. Similarly, not all Hamiltonian types are
57//! differentiable and may implement [`DeltaEnergyOne`] but not [`NetSiteForceAndVirial`].
58//!
59//! All the force interaction model traits can be automatically derived using a
60//! `#[derive()]` macro of the same name. The derived implementation sums over
61//! all the fields in the struct.
62//!
63//! # Univariate interactions
64//!
65//! Many interaction potentials are a function of one variable, typically the
66//! distance between two sites but sometimes the distance between a site and
67//! surface, or an angle. `hoomd-interaction` implements the most commonly
68//! used univariate potentials, such as [`LennardJones`] in [`univariate`].
69//! These types are not Hamiltonian terms on their own, but can be combined
70//! with other types to create interaction models.
71//!
72//! To implement your own univariate interactions, implement the
73//! [`UnivariateEnergy`] and/or [`UnivariateForce`] traits.
74//!
75//! [`LennardJones`]: univariate::LennardJones
76//! [`UnivariateEnergy`]: univariate::UnivariateEnergy
77//! [`UnivariateForce`]: univariate::UnivariateForce
78//!
79//! # Interactions between sites and external objects
80//!
81//! The [`SiteEnergy`] trait describes a type that computes the contribution
82//! of a single site to the total energy as a function only of that site's
83//! properties along with fixed external parameters. [`SiteForceAndVirial`] and
84//! [`SiteForceVirialAndTorque`] describe the force (and torque) on the site commensurate
85//! with that energy. The [`External`] type implements all the Hamiltonian and force
86//! interaction model traits. It applies the wrapped type's [`SiteEnergy`], [`SiteForceAndVirial`],
87//! and/or [`SiteForceVirialAndTorque`] implementations to all the sites in the microstate.
88//! See [`external`] for a list of built-in [`SiteEnergy`], [`SiteForceAndVirial`] and
89//! [`SiteForceVirialAndTorque`] implementations.
90//!
91//! # Interactions between all pairs of sites
92//!
93//! The [`SitePairEnergy`] trait describes a type that computes the energy
94//! that a pair of sites contributes to the Hamiltonian as a function of
95//! the properties of the two sites. Similarly, [`SitePairForceAndVirial`] and
96//! [`SitePairForceVirialAndTorque`] describe the force (and torque) between
97//! the pair commensurate with that energy. The [`PairwiseCutoff`] type implements
98//! all the Hamiltonian and force interaction model traits. It applies the wrapped
99//! type's [`SitePairEnergy`], [`SitePairForceAndVirial`], and/or [`SitePairForceVirialAndTorque`]
100//! to all pairs of sites that are within the maximum interaction range.
101//!
102//! The [`pairwise`] module provides numerous types that implement
103//! [`SitePairEnergy`], [`SitePairForceAndVirial`], and [`SitePairForceVirialAndTorque`]
104//! including [`Isotropic`] (which wraps any univariate potential), [`HardShape`]
105//! (which wraps a shape from [`hoomd_geometry`], and many others.
106//!
107//! # Zero
108//!
109//! [`Zero`] implements all Hamiltonian and force interaction traits. It
110//! represents $` H=0 `$.
111//!
112//! [`Isotropic`]: pairwise::Isotropic
113//! [`HardShape`]: pairwise::HardShape
114//!
115//! # Complete documentation
116//!
117//! `hoomd-interaction` is is a part of *hoomd-rs*. Read the [complete documentation]
118//! for more information.
119//!
120//! [complete documentation]: https://hoomd-rs.readthedocs.io
121
122pub mod external;
123pub mod pairwise;
124pub mod univariate;
125
126mod external_type;
127mod pairwise_cutoff;
128mod rigid;
129mod zero;
130
131pub use external_type::External;
132
133pub use hoomd_derive::{
134 DeltaEnergyInsert, DeltaEnergyOne, DeltaEnergyRemove, MaximumInteractionRange,
135 NetSiteForceAndVirial, NetSiteForceVirialAndTorque, SitePairEnergy, TotalEnergy,
136};
137use hoomd_microstate::{Body, Microstate};
138use hoomd_vector::{Outer, Wedge};
139pub use pairwise_cutoff::PairwiseCutoff;
140pub use rigid::Rigid;
141pub use zero::Zero;
142
143/// Compute the total energy of a potential applied to the microstate.
144///
145/// The `TotalEnergy` trait describes a type that can compute the energy of a
146/// given microstate. Depending on the type, `total_energy` might compute the
147/// total potential energy of the whole Hamiltonian or a single term, such as
148/// the Lennard-Jones potential energy.
149///
150/// # Example
151///
152/// ```
153/// use hoomd_interaction::{
154/// PairwiseCutoff, SitePairEnergy, TotalEnergy, pairwise::Isotropic,
155/// univariate::LennardJones,
156/// };
157/// use hoomd_microstate::{
158/// Body, Microstate,
159/// property::{Point, Position},
160/// };
161/// use hoomd_vector::{Cartesian, Vector};
162///
163/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
164/// let mut microstate = Microstate::new();
165/// microstate.extend_bodies([
166/// Body::point(Cartesian::from([0.0, 0.0])),
167/// Body::point(Cartesian::from([1.0, 0.0])),
168/// Body::point(Cartesian::from([0.0, 5.0])),
169/// Body::point(Cartesian::from([-1.0, 5.0])),
170/// ])?;
171///
172/// let lennard_jones: LennardJones = LennardJones {
173/// epsilon: 1.5,
174/// sigma: 1.0 / 2.0_f64.powf(1.0 / 6.0),
175/// };
176/// let pairwise_cutoff = PairwiseCutoff(Isotropic {
177/// interaction: lennard_jones,
178/// r_cut: 2.5,
179/// });
180///
181/// let total_energy = pairwise_cutoff.total_energy(µstate);
182/// assert_eq!(total_energy, -3.0);
183/// # Ok(())
184/// # }
185/// ```
186///
187/// # Derive macro
188///
189/// Use the [`TotalEnergy`](macro@TotalEnergy) derive macro to automatically implement
190/// the `TotalEnergy` trait on a type. The derived implementation sums the result of
191/// `total_energy` over all fields in the struct (in the order in which fields
192/// are named in the struct definition). The sum short circuits and returns
193/// `f64::INFINITY` when any one field returns infinity.
194/// ```
195/// use hoomd_interaction::{
196/// External, PairwiseCutoff, TotalEnergy, external::Linear,
197/// pairwise::Isotropic, univariate::Boxcar,
198/// };
199/// use hoomd_microstate::{Body, Microstate, property::Point};
200/// use hoomd_vector::Cartesian;
201///
202/// #[derive(TotalEnergy)]
203/// struct Hamiltonian {
204/// linear: External<Linear<Cartesian<2>>>,
205/// pairwise_cutoff: PairwiseCutoff<Isotropic<Boxcar>>,
206/// }
207///
208/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
209/// let mut microstate = Microstate::new();
210/// microstate.extend_bodies([
211/// Body::point(Cartesian::from([0.0, 4.0])),
212/// Body::point(Cartesian::from([1.0, 4.0])),
213/// ])?;
214///
215/// let epsilon = 2.0;
216/// let (left, right) = (0.0, 1.5);
217/// let boxcar = Boxcar {
218/// epsilon,
219/// left,
220/// right,
221/// };
222/// let pairwise_cutoff = PairwiseCutoff(Isotropic {
223/// interaction: boxcar,
224/// r_cut: right,
225/// });
226///
227/// let linear = External(Linear {
228/// alpha: 1.0,
229/// plane_origin: Cartesian::default(),
230/// plane_normal: [0.0, 1.0].try_into()?,
231/// });
232///
233/// let hamiltonian = Hamiltonian {
234/// pairwise_cutoff,
235/// linear,
236/// };
237///
238/// let total_energy = hamiltonian.total_energy(µstate);
239/// assert_eq!(total_energy, 10.0);
240/// # Ok(())
241/// # }
242/// ```
243pub trait TotalEnergy<M> {
244 /// Compute the energy.
245 #[must_use]
246 fn total_energy(&self, microstate: &M) -> f64;
247
248 /// Compute the difference in energy between two microstates.
249 ///
250 /// Returns $` E_\mathrm{final} - E_\mathrm{initial} `$.
251 #[inline]
252 #[must_use]
253 fn delta_energy_total(&self, initial_microstate: &M, final_microstate: &M) -> f64 {
254 self.total_energy(final_microstate) - self.total_energy(initial_microstate)
255 }
256}
257
258/// Compute the energy contribution of a single site.
259///
260/// The `SiteEnergy` trait describes a type that can compute the energy contribution
261/// of a site to the system's total energy *as a function only of that site's
262/// properties*.
263///
264/// The [`external`] module provides a number of commonly used implementations.
265/// Combine them with [`External`] newtype for use with MC and MD simulations or to
266/// compute system-wide properties.
267///
268/// The generic type names are:
269/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
270///
271/// ## Examples
272///
273/// Implement a custom site energy function:
274///
275/// ```
276/// use hoomd_interaction::{External, SiteEnergy, TotalEnergy};
277/// use hoomd_microstate::{
278/// Body, Microstate,
279/// property::{Point, Position},
280/// };
281/// use hoomd_vector::Cartesian;
282///
283/// struct Custom {
284/// a: f64,
285/// b: f64,
286/// }
287///
288/// impl<S> SiteEnergy<S> for Custom
289/// where
290/// S: Position<Position = Cartesian<2>>,
291/// {
292/// fn site_energy(&self, site_properties: &S) -> f64 {
293/// self.a * (site_properties.position()[0] / self.b).cos()
294/// }
295/// }
296///
297/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
298/// let mut microstate = Microstate::new();
299/// microstate.extend_bodies([
300/// Body::point(Cartesian::from([1.0, 0.0])),
301/// Body::point(Cartesian::from([-1.0, 2.0])),
302/// ])?;
303///
304/// let custom = Custom { a: 1.0, b: 10.0 };
305/// let site_energy = custom.site_energy(µstate.sites()[0].properties);
306///
307/// let custom = External(custom);
308/// let total_energy = custom.total_energy(µstate);
309/// # Ok(())
310/// # }
311/// ```
312///
313/// Custom method that checks for overlaps of a disk with a circular boundary.
314///
315/// ```
316/// use hoomd_interaction::{External, SiteEnergy, TotalEnergy};
317/// use hoomd_microstate::{
318/// Body, Microstate,
319/// property::{Point, Position},
320/// };
321/// use hoomd_vector::{Cartesian, Metric};
322///
323/// struct Custom {
324/// r: f64,
325/// }
326///
327/// impl<S> SiteEnergy<S> for Custom
328/// where
329/// S: Position<Position = Cartesian<2>>,
330/// {
331/// fn site_energy(&self, site_properties: &S) -> f64 {
332/// if site_properties.position().distance(&Cartesian::default())
333/// > self.r - 0.5
334/// {
335/// f64::INFINITY
336/// } else {
337/// 0.0
338/// }
339/// }
340///
341/// fn is_only_infinite_or_zero() -> bool {
342/// true
343/// }
344/// }
345///
346/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
347/// let mut microstate = Microstate::new();
348/// microstate.extend_bodies([Body::point(Cartesian::from([9.6, 0.0]))])?;
349///
350/// let custom = Custom { r: 10.0 };
351/// let site_energy = custom.site_energy(µstate.sites()[0].properties);
352/// assert_eq!(site_energy, f64::INFINITY);
353///
354/// let custom = External(custom);
355/// let total_energy = custom.total_energy(µstate);
356/// assert_eq!(total_energy, f64::INFINITY);
357/// # Ok(())
358/// # }
359/// ```
360pub trait SiteEnergy<S> {
361 /// Evaluate the energy contribution of a single site.
362 #[must_use]
363 fn site_energy(&self, site_properties: &S) -> f64;
364
365 /// Evaluate the energy contribution of a single site *in the initial state*.
366 ///
367 /// Override this method in potentials that have both infinite or zero
368 /// terms and finite terms, such as the sum of a hard site-wall interaction
369 /// plus an attractive well. `site_energy` should compute both terms and
370 /// `site_energy_initial` should compute only the finite terms.
371 ///
372 /// [`External`] calls `site_energy_initial` when evaluating the energy of
373 /// the initial state in a trial move. The infinite interaction term can be
374 /// assumed 0 in the initial state because no site will ever be placed in an
375 /// infinite energy configuration.
376 #[must_use]
377 #[inline]
378 fn site_energy_initial(&self, site_properties: &S) -> f64 {
379 self.site_energy(site_properties)
380 }
381
382 /// Does this potential only ever return infinity or zero?
383 ///
384 /// Override this method and return `true` for e.g. hard site-wall
385 /// interactions that always return infinity or zero and **never** any other
386 /// value. When this method returns `true`, [`External`] skips the initial
387 /// energy computation and assumes it is zero.
388 #[must_use]
389 #[inline]
390 fn is_only_infinite_or_zero() -> bool {
391 false
392 }
393}
394
395/// Compute the energy contribution from a pair of sites.
396///
397/// The `SitePairEnergy` trait describes a type that can compute the energy
398/// contribution from a pair of sites to the system's total energy *as a function
399/// only of those site's properties*.
400///
401/// The [`pairwise`] module provides a number of commonly used implementations,
402/// such as [`Isotropic`], [`Anisotropic`], and [`HardShape`]. Combine any
403/// of them with the [`PairwiseCutoff`] for use with MC and MD simulations or to
404/// compute system-wide properties.
405///
406/// The generic type names are:
407/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
408///
409/// [`Isotropic`]: pairwise::Isotropic
410/// [`Anisotropic`]: pairwise::Anisotropic
411/// [`HardShape`]: pairwise::HardShape
412///
413/// ## Examples
414///
415/// Implement a custom site energy method:
416/// ```
417/// use hoomd_interaction::{
418/// MaximumInteractionRange, PairwiseCutoff, SitePairEnergy, TotalEnergy,
419/// };
420/// use hoomd_microstate::{
421/// Body, Microstate,
422/// property::{Point, Position},
423/// };
424/// use hoomd_vector::{Cartesian, InnerProduct};
425///
426/// #[derive(MaximumInteractionRange)]
427/// struct Custom {
428/// epsilon: f64,
429/// maximum_interaction_range: f64,
430/// }
431///
432/// impl<S> SitePairEnergy<S> for Custom
433/// where
434/// S: Position<Position = Cartesian<2>>,
435/// {
436/// fn site_pair_energy(
437/// &self,
438/// site_properties_i: &S,
439/// site_properties_j: &S,
440/// ) -> f64 {
441/// self.epsilon
442/// * site_properties_i
443/// .position()
444/// .dot(&site_properties_j.position())
445/// }
446/// }
447///
448/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
449/// let mut microstate = Microstate::new();
450/// microstate.extend_bodies([
451/// Body::point(Cartesian::from([1.0, 0.0])),
452/// Body::point(Cartesian::from([0.0, 1.0])),
453/// ])?;
454///
455/// let custom = Custom {
456/// epsilon: 1.0,
457/// maximum_interaction_range: 2.5,
458/// };
459/// let site_pair_energy = custom.site_pair_energy(
460/// µstate.sites()[0].properties,
461/// µstate.sites()[1].properties,
462/// );
463///
464/// let custom = PairwiseCutoff(custom);
465/// let total_energy = custom.total_energy(µstate);
466/// # Ok(())
467/// # }
468/// ```
469///
470/// Implement a custom site overlap method:
471/// ```
472/// use hoomd_interaction::{
473/// MaximumInteractionRange, PairwiseCutoff, SitePairEnergy, TotalEnergy,
474/// };
475/// use hoomd_microstate::{
476/// Body, Microstate, Transform,
477/// property::{Point, Position},
478/// };
479/// use hoomd_utility::valid::PositiveReal;
480/// use hoomd_vector::{Cartesian, Metric};
481///
482/// #[derive(Default, Position)]
483/// struct CircleSiteProperties {
484/// position: Cartesian<2>,
485/// radius: PositiveReal,
486/// }
487///
488/// impl Transform<CircleSiteProperties> for Point<Cartesian<2>> {
489/// fn transform(
490/// &self,
491/// site_properties: &CircleSiteProperties,
492/// ) -> CircleSiteProperties {
493/// CircleSiteProperties {
494/// position: self.position + site_properties.position,
495/// ..*site_properties
496/// }
497/// }
498/// }
499///
500/// #[derive(MaximumInteractionRange)]
501/// struct PolydisperseCircleOverlap {
502/// maximum_interaction_range: f64,
503/// }
504///
505/// impl SitePairEnergy<CircleSiteProperties> for PolydisperseCircleOverlap {
506/// fn site_pair_energy(
507/// &self,
508/// a: &CircleSiteProperties,
509/// b: &CircleSiteProperties,
510/// ) -> f64 {
511/// let r = a.position().distance(b.position());
512///
513/// if r < a.radius.get() + b.radius.get() {
514/// f64::INFINITY
515/// } else {
516/// 0.0
517/// }
518/// }
519///
520/// fn is_only_infinite_or_zero() -> bool {
521/// true
522/// }
523///
524/// fn site_pair_energy_initial(
525/// &self,
526/// _a: &CircleSiteProperties,
527/// _b: &CircleSiteProperties,
528/// ) -> f64 {
529/// 0.0
530/// }
531/// }
532///
533/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
534/// let mut microstate = Microstate::new();
535/// microstate.extend_bodies([
536/// Body {
537/// properties: Point::new(Cartesian::from([0.0, 0.0])),
538/// sites: vec![CircleSiteProperties {
539/// position: Cartesian::from([0.0, 0.0]),
540/// radius: 0.5.try_into()?,
541/// }],
542/// },
543/// Body {
544/// properties: Point::new(Cartesian::from([1.4, 0.0])),
545/// sites: vec![CircleSiteProperties {
546/// position: Cartesian::from([0.0, 0.0]),
547/// radius: 1.0.try_into()?,
548/// }],
549/// },
550/// ])?;
551///
552/// let overlap = PolydisperseCircleOverlap {
553/// maximum_interaction_range: 1.5,
554/// };
555/// let site_pair_energy = overlap.site_pair_energy(
556/// µstate.sites()[0].properties,
557/// µstate.sites()[1].properties,
558/// );
559/// assert_eq!(site_pair_energy, f64::INFINITY);
560///
561/// let pairwise_cutoff = PairwiseCutoff(overlap);
562/// let total_energy = pairwise_cutoff.total_energy(µstate);
563/// assert_eq!(total_energy, f64::INFINITY);
564/// # Ok(())
565/// # }
566/// ```
567///
568/// # Derive macro
569///
570/// Use the [`SitePairEnergy`](macro@SitePairEnergy) derive macro to
571/// automatically implement the `SitePairEnergy` trait on a type.
572/// The implemented `site_pair_energy` sums the result of `site_pair_energy`
573/// over all fields. The implementation returns early when any one field returns
574/// infinity. The implemented `site_pair_energy_initial` behaves similarly.
575/// The derived `is_only_infinite_or_zero` returns true only when all fields
576/// also return true for the same method.
577///
578/// ```
579/// use hoomd_interaction::{
580/// MaximumInteractionRange, SitePairEnergy,
581/// pairwise::{AngularMask, Anisotropic, HardSphere},
582/// univariate::Boxcar,
583/// };
584/// use hoomd_vector::Cartesian;
585///
586/// #[derive(MaximumInteractionRange, SitePairEnergy)]
587/// struct SitePairInteraction {
588/// hard_disk: HardSphere,
589/// angular_mask: Anisotropic<AngularMask<Boxcar, Cartesian<2>>>,
590/// }
591/// ```
592pub trait SitePairEnergy<S> {
593 /// Evaluate the energy contribution from a pair of sites.
594 fn site_pair_energy(&self, site_properties_i: &S, site_properties_j: &S) -> f64;
595
596 /// Evaluate the energy contribution from a pair of sites *in the initial state*.
597 ///
598 /// Override this method in potentials that have both infinite or zero terms
599 /// and finite terms, such as the sum of a hard site-wall interaction plus
600 /// an attractive well. `site_pair_energy` should compute both terms and
601 /// `site_pair_energy_initial` should compute only the finite terms.
602 ///
603 /// [`PairwiseCutoff`] calls `site_pair_energy_initial` when evaluating the
604 /// energy of the initial state in a trial move. The infinite interaction
605 /// term can be assumed 0 in the initial state because no site will ever be
606 /// placed in an infinite energy configuration.
607 #[must_use]
608 #[inline]
609 fn site_pair_energy_initial(&self, site_properties_i: &S, site_properties_j: &S) -> f64 {
610 self.site_pair_energy(site_properties_i, site_properties_j)
611 }
612
613 /// Does this potential only ever return infinity or zero?
614 ///
615 /// Override this method and return `true` for e.g. hard particle
616 /// interactions that always return infinity or zero and **never** any other
617 /// value. When this method returns `true`, [`PairwiseCutoff`] skips the
618 /// initial energy computation and assumes it is zero.
619 #[must_use]
620 #[inline]
621 fn is_only_infinite_or_zero() -> bool {
622 false
623 }
624}
625
626/// Largest distance between two sites where the pairwise interaction may be non-zero.
627///
628/// [`PairwiseCutoff`] uses the provided maximum interaction range to
629/// efficiently compute only the needed interactions. All types that implement
630/// `SitePair*` traits must also implement [`MaximumInteractionRange`].
631///
632/// # Derive macro
633///
634/// Use the [`MaximumInteractionRange`](macro@MaximumInteractionRange) derive macro to
635/// automatically implement the `MaximumInteractionRange` trait on a type.
636///
637/// When the type has a field named `maximum_interaction_range`, the derived implementation
638/// returns it. When there is no such field, the derived implementation takes
639/// the maximum of the `maximum_interaction_range` over all fields in the struct.
640/// The former case is intended for use with custom site pair potentials and
641/// the latter is intended for use with multi-term Hamiltonian types.
642/// ```
643/// use hoomd_interaction::{
644/// External, MaximumInteractionRange, PairwiseCutoff, external::Linear,
645/// };
646/// use hoomd_vector::Cartesian;
647///
648/// #[derive(MaximumInteractionRange)]
649/// struct SitePairInteraction {
650/// // ...
651/// maximum_interaction_range: f64,
652/// }
653///
654/// #[derive(MaximumInteractionRange)]
655/// struct Hamiltonian {
656/// linear: External<Linear<Cartesian<2>>>,
657/// pairwise_cutoff: PairwiseCutoff<SitePairInteraction>,
658/// }
659/// ```
660pub trait MaximumInteractionRange {
661 /// The largest distance between two sites where the pairwise interaction may be non-zero.
662 fn maximum_interaction_range(&self) -> f64;
663}
664
665/// Compute the change in energy as a function of a single modified body.
666///
667/// Some trial moves apply to a single body at a time and use a Hamiltonian that
668/// implements `DeltaEnergyOne` to efficiently compute the change in energy.
669///
670/// The generic type names are:
671/// * `B`: The [`Body::properties`](hoomd_microstate::Body) type.
672/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
673/// * `X`: The spatial data structure type.
674/// * `C`: The [`boundary`](hoomd_microstate::boundary) condition type.
675///
676/// See the [Implementors](#implementors) section below for examples.
677///
678/// # Derive macro
679///
680/// Use the [`DeltaEnergyOne`](macro@DeltaEnergyOne) derive macro to
681/// automatically implement the `DeltaEnergyOne` trait on a type. The derived
682/// implementation sums the result of `delta_energy_one` over all fields in the
683/// struct (in the order in which fields are named in the struct definition).
684/// The sum short circuits and returns `f64::INFINITY` when any one field
685/// returns infinity.
686/// ```
687/// use hoomd_interaction::{
688/// DeltaEnergyOne, External, PairwiseCutoff, external::Linear,
689/// pairwise::Isotropic, univariate::Boxcar,
690/// };
691/// use hoomd_microstate::{Body, Microstate, property::Point};
692/// use hoomd_vector::Cartesian;
693///
694/// #[derive(DeltaEnergyOne)]
695/// struct Hamiltonian {
696/// linear: External<Linear<Cartesian<2>>>,
697/// pairwise_cutoff: PairwiseCutoff<Isotropic<Boxcar>>,
698/// }
699///
700/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
701/// let mut microstate = Microstate::new();
702/// microstate.extend_bodies([
703/// Body::point(Cartesian::from([0.0, 0.0])),
704/// Body::point(Cartesian::from([1.0, 0.0])),
705/// ])?;
706///
707/// let epsilon = 2.0;
708/// let (left, right) = (0.0, 1.5);
709/// let boxcar = Boxcar {
710/// epsilon,
711/// left,
712/// right,
713/// };
714/// let pairwise_cutoff = PairwiseCutoff(Isotropic {
715/// interaction: boxcar,
716/// r_cut: right,
717/// });
718///
719/// let linear = External(Linear {
720/// alpha: 10.0,
721/// plane_origin: Cartesian::default(),
722/// plane_normal: [0.0, 1.0].try_into()?,
723/// });
724///
725/// let hamiltonian = Hamiltonian {
726/// pairwise_cutoff,
727/// linear,
728/// };
729///
730/// let delta_energy = hamiltonian.delta_energy_one(
731/// µstate,
732/// 0,
733/// &Body::point([-1.0, 0.0].into()),
734/// );
735/// assert_eq!(delta_energy, -2.0);
736/// # Ok(())
737/// # }
738/// ```
739pub trait DeltaEnergyOne<B, S, X, C> {
740 /// Compute the change in energy.
741 ///
742 /// `initial_microstate` describes the initial configuration and `final_body`
743 /// describes the new body configuration. In the final configuration, the
744 /// body may have changed properties and/or sites. The index `body_index`
745 /// identifies which body in `initial_microstate` is changing.
746 ///
747 /// Returns:
748 /// ```math
749 /// \Delta E = E_\mathrm{final} - E_\mathrm{initial}
750 /// ```
751 #[must_use]
752 fn delta_energy_one(
753 &self,
754 initial_microstate: &Microstate<B, S, X, C>,
755 body_index: usize,
756 final_body: &Body<B, S>,
757 ) -> f64;
758}
759
760/// Compute the change in energy when a single body is inserted.
761///
762/// Some trial moves insert a single body at a time and use a Hamiltonian that
763/// implements `DeltaEnergyInsert` to efficiently compute the change in energy.
764///
765/// The generic type names are:
766/// * `B`: The [`Body::properties`](hoomd_microstate::Body) type.
767/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
768/// * `X`: The spatial data structure type.
769/// * `C`: The [`boundary`](hoomd_microstate::boundary) condition type.
770///
771/// See the [Implementors](#implementors) section below for examples.
772///
773/// # Derive macro
774///
775/// Use the [`DeltaEnergyInsert`](macro@DeltaEnergyInsert) derive macro to
776/// automatically implement the `DeltaEnergyInsert` trait on a type. The derived
777/// implementation sums the result of `delta_energy_insert` over all fields in the
778/// struct (in the order in which fields are named in the struct definition).
779/// The sum short circuits and returns `f64::INFINITY` when any one field
780/// returns infinity.
781/// ```
782/// use hoomd_interaction::{
783/// DeltaEnergyInsert, External, PairwiseCutoff, external::Linear,
784/// pairwise::Isotropic, univariate::Boxcar,
785/// };
786/// use hoomd_microstate::{Body, Microstate, property::Point};
787/// use hoomd_vector::Cartesian;
788///
789/// #[derive(DeltaEnergyInsert)]
790/// struct Hamiltonian {
791/// linear: External<Linear<Cartesian<2>>>,
792/// pairwise_cutoff: PairwiseCutoff<Isotropic<Boxcar>>,
793/// }
794///
795/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
796/// let mut microstate = Microstate::new();
797/// microstate.extend_bodies([Body::point(Cartesian::from([0.0, 4.0]))])?;
798///
799/// let epsilon = 2.0;
800/// let (left, right) = (0.0, 1.5);
801/// let boxcar = Boxcar {
802/// epsilon,
803/// left,
804/// right,
805/// };
806/// let pairwise_cutoff = PairwiseCutoff(Isotropic {
807/// interaction: boxcar,
808/// r_cut: right,
809/// });
810///
811/// let linear = External(Linear {
812/// alpha: 1.0,
813/// plane_origin: Cartesian::default(),
814/// plane_normal: [0.0, 1.0].try_into()?,
815/// });
816///
817/// let hamiltonian = Hamiltonian {
818/// pairwise_cutoff,
819/// linear,
820/// };
821///
822/// let new_body = Body::point(Cartesian::from([1.0, 4.0]));
823/// let delta_energy = hamiltonian.delta_energy_insert(µstate, &new_body);
824/// assert_eq!(delta_energy, 6.0);
825/// # Ok(())
826/// # }
827/// ```
828pub trait DeltaEnergyInsert<B, S, X, C> {
829 /// Compute the change in energy.
830 ///
831 /// `initial_microstate` describes the initial configuration and `new_body`
832 /// describes the new body configuration. The final configuration includes
833 /// all bodies in the initial microstate and `new_body`.
834 ///
835 /// Returns:
836 /// ```math
837 /// \Delta E = E_\mathrm{final} - E_\mathrm{initial}
838 /// ```
839 #[must_use]
840 fn delta_energy_insert(
841 &self,
842 initial_microstate: &Microstate<B, S, X, C>,
843 new_body: &Body<B, S>,
844 ) -> f64;
845}
846
847/// Compute the change in energy when a single body is removed.
848///
849/// Some trial moves remove a single body at a time and use a Hamiltonian that
850/// implements `DeltaEnergyRemove` to efficiently compute the change in energy.
851///
852/// The generic type names are:
853/// * `B`: The [`Body::properties`](hoomd_microstate::Body) type.
854/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
855/// * `X`: The spatial data structure type.
856/// * `C`: The [`boundary`](hoomd_microstate::boundary) condition type.
857///
858/// See the [Implementors](#implementors) section below for examples.
859///
860/// # Derive macro
861///
862/// Use the [`DeltaEnergyRemove`](macro@DeltaEnergyRemove) derive macro to
863/// automatically implement the `DeltaEnergyRemove` trait on a type. The derived
864/// implementation sums the result of `delta_energy_remove` over all fields in the
865/// struct (in the order in which fields are named in the struct definition).
866/// The sum short circuits and returns `f64::INFINITY` when any one field
867/// returns infinity.
868/// ```
869/// use hoomd_interaction::{
870/// DeltaEnergyRemove, External, PairwiseCutoff, external::Linear,
871/// pairwise::Isotropic, univariate::Boxcar,
872/// };
873/// use hoomd_vector::Cartesian;
874///
875/// #[derive(DeltaEnergyRemove)]
876/// struct Hamiltonian {
877/// linear: External<Linear<Cartesian<2>>>,
878/// pairwise_cutoff: PairwiseCutoff<Isotropic<Boxcar>>,
879/// }
880/// ```
881pub trait DeltaEnergyRemove<B, S, X, C> {
882 /// Compute the change in energy.
883 ///
884 /// `initial_microstate` describes the initial configuration and `body_index` is
885 /// the index of the body to remove. The final configuration includes all bodies
886 /// in the initial microstate except the body previously at `body_index`.
887 ///
888 /// Returns:
889 /// ```math
890 /// \Delta E = E_\mathrm{final} - E_\mathrm{initial}
891 /// ```
892 #[must_use]
893 fn delta_energy_remove(
894 &self,
895 initial_microstate: &Microstate<B, S, X, C>,
896 body_index: usize,
897 ) -> f64;
898}
899
900/// Sum all the forces and virials that act on a given body in a microstate.
901///
902/// See [`Rigid`] for an example.
903///
904/// The generic type names are:
905/// * `B`: The [`Body::properties`](hoomd_microstate::Body) type.
906/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
907/// * `C`: The [`boundary`](hoomd_microstate::boundary) condition type.
908pub trait NetBodyForceAndVirial<B, S, X, C> {
909 /// The type of the result force.
910 type Force: Outer;
911
912 /// Compute the net force and virial on a body in the microstate.
913 ///
914 /// `microstate` describes the configuration and `body_index` is the index
915 /// of the body to compute.
916 ///
917 /// Returns:
918 /// ```math
919 /// \left( \vec{F}_{body}, \mathbf{W}_{body} \right)
920 /// ```
921 #[must_use]
922 fn net_body_force_and_virial(
923 &self,
924 microstate: &Microstate<B, S, X, C>,
925 body_index: usize,
926 ) -> (Self::Force, <Self::Force as Outer>::Tensor);
927}
928
929/// Sum all the forces, virials, and torques that act on a given body in a microstate.
930///
931/// See [`Rigid`] for an example.
932///
933/// The generic type names are:
934/// * `B`: The [`Body::properties`](hoomd_microstate::Body) type.
935/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
936/// * `X`: The spatial data structure type.
937/// * `C`: The [`boundary`](hoomd_microstate::boundary) condition type.
938pub trait NetBodyForceVirialAndTorque<B, S, X, C> {
939 /// The type of the result force.
940 type Force: Wedge + Outer;
941
942 /// Compute the net force, virial, and torque on a body in the microstate.
943 ///
944 /// `microstate` describes the configuration and `body_index` is the index
945 /// of the body to compute.
946 ///
947 /// Returns:
948 /// ```math
949 /// \left( \vec{F}_{body}, \mathbf{W}_{body}, \vec{\tau}_{body} \right)
950 /// ```
951 #[must_use]
952 fn net_body_force_virial_and_torque(
953 &self,
954 microstate: &Microstate<B, S, X, C>,
955 body_index: usize,
956 ) -> (
957 Self::Force,
958 <Self::Force as Outer>::Tensor,
959 <Self::Force as Wedge>::Bivector,
960 );
961}
962
963/// Sum all the forces, virials, and torques that act on a given site in a microstate.
964///
965/// In molecular dynamics simulations, bodies move in response to the net force
966/// and torque applied to all sites in the body. As an intermediate step in that
967/// calculation, a type that describes a *force interaction model* must implement
968/// [`NetSiteForceVirialAndTorque`] and compute the net force and torque on a given
969/// *site* in the [`Microstate`].
970///
971/// The generic type names are:
972/// * `B`: The [`Body::properties`](hoomd_microstate::Body) type.
973/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
974/// * `X`: The spatial data structure type.
975/// * `C`: The [`boundary`](hoomd_microstate::boundary) condition type.
976///
977/// See the [Implementors](#implementors) section below for examples.
978///
979/// # Derive macro
980///
981/// Use the [`NetSiteForceVirialAndTorque`](macro@NetSiteForceVirialAndTorque) derive macro to
982/// automatically implement the `NetSiteForceVirialAndTorque` trait on a type. The derived
983/// implementation sums the result of `net_site_force_and_torque` over all fields in
984/// the struct (in the order in which fields are named in the struct definition).
985/// ```
986/// use hoomd_interaction::{
987/// External, NetSiteForceVirialAndTorque, PairwiseCutoff,
988/// external::ConstantForce, pairwise::Isotropic, univariate::LennardJones,
989/// };
990/// use hoomd_microstate::{Body, Microstate, property::Point};
991/// use hoomd_vector::Cartesian;
992///
993/// #[derive(NetSiteForceVirialAndTorque)]
994/// struct Hamiltonian {
995/// linear: External<ConstantForce<Cartesian<2>>>,
996/// pairwise_cutoff: PairwiseCutoff<Isotropic<LennardJones>>,
997/// }
998/// ```
999pub trait NetSiteForceVirialAndTorque<B, S, X, C> {
1000 /// The type of the result force.
1001 type Force: Wedge + Outer;
1002
1003 /// Compute the net force, virial, and torque on a given site.
1004 ///
1005 /// `microstate` describes the configuration and `site_index` is the index
1006 /// of the site to compute.
1007 ///
1008 /// Returns:
1009 /// ```math
1010 /// \left( \vec{F}_{site}, \mathbf{W}_{site}, \vec{\tau}_{site} \right)
1011 /// ```
1012 #[must_use]
1013 fn net_site_force_virial_and_torque(
1014 &self,
1015 microstate: &Microstate<B, S, X, C>,
1016 site_index: usize,
1017 ) -> (
1018 Self::Force,
1019 <Self::Force as Outer>::Tensor,
1020 <Self::Force as Wedge>::Bivector,
1021 );
1022}
1023
1024/// Sum all the forces and virials that act on a given site in a microstate.
1025///
1026/// In molecular dynamics simulations, bodies move in response to the net
1027/// force applied to all sites in the body. As an intermediate step in that
1028/// calculation, a type that describes a *force interaction model* must
1029/// implement [`NetSiteForceAndVirial`] and compute the net force and virial on
1030/// a given *site* in the [`Microstate`].
1031///
1032/// The generic type names are:
1033/// * `B`: The [`Body::properties`](hoomd_microstate::Body) type.
1034/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
1035/// * `X`: The spatial data structure type.
1036/// * `C`: The [`boundary`](hoomd_microstate::boundary) condition type.
1037///
1038/// See the [Implementors](#implementors) section below for examples.
1039///
1040/// # Derive macro
1041///
1042/// Use the [`NetSiteForceAndVirial`](macro@NetSiteForceAndVirial) derive macro to automatically
1043/// implement the `NetSiteForceAndVirial` trait on a type. The derived implementation
1044/// sums the result of `net_site_force` over all fields in the struct (in the
1045/// order in which fields are named in the struct definition).
1046/// ```
1047/// use hoomd_interaction::{
1048/// External, NetSiteForceAndVirial, PairwiseCutoff,
1049/// external::ConstantForce, pairwise::Isotropic, univariate::LennardJones,
1050/// };
1051/// use hoomd_microstate::{Body, Microstate, property::Point};
1052/// use hoomd_vector::Cartesian;
1053///
1054/// #[derive(NetSiteForceAndVirial)]
1055/// struct Hamiltonian {
1056/// linear: External<ConstantForce<Cartesian<2>>>,
1057/// pairwise_cutoff: PairwiseCutoff<Isotropic<LennardJones>>,
1058/// }
1059/// ```
1060pub trait NetSiteForceAndVirial<B, S, X, C> {
1061 /// The type of the result force.
1062 type Force: Outer;
1063
1064 /// Compute the net force and virial on a given site.
1065 ///
1066 /// `microstate` describes the configuration and `site_index` is the index
1067 /// of the site to compute.
1068 ///
1069 /// Returns:
1070 /// ```math
1071 /// \left( \vec{F}_{site}, \mathbf{W}_{site} \right)
1072 /// ```
1073 #[must_use]
1074 fn net_site_force_and_virial(
1075 &self,
1076 microstate: &Microstate<B, S, X, C>,
1077 site_index: usize,
1078 ) -> (Self::Force, <Self::Force as Outer>::Tensor);
1079}
1080
1081/// Compute the force and virial on a single site as a function of its properties.
1082///
1083/// The `SiteForceAndVirial` trait describes a type that can compute the force and virial on a
1084/// site *as a function only of that site's properties*.
1085///
1086/// The [`external`] module provides a number of commonly used implementations.
1087/// Combine them with [`External`] newtype for use with MD simulations.
1088///
1089/// The generic type names are:
1090/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
1091pub trait SiteForceAndVirial<S> {
1092 /// The type of the result force.
1093 type Force: Outer;
1094
1095 /// Evaluate the force and virial as a function of a single site's properties.
1096 fn site_force_and_virial(
1097 &self,
1098 site_properties: &S,
1099 ) -> (Self::Force, <Self::Force as Outer>::Tensor);
1100}
1101
1102/// Compute the force, virial, and torque on a single site as a function of its properties.
1103///
1104/// The `SiteForceVirialAndTorque` trait describes a type that can compute the force, virial, and torque
1105/// on a site *as a function only of that site's properties*.
1106///
1107/// The [`external`] module provides a number of commonly used implementations.
1108/// Combine them with [`External`] newtype for use with MD simulations.
1109///
1110/// The generic type names are:
1111/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
1112pub trait SiteForceVirialAndTorque<S> {
1113 /// The type of the result force.
1114 type Force: Wedge + Outer;
1115
1116 /// Evaluate the force, virial, and torque as a function of a single site's properties.
1117 fn site_force_virial_and_torque(
1118 &self,
1119 site_properties: &S,
1120 ) -> (
1121 Self::Force,
1122 <Self::Force as Outer>::Tensor,
1123 <Self::Force as Wedge>::Bivector,
1124 );
1125}
1126
1127/// Compute the pairwise force and virial on one site from another site.
1128///
1129/// The `SitePairForceAndVirial` trait describes a type that can compute the
1130/// force and virial on a site by another site *as a function of the two sites'
1131/// properties*.
1132///
1133/// The [`pairwise`] module provides a number of commonly used implementations.
1134/// Combine them with [`PairwiseCutoff`] newtype for use with MD simulations.
1135///
1136/// The generic type names are:
1137/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
1138pub trait SitePairForceAndVirial<S> {
1139 /// The type of the result force.
1140 type Force: Outer;
1141
1142 /// Evaluate the force and virial on site `i` caused by site `j`.
1143 fn site_pair_force_and_virial(
1144 &self,
1145 site_properties_i: &S,
1146 site_properties_j: &S,
1147 ) -> (Self::Force, <Self::Force as Outer>::Tensor);
1148}
1149
1150/// Compute the pairwise force, virial, and torque on one site from another site.
1151///
1152/// The `SitePairForceVirialAndTorque` trait describes a type that can compute
1153/// the force, virial, and torque on a site by another site *as a function of
1154/// the two sites' properties*.
1155///
1156/// The [`pairwise`] module provides a number of commonly used implementations.
1157/// Combine them with [`PairwiseCutoff`] newtype for use with MD simulations.
1158///
1159/// The generic type names are:
1160/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
1161pub trait SitePairForceVirialAndTorque<S> {
1162 /// The type of the result force.
1163 type Force: Wedge + Outer;
1164
1165 /// Evaluate the force, torque, and virial on site `i` caused by site `j`.
1166 fn site_pair_force_virial_and_torque(
1167 &self,
1168 site_properties_i: &S,
1169 site_properties_j: &S,
1170 ) -> (
1171 Self::Force,
1172 <Self::Force as Outer>::Tensor,
1173 <Self::Force as Wedge>::Bivector,
1174 );
1175}