tnc/builders/
circuit_builder.rs

1//! Building a tensor network from a quantum circuit.
2
3use std::marker::PhantomData;
4
5use itertools::Itertools;
6use num_complex::Complex64;
7use permutation::Permutation;
8
9use crate::tensornetwork::{
10    tensor::{EdgeIndex, Tensor},
11    tensordata::TensorData,
12};
13
14/// A quantum register, i.e., an array of qubits. Similar to the Qiskit / QASM
15/// idea, quantum registers group qubits (for instance, one qreg for ancillas), and
16/// a circuit can act on multiple qregs.
17#[derive(Debug)]
18pub struct QuantumRegister<'a> {
19    base: usize,
20    size: usize,
21    phantom: PhantomData<&'a Circuit>,
22}
23
24impl QuantumRegister<'_> {
25    /// Creates a new quantum register without any associated circuit. This is mainly
26    /// for testing.
27    #[cfg(test)]
28    pub(crate) fn new(size: usize) -> Self {
29        QuantumRegister {
30            base: 0,
31            size,
32            phantom: PhantomData,
33        }
34    }
35
36    /// Returns the qubit at a given index.
37    pub fn qubit(&self, index: usize) -> Qubit<'_> {
38        assert!(index < self.size);
39        Qubit {
40            index: self.base + index,
41            phantom: PhantomData,
42        }
43    }
44
45    /// Returns an iterator over all qubits in this register.
46    pub fn qubits(&self) -> impl Iterator<Item = Qubit<'_>> {
47        (self.base..self.base + self.size).map(|i| Qubit {
48            index: i,
49            phantom: PhantomData,
50        })
51    }
52
53    /// Returns the size of the register.
54    #[inline]
55    pub fn len(&self) -> usize {
56        self.size
57    }
58
59    /// Returns whether this register is empty, i.e., doesn't contain any qubits.
60    #[inline]
61    pub fn is_empty(&self) -> bool {
62        self.size == 0
63    }
64}
65
66/// A single qubit from a quantum register.
67pub struct Qubit<'a> {
68    index: usize,
69    phantom: PhantomData<&'a Circuit>,
70}
71
72/// A struct holding a permutation to be applied to a tensor.
73#[derive(Debug, Clone)]
74pub struct Permutor {
75    target_leg_order: Vec<EdgeIndex>,
76}
77
78impl Permutor {
79    fn new(target_legs: Vec<EdgeIndex>) -> Self {
80        Self {
81            target_leg_order: target_legs,
82        }
83    }
84
85    /// Permutates the tensor according to the stored permutation.
86    pub fn apply(&self, tensor: Tensor) -> Tensor {
87        assert!(tensor.is_leaf());
88        if self.is_identity() {
89            return tensor;
90        }
91
92        let Tensor {
93            mut legs,
94            mut bond_dims,
95            tensordata,
96            ..
97        } = tensor;
98        let mut data = tensordata.into_data();
99
100        // Find the permutation
101        let mut perm = Self::permutation_between(&legs, &self.target_leg_order);
102
103        // Permute legs, shape and data
104        perm.apply_slice_in_place(&mut legs);
105        perm.apply_slice_in_place(&mut bond_dims);
106        data.transpose(&perm);
107
108        Tensor {
109            tensors: vec![],
110            legs,
111            bond_dims,
112            tensordata: TensorData::Matrix(data),
113        }
114    }
115
116    /// Returns whether the permutor is identity, in which case it won't have any
117    /// effect.
118    #[inline]
119    pub fn is_identity(&self) -> bool {
120        self.target_leg_order.is_empty()
121    }
122
123    /// Computes the permutation that, when applied to `given`, returns `target`.
124    /// Assumes that `given` and `target` are equal up to permutation.
125    fn permutation_between(given: &[usize], target: &[usize]) -> Permutation {
126        let given_to_sorted = permutation::sort_unstable(given);
127        let target_to_sorted = permutation::sort_unstable(target);
128        &target_to_sorted.inverse() * &given_to_sorted
129    }
130}
131
132/// A quantum circuit builder that constructs a tensor network representing a quantum
133/// circuit.
134#[derive(Debug, Default)]
135pub struct Circuit {
136    /// The last open edges on each qubit.
137    open_edges: Vec<EdgeIndex>,
138    /// The next edge to be used.
139    next_edge: usize,
140    /// The tensors representing the circuit.
141    tensors: Vec<Tensor>,
142}
143
144impl Circuit {
145    /// The |0> state.
146    fn ket0() -> TensorData {
147        TensorData::new_from_data(&[2], vec![Complex64::ONE, Complex64::ZERO], None)
148    }
149
150    /// The |1> state.
151    fn ket1() -> TensorData {
152        TensorData::new_from_data(&[2], vec![Complex64::ZERO, Complex64::ONE], None)
153    }
154
155    /// The Z gate.
156    fn z() -> TensorData {
157        TensorData::Gate((String::from("z"), vec![], false))
158    }
159
160    /// Creates a new edge id.
161    fn new_edge(&mut self) -> usize {
162        let edge = self.next_edge;
163        self.next_edge += 1;
164        edge
165    }
166
167    /// Returns the total number of qubits allocated in this circuit.
168    ///
169    /// # Examples
170    /// ```
171    /// # use tnc::builders::circuit_builder::Circuit;
172    /// let mut circuit = Circuit::default();
173    /// let q1 = circuit.allocate_register(2);
174    /// let q2 = circuit.allocate_register(3);
175    /// assert_eq!(circuit.num_qubits(), 5);
176    /// ```
177    #[inline]
178    pub fn num_qubits(&self) -> usize {
179        self.open_edges.len()
180    }
181
182    /// Allocates a new quantum register. The qubits are initialized in the |0>
183    /// state.
184    pub fn allocate_register<'a>(&mut self, size: usize) -> QuantumRegister<'a> {
185        let previous_qubits = self.num_qubits();
186
187        self.open_edges.reserve(size);
188        self.tensors.reserve(size);
189        for _ in 0..size {
190            let edge = self.new_edge();
191            self.open_edges.push(edge);
192            let mut ket0 = Tensor::new_from_const(vec![edge], 2);
193            ket0.set_tensor_data(Self::ket0());
194            self.tensors.push(ket0);
195        }
196
197        QuantumRegister {
198            base: previous_qubits,
199            size,
200            phantom: PhantomData,
201        }
202    }
203
204    /// Appends a gate to the circuit on the specified qubits.
205    pub fn append_gate(&mut self, gate: TensorData, indices: &[Qubit]) {
206        assert!(
207            indices.iter().map(|q| q.index).all_unique(),
208            "Qubit arguments must be unique"
209        );
210
211        // Get the old and new edges
212        let old_edges = indices.iter().map(|q| self.open_edges[q.index]);
213        let new_edges = (0..indices.len()).map(|e| e + self.next_edge);
214        let edges = old_edges.chain(new_edges).collect_vec();
215        self.next_edge += indices.len();
216
217        // Update the open edges
218        for (q, next_edge) in indices.iter().zip(&edges[indices.len()..]) {
219            self.open_edges[q.index] = *next_edge;
220        }
221
222        // Create the new tensor
223        let mut new_tensor = Tensor::new_from_const(edges, 2);
224        new_tensor.set_tensor_data(gate);
225
226        // Push the new tensor to the tensors
227        self.tensors.push(new_tensor);
228    }
229
230    /// Converts the circuit to a tensor network that computes the amplitude for the
231    /// given bitstring.
232    ///
233    /// The bitstring can also contain wildcards `*`, in which case the tensor leg
234    /// corresponding to this qubit is left open. For every wildcard, the output
235    /// tensor will be doubled in size. In the extreme case where there's only
236    /// wildcards, the full statevector will be computed.
237    ///
238    /// Since the final tensor can end up with arbitrary permutation, a [`Permutor`]
239    /// is returned that can transpose the final tensor after contraction to the
240    /// natural order, i.e., sorted by increasing qubit number. If the bitstring
241    /// contains no wildcards, the final result is a scalar and the permutator can be
242    /// ignored.
243    pub fn into_amplitude_network(mut self, bitstring: &str) -> (Tensor, Permutor) {
244        assert_eq!(bitstring.len(), self.num_qubits());
245
246        // Apply the final bras
247        self.tensors.reserve(bitstring.len());
248        let mut final_legs = Vec::new();
249        for (c, e) in bitstring.chars().zip(self.open_edges) {
250            let bra = match c {
251                '0' => Self::ket0(),
252                '1' => Self::ket1(),
253                '*' => {
254                    final_legs.push(e);
255                    continue; // leave this edge open
256                }
257                _ => panic!("Only 0, 1 and * are allowed in bitstring"),
258            };
259            let mut tensor = Tensor::new_from_const(vec![e], 2);
260            tensor.set_tensor_data(bra);
261            self.tensors.push(tensor);
262        }
263
264        // The contraction can re-order the legs depending on the contraction order,
265        // so we need to return the order we want the legs to be in at the end, such
266        // that the user can transpose the final tensor and has the expected order of
267        // elements.
268        let out = Tensor::new_composite(self.tensors);
269        let permutor = Permutor::new(final_legs);
270        (out, permutor)
271    }
272
273    /// Converts the circuit to a tensor network that computes the full statevector.
274    ///
275    /// Since the final tensor can end up with arbitrary permutation, a [`Permutor`]
276    /// is returned that can transpose the final tensor after contraction to the
277    /// natural order, i.e., sorted by increasing qubit number.
278    #[inline]
279    pub fn into_statevector_network(self) -> (Tensor, Permutor) {
280        let qubits = self.num_qubits();
281        self.into_amplitude_network(&"*".repeat(qubits))
282    }
283
284    /// Creates the adjoint tensor of a given `tensor`. This not only modifies the
285    /// data, but also the order of legs and the bond dims vec. The legs of the new
286    /// tensor are offset by `leg_offset`.
287    fn tensor_adjoint(tensor: &Tensor, leg_offset: usize) -> Tensor {
288        // Transpose legs and shape of tensor
289        let half = tensor.legs().len() / 2;
290        let legs = tensor.legs()[half..]
291            .iter()
292            .chain(&tensor.legs()[..half])
293            .map(|l| l + leg_offset)
294            .collect();
295        let bond_dims = tensor.bond_dims()[half..]
296            .iter()
297            .chain(&tensor.bond_dims()[..half])
298            .copied()
299            .collect();
300
301        // Take actual adjoint of tensor data
302        let data = tensor.tensor_data().clone();
303        let data = data.adjoint();
304
305        let mut adjoint = Tensor::new(legs, bond_dims);
306        adjoint.set_tensor_data(data);
307        adjoint
308    }
309
310    /// Converts the circuit to a tensor network that computes the expectation value
311    /// with respect to standard observables (`Z`) on all qubits.
312    ///
313    /// The tensor network is roughly twice the size of the circuit, as it needs to
314    /// compute the adjoint of the circuit as well.
315    pub fn into_expectation_value_network(mut self) -> Tensor {
316        let offset = self.next_edge;
317        self.tensors.reserve(self.tensors.len() + self.num_qubits());
318
319        // Add the mirrored tensor network
320        let mut adjoint_tensors = Vec::with_capacity(self.tensors.len());
321        for tensor in &self.tensors {
322            let adjoint = Self::tensor_adjoint(tensor, offset);
323            adjoint_tensors.push(adjoint);
324        }
325        self.tensors.append(&mut adjoint_tensors);
326
327        // Add the layer of observables
328        for e in self.open_edges {
329            let mut t = Tensor::new_from_const(vec![e, e + offset], 2);
330            t.set_tensor_data(Self::z());
331            self.tensors.push(t);
332        }
333
334        Tensor::new_composite(self.tensors)
335    }
336}
337
338#[cfg(test)]
339mod tests {
340    use super::*;
341
342    use std::f64::consts::{FRAC_1_SQRT_2, FRAC_PI_3, FRAC_PI_4};
343
344    use float_cmp::assert_approx_eq;
345    use num_complex::Complex64;
346
347    use crate::{
348        contractionpath::paths::{
349            cotengrust::{Cotengrust, OptMethod},
350            FindPath,
351        },
352        tensornetwork::{
353            contraction::contract_tensor_network, tensor::Tensor, tensordata::TensorData,
354        },
355    };
356
357    fn test_permutation_between(given: &[usize], target: &[usize]) {
358        let perm = Permutor::permutation_between(given, target);
359        assert_eq!(perm.apply_slice(given), target);
360    }
361
362    #[test]
363    fn permutation_between() {
364        test_permutation_between(&[1, 2, 3, 4], &[1, 2, 3, 4]);
365        test_permutation_between(&[1, 2, 3, 4], &[4, 3, 2, 1]);
366        test_permutation_between(&[4, 3, 2, 1], &[1, 2, 3, 4]);
367        test_permutation_between(&[4, 1, 3, 2], &[2, 4, 3, 1]);
368        test_permutation_between(&[5, 1, 4, 3, 2, 6], &[1, 6, 3, 5, 2, 4]);
369    }
370
371    #[test]
372    fn hadamards_amplitude() {
373        let qubits = 5;
374        let mut circuit = Circuit::default();
375        let qr = circuit.allocate_register(qubits);
376        for q in qr.qubits() {
377            circuit.append_gate(TensorData::Gate((String::from("h"), vec![], false)), &[q]);
378        }
379        let (tensor_network, permutor) = circuit.into_amplitude_network("00000");
380        assert!(permutor.is_identity());
381
382        let mut opt = Cotengrust::new(&tensor_network, OptMethod::Greedy);
383        opt.find_path();
384        let path = opt.get_best_replace_path();
385
386        let result = contract_tensor_network(tensor_network, &path);
387
388        let mut tn_ref = Tensor::default();
389        tn_ref.set_tensor_data(TensorData::new_from_data(
390            &[],
391            vec![Complex64::new(FRAC_1_SQRT_2.powi(qubits as i32), 0.0)],
392            None,
393        ));
394
395        assert_approx_eq!(&Tensor, &result, &tn_ref);
396    }
397
398    #[test]
399    fn rx_expectation_value() {
400        let qubits = 2;
401        let mut circuit = Circuit::default();
402        let qr = circuit.allocate_register(qubits);
403        circuit.append_gate(
404            TensorData::Gate((String::from("rx"), vec![FRAC_PI_4], false)),
405            &[qr.qubit(0)],
406        );
407        circuit.append_gate(
408            TensorData::Gate((String::from("rx"), vec![FRAC_PI_3], false)),
409            &[qr.qubit(1)],
410        );
411        let tensor_network = circuit.into_expectation_value_network();
412
413        let mut opt = Cotengrust::new(&tensor_network, OptMethod::Greedy);
414        opt.find_path();
415        let path = opt.get_best_replace_path();
416
417        let result = contract_tensor_network(tensor_network, &path);
418
419        let mut tn_ref = Tensor::default();
420        tn_ref.set_tensor_data(TensorData::new_from_data(
421            &[],
422            vec![Complex64::new(FRAC_1_SQRT_2 * 0.5, 0.0)],
423            None,
424        ));
425
426        assert_approx_eq!(&Tensor, &result, &tn_ref);
427    }
428
429    #[test]
430    #[should_panic(expected = "Qubit arguments must be unique")]
431    fn duplicate_qubit_arg() {
432        let mut circuit = Circuit::default();
433        let qr = circuit.allocate_register(2);
434        circuit.append_gate(
435            TensorData::Gate((String::from("cx"), vec![], true)),
436            &[qr.qubit(1), qr.qubit(1)],
437        );
438    }
439}