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}