Skip to main content

cqlib_core/circuit/ansatz/
layers.rs

1// This code is part of Cqlib.
2//
3// (C) Copyright China Telecom Quantum Group 2026
4//
5// This code is licensed under the Apache License, Version 2.0. You may
6// obtain a copy of this license in the LICENSE.txt file in the root directory
7// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8//
9// Any modifications or derivative works of this code must retain this
10// copyright notice, and modified files need to carry a notice indicating
11// that they have been altered from the originals.
12
13//! Layer-style hardware-efficient ansatze.
14//!
15//! These templates mirror commonly used layer primitives: one simple entangler
16//! layer family and one more expressive strongly-entangling layer family.
17
18use super::traits::Ansatz;
19use crate::circuit::bit::Qubit;
20use crate::circuit::circuit_impl::Circuit;
21use crate::circuit::error::CircuitError;
22use crate::circuit::gate::StandardGate;
23use crate::circuit::{Instruction, Parameter, ParameterValue};
24
25fn validate_rotation_gate(name: &str, gate: StandardGate) -> Result<(), CircuitError> {
26    if !matches!(
27        gate,
28        StandardGate::RX | StandardGate::RY | StandardGate::RZ | StandardGate::Phase
29    ) {
30        return Err(CircuitError::InvalidOperation(format!(
31            "{name} rotation_gate must be RX, RY, RZ, or Phase, got {gate:?}"
32        )));
33    }
34    Ok(())
35}
36
37fn validate_entanglement_gate(name: &str, gate: StandardGate) -> Result<(), CircuitError> {
38    if !matches!(gate, StandardGate::CX | StandardGate::CY | StandardGate::CZ) {
39        return Err(CircuitError::InvalidOperation(format!(
40            "{name} entanglement_gate must be CX, CY, or CZ, got {gate:?}"
41        )));
42    }
43    Ok(())
44}
45
46fn parameter(prefix: &str, idx: usize) -> Result<ParameterValue, CircuitError> {
47    let param_name = format!("{prefix}_{idx}");
48    Parameter::try_from(param_name.as_str())
49        .map(ParameterValue::Param)
50        .map_err(|_| CircuitError::InvalidParameterValue(idx, f64::NAN))
51}
52
53/// Basic entangler layers.
54///
55/// Each layer applies one parameterized single-qubit rotation to every qubit,
56/// followed by a nearest-neighbor entangling ring. For two qubits the ring is
57/// a single edge `(0, 1)`; for one qubit no entanglement gate is added.
58#[derive(Debug, Clone)]
59pub struct BasicEntanglerLayers {
60    num_qubits: usize,
61    reps: usize,
62    rotation_gate: StandardGate,
63    entanglement_gate: StandardGate,
64}
65
66impl BasicEntanglerLayers {
67    /// Creates a new BasicEntanglerLayers template.
68    ///
69    /// Defaults: `reps = 1`, `rotation_gate = RX`, `entanglement_gate = CX`.
70    pub fn new(num_qubits: usize) -> Self {
71        Self {
72            num_qubits,
73            reps: 1,
74            rotation_gate: StandardGate::RX,
75            entanglement_gate: StandardGate::CX,
76        }
77    }
78
79    /// Sets the number of layers.
80    pub fn reps(mut self, reps: usize) -> Self {
81        self.reps = reps;
82        self
83    }
84
85    /// Sets the single-parameter rotation gate used in each layer.
86    pub fn rotation_gate(mut self, gate: StandardGate) -> Self {
87        self.rotation_gate = gate;
88        self
89    }
90
91    /// Sets the two-qubit entangling gate used after each rotation layer.
92    pub fn entanglement_gate(mut self, gate: StandardGate) -> Self {
93        self.entanglement_gate = gate;
94        self
95    }
96
97    fn entanglement_pairs(&self) -> Vec<(usize, usize)> {
98        if self.num_qubits < 2 {
99            return Vec::new();
100        }
101        let mut pairs: Vec<_> = (0..self.num_qubits - 1).map(|i| (i, i + 1)).collect();
102        if self.num_qubits > 2 {
103            pairs.push((self.num_qubits - 1, 0));
104        }
105        pairs
106    }
107}
108
109impl Ansatz for BasicEntanglerLayers {
110    fn validate(&self) -> Result<(), CircuitError> {
111        if self.num_qubits == 0 {
112            return Err(CircuitError::InvalidOperation(
113                "BasicEntanglerLayers requires at least 1 qubit".to_string(),
114            ));
115        }
116        validate_rotation_gate("BasicEntanglerLayers", self.rotation_gate)?;
117        validate_entanglement_gate("BasicEntanglerLayers", self.entanglement_gate)?;
118        Ok(())
119    }
120
121    fn build_circuit(&self, prefix: &str) -> Result<Circuit, CircuitError> {
122        self.validate()?;
123
124        let mut circuit = Circuit::new(self.num_qubits);
125        let mut param_idx = 0;
126        let pairs = self.entanglement_pairs();
127
128        for _layer in 0..self.reps {
129            for q in 0..self.num_qubits {
130                circuit.append(
131                    Instruction::Standard(self.rotation_gate),
132                    vec![Qubit::new(q as u32)],
133                    vec![parameter(prefix, param_idx)?],
134                    None,
135                )?;
136                param_idx += 1;
137            }
138            for (control, target) in &pairs {
139                circuit.append(
140                    Instruction::Standard(self.entanglement_gate),
141                    vec![Qubit::new(*control as u32), Qubit::new(*target as u32)],
142                    vec![],
143                    None,
144                )?;
145            }
146        }
147
148        Ok(circuit)
149    }
150
151    fn num_parameters(&self) -> usize {
152        self.reps * self.num_qubits
153    }
154
155    fn num_qubits(&self) -> usize {
156        self.num_qubits
157    }
158}
159
160/// Strongly entangling layers.
161///
162/// Each layer applies a general single-qubit `U(theta, phi, lambda)` on every
163/// qubit, then entangles every qubit `i` with `(i + range) mod num_qubits`.
164/// The default range cycles through `1..num_qubits` across layers.
165///
166/// `CX` is the default entangling gate because the layer uses a directed
167/// `(control, target)` connection pattern, and a controlled-X gate makes that
168/// direction explicit. Users can still select `CX`, `CY`, or `CZ` manually to
169/// match backend-native gates or experiment-specific circuit conventions.
170#[derive(Debug, Clone)]
171pub struct StronglyEntanglingLayers {
172    num_qubits: usize,
173    reps: usize,
174    entanglement_gate: StandardGate,
175    ranges: Option<Vec<usize>>,
176}
177
178impl StronglyEntanglingLayers {
179    /// Creates a new StronglyEntanglingLayers template.
180    ///
181    /// Defaults: `reps = 1`, `entanglement_gate = CX`, ranges cycle by layer.
182    /// The entanglement gate remains configurable so users can align the
183    /// template with hardware-native or experiment-specific two-qubit gates.
184    pub fn new(num_qubits: usize) -> Self {
185        Self {
186            num_qubits,
187            reps: 1,
188            entanglement_gate: StandardGate::CX,
189            ranges: None,
190        }
191    }
192
193    /// Sets the number of layers.
194    pub fn reps(mut self, reps: usize) -> Self {
195        self.reps = reps;
196        self
197    }
198
199    /// Sets the two-qubit entangling gate used after each U layer.
200    ///
201    /// Valid choices are `CX`, `CY`, and `CZ`. `CX` is the default because this
202    /// layer's range pattern is directed; custom gates are accepted when users
203    /// intentionally want a different controlled or symmetric entangler.
204    pub fn entanglement_gate(mut self, gate: StandardGate) -> Self {
205        self.entanglement_gate = gate;
206        self
207    }
208
209    /// Sets explicit entanglement ranges. Ranges are reused cyclically by layer.
210    pub fn ranges(mut self, ranges: Vec<usize>) -> Self {
211        self.ranges = Some(ranges);
212        self
213    }
214
215    fn range_for_layer(&self, layer: usize) -> Option<usize> {
216        if self.num_qubits < 2 {
217            return None;
218        }
219        match &self.ranges {
220            Some(ranges) => Some(ranges[layer % ranges.len()]),
221            None => Some((layer % (self.num_qubits - 1)) + 1),
222        }
223    }
224}
225
226impl Ansatz for StronglyEntanglingLayers {
227    fn validate(&self) -> Result<(), CircuitError> {
228        if self.num_qubits == 0 {
229            return Err(CircuitError::InvalidOperation(
230                "StronglyEntanglingLayers requires at least 1 qubit".to_string(),
231            ));
232        }
233        validate_entanglement_gate("StronglyEntanglingLayers", self.entanglement_gate)?;
234        if self.num_qubits > 1
235            && let Some(ranges) = &self.ranges
236        {
237            if ranges.is_empty() {
238                return Err(CircuitError::InvalidOperation(
239                    "StronglyEntanglingLayers ranges must not be empty".to_string(),
240                ));
241            }
242            for &range in ranges {
243                if range == 0 || range >= self.num_qubits {
244                    return Err(CircuitError::InvalidOperation(format!(
245                        "StronglyEntanglingLayers range must be in 1..{}, got {}",
246                        self.num_qubits, range
247                    )));
248                }
249            }
250        }
251        Ok(())
252    }
253
254    fn build_circuit(&self, prefix: &str) -> Result<Circuit, CircuitError> {
255        self.validate()?;
256
257        let mut circuit = Circuit::new(self.num_qubits);
258        let mut param_idx = 0;
259
260        for layer in 0..self.reps {
261            for q in 0..self.num_qubits {
262                circuit.u(
263                    Qubit::new(q as u32),
264                    parameter(prefix, param_idx)?,
265                    parameter(prefix, param_idx + 1)?,
266                    parameter(prefix, param_idx + 2)?,
267                )?;
268                param_idx += 3;
269            }
270
271            if let Some(range) = self.range_for_layer(layer) {
272                for control in 0..self.num_qubits {
273                    let target = (control + range) % self.num_qubits;
274                    circuit.append(
275                        Instruction::Standard(self.entanglement_gate),
276                        vec![Qubit::new(control as u32), Qubit::new(target as u32)],
277                        vec![],
278                        None,
279                    )?;
280                }
281            }
282        }
283
284        Ok(circuit)
285    }
286
287    fn num_parameters(&self) -> usize {
288        self.reps * self.num_qubits * 3
289    }
290
291    fn num_qubits(&self) -> usize {
292        self.num_qubits
293    }
294}
295
296#[cfg(test)]
297#[path = "layers_test.rs"]
298mod layers_test;