ESPResSo
Extensible Simulation Package for Research on Soft Matter Systems
Loading...
Searching...
No Matches
stoner_wohlfarth_thermal.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2025 The ESPResSo project
3 *
4 * This file is part of ESPResSo.
5 *
6 * ESPResSo is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * ESPResSo is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include <config/config.hpp>
21
22#ifdef ESPRESSO_THERMAL_STONER_WOHLFARTH
23
25
27#include "cells.hpp"
28#include "constraints/Constraints.hpp"
29#include "constraints/HomogeneousMagneticField.hpp"
30#include "errorhandling.hpp"
31#include "random.hpp"
32#include "rotation.hpp"
33#include "thermostat.hpp"
35
36#include <nlopt.hpp>
37
38#include <cassert>
39#include <cmath>
40#include <numbers>
41#include <tuple>
42#include <utility>
43#include <vector>
44
45// small perturbation to avoid starting exactly at a stationary point
46constexpr static double eps_phi = 1e-3;
47// absolute error precision required for the optimiser
48constexpr static double eps_abs = 1e-15;
49// relative error precision required for the optimiser
50constexpr static double eps_rel = 1e-15;
51
52/**
53 * @brief Objective (energy) function for the Stoner-Wohlfarth phi minimisation.
54 *
55 * Evaluate the magnetic energy (normalized by the anisotropy field) for a
56 * given in-plane angle phi according to Eq. 5 in @cite mostarac25a.
57 * Assumes minima lie in the plane phi = zeta and uses trig identities
58 * to reduce the expression.
59 *
60 * @param n Number of optimization variables (should be 1: phi).
61 * @param x Pointer to variables; x[0] is the angle phi.
62 * @param grad If non-null, gradient is written to grad[0].
63 * @param my_func_data Pointer to a double[2] array with {theta, h}.
64 * @return Energy value for the given phi.
65 */
66static double phi_objective(unsigned n, const double *x, double *grad,
67 void *my_func_data) {
68 auto const phi = x[0];
69 auto const *params = reinterpret_cast<double const *>(my_func_data);
70 auto const theta = params[0];
71 auto const h = params[1];
72 if (grad) {
73 grad[0] = std::sin(2. * (phi - theta)) + 2. * h * std::sin(phi);
74 }
75 return -0.5 - 0.5 * std::cos(2. * (phi - theta)) - 2. * h * std::cos(phi);
76}
77
78/**
79 * @brief Find the in-plane angle phi corresponding to the correct
80 * energy minimum for the thermal Stoner-Wohlfarth particles.
81 *
82 * @param theta Angle between anisotropy director and external field (rad).
83 * @param h Reduced field (external + dipolar) normalised by H_k.
84 * @param phi0 Initial in-plane angle guess (rad).
85 * @param ani_param Inverse thermal energy factor (@f$1/(k_B T V)@f$ scaled).
86 * @param tau0_inv Attempt frequency inverse (1/tau0).
87 * @param dt Time increment for switching probability.
88 * @param noise Uniform random number in (0,1) used for the kinetic MC step.
89 * @return In-plane angle phi in range @f$ [0,2\pi) @f$.
90 */
91static double get_phi_at_energy_min(double theta, double h, double phi0,
92 double ani_param, double tau0_inv,
93 double dt, double const &noise) {
94
95 auto constexpr pi = std::numbers::pi_v<double>;
96 auto constexpr two_pi = 2. * pi;
97
98 // critical field, above which there is only one minimum (no need to do the
99 // thermal step); Eq. 6 in @cite mostarac25a.
100 auto const h_crit = std::pow(std::pow(std::sin(theta), 2. / 3.) +
101 std::pow(std::cos(theta), 2. / 3.),
102 -3. / 2.);
103 nlopt::opt opt(nlopt::LD_MMA, 1);
104 double params[] = {theta, h};
105
106 opt.set_min_objective(phi_objective, &params);
107 opt.set_ftol_rel(eps_rel);
108 opt.set_ftol_abs(eps_abs);
109 std::vector<double> phi(1);
110
111 // make initial guess from previous position plus an arbitrary perturbation
112 phi[0] = phi0 + eps_phi;
113 auto min1 = 0.;
114 opt.optimize(phi, min1);
115 auto const phi_min1 = std::fmod(phi[0], two_pi);
116 auto solution = phi_min1;
117 if (std::fabs(h) < h_crit) {
118 opt.set_max_objective(phi_objective, &params);
119 phi[0] = phi0 + eps_phi;
120 auto max1 = 0.;
121 opt.optimize(phi, max1);
122 auto const phi_max1 = std::fmod(phi[0], two_pi);
123 phi[0] = std::fmod(phi_max1 + pi, two_pi);
124 auto max2 = 0.;
125 opt.optimize(phi, max2);
126 // Eqs. 12 in @cite mostarac25a.
127 auto const b1 = std::abs(max1 - min1) * ani_param;
128 auto const b2 = std::abs(max2 - min1) * ani_param;
129 auto const b_min = (b1 < b2) ? b1 : b2;
130 // Eq. 13 in @cite mostarac25a.
131 auto const tau_inv = tau0_inv * exp(-b_min);
132 // switching probability (without backflip)
133 auto const p12 = 1. - exp(-dt * tau_inv);
134 // if MC move accepted, find the location of the other minimum
135 if (noise < p12) {
136 opt.set_min_objective(phi_objective, &params);
137 phi[0] = std::fmod(phi_min1 + pi + eps_phi, two_pi);
138 /*try to find another minimimum from the other side*/
139 auto min2 = 0.;
140 opt.optimize(phi, min2);
141 auto const phi_min2 = std::fmod(phi[0], two_pi);
142 solution = phi_min2;
143 }
144 }
145 return std::fmod(solution + two_pi, two_pi);
146}
147
148/**
149 * @brief Collect external homogeneous magnetic field from active constraints.
150 *
151 * Iterate over constraints and sum the homogeneous magnetic field vectors
152 * provided by @ref Constraints::HomogeneousMagneticField objects.
153 *
154 * @return The total external homogeneous magnetic field.
155 */
156static auto get_external_field() {
157 using HomogeneousMagneticField = ::Constraints::HomogeneousMagneticField;
158 Utils::Vector3d ext_fld = {0., 0., 0.};
159 auto &system = System::get_system();
160 for (auto const &constraint : *system.constraints) {
161 auto ptr = std::dynamic_pointer_cast<HomogeneousMagneticField>(constraint);
162 if (ptr) {
163 ext_fld += ptr->H();
164 }
165 }
166 return ext_fld;
167}
168
169/**
170 * @brief Simplified Stoner-Wohlfarth update in field-free case.
171 *
172 * @param[in,out] p Virtual particle to update.
173 * @param e_k Anisotropy director of the reference particle.
174 * @param kT Thermal energy from thermostat.
175 * @param noise Uniform random number in (0,1) used for the kinetic MC step.
176 */
178 double const kT, double const noise) {
179 auto constexpr pi = std::numbers::pi_v<double>;
180 auto const ani_param = p.magnetic_anisotropy_energy() / kT;
181 auto const tau_inv = p.stoner_wohlfarth_tau0_inv() * std::exp(-ani_param);
182 auto const p12 = 1. - std::exp(-p.stoner_wohlfarth_dt_incr() * tau_inv);
183 auto const kernel = [&](bool flip) {
184 auto const sat_mag = (flip ? -1. : +1.) * p.saturation_magnetization();
185 auto const [quat, dipm] = convert_dip_to_quat(sat_mag * e_k);
186 p.stoner_wohlfarth_phi_0() = flip ? pi : 0.;
187 p.dipm() = dipm;
188 p.quat() = quat;
189 };
190 auto const flip = noise < p12;
191 if (p.stoner_wohlfarth_phi_0() == 0.) {
192 kernel(flip);
193 } else if (p.stoner_wohlfarth_phi_0() == pi) {
194 kernel(not flip);
195 } else {
196 auto const dist_0 = std::abs(p.stoner_wohlfarth_phi_0() - 0.);
197 auto const dist_pi = std::abs(p.stoner_wohlfarth_phi_0() - pi);
198 // Compare the differences and determine the nearest angle (simplifies
199 // down to an XNOR operation, i.e. equality operator for boolean types)
200 kernel(flip == (dist_0 < dist_pi));
201 }
202}
203
204/**
205 * @brief Update virtual site dipole moment according to the full in-field
206 * (incl. dipole field) thermal Stoner-Wohlfarth model (incl. the kinetic MC
207 * step)
208 *
209 * @param[in,out] p Virtual particle to update (modified).
210 * @param e_k Anisotropy director of the reference particle.
211 * @param ext_fld_dpl External homogeneous magnetic field + total dipolar field
212 * acting on the particle.
213 * @param kT Thermal energy from thermostat.
214 * @param noise Uniform random number in (0,1) used for the kinetic MC step.
215 */
217 Utils::Vector3d const &ext_fld_dpl,
218 double const kT, double const noise) {
219
220 auto constexpr pi = std::numbers::pi_v<double>;
221 auto constexpr pi_half = pi / 2.;
222
223 // reduced field; Eq. 4 in @cite mostarac25a.
224 auto h = ext_fld_dpl.norm() * p.magnetic_anisotropy_field_inv();
225 auto e_h = ext_fld_dpl.normalized();
226 auto theta = std::acos(e_h * e_k);
227 if (theta > pi_half) {
228 theta = pi - theta;
229 h = -h;
230 e_h = -e_h;
231 }
232 auto const rot_axis =
233 vector_product(vector_product(e_h, e_k), e_h).normalized();
234 auto const ani_param = p.magnetic_anisotropy_energy() / kT;
235 auto const phi = get_phi_at_energy_min(
236 theta, h, p.stoner_wohlfarth_phi_0(), ani_param,
238 auto const mom = e_h * std::cos(phi) + rot_axis * std::sin(phi);
239 p.stoner_wohlfarth_phi_0() = phi;
240 auto const [quat, dipm] =
242 p.dipm() = dipm;
243 p.quat() = quat;
244}
245
246/**
247 * @brief Run magnetodynamics update for local virtual particles.
248 *
249 * Iterate over local particles and update the dipole moment of virtual
250 * particles according to the thermal Stoner-Wohlfarth model.
251 * Collect active homogeneous external magnetic fields from constraints and
252 * add the per-particle dipolar contribution before performing either the
253 * simplified no-field update or the full thermal Stoner-Wohlfarth update.
254 *
255 * @param cell_structure CellStructure providing access to local particles.
256 * @param thermostat thermostat used to access Philox RNG state and seeds.
257 */
259 Thermostat::Thermostat const &thermostat) {
260 // collect HomogeneousMagneticFields if active
261 auto const ext_fld = get_external_field();
262 auto const kT = thermostat.kT;
263 cell_structure.for_each_local_particle([&](Particle &p) {
264 if (not p.is_virtual() or not p.stoner_wohlfarth_is_enabled()) {
265 return;
266 }
267
268 auto *p_ref = get_reference_particle(cell_structure, p);
269 if (not p_ref) {
270 return;
271 }
272 assert(thermostat.thermo_switch & THERMO_LANGEVIN);
273 auto const &langevin = *thermostat.langevin;
274 auto const e_k = p_ref->calc_director();
275 auto const ext_fld_dpl = ext_fld + p.dip_fld();
276 auto const random_ints =
277 Random::philox_4_uint64s<RNGSalt::THERMAL_STONER_WOHLFARTH>(
278 langevin.rng_counter(), langevin.rng_seed(), p.id());
279 auto const noise = Utils::uniform(random_ints[0]);
280 if (ext_fld_dpl.norm2() == 0.) {
281 stoner_wohlfarth_no_field(p, e_k, kT, noise);
282 } else {
283 // full Stoner-Wohlfarth update with external + dipolar field
284 stoner_wohlfarth_main(p, e_k, ext_fld_dpl, kT, noise);
285 }
286 });
287}
288
289#endif // ESPRESSO_THERMAL_STONER_WOHLFARTH
@ THERMO_LANGEVIN
This file contains everything related to the global cell structure / cell system.
Describes a cell structure / cell system.
void for_each_local_particle(ParticleUnaryOp &&f, bool parallel=true) const
Run a kernel on all local particles.
std::shared_ptr< LangevinThermostat > langevin
int thermo_switch
Bitmask of currently active thermostats.
double kT
Thermal energy of the simulated heat bath.
T norm() const
Definition Vector.hpp:160
Vector normalized() const
Definition Vector.hpp:178
__device__ void vector_product(float const *a, float const *b, float *out)
This file contains the errorhandling code for severe errors, like a broken bond or illegal parameter ...
System & get_system()
constexpr double uniform(uint64_t in)
Uniformly map unsigned integer to double.
Definition uniform.hpp:36
Random number generation using Philox.
Particle * get_reference_particle(CellStructure &cell_structure, Particle const &p)
Get real particle tracked by a virtual site.
Definition relative.cpp:75
This file contains all subroutines required to process rotational motion.
std::pair< Utils::Quaternion< double >, double > convert_dip_to_quat(const Utils::Vector3d &dip)
convert a dipole moment to quaternions and dipolar strength
Definition rotation.hpp:109
static SteepestDescentParameters params
Currently active steepest descent instance.
static double phi_objective(unsigned n, const double *x, double *grad, void *my_func_data)
Objective (energy) function for the Stoner-Wohlfarth phi minimisation.
void stoner_wohlfarth_no_field(Particle &p, Utils::Vector3d const &e_k, double const kT, double const noise)
Simplified Stoner-Wohlfarth update in field-free case.
void run_magnetodynamics(CellStructure &cell_structure, Thermostat::Thermostat const &thermostat)
Run magnetodynamics update for local virtual particles.
static auto get_external_field()
Collect external homogeneous magnetic field from active constraints.
static constexpr double eps_abs
static constexpr double eps_rel
static double get_phi_at_energy_min(double theta, double h, double phi0, double ani_param, double tau0_inv, double dt, double const &noise)
Find the in-plane angle phi corresponding to the correct energy minimum for the thermal Stoner-Wohlfa...
static constexpr double eps_phi
static void stoner_wohlfarth_main(Particle &p, Utils::Vector3d const &e_k, Utils::Vector3d const &ext_fld_dpl, double const kT, double const noise)
Update virtual site dipole moment according to the full in-field (incl.
Struct holding all information for one particle.
Definition Particle.hpp:450
auto const & dip_fld() const
Definition Particle.hpp:583
auto const & magnetic_anisotropy_energy() const
Definition Particle.hpp:569
auto const & stoner_wohlfarth_phi_0() const
Definition Particle.hpp:557
auto is_virtual() const
Definition Particle.hpp:603
auto const & magnetic_anisotropy_field_inv() const
Definition Particle.hpp:563
auto const & quat() const
Definition Particle.hpp:532
auto const & stoner_wohlfarth_tau0_inv() const
Definition Particle.hpp:573
auto const & saturation_magnetization() const
Definition Particle.hpp:559
auto const & stoner_wohlfarth_dt_incr() const
Definition Particle.hpp:577
auto const & stoner_wohlfarth_is_enabled() const
Definition Particle.hpp:553
auto const & dipm() const
Definition Particle.hpp:548
auto const & id() const
Definition Particle.hpp:469