1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
// SPDX-FileCopyrightText: 2026 Evandro Chagas Ribeiro da Rosa <evandro@quantuloop.com>
//
// SPDX-License-Identifier: Apache-2.0
//! Error types for the Libket quantum computing library.
//!
//! All fallible operations in Libket return a [`Result<T>`], where the error
//! variant is a [`KetError`]. Each variant carries a human-readable description
//! of the failure condition that is suitable for end-user display.
//!
//! The enum is `#[repr(i32)]` so that it can be transmitted across FFI
//! boundaries as a plain integer code. Use [`KetError::error_code`] to obtain
//! the discriminant value and [`KetError::from_error_code`] to reconstruct the
//! variant on the receiving side.
/// The exhaustive set of errors that Libket operations can produce.
///
/// Every variant has a stable, non-zero integer discriminant (with the sole
/// exception of [`KetError::Success`], which is always `0`) so that the enum
/// can be used as a C-compatible status code.
#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
#[repr(i32)]
pub enum KetError {
/// No error occurred. This variant is only used as a sentinel value in FFI
/// contexts where an integer `0` signals success.
#[error("Operation completed successfully.")]
Success = 0,
/// A qubit was specified as a control qubit more than once in the same gate
/// instruction. Each physical qubit may only appear once in the control set.
///
/// Returned by [`crate::ir::gate::GateInstruction::control`] (and transitively
/// by [`crate::ir::block::BasicBlock::control`]) when
/// `control_qubits` contains a duplicate entry or overlaps with the existing
/// control set of the instruction.
///
/// # Example
/// Calling `gate.control(&[q0, q0])` would trigger this error.
#[error(
"Duplicate control qubit: the same qubit index appears more than once in the \
control set of a single gate instruction. Each qubit may be listed at most once."
)]
DuplicateControlQubit,
/// A qubit was used as both a control and the target of the same gate
/// instruction, which is physically undefined.
///
/// Returned by [`crate::ir::gate::GateInstruction::control`] (and transitively
/// by [`crate::ir::block::BasicBlock::control`]) when
/// one of the provided `control_qubits` equals `self.target`.
///
/// # Example
/// Calling `gate.control(&[gate.target])` would trigger this error.
#[error(
"Control-target qubit conflict: a qubit index appears in both the control set \
and as the target of the same gate. A qubit cannot control its own gate."
)]
ControlTargetConflict,
/// An attempt was made to add control qubits to a parameterized gate.
///
/// Adding control qubits to gates with `Param::Ref` parameters is not supported.
#[error(
"Control parameterized gate: adding control qubits to parameterized gates is not supported."
)]
ControlParameterizedGate,
/// The process has already allocated the maximum number of qubits permitted
/// by the QPU configuration and no additional qubits can be created.
///
/// Returned by [`crate::process::Process::alloc`] when
/// `qubit_counter == qpu_config.num_qubits`.
#[error(
"Qubit allocation limit reached: the process has already allocated all \
qubits allowed by the QPU configuration."
)]
QubitLimitExceeded,
/// A gate or measurement referenced a qubit index that exceeds the number
/// of qubits allocated so far by the current process.
///
/// Returned by [`crate::process::Process::append_block`]
/// when the maximum qubit index
/// referenced inside the block is ≥ `qubit_counter`. Call
/// [`crate::process::Process::alloc`] to allocate the required qubits
/// before appending the block.
#[error(
"Qubit index out of range: the gate block references a qubit that has not \
been allocated. Call alloc() for each qubit before using it in a block."
)]
QubitIndexOutOfRange,
/// An attempt was made to allocate a qubit on a process that has already
/// been executed and is in the `Terminated` state.
///
/// Returned by [`crate::process::Process::alloc`]
/// when `process.state == ProcessState::Terminated`. Create a new
/// [`crate::process::Process`] to run additional circuits.
#[error(
"Process is terminated: qubit allocation is not allowed after a process has \
been executed. Create a new Process to run additional circuits."
)]
ProcessTerminated,
/// An attempt was made to append a gate block to a process that is not
/// in the `AcceptingGate` state.
///
/// Returned by [`crate::process::Process::append_block`]
/// when the process state is anything other than
/// [`crate::process::ProcessState::AcceptingGate`], for example,
/// after `sample` or `exp_value` has already been registered in
/// batch mode (which advancesthe state to
/// [`crate::process::ProcessState::ReadyToExecute`] or
/// [`crate::process::ProcessState::AcceptingHamiltonian`]).
#[error(
"Cannot append gates: the process is not in the AcceptingGate state. \
Gates cannot be added after a sample or exp_value request has been \
registered in batch mode."
)]
GateAppendForbidden,
/// A single-shot mid-circuit measurement was requested but the process is
/// not configured for live execution.
///
/// Returned by [`crate::process::Process::measure`]
/// when `qpu_config.quantum_execution` is
/// `Some(QuantumExecution::Batch { .. })` or `None`. Measurements that
/// collapse qubit state and return a result inline are only available in
/// [`crate::execution::QuantumExecution::Live`] mode.
/// Use `sample` for batch-mode histograms.
#[error(
"Single-shot measurement is unavailable outside of Live execution mode. \
Use 'sample' to collect a measurement histogram."
)]
MeasurementUnavailableInBatch,
/// A `sample` request was made but cannot be registered in the current
/// process state.
///
/// Returned by [`crate::process::Process::sample`] in batch mode when
/// the process state is not [`crate::process::ProcessState::AcceptingGate`]
/// (i.e., a `sample` or `exp_value` request has already
/// been registered), or when no execution backend is configured
/// (`qpu_config.quantum_execution == None`). Only one `sample` call is
/// permitted per batch execution.
#[error(
"Sampling unavailable: a sample request can only be registered once per batch \
execution, and only while the process is in the AcceptingGate state. \
Ensure an execution backend is configured and no conflicting request exists."
)]
SamplingUnavailable,
/// An `exp_value` request was made but cannot be registered in the current
/// process state.
///
/// Returned by [`crate::process::Process::exp_value`] in batch mode when the
/// process state is not [`crate::process::ProcessState::AcceptingGate`] or
/// [`crate::process::ProcessState::AcceptingHamiltonian`], for example, after the
/// process has transitioned to `ReadyToExecute` because a `sample` request
/// or a gradient-enabled `exp_value` was already registered, or when no
/// execution backend is configured.
#[error(
"Expectation-value unavailable: exp_value can only be registered while the process \
is in the AcceptingGate or AcceptingHamiltonian state. It cannot be combined with \
a pending sample request, and requires an execution backend to be configured."
)]
ExpectationValueUnavailable,
/// A quantum state dump was requested but the process is not configured
/// for live execution.
///
/// Returned by [`crate::process::Process::dump`]
/// when `qpu_config.quantum_execution` is `Some(QuantumExecution::Batch { .. })`
/// or `None`. Full state-vector inspection is only available in
/// [`crate::execution::QuantumExecution::Live`] mode.
#[error(
"State dump is unavailable outside of Live execution mode. \
Full state-vector inspection via 'dump' requires Live execution."
)]
DumpUnavailableInBatch,
/// `execute` was called on a process that has no pending measurement or
/// expectation-value request.
///
/// Returned by [`crate::process::Process::execute`] when the process state
/// is [`crate::process::ProcessState::AcceptingGate`],
/// meaning neither `sample` nor `exp_value` has been called since the
/// last execution. Register at least one `sample` or `exp_value` request
/// before calling `execute` in batch mode.
#[error(
"No pending measurement: 'execute' requires at least one 'sample' or 'exp_value' \
call before it can be invoked. The process is still in the AcceptingGate state."
)]
NoPendingMeasurement,
/// `execute` (or an internal `compile`) was called on a process configured
/// for live execution.
///
/// Returned by [`crate::process::Process::execute`] when
/// `qpu_config.quantum_execution` is `Some(QuantumExecution::Live { .. })`.
/// In live mode every gate is dispatched to the backend immediately as it
/// is appended; there is no deferred compilation step.
#[error(
"Explicit execution is not supported in Live mode: gates and measurements are \
dispatched to the backend immediately as they are appended. \
Use Batch execution to compile and run the circuit via 'execute'."
)]
ExplicitExecuteInLiveMode,
/// The quantum execution backend reported an unrecoverable failure.
///
/// This is a generic error code that backend implementations
/// should return for hardware faults, decoherence events, calibration errors,
/// or simulator assertion failures.
/// It surfaces in Rust through [`KetError::from_error_code`] after the C
/// callback returns a non-zero status. Consult the backend's own error
/// channel for a more detailed diagnostic.
#[error(
"Execution failed: the quantum backend reported an unrecoverable error. \
Consult the backend's diagnostic output for details."
)]
ExecutionFailed,
/// The requested number of measurement shots is out of range for the
/// active backend.
///
/// Backend implementations should return this error code
/// from their `sample` callback when `shots` is zero, exceeds an
/// implementation-defined maximum, or otherwise falls outside the
/// backend's accepted range. It reaches Rust via [`KetError::from_error_code`].
#[error(
"Invalid shot count: the number of measurement shots is out of range for \
the active backend. Check the backend's accepted minimum and maximum."
)]
ShotCountInvalid,
/// A gate produced by the [`crate::execution::NativeGateSet`] translation
/// is not supported by the backend.
///
/// Custom [`crate::execution::NativeGateSet`] implementations should
/// return this error from `translate`, `cnot`, or `swap` when the
/// requested operation cannot be expressed in the hardware's instruction
/// set. It is also the recommended return code for batch-execution C
/// callbacks that receive an unrecognized gate name.
#[error(
"Unsupported native gate: the gate produced by the NativeGateSet translation \
is not recognized by the backend. Ensure the NativeGateSet is correctly \
configured for the target hardware."
)]
NativeGateUnsupported,
/// A gate instruction from the Libket IR was received by a backend that
/// does not accept IR-level gates.
///
/// Returned as the default implementation of [`crate::execution::BatchExecution::sample`]
/// and [`crate::execution::BatchExecution::exp_value`] when the backend only
/// supports native gates. Implementors that accept IR-level gates should
/// override those methods instead of propagating this error.
#[error("Unsupported gate: gates from the Libket IR is not supported.")]
GateUnsupported,
/// The backend does not support native expectation-value computation but
/// [`crate::execution::ExpValueStrategy::Native`] was selected.
///
/// Backend callbacks should return this error code from their
/// `exp_value` callback when the hardware or simulator cannot compute
/// expectation values directly. Switch to
/// [`crate::execution::ExpValueStrategy::QubitWiseCommutation`] or
/// [`crate::execution::ExpValueStrategy::ClassicalShadows`] to estimate
/// expectation values through repeated sampling instead.
#[error(
"Native expectation-value computation is not supported by this backend. \
Use ExpValueStrategy::QubitWiseCommutation or ExpValueStrategy::ClassicalShadows \
to estimate expectation values via sampling."
)]
ExpValueNotSupported,
/// JSON serialization or deserialization failed in the C API layer.
///
/// Emitted when `serde_json` fails to serialize a Rust value to a
/// C string, or when a JSON string received from a C callback cannot be
/// parsed into the expected Rust type. Verify that the JSON payload
/// conforms to the schema expected by the called function.
#[error(
"Serialization/deserialization error: a JSON payload could not be encoded \
or decoded in the C API layer. Ensure the JSON string is valid and matches \
the expected schema."
)]
SerdeError,
/// The classical shadows bias tuple `(X, Y, Z)` has all three weights set
/// to zero, making it impossible to sample a measurement basis.
///
/// Returned by [`crate::process::Process::execute`] (via
/// `execute_classical_shadows`) when
/// [`crate::execution::ExpValueStrategy::ClassicalShadows`] is configured
/// with `bias = (0, 0, 0)`. At least one of the three weights must be
/// non-zero.
#[error(
"Invalid classical shadows bias: all three Pauli basis weights (X, Y, Z) are zero. \
At least one weight must be non-zero to sample a measurement basis."
)]
InvalidBias,
/// The backend does not support native gradient computation but
/// [`crate::execution::GradientStrategy::Native`] was selected.
///
/// Returned as the default implementation of
/// [`crate::execution::BatchExecution::gradient`]. Switch to
/// [`crate::execution::GradientStrategy::ParameterShiftRule`] to estimate
/// gradients analytically via repeated circuit evaluations.
#[error(
"Native Gradient Unsupported: The simulator does not support gradient calculation. \
Use ParameterShiftRule."
)]
NativeGradientUnsuported,
/// An unrecognized error code was received from a C callback.
///
/// Returned by [`KetError::from_error_code`] as a catch-all when the
/// integer code does not match any known variant. This typically means a
/// C-side backend returned a non-standard status code. Treat it as an
/// unrecoverable backend error.
#[error(
"Unknown error: an unrecognized error code was returned by the backend. \
This may indicate a version mismatch or a non-standard status code."
)]
UnknownError,
}
impl KetError {
/// Returns the integer discriminant of this error variant.
///
/// The returned value is stable and suitable for FFI use. `Success` always
/// returns `0`; all other variants return a positive non-zero value.
#[must_use]
pub const fn error_code(&self) -> i32 {
match self {
Self::Success => 0,
Self::DuplicateControlQubit => 1,
Self::ControlTargetConflict => 2,
Self::QubitLimitExceeded => 3,
Self::QubitIndexOutOfRange => 4,
Self::ProcessTerminated => 5,
Self::GateAppendForbidden => 6,
Self::MeasurementUnavailableInBatch => 7,
Self::SamplingUnavailable => 8,
Self::ExpectationValueUnavailable => 9,
Self::DumpUnavailableInBatch => 10,
Self::NoPendingMeasurement => 11,
Self::ExplicitExecuteInLiveMode => 12,
Self::ExecutionFailed => 13,
Self::ShotCountInvalid => 14,
Self::NativeGateUnsupported => 15,
Self::ExpValueNotSupported => 16,
Self::SerdeError => 17,
Self::InvalidBias => 18,
Self::ControlParameterizedGate => 19,
Self::GateUnsupported => 20,
Self::NativeGradientUnsuported => 21,
Self::UnknownError => i32::MAX,
}
}
/// Reconstructs a `KetError` from its integer discriminant.
///
/// Variants that carry additional context (such as qubit indices) are
/// reconstructed with sentinel values (`0` for `usize` fields) because the
/// context is not preserved in the raw integer code.
///
/// # Safety
/// The caller must guarantee that `error_code` was produced by a prior call
/// to [`KetError::error_code`]. Passing an arbitrary integer is undefined
/// behaviour because this function uses [`std::mem::transmute`] internally
/// for the simple variants and pattern-matches for the contextual ones.
#[must_use]
pub fn from_error_code(error_code: i32) -> Self {
match error_code {
0 => Self::Success,
1 => Self::DuplicateControlQubit,
2 => Self::ControlTargetConflict,
3 => Self::QubitLimitExceeded,
4 => Self::QubitIndexOutOfRange,
5 => Self::ProcessTerminated,
6 => Self::GateAppendForbidden,
7 => Self::MeasurementUnavailableInBatch,
8 => Self::SamplingUnavailable,
9 => Self::ExpectationValueUnavailable,
10 => Self::DumpUnavailableInBatch,
11 => Self::NoPendingMeasurement,
12 => Self::ExplicitExecuteInLiveMode,
13 => Self::ExecutionFailed,
14 => Self::ShotCountInvalid,
15 => Self::NativeGateUnsupported,
16 => Self::ExpValueNotSupported,
17 => Self::SerdeError,
18 => Self::InvalidBias,
19 => Self::ControlParameterizedGate,
20 => Self::GateUnsupported,
21 => Self::NativeGradientUnsuported,
_ => Self::UnknownError,
}
}
}
#[cfg(test)]
mod tests {
use super::KetError;
#[test]
fn success_is_zero() {
assert_eq!(KetError::Success.error_code(), 0);
}
#[test]
fn error_codes_are_distinct() {
let variants = vec![
KetError::Success,
KetError::DuplicateControlQubit,
KetError::ControlTargetConflict,
KetError::QubitLimitExceeded,
KetError::QubitIndexOutOfRange,
KetError::ProcessTerminated,
KetError::GateAppendForbidden,
KetError::MeasurementUnavailableInBatch,
KetError::SamplingUnavailable,
KetError::ExpectationValueUnavailable,
KetError::DumpUnavailableInBatch,
KetError::NoPendingMeasurement,
KetError::ExplicitExecuteInLiveMode,
KetError::ExecutionFailed,
KetError::ShotCountInvalid,
KetError::NativeGateUnsupported,
KetError::ExpValueNotSupported,
KetError::SerdeError,
KetError::InvalidBias,
KetError::ControlParameterizedGate,
KetError::GateUnsupported,
KetError::NativeGradientUnsuported,
KetError::UnknownError,
];
let mut codes: Vec<i32> = variants.iter().map(|e| e.error_code()).collect();
codes.sort_unstable();
codes.dedup();
assert_eq!(
codes.len(),
variants.len(),
"Duplicate error codes detected"
);
}
#[test]
fn roundtrip_simple_variants() {
let variants = vec![
KetError::Success,
KetError::DuplicateControlQubit,
KetError::ProcessTerminated,
KetError::GateAppendForbidden,
KetError::MeasurementUnavailableInBatch,
KetError::SamplingUnavailable,
KetError::ExpectationValueUnavailable,
KetError::DumpUnavailableInBatch,
KetError::NoPendingMeasurement,
KetError::ExplicitExecuteInLiveMode,
KetError::ExpValueNotSupported,
];
for v in &variants {
let code = v.error_code();
let reconstructed = KetError::from_error_code(code);
assert_eq!(reconstructed.error_code(), code);
}
}
}