interatomic 0.5.0

Library for calculating inter-particle interactions
Documentation
// Copyright 2023 Mikael Lund
//
// Licensed under the Apache license, version 2.0 (the "license");
// you may not use this file except in compliance with the license.
// You may obtain a copy of the license at
//
//     http://www.apache.org/licenses/license-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the license is distributed on an "as is" basis,
// without warranties or conditions of any kind, either express or implied.
// See the license for the specific language governing permissions and
// limitations under the license.

//! Implementation of the harmonic potential.

use super::IsotropicTwobodyEnergy;
use crate::Cutoff;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

/// Harmonic potential
///
/// $$ u(r) = \frac{1}{2} k (r - r_{eq})^2 $$
///
/// where $k$ is the spring constant and $r_{eq}$ is the equilibrium distance.
/// More information [here](https://en.wikipedia.org/wiki/Harmonic_oscillator).
///
/// # Examples
/// ~~~
/// use interatomic::twobody::{Harmonic, IsotropicTwobodyEnergy};
/// let harmonic = Harmonic::new(1.0, 0.5);
/// let distance: f64 = 2.0;
/// assert_eq!(harmonic.isotropic_twobody_energy(distance.powi(2)), 0.25);
/// ~~~
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(
    feature = "serde",
    derive(Deserialize, Serialize),
    serde(deny_unknown_fields)
)]
pub struct Harmonic {
    #[cfg_attr(feature = "serde", serde(rename = "req"))]
    eq_distance: f64,
    #[cfg_attr(feature = "serde", serde(rename = "k"))]
    spring_constant: f64,
}

impl Harmonic {
    /// Create a new harmonic potential with equilibrium distance and spring constant.
    pub const fn new(eq_distance: f64, spring_constant: f64) -> Self {
        Self {
            eq_distance,
            spring_constant,
        }
    }

    /// Equilibrium distance.
    pub const fn eq_distance(&self) -> f64 {
        self.eq_distance
    }

    /// Spring constant.
    pub const fn spring_constant(&self) -> f64 {
        self.spring_constant
    }
}

impl IsotropicTwobodyEnergy for Harmonic {
    #[inline(always)]
    fn isotropic_twobody_energy(&self, distance_squared: f64) -> f64 {
        0.5 * self.spring_constant * (distance_squared.sqrt() - self.eq_distance).powi(2)
    }

    #[inline(always)]
    fn isotropic_twobody_force(&self, distance_squared: f64) -> f64 {
        let r = distance_squared.sqrt();
        -self.spring_constant * (r - self.eq_distance) / (2.0 * r)
    }
}

impl Cutoff for Harmonic {
    fn cutoff(&self) -> f64 {
        f64::INFINITY
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use approx::assert_relative_eq;

    #[test]
    fn test_harmonic_force() {
        let h = Harmonic::new(2.0, 100.0);
        // At r=r_eq: force = 0
        assert_relative_eq!(h.isotropic_twobody_force(4.0), 0.0, epsilon = 1e-10);
        // At r=3 (stretched): negative (attractive, wants to contract)
        assert_relative_eq!(
            h.isotropic_twobody_force(9.0),
            -100.0 / 6.0,
            epsilon = 1e-10
        );
        // At r=1 (compressed): positive (repulsive, wants to expand)
        assert_relative_eq!(h.isotropic_twobody_force(1.0), 50.0, epsilon = 1e-10);
    }
}