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 /// TODO
919 #[must_use]
920 fn net_body_force_and_virial(
921 &self,
922 microstate: &Microstate<B, S, X, C>,
923 body_index: usize,
924 ) -> (Self::Force, <Self::Force as Outer>::Tensor);
925}
926
927/// Sum all the forces, virials, and torques that act on a given body in a microstate.
928///
929/// See [`Rigid`] for an example.
930///
931/// The generic type names are:
932/// * `B`: The [`Body::properties`](hoomd_microstate::Body) type.
933/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
934/// * `X`: The spatial data structure type.
935/// * `C`: The [`boundary`](hoomd_microstate::boundary) condition type.
936pub trait NetBodyForceVirialAndTorque<B, S, X, C> {
937 /// The type of the result force.
938 type Force: Wedge + Outer;
939
940 /// Compute the net force, virial, and torque on a body in the microstate.
941 ///
942 /// `microstate` describes the configuration and `body_index` is the index
943 /// of the body to compute.
944 ///
945 /// Returns:
946 /// TODO
947 #[must_use]
948 fn net_body_force_virial_and_torque(
949 &self,
950 microstate: &Microstate<B, S, X, C>,
951 body_index: usize,
952 ) -> (
953 Self::Force,
954 <Self::Force as Outer>::Tensor,
955 <Self::Force as Wedge>::Bivector,
956 );
957}
958
959/// Sum all the forces, virials, and torques that act on a given site in a microstate.
960///
961/// In molecular dynamics simulations, bodies move in response to the net force
962/// and torque applied to all sites in the body. As an intermediate step in that
963/// calculation, a type that describes a *force interaction model* must implement
964/// [`NetSiteForceVirialAndTorque`] and compute the net force and torque on a given
965/// *site* in the [`Microstate`].
966///
967/// The generic type names are:
968/// * `B`: The [`Body::properties`](hoomd_microstate::Body) type.
969/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
970/// * `X`: The spatial data structure type.
971/// * `C`: The [`boundary`](hoomd_microstate::boundary) condition type.
972///
973/// See the [Implementors](#implementors) section below for examples.
974///
975/// # Derive macro
976///
977/// Use the [`NetSiteForceVirialAndTorque`](macro@NetSiteForceVirialAndTorque) derive macro to
978/// automatically implement the `NetSiteForceVirialAndTorque` trait on a type. The derived
979/// implementation sums the result of `net_site_force_and_torque` over all fields in
980/// the struct (in the order in which fields are named in the struct definition).
981/// ```
982/// use hoomd_interaction::{
983/// External, NetSiteForceVirialAndTorque, PairwiseCutoff,
984/// external::ConstantForce, pairwise::Isotropic, univariate::LennardJones,
985/// };
986/// use hoomd_microstate::{Body, Microstate, property::Point};
987/// use hoomd_vector::Cartesian;
988///
989/// #[derive(NetSiteForceVirialAndTorque)]
990/// struct Hamiltonian {
991/// linear: External<ConstantForce<Cartesian<2>>>,
992/// pairwise_cutoff: PairwiseCutoff<Isotropic<LennardJones>>,
993/// }
994/// ```
995pub trait NetSiteForceVirialAndTorque<B, S, X, C> {
996 /// The type of the result force.
997 type Force: Wedge + Outer;
998
999 /// Compute the net force, virial, and torque on a given site.
1000 ///
1001 /// `microstate` describes the configuration and `site_index` is the index
1002 /// of the site to compute.
1003 ///
1004 /// Returns:
1005 ///
1006 /// TODO
1007 #[must_use]
1008 fn net_site_force_virial_and_torque(
1009 &self,
1010 microstate: &Microstate<B, S, X, C>,
1011 site_index: usize,
1012 ) -> (
1013 Self::Force,
1014 <Self::Force as Outer>::Tensor,
1015 <Self::Force as Wedge>::Bivector,
1016 );
1017}
1018
1019/// Sum all the forces and virials that act on a given site in a microstate.
1020///
1021/// In molecular dynamics simulations, bodies move in response to the net
1022/// force applied to all sites in the body. As an intermediate step in that
1023/// calculation, a type that describes a *force interaction model* must
1024/// implement [`NetSiteForceAndVirial`] and compute the net force and virial on
1025/// a given *site* in the [`Microstate`].
1026///
1027/// The generic type names are:
1028/// * `B`: The [`Body::properties`](hoomd_microstate::Body) type.
1029/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
1030/// * `X`: The spatial data structure type.
1031/// * `C`: The [`boundary`](hoomd_microstate::boundary) condition type.
1032///
1033/// See the [Implementors](#implementors) section below for examples.
1034///
1035/// # Derive macro
1036///
1037/// Use the [`NetSiteForceAndVirial`](macro@NetSiteForceAndVirial) derive macro to automatically
1038/// implement the `NetSiteForceAndVirial` trait on a type. The derived implementation
1039/// sums the result of `net_site_force` over all fields in the struct (in the
1040/// order in which fields are named in the struct definition).
1041/// ```
1042/// use hoomd_interaction::{
1043/// External, NetSiteForceAndVirial, PairwiseCutoff,
1044/// external::ConstantForce, pairwise::Isotropic, univariate::LennardJones,
1045/// };
1046/// use hoomd_microstate::{Body, Microstate, property::Point};
1047/// use hoomd_vector::Cartesian;
1048///
1049/// #[derive(NetSiteForceAndVirial)]
1050/// struct Hamiltonian {
1051/// linear: External<ConstantForce<Cartesian<2>>>,
1052/// pairwise_cutoff: PairwiseCutoff<Isotropic<LennardJones>>,
1053/// }
1054/// ```
1055pub trait NetSiteForceAndVirial<B, S, X, C> {
1056 /// The type of the result force.
1057 type Force: Outer;
1058
1059 /// Compute the net force and virial on a given site.
1060 ///
1061 /// `microstate` describes the configuration and `site_index` is the index
1062 /// of the site to compute.
1063 ///
1064 /// Returns:
1065 ///
1066 /// TODO
1067 #[must_use]
1068 fn net_site_force_and_virial(
1069 &self,
1070 microstate: &Microstate<B, S, X, C>,
1071 site_index: usize,
1072 ) -> (Self::Force, <Self::Force as Outer>::Tensor);
1073}
1074
1075/// Compute the force and virial on a single site as a function of its properties.
1076///
1077/// The `SiteForceAndVirial` trait describes a type that can compute the force and virial on a
1078/// site *as a function only of that site's properties*.
1079///
1080/// The [`external`] module provides a number of commonly used implementations.
1081/// Combine them with [`External`] newtype for use with MD simulations.
1082///
1083/// The generic type names are:
1084/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
1085pub trait SiteForceAndVirial<S> {
1086 /// The type of the result force.
1087 type Force: Outer;
1088
1089 /// Evaluate the force and virial as a function of a single site's properties.
1090 fn site_force_and_virial(
1091 &self,
1092 site_properties: &S,
1093 ) -> (Self::Force, <Self::Force as Outer>::Tensor);
1094}
1095
1096/// Compute the force, virial, and torque on a single site as a function of its properties.
1097///
1098/// The `SiteForceVirialAndTorque` trait describes a type that can compute the force, virial, and torque
1099/// on a site *as a function only of that site's properties*.
1100///
1101/// The [`external`] module provides a number of commonly used implementations.
1102/// Combine them with [`External`] newtype for use with MD simulations.
1103///
1104/// The generic type names are:
1105/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
1106pub trait SiteForceVirialAndTorque<S> {
1107 /// The type of the result force.
1108 type Force: Wedge + Outer;
1109
1110 /// Evaluate the force, virial, and torque as a function of a single site's properties.
1111 fn site_force_virial_and_torque(
1112 &self,
1113 site_properties: &S,
1114 ) -> (
1115 Self::Force,
1116 <Self::Force as Outer>::Tensor,
1117 <Self::Force as Wedge>::Bivector,
1118 );
1119}
1120
1121/// Compute the pairwise force and virial on one site from another site.
1122///
1123/// The `SitePairForceAndVirial` trait describes a type that can compute the
1124/// force and virial on a site by another site *as a function of the two sites'
1125/// properties*.
1126///
1127/// The [`pairwise`] module provides a number of commonly used implementations.
1128/// Combine them with [`PairwiseCutoff`] newtype for use with MD simulations.
1129///
1130/// The generic type names are:
1131/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
1132pub trait SitePairForceAndVirial<S> {
1133 /// The type of the result force.
1134 type Force: Outer;
1135
1136 /// Evaluate the force and virial on site `i` caused by site `j`.
1137 fn site_pair_force_and_virial(
1138 &self,
1139 site_properties_i: &S,
1140 site_properties_j: &S,
1141 ) -> (Self::Force, <Self::Force as Outer>::Tensor);
1142}
1143
1144/// Compute the pairwise force, virial, and torque on one site from another site.
1145///
1146/// The `SitePairForceVirialAndTorque` trait describes a type that can compute
1147/// the force, virial, and torque on a site by another site *as a function of
1148/// the two sites' properties*.
1149///
1150/// The [`pairwise`] module provides a number of commonly used implementations.
1151/// Combine them with [`PairwiseCutoff`] newtype for use with MD simulations.
1152///
1153/// The generic type names are:
1154/// * `S`: The [`Site::properties`](hoomd_microstate::Site) type.
1155pub trait SitePairForceVirialAndTorque<S> {
1156 /// The type of the result force.
1157 type Force: Wedge + Outer;
1158
1159 /// Evaluate the force, torque, and virial on site `i` caused by site `j`.
1160 fn site_pair_force_virial_and_torque(
1161 &self,
1162 site_properties_i: &S,
1163 site_properties_j: &S,
1164 ) -> (
1165 Self::Force,
1166 <Self::Force as Outer>::Tensor,
1167 <Self::Force as Wedge>::Bivector,
1168 );
1169}