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}