Phyth v0.1.0
A dimensional safe C++ header-only mechanics and electromagnetics simulation library.
Loading...
Searching...
No Matches
MagneticDipole.hpp
Go to the documentation of this file.
1#ifndef PHYTH_MAGNETIC_DIPOLE_HPP
2#define PHYTH_MAGNETIC_DIPOLE_HPP
3
4#include "MagneticSource.hpp"
5#include "ChargeSource.hpp"
10
40 public MagneticSource,
41 public ChargeSource {
42 public:
71
80 void SetComputeForcesFunction(const std::function<void(MagneticDipole *)> &func) noexcept {
81 Particle::SetComputeForcesFunction([func](Particle *p) {
82 func(dynamic_cast<MagneticDipole *>(p));
83 });
84 }
85
102 const auto r = point - position_;
103 const auto dist = r.Length();
104
106 throw std::runtime_error("MagneticDipole: field diverges at dipole location");
107 }
108
109 if (fixed_ || position_history_.GetSize() < 4) {
110 const auto hat_r = r / dist;
111 constexpr auto coeff = Consts::mu_0 / (4 * Consts::pi);
112 return coeff * (3 * moment_.Dot(hat_r) * hat_r - moment_) / Utils::cube(dist);
113 }
114
115 try {
121
122 const auto R = point - retarded_pos;
123 const auto R_mag = R.Length();
125 throw std::runtime_error("MagneticDipole: field diverges at dipole location");
126 }
127 const auto hat_R = R / R_mag;
128 constexpr auto coeff = Consts::mu_0 / (4 * Consts::pi);
129
130 const auto term1 = coeff * (3 * retarded_m.Dot(hat_R) * hat_R - retarded_m) / Utils::cube(R_mag);
131 const auto term2 = coeff * (3 * retarded_m_dot.Dot(hat_R) * hat_R - retarded_m_dot) /
132 (Consts::c * R_mag * R_mag);
133 const auto term3 = coeff * retarded_m_ddot.Cross(hat_R).Cross(hat_R) / (Consts::c * Consts::c * R_mag);
134
135 return term1 + term2 + term3;
136 } catch (const std::runtime_error &) {
137 const auto hat_r = r / dist;
138 constexpr auto coeff = Consts::mu_0 / (4 * Consts::pi);
139 return coeff * (3 * moment_.Dot(hat_r) * hat_r - moment_) / Utils::cube(dist);
140 }
141 }
142
160 const auto r = point - position_;
161 const auto dist = r.Length();
162
164 throw std::runtime_error("MagneticDipole: field diverges at dipole location");
165 }
166
167 if (fixed_ || position_history_.GetSize() < 4 || velocity_.Length() < Quantity<MeterPerSecond>(
170 const auto hat_r = r.Normalized();
171 return Consts::k_E * (3 * p.Dot(hat_r) * hat_r - p) / Utils::cube(dist);
172 }
173
174 try {
177
181
182 const auto R = point - retarded_pos;
183 const auto R_mag = R.Length();
185 throw std::runtime_error("MagneticDipole: field diverges at dipole location");
186 }
187 const auto hat_R = R / R_mag;
188
189 const auto term1 = Consts::k_E * (3 * retarded_p.Dot(hat_R) * hat_R - retarded_p) / Utils::cube(R_mag);
190 const auto term2 = Consts::k_E * (3 * retarded_p_dot.Dot(hat_R) * hat_R - retarded_p_dot) /
191 (Consts::c * R_mag * R_mag);
192 const auto term3 = Consts::k_E * retarded_p_ddot.Cross(hat_R).Cross(hat_R) /
194
195 return term1 + term2 + term3;
196 } catch (const std::runtime_error &) {
198 const auto hat_r = r.Normalized();
199 return Consts::k_E * (3 * p.Dot(hat_r) * hat_r - p) / Utils::cube(dist);
200 }
201 }
202
221 throw std::runtime_error("MagneticDipole: potential diverges at dipole location");
222 }
223
224 if (fixed_ || position_history_.GetSize() < 4 || velocity_.Length() < Quantity<MeterPerSecond>(
226 return 0_V;
227 }
228
229 try {
234
235 const auto R_vec = point - retarded_pos;
236 const auto R = R_vec.Length();
237 const auto hat_R = R_vec.Normalized();
238
239 const auto beta = retarded_v / Consts::c;
240 const auto denominator_factor = Scalar(1) - hat_R.Dot(beta);
241
243 throw std::runtime_error("MagneticDipole: line-of-sight aligns with velocity at light speed");
244 }
245
246 return Consts::k_E * retarded_p.Dot(hat_R) / (R * R * denominator_factor);
247 } catch (const std::runtime_error &) {
248 return 0_V;
249 }
250 }
251
258 return 0_C;
259 }
260
269 return (point - position_).Length();
270 }
271
277
289
298 const auto B = field_source.GetMagneticFieldAt(position_);
299 const auto torque = moment_.Cross(B).As<NewtonMeter>();
301 }
302
314
323 const auto E = field_source.GetElectricFieldAt(position_);
325 const auto torque = p.Cross(E).As<NewtonMeter>();
327 }
328
334
340
352
363
370 void Integrate() noexcept override {
371 Particle::Integrate();
372
373 if (moment_of_inertia_ > 0_kgm2) {
374 const auto alpha = external_torque_ / moment_of_inertia_;
375 angular_velocity_ += alpha * Config::dt;
376
377 if (const auto delta_angle = angular_velocity_ * Config::dt; delta_angle.Length() > 0_rad) {
378 const auto axis = delta_angle.Normalized();
379 const auto angle = delta_angle.Length();
380
383 orientation_ = orientation_.Normalized();
384
385 moment_ = orientation_.Rotated(initial_moment_.Normalized()) * initial_moment_.Length();
386 }
387
388 external_torque_ = {};
389 }
390
394 moment_history_.Register(moment_);
395
397 electric_dipole_history_.Register(p);
398 }
399
410 moment_ = orientation_.Rotated(initial_moment_.Normalized()) * initial_moment_.Length();
411 }
412
415 return moment_;
416 }
417
420 return orientation_;
421 }
422
431 orientation_ = q.Normalized();
432 moment_ = orientation_.Rotated(initial_moment_.Normalized()) * initial_moment_.Length();
433 }
434
439
444
449
454
455 private:
458
463
468
470
484 ) {
485 const auto beta = v / Consts::c;
486 const auto beta2 = beta.LengthSquared();
487 if (beta2 > Scalar(1 - Config::epsilon)) {
488 return -v.Cross(m) / (Consts::c * Consts::c);
489 }
490 const auto gamma = Scalar(1) / Utils::sqrt(Scalar(1) - beta2);
491 return -gamma * v.Cross(m) / (Consts::c * Consts::c);
492 }
493
499
503 if (offset == 0_s) return position_;
504 return position_history_.GetValueByOffset(offset);
505 }
506
510 if (offset == 0_s) return velocity_;
511 return velocity_history_.GetValueByOffset(offset);
512 }
513
517 if (offset == 0_s) return external_force_ / mass_;
518 return acceleration_history_.GetValueByOffset(offset);
519 }
520
524 if (offset == 0_s) return moment_;
525 return moment_history_.GetValueByOffset(offset);
526 }
527
532 return electric_dipole_history_.GetValueByOffset(offset);
533 }
534
543 if (position_history_.IsEmpty()) {
544 return 0_s;
545 }
546
549
550 const Quantity<Second> max_dt = static_cast<double>(position_history_.GetSize())
551 * Config::dt;
552 dt_delay = std::clamp(dt_delay, Quantity<Second>(0.0), max_dt);
553
554 for (int iter = 0; iter < Config::max_iterations; ++iter) {
555 const auto pos_r = GetPositionAtOffset(dt_delay);
556 const auto v_r = GetVelocityAtOffset(dt_delay);
557
559 const Quantity<Meter> R = R_vec.Length();
561
563
565 return dt_delay;
566 }
567
569
571 break;
572 }
573
574 const Quantity<Second> delta = f / df;
576 dt_delay = std::clamp(dt_delay, Quantity<Second>(0.0), max_dt);
577 }
578
580 }
581
591 auto ComputeResidual = [&](const Quantity<Second> dt) -> Quantity<Meter> {
592 const auto pos_r = GetPositionAtOffset(dt);
593 const Quantity<Meter> R = (point - pos_r).Length();
594 return R - Consts::c * Config::dt;
595 };
596
599
600 if (f_low * f_high > 0_m2) {
602 }
603
604 for (int iter = 0; iter < Config::max_iterations; ++iter) {
605 const Quantity<Second> dt_mid = (dt_low + dt_high) / 2;
607
609 return dt_mid;
610 }
611
612 if (f_low * f_mid < 0_m2) {
613 dt_high = dt_mid;
614 f_high = f_mid;
615 } else {
616 dt_low = dt_mid;
617 f_low = f_mid;
618 }
619 }
620
621 return (dt_low + dt_high) / 2;
622 }
623
627 [[nodiscard]] Vector3<decltype(0_Am2 / 1_s)>
629 const auto size = moment_history_.GetSize();
630 const auto idx = (retarded_dt / Config::dt).To<size_t>();
631
632 if (size < 2) return {};
633
634 if (idx < 1) {
635 const auto m0 = GetMomentAtOffset(retarded_dt);
637 return (m1 - m0) / Config::dt;
638 }
639 if (idx >= size - 1) {
640 const auto m0 = GetMomentAtOffset(retarded_dt);
642 return (m0 - m1) / Config::dt;
643 }
646 return (m_next - m_prev) / (2 * Config::dt);
647 }
648
652 [[nodiscard]] Vector3<decltype(0_Am2 / 1_s / 1_s)>
654 const auto size = moment_history_.GetSize();
655 const auto idx = (retarded_dt / Config::dt).To<size_t>();
656
657 if (size < 3) return {};
658
659 if (idx < 2) {
660 const auto m0 = GetMomentAtOffset(retarded_dt);
662 const auto m2 = GetMomentAtOffset(retarded_dt + 2 * Config::dt);
663 return (m2 - 2 * m1 + m0) / (Config::dt * Config::dt);
664 }
665 if (idx >= size - 1) {
666 const auto m0 = GetMomentAtOffset(retarded_dt);
668 const auto m2 = GetMomentAtOffset(retarded_dt - 2 * Config::dt);
669 return (m0 - 2 * m1 + m2) / (Config::dt * Config::dt);
670 }
674 return (m_next - 2 * m_curr + m_prev) / (Config::dt * Config::dt);
675 }
676
680 [[nodiscard]] Vector3<decltype(1_Cm / 1_s)>
682 const auto size = electric_dipole_history_.GetSize();
683 const auto idx = (retarded_dt / Config::dt).To<size_t>();
684
685 if (size < 2) return {};
686
687 if (idx < 1) {
690 return (p1 - p0) / Config::dt;
691 }
692 if (idx >= size - 1) {
695 return (p0 - p1) / Config::dt;
696 }
699 return (p_next - p_prev) / (2 * Config::dt);
700 }
701
705 [[nodiscard]] Vector3<decltype(1_Cm / 1_s / 1_s)>
707 const auto size = electric_dipole_history_.GetSize();
708 const auto idx = (retarded_dt / Config::dt).To<size_t>();
709
710 if (size < 3) return {};
711
712 if (idx < 2) {
716 return (p2 - 2 * p1 + p0) / (Config::dt * Config::dt);
717 }
718 if (idx >= size - 1) {
722 return (p0 - 2 * p1 + p2) / (Config::dt * Config::dt);
723 }
727 return (p_next - 2 * p_curr + p_prev) / (Config::dt * Config::dt);
728 }
729
733 [[nodiscard]] Vector3<decltype(1_mps2 / 1_s)>
735 const auto size = acceleration_history_.GetSize();
736 const auto idx = (retarded_dt / Config::dt).To<size_t>();
737
738 if (size < 2) return {};
739
740 if (idx < 1) {
743 return (a1 - a0) / Config::dt;
744 }
745 if (idx >= size - 1) {
748 return (a0 - a1) / Config::dt;
749 }
752 return (a_next - a_prev) / (2 * Config::dt);
753 }
754
765 const auto m = moment_;
766
768
769 auto pos_x = position_;
770 pos_x.x += eps_m;
771 auto pos_x_neg = position_;
772 pos_x_neg.x -= eps_m;
773 const auto dB_dx = (field_source.GetMagneticFieldAt(pos_x) -
774 field_source.GetMagneticFieldAt(pos_x_neg)) / (2 * eps_m);
775 force.x = m.Dot(dB_dx);
776
777 auto pos_y = position_;
778 pos_y.y += eps_m;
779 auto pos_y_neg = position_;
780 pos_y_neg.y -= eps_m;
781 const auto dB_dy = (field_source.GetMagneticFieldAt(pos_y) -
782 field_source.GetMagneticFieldAt(pos_y_neg)) / (2 * eps_m);
783 force.y = m.Dot(dB_dy);
784
785 auto pos_z = position_;
786 pos_z.z += eps_m;
787 auto pos_z_neg = position_;
788 pos_z_neg.z -= eps_m;
789 const auto dB_dz = (field_source.GetMagneticFieldAt(pos_z) -
790 field_source.GetMagneticFieldAt(pos_z_neg)) / (2 * eps_m);
791 force.z = m.Dot(dB_dz);
792
793 return force;
794 }
795
807
809
810 auto pos_x = position_;
811 pos_x.x += eps_m;
812 auto pos_x_neg = position_;
813 pos_x_neg.x -= eps_m;
814 const auto dE_dx = (field_source.GetElectricFieldAt(pos_x) -
815 field_source.GetElectricFieldAt(pos_x_neg)) / (2 * eps_m);
816 force.x = p.Dot(dE_dx);
817
818 auto pos_y = position_;
819 pos_y.y += eps_m;
820 auto pos_y_neg = position_;
821 pos_y_neg.y -= eps_m;
822 const auto dE_dy = (field_source.GetElectricFieldAt(pos_y) -
823 field_source.GetElectricFieldAt(pos_y_neg)) / (2 * eps_m);
824 force.y = p.Dot(dE_dy);
825
826 auto pos_z = position_;
827 pos_z.z += eps_m;
828 auto pos_z_neg = position_;
829 pos_z_neg.z -= eps_m;
830 const auto dE_dz = (field_source.GetElectricFieldAt(pos_z) -
831 field_source.GetElectricFieldAt(pos_z_neg)) / (2 * eps_m);
832 force.z = p.Dot(dE_dz);
833
834 return force;
835 }
836 };
837}
838
839#endif // PHYTH_MAGNETIC_DIPOLE_HPP
Abstract base class for all charge distributions.
Magnetic dipole with full electromagnetic field and dynamics.
Vector3< Quantity< Newton > > ComputeMagneticGradientForce(const MagneticSource &field_source) const
Compute magnetic gradient force using finite differences.
void ApplyElectricGradientForce(const ChargeSource &field_source)
Apply force from electric field gradient.
const Vector3< Quantity< RadianPerSecond > > & GetAngularVelocity() const
Vector3< decltype(1_Cm/1_s/1_s)> ComputeElectricDipoleSecondDerivative(const Quantity< Second > &retarded_dt) const
Compute second time derivative of electric dipole moment at retarded time.
TimeHistory< Vector3< Quantity< Meter > > > position_history_
TimeHistory< Vector3< Quantity< MeterPerSecondSquared > > > acceleration_history_
void ApplyElectricTorque(const ChargeSource &field_source)
Apply torque from electric field.
Quantity< Second > SolveRetardedTimeOffsetBisection(const Vector3< Quantity< Meter > > &point, Quantity< Second > dt_low, Quantity< Second > dt_high) const
Solve for the retarded time offset using bisection method.
Vector3< decltype(1_mps2/1_s)> ComputeAccelerationFirstDerivative(const Quantity< Second > &retarded_dt) const
Compute first time derivative of acceleration at retarded time.
Vector3< Quantity< MeterPerSecondSquared > > GetAccelerationAtOffset(const Quantity< Second > &offset) const
Get acceleration at a time offset in the past.
void SetMoment(const Vector3< Quantity< AmpereMeterSquared > > &m)
Set the magnetic dipole moment.
Vector3< Quantity< CoulombMeter > > GetElectricDipoleAtOffset(const Quantity< Second > &offset) const
Get electric dipole moment at a time offset in the past.
void ApplyMagneticTorque(const MagneticSource &field_source)
Apply torque from magnetic field.
static Vector3< Quantity< CoulombMeter > > ComputeRelativisticDipoleMoment(const Vector3< Quantity< MeterPerSecond > > &v, const Vector3< Quantity< AmpereMeterSquared > > &m)
Compute the relativistic electric dipole moment.
Vector3< decltype(0_Am2/1_s/1_s)> ComputeMomentSecondDerivative(const Quantity< Second > &retarded_dt) const
Compute second time derivative of magnetic moment at retarded time.
Vector3< Quantity< RadianPerSecond > > angular_velocity_
Vector3< Quantity< NewtonPerCoulomb > > GetElectricFieldAt(const Vector3< Quantity< Meter > > &point) const override
Get the electric field at a point.
Quantity< Meter > GetMinimumDistanceTo(const Vector3< Quantity< Meter > > &point) const override
Get the minimum distance from a point to the dipole location.
void ApplyElectromagneticForce(const MagneticSource &magnetic_source, const ChargeSource &electric_source)
Apply both magnetic and electric forces.
void ApplyTorque(const Vector3< Quantity< NewtonMeter > > &torque)
Apply a torque to the dipole.
void ApplyMagneticGradientForce(const MagneticSource &field_source)
Apply force from magnetic field gradient.
void SetMomentOfInertia(const Quantity< KilogramMeterSquared > &I)
Set the moment of inertia.
Quantity< KilogramMeterSquared > moment_of_inertia_
Vector3< Quantity< AmpereMeterSquared > > moment_
Quantity< Volt > GetElectricPotentialAt(const Vector3< Quantity< Meter > > &point) const override
Get the electric potential at a point.
Vector3< Quantity< AmpereMeterSquared > > initial_moment_
TimeHistory< Vector3< Quantity< CoulombMeter > > > electric_dipole_history_
Vector3< Quantity< CoulombMeter > > GetElectricDipoleMoment() const
TimeHistory< Vector3< Quantity< MeterPerSecond > > > velocity_history_
Vector3< Quantity< Tesla > > GetMagneticFieldAt(const Vector3< Quantity< Meter > > &point) const override
Get the magnetic field at a point.
Quantity< Coulomb > GetTotalChargeValue() const override
Get the total charge of the dipole.
MagneticDipole(const Quantity< Kilogram > mass, const Vector3< Quantity< Meter > > &position, const Vector3< Quantity< AmpereMeterSquared > > &moment, const Quantity< KilogramMeterSquared > &moment_of_inertia=0_kgm2)
Construct a magnetic dipole.
Vector3< decltype(0_Am2/1_s)> ComputeMomentFirstDerivative(const Quantity< Second > &retarded_dt) const
Compute first time derivative of magnetic moment at retarded time.
void ApplyMagneticForce(const MagneticSource &field_source)
Apply all magnetic forces (gradient + torque)
const Vector3< Quantity< AmpereMeterSquared > > & GetMoment() const
Quantity< Second > SolveRetardedTimeOffset(const Vector3< Quantity< Meter > > &point) const
Solve for the retarded time offset using Newton's method.
Vector3< Quantity< NewtonMeter > > external_torque_
Vector3< decltype(1_Cm/1_s)> ComputeElectricDipoleFirstDerivative(const Quantity< Second > &retarded_dt) const
Compute first time derivative of electric dipole moment at retarded time.
Vector3< Quantity< MeterPerSecond > > GetVelocityAtOffset(const Quantity< Second > &offset) const
Get velocity at a time offset in the past.
Vector3< Quantity< AmpereMeterSquared > > GetMomentAtOffset(const Quantity< Second > &offset) const
Get magnetic moment at a time offset in the past.
TimeHistory< Vector3< Quantity< AmpereMeterSquared > > > moment_history_
Vector3< Quantity< Newton > > ComputeElectricGradientForce(const ChargeSource &field_source) const
Compute electric gradient force using finite differences.
void Integrate() noexcept override
Integrate dynamics using velocity Verlet.
Quantity< KilogramMeterSquared > GetMomentOfInertia() const
void SetComputeForcesFunction(const std::function< void(MagneticDipole *)> &func) noexcept
Set the force computation function.
Vector3< Quantity< Meter > > GetPositionAtOffset(const Quantity< Second > &offset) const
Get position at a time offset in the past.
Vector3< Quantity< CoulombMeter > > GetRelativisticDipoleMomentCurrent() const
void SetOrientation(const Quaternion< Scalar > &q)
Set the orientation quaternion.
void SetAngularVelocity(const Vector3< Quantity< RadianPerSecond > > &omega)
Set the angular velocity.
void ApplyElectricForce(const ChargeSource &field_source)
Apply all electric forces (gradient + torque)
const Quaternion< Scalar > & GetOrientation() const
Abstract base class for all magnetic field sources.
Physical particle with mass, position, velocity, and force accumulation.
Definition Particle.hpp:36
Quantity< Kilogram > mass_
Definition Particle.hpp:197
Vector3< Quantity< MeterPerSecond > > velocity_
Definition Particle.hpp:198
Vector3< Quantity< Meter > > position_
Definition Particle.hpp:202
Vector3< Quantity< Newton > > external_force_
Definition Particle.hpp:201
Particle(const Quantity< Kilogram > &mass, const Vector3< Quantity< Meter > > &position) noexcept
Construct a particle with mass and initial position.
Definition Particle.hpp:46
void ApplyForce(const Vector3< Quantity< Newton > > &force)
Accumulate an external force on the particle.
Definition Particle.hpp:63
Time-based history with linear interpolation.
int max_iterations
Definition Config.hpp:14
double epsilon
Definition Config.hpp:13
double tolerance
Definition Config.hpp:15
constexpr auto k_E
constexpr auto c
constexpr auto mu_0
constexpr double pi
Definition Constants.hpp:5
auto sqrt(Quantity< UnitT > q)
Square root of a quantity.
auto cube(Quantity< UnitT > q)
Cube of a quantity.
auto abs(Quantity< UnitT > q)
Absolute value of a quantity.
Quantity< ScalarUnit > Scalar
Definition Quantities.hpp:9
Dimension< std::ratio< 1 >, std::ratio< 0 >, std::ratio< 0 >, std::ratio< 0 >, std::ratio< 0 >, std::ratio< 0 >, std::ratio< 0 > > Length
constexpr bool is_unit_v
Definition Unit.hpp:79
Physical quantity with a value and a compile-time unit.
Definition Quantity.hpp:27
Quaternion with Quantity components.
static Quaternion FromAxisAngle(const Vector3< T > &axis, const Scalar theta)
Create a unit quaternion from axis and angle.
3D vector with Quantity components
Definition Vector3.hpp:28
Vector3< Scalar > Normalized() const
Normalized vector (unit vector in the same direction)
Definition Vector3.hpp:382