deep_causality_physics/electromagnetism/fields.rs
1/*
2 * SPDX-License-Identifier: MIT
3 * Copyright (c) "2025" . The DeepCausality Authors and Contributors. All Rights Reserved.
4 */
5
6use deep_causality_multivector::{CausalMultiVector, MultiVector};
7
8// Kernels
9
10use crate::PhysicsError;
11use deep_causality_tensor::CausalTensor;
12use deep_causality_topology::Manifold;
13
14/// Calculates the Maxwell gradient (Electromagnetic Field Tensor).
15///
16/// Computes the exterior derivative $F = dA$ of the electromagnetic potential 1-form $A$.
17/// Requires the potential to be defined on a Manifold structure to provide spatial context.
18///
19/// # Arguments
20/// * `potential_manifold` - Manifold containing the potential 1-form $A$ on its 1-simplices.
21///
22/// # Returns
23/// * `Result<CausalTensor<f64>, PhysicsError>` - Field tensor $F$ (2-form) on the 2-simplices.
24pub fn maxwell_gradient_kernel(
25 potential_manifold: &Manifold<f64>,
26) -> Result<CausalTensor<f64>, PhysicsError> {
27 // F = dA (Exterior Derivative) on 1-forms -> 2-forms
28 let f_tensor = potential_manifold.exterior_derivative(1);
29 // Validate that a 2-form was actually produced (non-empty, expected rank)
30 if f_tensor.is_empty() || f_tensor.shape().is_empty() {
31 return Err(PhysicsError::new(
32 crate::PhysicsErrorEnum::DimensionMismatch(
33 "Maxwell gradient produced empty or invalid 2-form".into(),
34 ),
35 ));
36 }
37 Ok(f_tensor)
38}
39
40/// Calculates the Lorenz gauge condition: $\nabla \cdot A = 0$.
41///
42/// Computes the codifferential $\delta A$ (divergence) of the potential 1-form.
43///
44/// # Arguments
45/// * `potential_manifold` - Manifold containing the potential 1-form $A$.
46///
47/// # Returns
48/// * `Result<CausalTensor<f64>, PhysicsError>` - Divergence scalar field (0-form) on vertices.
49pub fn lorenz_gauge_kernel(
50 potential_manifold: &Manifold<f64>,
51) -> Result<CausalTensor<f64>, PhysicsError> {
52 // Lorenz Gauge: Div A = 0
53 // Div A is represented by codifferential delta(A) for a 1-form.
54 // delta: k-form -> (k-1)-form.
55 // Result is a 0-form (scalar field on vertices).
56 let divergence = potential_manifold.codifferential(1);
57 Ok(divergence)
58}
59
60/// Calculates the Poynting Vector (Energy Flux): $S = E \times B$.
61///
62/// Uses the outer product $E \wedge B$ to represent flux as a bivector.
63///
64/// # Arguments
65/// * `e` - Electric field vector $E$.
66/// * `b` - Magnetic field vector $B$.
67///
68/// # Returns
69/// * `Result<CausalMultiVector<f64>, PhysicsError>` - Poynting vector (as bivector).
70pub fn poynting_vector_kernel(
71 e: &CausalMultiVector<f64>,
72 b: &CausalMultiVector<f64>,
73) -> Result<CausalMultiVector<f64>, PhysicsError> {
74 // S = E x B / mu0
75 // Returns the Poynting Vector (flux of energy).
76 // DeepCausality uses Geometric Algebra.
77 // The outer product E ^ B represents the specific plane of energy flux (bivector).
78 // This is the dual of the classical vector cross product.
79 // We return Energy Density Flux in this bivector form.
80 if e.metric() != b.metric() {
81 return Err(PhysicsError::new(
82 crate::PhysicsErrorEnum::DimensionMismatch(format!(
83 "Metric mismatch in Poynting Vector: {:?} vs {:?}",
84 e.metric(),
85 b.metric()
86 )),
87 ));
88 }
89 if e.data().iter().any(|v| !v.is_finite()) || b.data().iter().any(|v| !v.is_finite()) {
90 return Err(PhysicsError::new(
91 crate::PhysicsErrorEnum::NumericalInstability(
92 "Non-finite input in Poynting Vector".into(),
93 ),
94 ));
95 }
96 let s = e.outer_product(b);
97 if s.data().iter().any(|v| !v.is_finite()) {
98 return Err(PhysicsError::new(
99 crate::PhysicsErrorEnum::NumericalInstability(
100 "Non-finite result in Poynting Vector".into(),
101 ),
102 ));
103 }
104 Ok(s)
105}
106
107/// Calculates Magnetic Helicity Density: $h = A \cdot B$.
108///
109/// # Arguments
110/// * `potential` - Vector potential $A$.
111/// * `field` - Magnetic field $B$.
112///
113/// # Returns
114/// * `Result<f64, PhysicsError>` - Helicity density scalar.
115pub fn magnetic_helicity_density_kernel(
116 potential: &CausalMultiVector<f64>,
117 field: &CausalMultiVector<f64>,
118) -> Result<f64, crate::PhysicsError> {
119 // Helicity Density h = A . B
120 // Total Helicity H is the integral of h over volume.
121 // This function computes the local density.
122
123 if potential.metric() != field.metric() {
124 return Err(PhysicsError::new(
125 crate::PhysicsErrorEnum::DimensionMismatch(format!(
126 "Metric mismatch in Magnetic Helicity: {:?} vs {:?}",
127 potential.metric(),
128 field.metric()
129 )),
130 ));
131 }
132
133 let h_scalar_mv = potential.inner_product(field);
134 // Extract Grade 0 (Scalar)
135 let h_val = h_scalar_mv.data()[0];
136 Ok(h_val)
137}
138
139/// Calculates the Proca Equation (Massive Electromagnetism): $\delta F + m^2 A = J$.
140///
141/// Computes the source current density 1-form $J$ given the field $F$, potential $A$, and mass $m$.
142///
143/// # Arguments
144/// * `field_manifold` - Manifold containing the field 2-form $F$ (maxwell gradient).
145/// * `potential_manifold` - Manifold containing the potential 1-form $A$.
146/// * `mass` - Mass of the photon $m$ (typically $\approx 0$, but $>0$ in Proca theory).
147///
148/// # Returns
149/// * `Result<CausalTensor<f64>, PhysicsError>` - Current density 1-form $J$.
150pub fn proca_equation_kernel(
151 field_manifold: &Manifold<f64>,
152 potential_manifold: &Manifold<f64>,
153 mass: f64,
154) -> Result<CausalTensor<f64>, PhysicsError> {
155 // Proca: delta F + m^2 A = J
156 // F is 2-form. delta F is 1-form.
157 // A is 1-form. m^2 A is 1-form.
158 // Result J is 1-form.
159
160 // 1. Compute delta F (codifferential of 2-form)
161 let delta_f = field_manifold.codifferential(2);
162
163 if !mass.is_finite() {
164 return Err(PhysicsError::new(
165 crate::PhysicsErrorEnum::NumericalInstability("Non-finite mass in Proca".into()),
166 ));
167 }
168 if delta_f.as_slice().iter().any(|v| !v.is_finite()) {
169 return Err(PhysicsError::new(
170 crate::PhysicsErrorEnum::NumericalInstability("delta(F) has non-finite entries".into()),
171 ));
172 }
173
174 // 2. Compute m^2 A (ensure it's a 1-form tensor compatible with delta F)
175 let m2 = mass * mass;
176 if !m2.is_finite() {
177 return Err(PhysicsError::new(
178 crate::PhysicsErrorEnum::NumericalInstability("m^2 overflowed in Proca".into()),
179 ));
180 }
181 let a_full = potential_manifold.data(); // underlying data tensor
182
183 // Build an A tensor on the same shape as delta_f (1-form domain)
184 let a_shape = delta_f.shape().to_vec();
185 let needed_len = delta_f.len();
186 if a_full.len() < needed_len {
187 return Err(PhysicsError::new(
188 crate::PhysicsErrorEnum::DimensionMismatch(format!(
189 "Proca: potential data shorter than 1-form domain: {} < {}",
190 a_full.len(),
191 needed_len
192 )),
193 ));
194 }
195 if a_full.as_slice()[..needed_len]
196 .iter()
197 .any(|v| !v.is_finite())
198 {
199 return Err(PhysicsError::new(
200 crate::PhysicsErrorEnum::NumericalInstability(
201 "A(1-form) has non-finite entries".into(),
202 ),
203 ));
204 }
205 let a_1form = CausalTensor::new(a_full.as_slice()[..needed_len].to_vec(), a_shape)?;
206
207 let m2_a = a_1form * m2;
208 if m2_a.as_slice().iter().any(|v| !v.is_finite()) {
209 return Err(PhysicsError::new(
210 crate::PhysicsErrorEnum::NumericalInstability("m^2 A has non-finite entries".into()),
211 ));
212 }
213
214 // 3. Sum: J = delta F + m^2 A
215 // Note: CausalTensor implements Add
216 // Check shapes before addition (J = delta_f + m2_a)
217 if delta_f.shape() != m2_a.shape() {
218 return Err(PhysicsError::new(
219 crate::PhysicsErrorEnum::DimensionMismatch(format!(
220 "Shape mismatch in Proca Equation: delta F {:?} vs m^2 A {:?}",
221 delta_f.shape(),
222 m2_a.shape()
223 )),
224 ));
225 }
226 let j = delta_f + m2_a;
227
228 if j.as_slice().iter().any(|v| !v.is_finite()) {
229 return Err(PhysicsError::new(
230 crate::PhysicsErrorEnum::NumericalInstability(
231 "Proca current J has non-finite entries".into(),
232 ),
233 ));
234 }
235
236 Ok(j)
237}