blob: 466d7ea64088fc17d1a2437fed4b32d57858bf4d [file]
//
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Loader bindings (Linux only; qdp-core pipeline types only built on Linux)
#[cfg(target_os = "linux")]
mod loader_impl {
use crate::tensor::QuantumTensor;
use pyo3::exceptions::PyRuntimeError;
use pyo3::prelude::*;
use qdp_core::reader::NullHandling;
use qdp_core::{Dtype, Encoding, PipelineConfig, PipelineIterator, QdpEngine as CoreEngine};
/// Rust-backed iterator yielding one QuantumTensor per batch; used by QuantumDataLoader.
#[pyclass]
pub struct PyQuantumLoader {
pub inner: Option<PipelineIterator>,
}
impl PyQuantumLoader {
pub fn new(inner: Option<PipelineIterator>) -> Self {
Self { inner }
}
}
#[pymethods]
impl PyQuantumLoader {
fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
slf
}
fn __next__(mut slf: PyRefMut<'_, Self>) -> PyResult<QuantumTensor> {
let mut iter: PipelineIterator = match slf.inner.take() {
Some(i) => i,
None => return Err(pyo3::exceptions::PyStopIteration::new_err("")),
};
// Call next_batch without releasing GIL (return type *mut DLManagedTensor is !Send).
let result = iter.next_batch();
match result {
Ok(Some(ptr)) => {
slf.inner = Some(iter);
Ok(QuantumTensor {
ptr,
consumed: false,
})
}
Ok(None) => {
// Exhausted; do not put iterator back
Err(pyo3::exceptions::PyStopIteration::new_err(""))
}
Err(e) => {
slf.inner = Some(iter);
Err(PyRuntimeError::new_err(format!(
"Pipeline next_batch failed: {}",
e
)))
}
}
}
}
/// Parse a Python null-handling string into the Rust enum.
pub fn parse_null_handling(s: Option<&str>) -> PyResult<NullHandling> {
match s {
None | Some("fill_zero") => Ok(NullHandling::FillZero),
Some("reject") => Ok(NullHandling::Reject),
Some(other) => Err(pyo3::exceptions::PyValueError::new_err(format!(
"Invalid null_handling policy '{}'. Expected 'fill_zero' or 'reject'.",
other
))),
}
}
/// Parse an optional Python dtype string into the Rust enum. Defaults to f64 so
/// file loads are lossless unless the caller explicitly opts into f32 narrowing.
pub fn parse_dtype(s: Option<&str>) -> PyResult<Dtype> {
match s {
None => Ok(Dtype::Float64),
Some(v) => Dtype::from_str_ci(v).map_err(|e| {
pyo3::exceptions::PyValueError::new_err(format!(
"Invalid dtype '{v}': {e}. Expected 'float32'/'f32' or 'float64'/'f64'."
))
}),
}
}
/// Build PipelineConfig from Python args. device_id is 0 (engine does not expose it); iterator uses engine clone with correct device.
#[allow(clippy::too_many_arguments)]
pub fn config_from_args(
_engine: &CoreEngine,
batch_size: usize,
num_qubits: u32,
encoding_method: &str,
total_batches: usize,
seed: Option<u64>,
null_handling: NullHandling,
dtype: Dtype,
) -> PyResult<PipelineConfig> {
let encoding = Encoding::from_str_ci(encoding_method)
.map_err(|e| PyRuntimeError::new_err(format!("Invalid encoding: {e}")))?;
Ok(PipelineConfig {
device_id: 0,
num_qubits,
batch_size,
total_batches,
encoding,
seed,
warmup_batches: 0,
null_handling,
dtype,
prefetch_depth: 16,
})
}
/// Resolve path from Python str or pathlib.Path (__fspath__).
pub fn path_from_py(path: &Bound<'_, PyAny>) -> PyResult<String> {
path.extract::<String>().or_else(|_| {
path.call_method0("__fspath__")
.and_then(|m| m.extract::<String>())
})
}
}
#[cfg(target_os = "linux")]
pub use loader_impl::{
PyQuantumLoader, config_from_args, parse_dtype, parse_null_handling, path_from_py,
};