• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Qiskit / qiskit / 14202044040

01 Apr 2025 04:47PM CUT coverage: 88.055% (+0.004%) from 88.051%
14202044040

push

github

web-flow
Bump PyO3 to 0.24.1 and numpy to 0.24.0 (#14149)

This commit bumps the PyO3 version we use in Qiskit to the latest
pyo3 release 0.24.1. Luckily this time there don't seem to be any API
changes required to upgrade so it's a straight version bump.

72806 of 82682 relevant lines covered (88.06%)

366165.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/crates/cext/src/sparse_observable.rs
1
// This code is part of Qiskit.
2
//
3
// (C) Copyright IBM 2024
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
use std::ffi::{c_char, CString};
14

15
use crate::exit_codes::{CInputError, ExitCode};
16
use num_complex::Complex64;
17

18
use qiskit_accelerate::sparse_observable::{BitTerm, SparseObservable, SparseTermView};
19

20
#[cfg(feature = "python_binding")]
21
use pyo3::ffi::PyObject;
22
#[cfg(feature = "python_binding")]
23
use pyo3::{Py, Python};
24
#[cfg(feature = "python_binding")]
25
use qiskit_accelerate::sparse_observable::PySparseObservable;
26

27
/// A term in a ``QkObs``.
28
///
29
/// This contains the coefficient (``coeff``), the number of qubits of the observable
30
/// (``num_qubits``) and pointers to the ``bit_terms`` and ``indices`` arrays, which have
31
/// length ``len``. It's the responsibility of the user that the data is coherent,
32
/// see also the below section on safety.
33
///
34
/// # Safety
35
///
36
/// * ``bit_terms`` must be a non-null, aligned pointer to ``len`` elements of type ``QkBitTerm``.
37
/// * ``indices`` must be a non-null, aligned pointer to ``len`` elements of type ``uint32_t``.
38
#[repr(C)]
39
pub struct CSparseTerm {
40
    /// The coefficient of the observable term.
41
    coeff: Complex64,
42
    /// Length of the ``bit_terms`` and ``indices`` arrays.
43
    len: usize,
44
    /// A non-null, aligned pointer to ``len`` elements of type ``QkBitTerm``.
45
    bit_terms: *mut BitTerm,
46
    /// A non-null, aligned pointer to ``len`` elements of type ``uint32_t``.
47
    indices: *mut u32,
48
    /// The number of qubits the observable term is defined on.
49
    num_qubits: u32,
50
}
51

52
impl TryFrom<&CSparseTerm> for SparseTermView<'_> {
53
    type Error = CInputError;
54

55
    fn try_from(value: &CSparseTerm) -> Result<Self, Self::Error> {
×
56
        check_ptr(value.bit_terms)?;
×
57
        check_ptr(value.indices)?;
×
58

59
        // SAFETY: At this point we know the pointers are non-null and aligned. We rely on C
60
        // that the arrays have the appropriate length, which is documented as requirement in the
61
        // CSparseTerm class.
62
        let bit_terms = unsafe { ::std::slice::from_raw_parts(value.bit_terms, value.len) };
×
63
        let indices = unsafe { ::std::slice::from_raw_parts(value.indices, value.len) };
×
64

×
65
        Ok(SparseTermView {
×
66
            num_qubits: value.num_qubits,
×
67
            coeff: value.coeff,
×
68
            bit_terms,
×
69
            indices,
×
70
        })
×
71
    }
×
72
}
73

74
/// Check the pointer is not null and is aligned.
75
fn check_ptr<T>(ptr: *const T) -> Result<(), CInputError> {
×
76
    if ptr.is_null() {
×
77
        return Err(CInputError::NullPointerError);
×
78
    };
×
79
    if !ptr.is_aligned() {
×
80
        return Err(CInputError::AlignmentError);
×
81
    };
×
82
    Ok(())
×
83
}
×
84

85
/// Casts a const pointer to a reference. Panics is the pointer is null or not aligned.
86
///
87
/// # Safety
88
///
89
/// This function requires ``ptr`` to be point to an initialized object of type ``T``.
90
/// While the resulting reference exists, the memory pointed to must not be mutated.
91
unsafe fn const_ptr_as_ref<'a, T>(ptr: *const T) -> &'a T {
×
92
    check_ptr(ptr).unwrap();
×
93
    let as_ref = unsafe { ptr.as_ref() };
×
94
    as_ref.unwrap() // we know the pointer is not null, hence we can safely unwrap
×
95
}
×
96

97
/// Casts a mut pointer to a mut reference. Panics is the pointer is null or not aligned.
98
///
99
/// # Safety
100
///
101
/// This function requires ``ptr`` to be point to an initialized object of type ``T``.
102
/// While the resulting reference exists, the memory pointed to must not be accessed otherwise.
103
unsafe fn mut_ptr_as_ref<'a, T>(ptr: *mut T) -> &'a mut T {
×
104
    check_ptr(ptr).unwrap();
×
105
    let as_mut_ref = unsafe { ptr.as_mut() };
×
106
    as_mut_ref.unwrap() // we know the pointer is not null, hence we can safely unwrap
×
107
}
×
108

109
/// @ingroup QkObs
110
/// Construct the zero observable (without any terms).
111
///
112
/// @param num_qubits The number of qubits the observable is defined on.
113
///
114
/// @return A pointer to the created observable.
115
///
116
/// # Example
117
///
118
///     QkObs *zero = qk_obs_zero(100);
119
///
120
#[no_mangle]
121
#[cfg(feature = "cbinding")]
122
pub extern "C" fn qk_obs_zero(num_qubits: u32) -> *mut SparseObservable {
×
123
    let obs = SparseObservable::zero(num_qubits);
×
124
    Box::into_raw(Box::new(obs))
×
125
}
×
126

127
/// @ingroup QkObs
128
/// Construct the identity observable.
129
///
130
/// @param num_qubits The number of qubits the observable is defined on.
131
///
132
/// @return A pointer to the created observable.
133
///
134
/// # Example
135
///
136
///     QkObs *identity = qk_obs_identity(100);
137
///
138
#[no_mangle]
139
#[cfg(feature = "cbinding")]
140
pub extern "C" fn qk_obs_identity(num_qubits: u32) -> *mut SparseObservable {
×
141
    let obs = SparseObservable::identity(num_qubits);
×
142
    Box::into_raw(Box::new(obs))
×
143
}
×
144

145
/// @ingroup QkObs
146
/// Construct a new observable from raw data.
147
///
148
/// @param num_qubits The number of qubits the observable is defined on.
149
/// @param num_terms The number of terms.
150
/// @param num_bits The total number of non-identity bit terms.
151
/// @param coeffs A pointer to the first element of the coefficients array, which has length
152
///     ``num_terms``.
153
/// @param bit_terms A pointer to the first element of the bit terms array, which has length
154
///     ``num_bits``.
155
/// @param indices A pointer to the first element of the indices array, which has length
156
///     ``num_bits``. Note that, per term, these *must* be sorted incrementally.
157
/// @param boundaries A pointer to the first element of the boundaries array, which has length
158
///     ``num_terms + 1``.
159
///
160
/// @return If the input data was coherent and the construction successful, the result is a pointer
161
///     to the observable. Otherwise a null pointer is returned.
162
///
163
/// # Example
164
///
165
///     // define the raw data for the 100-qubit observable |01><01|_{0, 1} - |+-><+-|_{98, 99}
166
///     uint32_t num_qubits = 100;
167
///     uint64_t num_terms = 2;  // we have 2 terms: |01><01|, -1 * |+-><+-|
168
///     uint64_t num_bits = 4; // we have 4 non-identity bits: 0, 1, +, -
169
///
170
///     complex double coeffs[2] = {1, -1};
171
///     QkBitTerm bits[4] = {QkBitTerm_Zero, QkBitTerm_One, QkBitTerm_Plus, QkBitTerm_Minus};
172
///     uint32_t indices[4] = {0, 1, 98, 99};  // <-- e.g. {1, 0, 99, 98} would be invalid
173
///     size_t boundaries[3] = {0, 2, 4};
174
///
175
///     QkObs *obs = qk_obs_new(
176
///         num_qubits, num_terms, num_bits, coeffs, bits, indices, boundaries
177
///     );
178
///
179
/// # Safety
180
///
181
/// Behavior is undefined if any of the following conditions are violated:
182
///
183
///   * ``coeffs`` is a pointer to a ``complex double`` array of length ``num_terms``
184
///   * ``bit_terms`` is a pointer to an array of valid ``QkBitTerm`` elements of length ``num_bits``
185
///   * ``indices`` is a pointer to a ``uint32_t`` array of length ``num_bits``, which is
186
///     term-wise sorted in strict ascending order, and every element is smaller than ``num_qubits``
187
///   * ``boundaries`` is a pointer to a ``size_t`` array of length ``num_terms + 1``, which is
188
///     sorted in ascending order, the first element is 0 and the last element is
189
///     smaller than ``num_terms``
190
#[no_mangle]
191
#[cfg(feature = "cbinding")]
192
pub unsafe extern "C" fn qk_obs_new(
×
193
    num_qubits: u32,
×
194
    num_terms: u64,
×
195
    num_bits: u64,
×
196
    coeffs: *mut Complex64,
×
197
    bit_terms: *mut BitTerm,
×
198
    indices: *mut u32,
×
199
    boundaries: *mut usize,
×
200
) -> *mut SparseObservable {
×
201
    let num_terms = num_terms as usize;
×
202
    let num_bits = num_bits as usize;
×
203

×
204
    check_ptr(coeffs).unwrap();
×
205
    check_ptr(bit_terms).unwrap();
×
206
    check_ptr(indices).unwrap();
×
207
    check_ptr(boundaries).unwrap();
×
208

×
209
    // SAFETY: At this point we know the pointers are non-null and aligned. We rely on C that
×
210
    // the pointers point to arrays of appropriate length, as specified in the function docs.
×
211
    let coeffs = unsafe { ::std::slice::from_raw_parts(coeffs, num_terms).to_vec() };
×
212
    let bit_terms = unsafe { ::std::slice::from_raw_parts(bit_terms, num_bits).to_vec() };
×
213
    let indices = unsafe { ::std::slice::from_raw_parts(indices, num_bits).to_vec() };
×
214
    let boundaries = unsafe { ::std::slice::from_raw_parts(boundaries, num_terms + 1).to_vec() };
×
215

×
216
    let result = SparseObservable::new(num_qubits, coeffs, bit_terms, indices, boundaries);
×
217
    match result {
×
218
        Ok(obs) => Box::into_raw(Box::new(obs)),
×
219
        Err(_) => ::std::ptr::null_mut(),
×
220
    }
221
}
×
222

223
/// @ingroup QkObs
224
/// Free the observable.
225
///
226
/// @param obs A pointer to the observable to free.
227
///
228
/// # Example
229
///
230
///     QkObs *obs = qk_obs_zero(100);
231
///     qk_obs_free(obs);
232
///
233
/// # Safety
234
///
235
/// Behavior is undefined if ``obs`` is not either null or a valid pointer to a ``QkObs``.
236
#[no_mangle]
237
#[cfg(feature = "cbinding")]
238
pub unsafe extern "C" fn qk_obs_free(obs: *mut SparseObservable) {
×
239
    if !obs.is_null() {
×
240
        if !obs.is_aligned() {
×
241
            panic!("Attempted to free a non-aligned pointer.")
×
242
        }
×
243

×
244
        // SAFETY: We have verified the pointer is non-null and aligned, so it should be
×
245
        // readable by Box.
×
246
        unsafe {
×
247
            let _ = Box::from_raw(obs);
×
248
        }
×
249
    }
×
250
}
×
251

252
/// @ingroup QkObs
253
/// Add a term to the observable.
254
///
255
/// @param obs A pointer to the observable.
256
/// @param cterm A pointer to the term to add.
257
///
258
/// @return An exit code. This is ``>0`` if the term is incoherent or adding the term fails.
259
///
260
/// # Example
261
///
262
///     uint32_t num_qubits = 100;
263
///     QkObs *obs = qk_obs_zero(num_qubits);
264
///
265
///     complex double coeff = 1;
266
///     QkBitTerm bit_terms[3] = {QkBitTerm_X, QkBitTerm_Y, QkBitTerm_Z};
267
///     uint32_t indices[3] = {0, 1, 2};
268
///     QkObsTerm term = {&coeff, 3, bit_terms, indices, num_qubits};
269
///
270
///     int exit_code = qk_obs_add_term(obs, &term);
271
///
272
/// # Safety
273
///
274
/// Behavior is undefined if any of the following is violated:
275
///
276
///   * ``obs`` is a valid, non-null pointer to a ``QkObs``
277
///   * ``cterm`` is a valid, non-null pointer to a ``QkObsTerm``
278
#[no_mangle]
279
#[cfg(feature = "cbinding")]
280
pub unsafe extern "C" fn qk_obs_add_term(
×
281
    obs: *mut SparseObservable,
×
282
    cterm: *const CSparseTerm,
×
283
) -> ExitCode {
×
284
    // SAFETY: Per documentation, the pointers are non-null and aligned.
×
285
    let obs = unsafe { mut_ptr_as_ref(obs) };
×
286
    let cterm = unsafe { const_ptr_as_ref(cterm) };
×
287

288
    let view = match cterm.try_into() {
×
289
        Ok(view) => view,
×
290
        Err(err) => return ExitCode::from(err),
×
291
    };
292

293
    match obs.add_term(view) {
×
294
        Ok(_) => ExitCode::Success,
×
295
        Err(err) => ExitCode::from(err),
×
296
    }
297
}
×
298

299
/// @ingroup QkObs
300
/// Get an observable term by reference.
301
///
302
/// A ``QkObsTerm`` contains pointers to the indices and bit terms in the term, which
303
/// can be used to modify the internal data of the observable. This can leave the observable
304
/// in an incoherent state and should be avoided, unless great care is taken. It is generally
305
/// safer to construct a new observable instead of attempting in-place modifications.
306
///
307
/// @param obs A pointer to the observable.
308
/// @param index The index of the term to get.
309
/// @param out A pointer to a ``QkObsTerm`` used to return the observable term.
310
///
311
/// @return An exit code.
312
///
313
/// # Example
314
///
315
///     QkObs *obs = qk_obs_identity(100);
316
///     QkObsTerm term;
317
///     int exit_code = qk_obs_term(obs, 0, &term);
318
///     // out-of-bounds indices return an error code
319
///     // int error = qk_obs_term(obs, 12, &term);
320
///
321
/// # Safety
322
///
323
/// Behavior is undefined if any of the following is violated
324
/// * ``obs`` is a valid, non-null pointer to a ``QkObs``
325
/// * ``out`` is a valid, non-null pointer to a ``QkObsTerm``
326
#[no_mangle]
327
#[cfg(feature = "cbinding")]
328
pub unsafe extern "C" fn qk_obs_term(
×
329
    obs: *mut SparseObservable,
×
330
    index: u64,
×
331
    out: *mut CSparseTerm,
×
332
) -> ExitCode {
×
333
    // SAFETY: Per documentation, the pointers are non-null and aligned.
×
334
    let out = unsafe { mut_ptr_as_ref(out) };
×
335
    let obs = unsafe { mut_ptr_as_ref(obs) };
×
336

×
337
    let index = index as usize;
×
338
    if index > obs.num_terms() {
×
339
        return ExitCode::IndexError;
×
340
    }
×
341

×
342
    out.len = obs.boundaries()[index + 1] - obs.boundaries()[index];
×
343
    out.coeff = obs.coeffs()[index];
×
344
    out.num_qubits = obs.num_qubits();
×
345

×
346
    let start = obs.boundaries()[index];
×
347
    out.bit_terms = &mut obs.bit_terms_mut()[start];
×
348
    // SAFETY: mutating the indices can leave the observable in an incoherent state.
×
349
    out.indices = &mut unsafe { obs.indices_mut() }[start];
×
350

×
351
    ExitCode::Success
×
352
}
×
353

354
/// @ingroup QkObs
355
/// Get the number of terms in the observable.
356
///
357
/// @param obs A pointer to the observable.
358
///
359
/// @return The number of terms in the observable.
360
///
361
/// # Example
362
///
363
///     QkObs *obs = qk_obs_identity(100);
364
///     size_t num_terms = qk_obs_num_terms(obs);  // num_terms==1
365
///
366
/// # Safety
367
///
368
/// Behavior is undefined ``obs`` is not a valid, non-null pointer to a ``QkObs``.
369
#[no_mangle]
370
#[cfg(feature = "cbinding")]
371
pub unsafe extern "C" fn qk_obs_num_terms(obs: *const SparseObservable) -> usize {
×
372
    // SAFETY: Per documentation, the pointer is non-null and aligned.
×
373
    let obs = unsafe { const_ptr_as_ref(obs) };
×
374

×
375
    obs.num_terms()
×
376
}
×
377

378
/// @ingroup QkObs
379
/// Get the number of qubits the observable is defined on.
380
///
381
/// @param obs A pointer to the observable.
382
///
383
/// @return The number of qubits the observable is defined on.
384
///
385
/// # Example
386
///
387
///     QkObs *obs = qk_obs_identity(100);
388
///     uint32_t num_qubits = qk_obs_num_qubits(obs);  // num_qubits==100
389
///
390
/// # Safety
391
///
392
/// Behavior is undefined ``obs`` is not a valid, non-null pointer to a ``QkObs``.
393
#[no_mangle]
394
#[cfg(feature = "cbinding")]
395
pub unsafe extern "C" fn qk_obs_num_qubits(obs: *const SparseObservable) -> u32 {
×
396
    // SAFETY: Per documentation, the pointer is non-null and aligned.
×
397
    let obs = unsafe { const_ptr_as_ref(obs) };
×
398

×
399
    obs.num_qubits()
×
400
}
×
401

402
/// @ingroup QkObs
403
/// Get the number of bit terms/indices in the observable.
404
///
405
/// @param obs A pointer to the observable.
406
///
407
/// @return The number of terms in the observable.
408
///
409
/// # Example
410
///
411
///     QkObs *obs = qk_obs_identity(100);
412
///     size_t len = qk_obs_len(obs);  // len==0, as there are no non-trivial bit terms
413
///
414
/// # Safety
415
///
416
/// Behavior is undefined ``obs`` is not a valid, non-null pointer to a ``QkObs``.
417
#[no_mangle]
418
#[cfg(feature = "cbinding")]
419
pub unsafe extern "C" fn qk_obs_len(obs: *const SparseObservable) -> usize {
×
420
    // SAFETY: Per documentation, the pointer is non-null and aligned.
×
421
    let obs = unsafe { const_ptr_as_ref(obs) };
×
422

×
423
    obs.bit_terms().len()
×
424
}
×
425

426
/// @ingroup QkObs
427
/// Get a pointer to the coefficients.
428
///
429
/// This can be used to read and modify the observable's coefficients. The resulting
430
/// pointer is valid to read for ``qk_obs_num_terms(obs)`` elements of ``complex double``.
431
///
432
/// @param obs A pointer to the observable.
433
///
434
/// @return A pointer to the coefficients.
435
///
436
/// # Example
437
///
438
///     QkObs *obs = qk_obs_identity(100);
439
///     size_t num_terms = qk_obs_num_terms(obs);
440
///     complex double *coeffs = qk_obs_coeffs(obs);
441
///
442
///     for (size_t i = 0; i < num_terms; i++) {
443
///         printf("%f + i%f\n", creal(coeffs[i]), cimag(coeffs[i]));
444
///     }
445
///
446
/// # Safety
447
///
448
/// Behavior is undefined ``obs`` is not a valid, non-null pointer to a ``QkObs``.
449
#[no_mangle]
450
#[cfg(feature = "cbinding")]
451
pub unsafe extern "C" fn qk_obs_coeffs(obs: *mut SparseObservable) -> *mut Complex64 {
×
452
    // SAFETY: Per documentation, the pointer is non-null and aligned.
×
453
    let obs = unsafe { mut_ptr_as_ref(obs) };
×
454

×
455
    obs.coeffs_mut().as_mut_ptr()
×
456
}
×
457

458
/// @ingroup QkObs
459
/// Get a pointer to the indices.
460
///
461
/// This can be used to read and modify the observable's indices. The resulting pointer is
462
/// valid to read for ``qk_obs_len(obs)`` elements of size ``uint32_t``.
463
///
464
/// @param obs A pointer to the observable.
465
///
466
/// @return A pointer to the indices.
467
///
468
/// # Example
469
///
470
///     uint32_t num_qubits = 100;
471
///     QkObs *obs = qk_obs_zero(num_qubits);
472
///
473
///     complex double coeff = 1;
474
///     QkBitTerm bit_terms[3] = {QkBitTerm_X, QkBitTerm_Y, QkBitTerm_Z};
475
///     uint32_t indices[3] = {0, 1, 2};
476
///     QkObsTerm term = {&coeff, 3, bit_terms, indices, num_qubits};
477
///     qk_obs_add_term(obs, &term);
478
///
479
///     size_T len = qk_obs_len(obs);
480
///     uint32_t *indices = qk_obs_indices(obs);
481
///
482
///     for (size_t i = 0; i < len; i++) {
483
///         printf("index %i: %i\n", i, indices[i]);
484
///     }
485
///
486
/// # Safety
487
///
488
/// Behavior is undefined ``obs`` is not a valid, non-null pointer to a ``QkObs``.
489
#[no_mangle]
490
#[cfg(feature = "cbinding")]
491
pub unsafe extern "C" fn qk_obs_indices(obs: *mut SparseObservable) -> *mut u32 {
×
492
    // SAFETY: Per documentation, the pointer is non-null and aligned.
×
493
    let obs = unsafe { mut_ptr_as_ref(obs) };
×
494

×
495
    // SAFETY: Mutating the indices can leave the observable in an incoherent state.
×
496
    unsafe { obs.indices_mut() }.as_mut_ptr()
×
497
}
×
498

499
/// @ingroup QkObs
500
/// Get a pointer to the term boundaries.
501
///
502
/// This can be used to read and modify the observable's term boundaries. The resulting pointer is
503
/// valid to read for ``qk_obs_num_terms(obs) + 1`` elements of size ``size_t``.
504
///
505
/// @param obs A pointer to the observable.
506
///
507
/// @return A pointer to the boundaries.
508
///
509
/// # Example
510
///
511
///     uint32_t num_qubits = 100;
512
///     QkObs *obs = qk_obs_zero(num_qubits);
513
///
514
///     complex double coeff = 1;
515
///     QkBitTerm bit_terms[3] = {QkBitTerm_X, QkBitTerm_Y, QkBitTerm_Z};
516
///     uint32_t indices[3] = {0, 1, 2};
517
///     QkObsTerm term = {&coeff, 3, bit_terms, indices, num_qubits};
518
///     qk_obs_add_term(obs, &term);
519
///
520
///     size_t num_terms = qk_obs_num_terms(obs);
521
///     uint32_t *boundaries = qk_obs_boundaries(obs);
522
///
523
///     for (size_t i = 0; i < num_terms + 1; i++) {
524
///         printf("boundary %i: %i\n", i, boundaries[i]);
525
///     }
526
///
527
/// # Safety
528
///
529
/// Behavior is undefined ``obs`` is not a valid, non-null pointer to a ``QkObs``.
530
#[no_mangle]
531
#[cfg(feature = "cbinding")]
532
pub unsafe extern "C" fn qk_obs_boundaries(obs: *mut SparseObservable) -> *mut usize {
×
533
    // SAFETY: Per documentation, the pointer is non-null and aligned.
×
534
    let obs = unsafe { mut_ptr_as_ref(obs) };
×
535

×
536
    // SAFETY: Modifying the boundaries can leave the observable in an incoherent state. It is
×
537
    // the responsibility of the user that the data is coherent.
×
538
    unsafe { obs.boundaries_mut() }.as_mut_ptr()
×
539
}
×
540

541
/// @ingroup QkObs
542
/// Get a pointer to the bit terms.
543
///
544
/// This can be used to read and modify the observable's bit terms. The resulting pointer is
545
/// valid to read for ``qk_obs_len(obs)`` elements of size ``uint8_t``.
546
///
547
/// @param obs A pointer to the observable.
548
///
549
/// @return A pointer to the bit terms.
550
///
551
/// # Example
552
///
553
///     uint32_t num_qubits = 100;
554
///     QkObs *obs = qk_obs_zero(num_qubits);
555
///
556
///     complex double coeff = 1;
557
///     QkBitTerm bit_terms[3] = {QkBitTerm_X, QkBitTerm_Y, QkBitTerm_Z};
558
///     uint32_t indices[3] = {0, 1, 2};
559
///     QkObsTerm term = {&coeff, 3, bit_terms, indices, num_qubits};
560
///     qk_obs_add_term(obs, &term);
561
///
562
///     size_t len = qk_obs_len(obs);
563
///     QkBitTerm *bits = qk_obs_bit_terms(obs);
564
///
565
///     for (size_t i = 0; i < len; i++) {
566
///         printf("bit term %i: %i\n", i, bits[i]);
567
///     }
568
///
569
/// # Safety
570
///
571
/// Behavior is undefined ``obs`` is not a valid, non-null pointer to a ``QkObs``,
572
/// or if invalid valus are written into the resulting ``QkBitTerm`` pointer.
573
#[no_mangle]
574
#[cfg(feature = "cbinding")]
575
pub unsafe extern "C" fn qk_obs_bit_terms(obs: *mut SparseObservable) -> *mut BitTerm {
×
576
    // SAFETY: Per documentation, the pointer is non-null and aligned.
×
577
    let obs = unsafe { mut_ptr_as_ref(obs) };
×
578

×
579
    obs.bit_terms_mut().as_mut_ptr()
×
580
}
×
581

582
/// @ingroup QkObs
583
/// Multiply the observable by a complex coefficient.
584
///
585
/// @param obs A pointer to the observable.
586
/// @param coeff The coefficient to multiply the observable with.
587
///
588
/// # Example
589
///
590
///     QkObs *obs = qk_obs_identity(100);
591
///     complex double coeff = 2;
592
///     QkObs *result = qk_obs_multiply(obs, &coeff);
593
///
594
/// # Safety
595
///
596
/// Behavior is undefined if any of the following is violated
597
/// * ``obs`` is a valid, non-null pointer to a ``QkObs``
598
/// * ``coeff`` is a valid, non-null pointer to a ``complex double``
599
#[no_mangle]
600
#[cfg(feature = "cbinding")]
601
pub unsafe extern "C" fn qk_obs_multiply(
×
602
    obs: *const SparseObservable,
×
603
    coeff: *const Complex64,
×
604
) -> *mut SparseObservable {
×
605
    // SAFETY: Per documentation, the pointers are non-null and aligned.
×
606
    let obs = unsafe { const_ptr_as_ref(obs) };
×
607
    let coeff = unsafe { const_ptr_as_ref(coeff) };
×
608

×
609
    let result = obs * (*coeff);
×
610
    Box::into_raw(Box::new(result))
×
611
}
×
612

613
/// @ingroup QkObs
614
/// Add two observables.
615
///
616
/// @param left A pointer to the left observable.
617
/// @param right A pointer to the right observable.
618
///
619
/// @return A pointer to the result ``left + right``.
620
///
621
/// # Example
622
///
623
///     QkObs *left = qk_obs_identity(100);
624
///     QkObs *right = qk_obs_zero(100);
625
///     QkObs *result = qk_obs_add(left, right);
626
///
627
/// # Safety
628
///
629
/// Behavior is undefined if ``left`` or ``right`` are not valid, non-null pointers to
630
/// ``QkObs``\ s.
631
#[no_mangle]
632
#[cfg(feature = "cbinding")]
633
pub unsafe extern "C" fn qk_obs_add(
×
634
    left: *const SparseObservable,
×
635
    right: *const SparseObservable,
×
636
) -> *mut SparseObservable {
×
637
    // SAFETY: Per documentation, the pointers are non-null and aligned.
×
638
    let left = unsafe { const_ptr_as_ref(left) };
×
639
    let right = unsafe { const_ptr_as_ref(right) };
×
640

×
641
    let result = left + right;
×
642
    Box::into_raw(Box::new(result))
×
643
}
×
644

645
/// @ingroup QkObs
646
/// Compose (multiply) two observables.
647
///
648
/// @param first One observable.
649
/// @param second The other observable.
650
///
651
/// @return ``first.compose(second)`` which equals the observable ``result = second @ first``,
652
///     in terms of the matrix multiplication ``@``.
653
///
654
/// # Example
655
///
656
///     QkObs *first = qk_obs_zero(100);
657
///     QkObs *second = qk_obs_identity(100);
658
///     QkObs *result = qk_obs_compose(first, second);
659
///
660
/// # Safety
661
///
662
/// Behavior is undefined if ``first`` or ``second`` are not valid, non-null pointers to
663
/// ``QkObs``\ s.
664
#[no_mangle]
665
#[cfg(feature = "cbinding")]
666
pub unsafe extern "C" fn qk_obs_compose(
×
667
    first: *const SparseObservable,
×
668
    second: *const SparseObservable,
×
669
) -> *mut SparseObservable {
×
670
    // SAFETY: Per documentation, the pointers are non-null and aligned.
×
671
    let first = unsafe { const_ptr_as_ref(first) };
×
672
    let second = unsafe { const_ptr_as_ref(second) };
×
673

×
674
    let result = first.compose(second);
×
675
    Box::into_raw(Box::new(result))
×
676
}
×
677

678
/// @ingroup QkObs
679
/// Compose (multiply) two observables according to a custom qubit order.
680
///
681
/// Notably, this allows composing two observables of different size.
682
///
683
/// @param first One observable.
684
/// @param second The other observable. The number of qubits must match the length of ``qargs``.
685
/// @param qargs The qubit arguments specified which indices in ``first`` to associate with
686
///     the ones in ``second``.
687
///
688
/// @return ``first.compose(second)`` which equals the observable ``result = second @ first``,
689
///     in terms of the matrix multiplication ``@``.
690
///
691
/// # Example
692
///
693
///     QkObs *first = qk_obs_zero(100);
694
///     QkObs *second = qk_obs_identity(100);
695
///     QkObs *result = qk_obs_compose(first, second);
696
///
697
/// # Safety
698
///
699
/// To call this function safely
700
///
701
///   * ``first`` and ``second`` must be valid, non-null pointers to ``QkObs``\ s
702
///   * ``qargs`` must point to an array of ``uint32_t``, readable for ``qk_obs_num_qubits(second)``
703
///     elements (meaning the number of qubits in ``second``)
704
#[no_mangle]
705
#[cfg(feature = "cbinding")]
706
pub unsafe extern "C" fn qk_obs_compose_map(
×
707
    first: *const SparseObservable,
×
708
    second: *const SparseObservable,
×
709
    qargs: *const u32,
×
710
) -> *mut SparseObservable {
×
711
    // SAFETY: Per documentation, the pointers are non-null and aligned.
×
712
    let first = unsafe { const_ptr_as_ref(first) };
×
713
    let second = unsafe { const_ptr_as_ref(second) };
×
714

715
    let qargs = if qargs.is_null() {
×
716
        if second.num_qubits() != 0 {
×
717
            panic!("If qargs is null, then second must have 0 qubits.");
×
718
        }
×
719
        &[]
×
720
    } else {
721
        if !qargs.is_aligned() {
×
722
            panic!("qargs pointer is not aligned to u32");
×
723
        }
×
724
        // SAFETY: Per documentation, qargs is safe to read up to ``second.num_qubits()`` elements,
×
725
        // which is the maximal value of ``index`` here.
×
726
        unsafe { ::std::slice::from_raw_parts(qargs, second.num_qubits() as usize) }
×
727
    };
728

729
    let qargs_map = |index: u32| qargs[index as usize];
×
730

731
    let result = first.compose_map(second, qargs_map);
×
732
    Box::into_raw(Box::new(result))
×
733
}
×
734

735
/// @ingroup QkObs
736
/// Calculate the canonical representation of the observable.
737
///
738
/// @param obs A pointer to the observable.
739
/// @param tol The tolerance below which coefficients are considered to be zero.
740
///
741
/// @return The canonical representation of the observable.
742
///
743
/// # Example
744
///
745
///     QkObs *iden = qk_obs_identity(100);
746
///     QkObs *two = qk_obs_add(iden, iden);
747
///
748
///     double tol = 1e-6;
749
///     QkObs *canonical = qk_obs_canonicalize(two);
750
///
751
/// # Safety
752
///
753
/// Behavior is undefined ``obs`` is not a valid, non-null pointer to a ``QkObs``.
754
#[no_mangle]
755
#[cfg(feature = "cbinding")]
756
pub unsafe extern "C" fn qk_obs_canonicalize(
×
757
    obs: *const SparseObservable,
×
758
    tol: f64,
×
759
) -> *mut SparseObservable {
×
760
    // SAFETY: Per documentation, the pointer is non-null and aligned.
×
761
    let obs = unsafe { const_ptr_as_ref(obs) };
×
762

×
763
    let result = obs.canonicalize(tol);
×
764
    Box::into_raw(Box::new(result))
×
765
}
×
766

767
/// @ingroup QkObs
768
/// Copy the observable.
769
///
770
/// @param obs A pointer to the observable.
771
///
772
/// @return A pointer to a copy of the observable.
773
///
774
/// # Example
775
///
776
///     QkObs *original = qk_obs_identity(100);
777
///     QkObs *copied = qk_obs_copy(original);
778
///
779
/// # Safety
780
///
781
/// Behavior is undefined ``obs`` is not a valid, non-null pointer to a ``QkObs``.
782
#[no_mangle]
783
#[cfg(feature = "cbinding")]
784
pub unsafe extern "C" fn qk_obs_copy(obs: *const SparseObservable) -> *mut SparseObservable {
×
785
    // SAFETY: Per documentation, the pointer is non-null and aligned.
×
786
    let obs = unsafe { const_ptr_as_ref(obs) };
×
787

×
788
    let copied = obs.clone();
×
789
    Box::into_raw(Box::new(copied))
×
790
}
×
791

792
/// @ingroup QkObs
793
/// Compare two observables for equality.
794
///
795
/// Note that this does not compare mathematical equality, but data equality. This means
796
/// that two observables might represent the same observable but not compare as equal.
797
///
798
/// @param obs A pointer to one observable.
799
/// @param other A pointer to another observable.
800
///
801
/// @return ``true`` if the observables are equal, ``false`` otherwise.
802
///
803
/// # Example
804
///
805
///     QkObs *observable = qk_obs_identity(100);
806
///     QkObs *other = qk_obs_identity(100);
807
///     bool are_equal = qk_obs_equal(observable, other);
808
///
809
/// # Safety
810
///
811
/// Behavior is undefined if ``obs`` or ``other`` are not valid, non-null pointers to
812
/// ``QkObs``\ s.
813
#[no_mangle]
814
#[cfg(feature = "cbinding")]
815
pub unsafe extern "C" fn qk_obs_equal(
×
816
    obs: *const SparseObservable,
×
817
    other: *const SparseObservable,
×
818
) -> bool {
×
819
    // SAFETY: Per documentation, the pointers are non-null and aligned.
×
820
    let obs = unsafe { const_ptr_as_ref(obs) };
×
821
    let other = unsafe { const_ptr_as_ref(other) };
×
822

×
823
    obs.eq(other)
×
824
}
×
825

826
/// @ingroup QkObs
827
/// Return a string representation of a ``QkObs``.
828
///
829
/// @param obs A pointer to the ``QkObs`` to get the string for.
830
///
831
/// @return A pointer to a nul-terminated char array of the string representation for ``obs``
832
///
833
/// # Example
834
///
835
///     QkObs *obs = qk_obs_identity(100);
836
///     char *string = qk_obs_str(obs);
837
///     qk_str_free(string);
838
///
839
/// # Safety
840
///
841
/// Behavior is undefined ``obs`` is not a valid, non-null pointer to a ``QkObs``.
842
///
843
/// The string must not be freed with the normal C free, you must use ``qk_str_free`` to
844
/// free the memory consumed by the String. Not calling ``qk_str_free`` will lead to a
845
/// memory leak.
846
///
847
/// Do not change the length of the string after it's returned (by writing a nul byte somewhere
848
/// inside the string or removing the final one), although values can be mutated.
849
#[no_mangle]
850
#[cfg(feature = "cbinding")]
851
pub unsafe extern "C" fn qk_obs_str(obs: *const SparseObservable) -> *mut c_char {
×
852
    // SAFETY: Per documentation, the pointer is non-null and aligned.
×
853
    let obs = unsafe { const_ptr_as_ref(obs) };
×
854
    let string: String = format!("{:?}", obs);
×
855
    CString::new(string).unwrap().into_raw()
×
856
}
×
857

858
/// @ingroup QkObs
859
/// Free a string representation.
860
///
861
/// @param string A pointer to the returned string representation from ``qk_obs_str`` or
862
///     ``qk_obsterm_str``.
863
///
864
/// # Safety
865
///
866
/// Behavior is undefined if ``str`` is not a pointer returned by ``qk_obs_str`` or
867
/// ``qk_obsterm_str``.
868
#[no_mangle]
869
#[cfg(feature = "cbinding")]
870
pub unsafe extern "C" fn qk_str_free(string: *mut c_char) {
×
871
    unsafe {
×
872
        let _ = CString::from_raw(string);
×
873
    }
×
874
}
×
875

876
/// @ingroup QkObsTerm
877
/// Return a string representation of the sparse term.
878
///
879
/// @param term A pointer to the term.
880
///
881
/// @return The function exit code. This is ``>0`` if reading the term failed.
882
///
883
/// # Example
884
///
885
///     QkObs *obs = qk_obs_identity(100);
886
///     QkObsTerm term;
887
///     qk_obs_term(obs, 0, &term);
888
///     char *string = qk_obsterm_str(&term);
889
///     qk_str_free(string);
890
///
891
/// # Safety
892
///
893
/// Behavior is undefined ``term`` is not a valid, non-null pointer to a ``QkObsTerm``.
894
///
895
/// The string must not be freed with the normal C free, you must use ``qk_str_free`` to
896
/// free the memory consumed by the String. Not calling ``qk_str_free`` will lead to a
897
/// memory leak.
898
///
899
/// Do not change the length of the string after it's returned, although values can be mutated.
900
#[no_mangle]
901
#[cfg(feature = "cbinding")]
902
pub unsafe extern "C" fn qk_obsterm_str(term: *const CSparseTerm) -> *mut c_char {
×
903
    // SAFETY: Per documentation, the pointer is non-null and aligned.
×
904
    let term = unsafe { const_ptr_as_ref(term) };
×
905

×
906
    let view: SparseTermView = term.try_into().unwrap();
×
907
    let string: String = format!("{:?}", view);
×
908
    CString::new(string).unwrap().into_raw()
×
909
}
×
910

911
/// @ingroup QkBitTerm
912
/// Get the label for a bit term.
913
///
914
/// @param bit_term The bit term.
915
///
916
/// @return The label as ``uint8_t``, which can be cast to ``char`` to obtain the character.
917
///
918
/// # Example
919
///
920
///     QkBitTerm bit_term = QkBitTerm_Y;
921
///     // cast the uint8_t to char
922
///     char label = qk_bitterm_label(bit_term);
923
///
924
/// # Safety
925
///
926
/// The behavior is undefined if ``bit_term`` is not a valid ``uint8_t`` value of a ``QkBitTerm``.
927
#[no_mangle]
928
#[cfg(feature = "cbinding")]
929
pub extern "C" fn qk_bitterm_label(bit_term: BitTerm) -> u8 {
×
930
    // BitTerm is implemented as u8, which is calling convention compatible with C,
×
931
    // hence we can pass ``bit_term`` by value
×
932
    bit_term
×
933
        .py_label()
×
934
        .chars()
×
935
        .next()
×
936
        .expect("Label has exactly one character") as u8
×
937
}
×
938

939
/// @ingroup QkObs
940
/// Convert to a Python-space ``SparseObservable``.
941
///
942
/// @param obs The C-space ``QkObs`` pointer.
943
///
944
/// @return A Python object representing the ``SparseObservable``.
945
///
946
/// # Safety
947
///
948
/// Behavior is undefined if ``obs`` is not a valid, non-null pointer to a ``QkObs``.
949
///
950
/// It is assumed that the thread currently executing this function holds the
951
/// Python GIL this is required to create the Python object returned by this
952
/// function.
953
#[no_mangle]
954
#[cfg(feature = "python_binding")]
955
#[cfg(feature = "cbinding")]
956
pub unsafe extern "C" fn qk_obs_to_python(obs: *const SparseObservable) -> *mut PyObject {
×
957
    // SAFETY: Per documentation, the pointer is non-null and aligned.
×
958
    let obs = unsafe { const_ptr_as_ref(obs) };
×
959
    let py_obs: PySparseObservable = obs.clone().into();
×
960

×
961
    // SAFETY: the C caller is required to hold the GIL.
×
962
    unsafe {
×
963
        let py = Python::assume_gil_acquired();
×
964
        Py::new(py, py_obs)
×
965
            .expect("Unable to create a Python object")
×
966
            .into_ptr()
×
967
    }
×
968
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc