Skip to main content

tensor4all_quanticstransform/
phase_rotation.rs

1//! Phase rotation operator: f(x) = exp(i*θ*x) * g(x)
2//!
3//! This transformation multiplies the function by a phase factor.
4
5use anyhow::Result;
6use num_complex::Complex64;
7use num_traits::One;
8use std::f64::consts::PI;
9use tensor4all_simplett::{types::tensor3_zeros, Tensor3Ops, TensorTrain};
10
11use crate::common::{
12    embed_single_var_mpo, tensortrain_to_linear_operator,
13    tensortrain_to_linear_operator_asymmetric, QuanticsOperator,
14};
15
16/// Create a phase rotation operator: f(x) = exp(i*θ*x) * g(x)
17///
18/// This MPO multiplies a function g(x) by the phase factor exp(i*θ*x).
19///
20/// In quantics representation, x = Σ_n x_n * 2^(R-n), so:
21/// exp(i*θ*x) = Π_n exp(i*θ*2^(R-n)*x_n)
22///
23/// Each site contributes an independent phase factor, making this a diagonal
24/// operator with bond dimension 1.
25///
26/// # Arguments
27/// * `r` - Number of bits (sites)
28/// * `theta` - Phase angle in radians
29///
30/// # Returns
31/// LinearOperator representing the phase rotation
32///
33/// # Examples
34///
35/// ```
36/// use tensor4all_quanticstransform::phase_rotation_operator;
37/// use std::f64::consts::PI;
38///
39/// // Create phase rotation by π/4 for 4-bit quantics
40/// let op = phase_rotation_operator(4, PI / 4.0).unwrap();
41///
42/// // The operator has one MPO tensor per bit
43/// assert_eq!(op.mpo.node_count(), 4);
44///
45/// // Phase rotation is a diagonal operator (bond dimension 1)
46/// // Error on invalid input
47/// assert!(phase_rotation_operator(0, 1.0).is_err());
48/// assert!(phase_rotation_operator(4, f64::NAN).is_err());
49/// assert!(phase_rotation_operator(4, f64::INFINITY).is_err());
50/// ```
51pub fn phase_rotation_operator(r: usize, theta: f64) -> Result<QuanticsOperator> {
52    if r == 0 {
53        return Err(anyhow::anyhow!("Number of sites must be positive"));
54    }
55    if !theta.is_finite() {
56        anyhow::bail!("theta must be finite, got {theta}");
57    }
58
59    let mpo = phase_rotation_mpo(r, theta)?;
60    let site_dims = vec![2; r];
61    tensortrain_to_linear_operator(&mpo, &site_dims)
62}
63
64/// Create a phase rotation operator for one variable in a multi-variable system.
65///
66/// Acts as phase rotation on `target_var` and identity on all other variables.
67/// The resulting operator works on interleaved quantics encoding where each
68/// site has local dimension `2^nvariables`.
69///
70/// # Arguments
71/// * `r` - Number of bits (sites)
72/// * `theta` - Phase angle in radians
73/// * `nvariables` - Total number of variables
74/// * `target_var` - Which variable to apply phase rotation to (0-indexed)
75///
76/// # Examples
77///
78/// ```
79/// use tensor4all_quanticstransform::phase_rotation_operator_multivar;
80/// use std::f64::consts::PI;
81///
82/// // Phase rotate only the x-variable of a 2-variable function f(x, y)
83/// let op = phase_rotation_operator_multivar(4, PI / 4.0, 2, 0).unwrap();
84/// assert_eq!(op.mpo.node_count(), 4);
85/// ```
86pub fn phase_rotation_operator_multivar(
87    r: usize,
88    theta: f64,
89    nvariables: usize,
90    target_var: usize,
91) -> Result<QuanticsOperator> {
92    if r == 0 {
93        return Err(anyhow::anyhow!("Number of sites must be positive"));
94    }
95    if !theta.is_finite() {
96        anyhow::bail!("theta must be finite, got {theta}");
97    }
98
99    let mpo = phase_rotation_mpo(r, theta)?;
100    let embedded = embed_single_var_mpo(&mpo, nvariables, target_var)?;
101    let dim_multi = 1 << nvariables;
102    let dims = vec![dim_multi; r];
103    tensortrain_to_linear_operator_asymmetric(&embedded, &dims, &dims)
104}
105
106/// Create the phase rotation MPO as a TensorTrain.
107///
108/// Each site tensor is diagonal with entries:
109/// - For x_n = 0: 1
110/// - For x_n = 1: exp(i*θ*2^(R-1-n))
111///
112/// Uses big-endian convention: site n corresponds to bit 2^(R-1-n) (MSB at site 0).
113/// This matches Julia Quantics.jl's convention.
114fn phase_rotation_mpo(r: usize, theta: f64) -> Result<TensorTrain<Complex64>> {
115    if r == 0 {
116        return Err(anyhow::anyhow!("Number of sites must be positive"));
117    }
118    if !theta.is_finite() {
119        anyhow::bail!("theta must be finite, got {theta}");
120    }
121
122    // Normalize theta to [0, 2π)
123    let theta_mod = theta.rem_euclid(2.0 * PI);
124
125    let mut tensors = Vec::with_capacity(r);
126
127    for n in 0..r {
128        // Phase for this site: θ * 2^(R-1-n) (big-endian: site 0 = MSB)
129        // This matches Julia's convention: site n=1 (Julia) has power R-n
130        let power = (r - 1 - n) as f64;
131        let site_phase = (theta_mod * 2.0_f64.powf(power)).rem_euclid(2.0 * PI);
132
133        // Diagonal MPO tensor: out_bit == in_bit
134        // Shape: (1, 4, 1) where 4 = 2*2 for (out, in)
135        let mut t = tensor3_zeros(1, 4, 1);
136
137        // s = out_bit * 2 + in_bit
138        // Diagonal entries: out_bit == in_bit
139        // s=0: (0,0), s=3: (1,1)
140        t.set3(0, 0, 0, Complex64::one()); // x_n = 0: phase = 1
141
142        // x_n = 1: phase = exp(i * site_phase)
143        let phase_factor = Complex64::new(site_phase.cos(), site_phase.sin());
144        t.set3(0, 3, 0, phase_factor);
145
146        tensors.push(t);
147    }
148
149    TensorTrain::new(tensors)
150        .map_err(|e| anyhow::anyhow!("Failed to create phase rotation MPO: {}", e))
151}
152
153#[cfg(test)]
154mod tests;