quest_bind/operators.rs
1use std::ffi::CString;
2
3use crate::{
4 error::catch_quest_exception,
5 ffi::{
6 self,
7 pauliOpType as PauliOpType,
8 },
9 Qcomplex,
10 Qreal,
11 QuestEnv,
12 QuestError,
13 Qureg,
14};
15
16#[derive(Debug)]
17pub struct PauliHamil(pub(crate) ffi::PauliHamil);
18
19impl PauliHamil {
20 /// Dynamically allocates a Hamiltonian
21 ///
22 /// The Hamiltonian is expressed as a real-weighted sum of products of
23 /// Pauli operators.
24 ///
25 /// # Examples
26 ///
27 /// ```rust
28 /// # use quest_bind::*;
29 /// let hamil = PauliHamil::try_new(2, 3).unwrap();
30 /// ```
31 ///
32 /// See [QuEST API] for more information.
33 ///
34 /// # Errors
35 ///
36 /// Returns [`QuestError::InvalidQuESTInputError`](crate::QuestError::InvalidQuESTInputError) on
37 /// failure. This is an exception thrown by `QuEST`.
38 ///
39 /// [QuEST API]: https://quest-kit.github.io/QuEST/modules.html
40 pub fn try_new(
41 num_qubits: i32,
42 num_sum_terms: i32,
43 ) -> Result<Self, QuestError> {
44 catch_quest_exception(|| {
45 Self(unsafe { ffi::createPauliHamil(num_qubits, num_sum_terms) })
46 })
47 }
48
49 /// Creates a [`PauliHamil`] instance
50 /// populated with the data in filename `fn_`.
51 ///
52 /// # Bugs
53 ///
54 /// This function calls its C equivalent which unfortunately behaves
55 /// erratically when the file specified is incorrectly formatted or
56 /// inaccessible, often leading to seg-faults. Use at your own risk.
57 pub fn try_new_from_file(fn_: &str) -> Result<Self, QuestError> {
58 let filename = CString::new(fn_).map_err(QuestError::NulError)?;
59 catch_quest_exception(|| {
60 Self(unsafe { ffi::createPauliHamilFromFile((*filename).as_ptr()) })
61 })
62 }
63}
64
65impl Drop for PauliHamil {
66 fn drop(&mut self) {
67 catch_quest_exception(|| unsafe { ffi::destroyPauliHamil(self.0) })
68 .expect("dropping PauliHamil should always succeed");
69 }
70}
71
72#[derive(Debug)]
73pub struct DiagonalOp<'a> {
74 pub(crate) env: &'a QuestEnv,
75 pub(crate) op: ffi::DiagonalOp,
76}
77
78impl<'a> DiagonalOp<'a> {
79 pub fn try_new(
80 num_qubits: i32,
81 env: &'a QuestEnv,
82 ) -> Result<Self, QuestError> {
83 Ok(Self {
84 env,
85 op: catch_quest_exception(|| unsafe {
86 ffi::createDiagonalOp(num_qubits, env.0)
87 })?,
88 })
89 }
90
91 pub fn try_new_from_file(
92 fn_: &str,
93 env: &'a QuestEnv,
94 ) -> Result<Self, QuestError> {
95 let filename = CString::new(fn_).map_err(QuestError::NulError)?;
96
97 Ok(Self {
98 env,
99 op: catch_quest_exception(|| unsafe {
100 ffi::createDiagonalOpFromPauliHamilFile(
101 (*filename).as_ptr(),
102 env.0,
103 )
104 })?,
105 })
106 }
107}
108
109impl<'a> Drop for DiagonalOp<'a> {
110 fn drop(&mut self) {
111 catch_quest_exception(|| unsafe {
112 ffi::destroyDiagonalOp(self.op, self.env.0);
113 })
114 .expect("dropping DiagonalOp should always succeed");
115 }
116}
117
118/// Initialize [`PauliHamil`](crate::PauliHamil) instance with the given term
119/// coefficients
120///
121/// # Examples
122///
123/// ```rust
124/// # use quest_bind::*;
125/// use quest_bind::PauliOpType::*;
126///
127/// let hamil = &mut PauliHamil::try_new(2, 2).unwrap();
128///
129/// init_pauli_hamil(
130/// hamil,
131/// &[0.5, -0.5],
132/// &[PAULI_X, PAULI_Y, PAULI_I, PAULI_I, PAULI_Z, PAULI_X],
133/// )
134/// .unwrap();
135/// ```
136///
137/// See [QuEST API] for more information.
138///
139/// [QuEST API]: https://quest-kit.github.io/QuEST/modules.html
140#[allow(clippy::needless_pass_by_ref_mut)]
141pub fn init_pauli_hamil(
142 hamil: &mut PauliHamil,
143 coeffs: &[Qreal],
144 codes: &[PauliOpType],
145) -> Result<(), QuestError> {
146 catch_quest_exception(|| unsafe {
147 ffi::initPauliHamil(hamil.0, coeffs.as_ptr(), codes.as_ptr());
148 })
149}
150
151/// Update the GPU memory with the current values in `op`.
152///
153/// # Examples
154///
155/// ```rust
156/// # use quest_bind::*;
157/// let env = &QuestEnv::new();
158/// let op = &mut DiagonalOp::try_new(1, env).unwrap();
159///
160/// sync_diagonal_op(op).unwrap();
161/// ```
162/// See [QuEST API] for more information.
163///
164/// [QuEST API]: https://quest-kit.github.io/QuEST/modules.html
165#[allow(clippy::needless_pass_by_ref_mut)]
166pub fn sync_diagonal_op(op: &mut DiagonalOp<'_>) -> Result<(), QuestError> {
167 catch_quest_exception(|| unsafe {
168 ffi::syncDiagonalOp(op.op);
169 })
170}
171
172/// Overwrites the entire `DiagonalOp` with the given elements.
173///
174/// # Examples
175///
176/// ```rust
177/// # use quest_bind::*;
178/// let env = &QuestEnv::new();
179/// let mut op = &mut DiagonalOp::try_new(2, env).unwrap();
180///
181/// let real = &[1., 2., 3., 4.];
182/// let imag = &[5., 6., 7., 8.];
183/// init_diagonal_op(op, real, imag);
184/// ```
185/// See [QuEST API] for more information.
186///
187/// # Panics
188///
189/// This function will panic, if either `real` or `imag`
190/// have length smaller than `2.pow(num_qubits)`.
191///
192/// [QuEST API]: https://quest-kit.github.io/QuEST/modules.html
193#[allow(clippy::cast_sign_loss)]
194#[allow(clippy::needless_pass_by_ref_mut)]
195pub fn init_diagonal_op(
196 op: &mut DiagonalOp<'_>,
197 real: &[Qreal],
198 imag: &[Qreal],
199) -> Result<(), QuestError> {
200 let len_required = 2usize.pow(op.op.numQubits as u32);
201 assert!(real.len() >= len_required);
202 assert!(imag.len() >= len_required);
203 catch_quest_exception(|| unsafe {
204 ffi::initDiagonalOp(op.op, real.as_ptr(), imag.as_ptr());
205 })
206}
207
208/// Populates the diagonal operator \p op to be equivalent to the given Pauli
209/// Hamiltonian
210///
211/// Assuming `hamil` contains only `PAULI_I` or `PAULI_Z` operators.
212///
213/// # Examples
214///
215/// ```rust
216/// # use quest_bind::*;
217/// use quest_bind::PauliOpType::*;
218///
219/// let hamil = &mut PauliHamil::try_new(2, 2).unwrap();
220/// init_pauli_hamil(
221/// hamil,
222/// &[0.5, -0.5],
223/// &[PAULI_I, PAULI_Z, PAULI_Z, PAULI_Z],
224/// )
225/// .unwrap();
226///
227/// let env = &QuestEnv::new();
228/// let mut op = &mut DiagonalOp::try_new(2, env).unwrap();
229///
230/// init_diagonal_op_from_pauli_hamil(op, hamil).unwrap();
231/// ```
232///
233/// See [QuEST API] for more information.
234///
235/// [QuEST API]: https://quest-kit.github.io/QuEST/modules.html
236#[allow(clippy::needless_pass_by_ref_mut)]
237pub fn init_diagonal_op_from_pauli_hamil(
238 op: &mut DiagonalOp<'_>,
239 hamil: &PauliHamil,
240) -> Result<(), QuestError> {
241 catch_quest_exception(|| unsafe {
242 ffi::initDiagonalOpFromPauliHamil(op.op, hamil.0);
243 })
244}
245
246/// Modifies a subset of elements of `DiagonalOp`.
247///
248/// Starting at index `start_ind`, and ending at index
249/// `start_ind + num_elems`.
250///
251/// # Examples
252///
253/// ```rust
254/// # use quest_bind::*;
255/// let env = &QuestEnv::new();
256/// let op = &mut DiagonalOp::try_new(3, env).unwrap();
257///
258/// let num_elems = 4;
259/// let re = &[1., 2., 3., 4.];
260/// let im = &[1., 2., 3., 4.];
261/// set_diagonal_op_elems(op, 0, re, im, num_elems).unwrap();
262/// ```
263///
264/// # Panics
265///
266/// This function will panic if either
267/// `real.len() >= num_elems as usize`, or
268/// `imag.len() >= num_elems as usize`.
269///
270/// See [QuEST API] for more information.
271///
272/// [QuEST API]: https://quest-kit.github.io/QuEST/modules.html
273#[allow(clippy::cast_sign_loss)]
274#[allow(clippy::cast_possible_truncation)]
275#[allow(clippy::needless_pass_by_ref_mut)]
276pub fn set_diagonal_op_elems(
277 op: &mut DiagonalOp<'_>,
278 start_ind: i64,
279 real: &[Qreal],
280 imag: &[Qreal],
281 num_elems: i64,
282) -> Result<(), QuestError> {
283 assert!(real.len() >= num_elems as usize);
284 assert!(imag.len() >= num_elems as usize);
285
286 catch_quest_exception(|| unsafe {
287 ffi::setDiagonalOpElems(
288 op.op,
289 start_ind,
290 real.as_ptr(),
291 imag.as_ptr(),
292 num_elems,
293 );
294 })
295}
296
297/// Apply a diagonal operator to the entire `qureg`.
298///
299/// # Examples
300///
301/// ```rust
302/// # use quest_bind::*;
303/// let env = &QuestEnv::new();
304/// let qureg =
305/// &mut Qureg::try_new(2, &env).expect("cannot allocate memory for Qureg");
306/// let op = &mut DiagonalOp::try_new(2, env).unwrap();
307///
308/// init_diagonal_op(op, &[1., 2., 3., 4.], &[5., 6., 7., 8.]).unwrap();
309/// apply_diagonal_op(qureg, &op).unwrap();
310/// ```
311///
312/// See [QuEST API] for more information.
313///
314/// [QuEST API]: https://quest-kit.github.io/QuEST/modules.html
315#[allow(clippy::needless_pass_by_ref_mut)]
316pub fn apply_diagonal_op(
317 qureg: &mut Qureg<'_>,
318 op: &DiagonalOp<'_>,
319) -> Result<(), QuestError> {
320 catch_quest_exception(|| unsafe {
321 ffi::applyDiagonalOp(qureg.reg, op.op);
322 })
323}
324
325/// Computes the expected value of the diagonal operator `op`.
326///
327/// Since `op` is not necessarily Hermitian, the expected value may be a complex
328/// number.
329///
330/// # Examples
331///
332/// ```rust
333/// # use quest_bind::*;
334/// let env = &QuestEnv::new();
335/// let qureg =
336/// &mut Qureg::try_new(2, &env).expect("cannot allocate memory for Qureg");
337/// let op = &mut DiagonalOp::try_new(2, env).unwrap();
338///
339/// init_diagonal_op(op, &[1., 2., 3., 4.], &[5., 6., 7., 8.]).unwrap();
340///
341/// let expec_val = calc_expec_diagonal_op(qureg, op).unwrap();
342///
343/// assert!((expec_val.re - 1.).abs() < EPSILON);
344/// assert!((expec_val.im - 5.).abs() < EPSILON);
345/// ```
346///
347/// See [QuEST API] for more information.
348///
349/// [QuEST API]: https://quest-kit.github.io/QuEST/modules.html
350pub fn calc_expec_diagonal_op(
351 qureg: &Qureg<'_>,
352 op: &DiagonalOp<'_>,
353) -> Result<Qcomplex, QuestError> {
354 catch_quest_exception(|| unsafe {
355 ffi::calcExpecDiagonalOp(qureg.reg, op.op)
356 })
357 .map(Into::into)
358}