deep_causality_multivector/extensions/quantum/
quantum_ops.rs

1/*
2 * SPDX-License-Identifier: MIT
3 * Copyright (c) "2025" . The DeepCausality Authors and Contributors. All Rights Reserved.
4 */
5
6use crate::{HilbertState, MultiVector};
7use deep_causality_num::{Complex, Complex64, DivisionAlgebra, Zero};
8
9/// Operations specific to Quantum Mechanics / Hilbert Spaces.
10/// These correspond to Bra-Ket notation operations.
11pub trait QuantumOps {
12    /// The Hermitian Conjugate (The "Bra" <psi|)
13    /// Corresponds to Reversion (Geometry) + Complex Conjugation (Coefficients).
14    fn dag(&self) -> Self;
15
16    /// The Inner Product <self | other>
17    /// Returns the Probability Amplitude (Scalar).
18    fn bracket(&self, other: &Self) -> Complex64;
19
20    /// The Expectation Value <self | Operator | self>
21    /// Returns the observable value (Scalar).
22    fn expectation_value(&self, operator: &Self) -> Complex64;
23
24    /// Normalizes the state so <psi|psi> = 1
25    fn normalize(&self) -> Self;
26}
27
28impl QuantumOps for HilbertState {
29    /// The Hermitian Conjugate: $\psi^\dagger$.
30    ///
31    /// In quantum mechanics, the Hermitian conjugate (or adjoint) of a ket $|\psi\rangle$ is the bra $\langle\psi|$.
32    /// For operators, it generalizes to $O^\dagger$.
33    /// In the context of Geometric Algebra with complex coefficients, this operation involves two steps:
34    /// 1.  **Geometric Reversion**: Reversing the order of basis vectors within each blade. This changes the sign
35    ///     of certain blades based on their grade (e.g., $(e_1 e_2)^\dagger = e_2 e_1 = -e_1 e_2$).
36    /// 2.  **Complex Conjugation**: Taking the complex conjugate of all scalar coefficients.
37    ///
38    /// Mathematically, if $A = \sum_I (a_I + i b_I) e_I$, then $A^\dagger = \sum_I (a_I - i b_I) \tilde{e_I}$,
39    /// where $\tilde{e_I}$ is the reversion of the basis blade $e_I$.
40    ///
41    /// # Rust Details
42    /// This is implemented by first calling the `reversion()` method on the underlying `CausalMultiVector`
43    /// to handle the geometric part, and then iterating through the resulting `data` vector to apply
44    /// `c.conj()` (complex conjugate) to each `Complex64` coefficient.
45    fn dag(&self) -> Self {
46        // 1. Geometric Reversion
47        let mut reversed = self.mv().reversion();
48
49        // 2. Complex Conjugation of coefficients
50        for c in reversed.data.iter_mut() {
51            *c = c.conjugate();
52        }
53
54        Self::from(reversed)
55    }
56
57    /// The Inner Product: $\langle \psi | \phi \rangle$.
58    ///
59    /// # Physics
60    /// In quantum mechanics, the inner product of two state vectors (a bra $\langle \psi |$ and a ket $| \phi \rangle$)
61    /// results in a complex scalar, known as a probability amplitude. Its squared magnitude, $|\langle \psi | \phi \rangle|^2$,
62    /// represents the probability of finding the system in state $|\psi\rangle$ given it was prepared in state $|\phi\rangle$.
63    ///
64    /// # Math
65    /// The inner product is calculated as the scalar (grade 0) component of the geometric product of the
66    /// Hermitian conjugate of the first state with the second state:
67    /// $ \langle \psi | \phi \rangle = \text{ScalarPart}(\psi^\dagger \cdot \phi) $
68    ///
69    /// # Rust Details
70    /// The `dag()` method is first called on `self` to obtain the bra $\langle \psi |$.
71    /// Then, the `geometric_product()` of the resulting bra's underlying multivector and the `other` ket's
72    /// underlying multivector (`other.mv()`) is computed. Finally, the scalar component (grade 0, index 0)
73    /// is extracted from the result.
74    fn bracket(&self, other: &Self) -> Complex64 {
75        let bra = self.dag();
76
77        // Geometric Product: Bra * Ket
78        // The underlying CausalMultiVector handles metric consistency.
79        let product = bra.mv().geometric_product(other.mv());
80
81        // Extract Grade 0 (Scalar) component.
82        // Returns Complex::zero() if for some reason the scalar component is not found,
83        // though this should not happen in a valid CausalMultiVector.
84        product.get(0).cloned().unwrap_or(Complex::zero())
85    }
86
87    /// The Expectation Value: $\langle \psi | \hat{O} | \psi \rangle$.
88    ///
89    /// # Physics
90    /// The expectation value of an observable (represented by a Hermitian operator $\hat{O}$)
91    /// for a quantum system in state $|\psi\rangle$ is the average value of many measurements
92    /// of that observable. It is a real scalar.
93    ///
94    /// # Math
95    /// The expectation value is calculated using the "sandwich" product:
96    /// $ \langle \psi | \hat{O} | \psi \rangle = \text{ScalarPart}(\psi^\dagger \cdot \hat{O} \cdot \psi) $
97    ///
98    /// The operator `operator` is passed as `&Self`. In Furey's algebraic approach,
99    /// operators (End(A)) and states (Ideals) can live in the same algebra (e.g., Cl(0,10)),
100    /// so representing the operator as a `HilbertState` (or similar `Newtype`) is a valid type-safe approach.
101    ///
102    /// # Rust Details
103    /// The implementation first obtains the bra $\langle \psi |$ using `self.dag()`.
104    /// It then performs two sequential geometric products:
105    /// 1.  `op.geometric_product(ket)`: Applies the operator $\hat{O}$ to the ket $|\psi\rangle$,
106    ///     resulting in an intermediate state $|\phi\rangle = \hat{O}|\psi\rangle$.
107    /// 2.  `bra.mv().geometric_product(&phi)`: Takes the inner product $\langle \psi | \phi \rangle$.
108    /// 3.  Finally, the scalar (grade 0) component of the result is extracted.
109    fn expectation_value(&self, operator: &Self) -> Complex64 {
110        let bra = self.dag();
111        let ket = self.mv();
112        let op = operator.mv();
113
114        // 1. Apply Operator: |phi> = \hat{O} |psi>
115        let phi = op.geometric_product(ket);
116
117        // 2. Closure: <psi | phi>
118        let result = bra.mv().geometric_product(&phi);
119
120        result.get(0).cloned().unwrap_or(Complex::zero())
121    }
122
123    /// Normalizes the state so $\langle \psi | \psi \rangle = 1$.
124    ///
125    /// # Physics
126    /// In quantum mechanics, physical states are represented by normalized vectors in Hilbert space.
127    /// The normalization condition $\langle \psi | \psi \rangle = 1$ ensures that the total probability
128    /// of finding the system in any possible state is unity.
129    ///
130    /// # Math
131    /// A state $|\psi\rangle$ is normalized by dividing it by its norm (magnitude):
132    /// $ |\psi'\rangle = \frac{|\psi\rangle}{\sqrt{\langle \psi | \psi \rangle}} $
133    /// where $\sqrt{\langle \psi | \psi \rangle}$ is the L2 norm of the state vector.
134    /// The term $\langle \psi | \psi \rangle$ is the squared norm (or probability density), which is
135    /// a real, non-negative scalar.
136    ///
137    /// # Rust Details
138    /// 1.  The `bracket(self)` method is called to calculate $\langle \psi | \psi \rangle$.
139    ///     Since this must be a real number, its real part (`.re`) is extracted.
140    /// 2.  A check is performed for `norm_sq <= f64::EPSILON` to handle cases where the state is
141    ///     effectively a zero vector, preventing division by zero or NaN results. In such cases,
142    ///     the original (unnormalized) state is returned.
143    /// 3.  The scaling factor is computed as `1.0 / norm_sq.sqrt()`.
144    /// 4.  Each complex coefficient in the underlying `CausalMultiVector`'s `data` is multiplied
145    ///     by this scaling factor.
146    /// 5.  A new `HilbertState` is constructed using `Self::new_unchecked()`. This is safe because
147    ///     normalization only scales coefficients and does not change the metric or the structure
148    ///     of the multivector, thus preserving its validity with respect to its original construction.
149    fn normalize(&self) -> Self {
150        // 1. Calculate Norm Squared (Real number)
151        let norm_sq = self.bracket(self).re; // .re gets the real part of Complex
152
153        // Handle zero vectors to avoid NaN
154        if norm_sq <= f64::EPSILON {
155            return self.clone();
156        }
157
158        // 2. Calculate scaling factor
159        let scale = 1.0 / norm_sq.sqrt();
160
161        // 3. Scale all coefficients
162        let new_data = self.mv().data.iter().map(|c| *c * scale).collect();
163
164        // Reconstruct directly to avoid re-checking metric
165        Self::new_unchecked(new_data, self.mv().metric)
166    }
167}