Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions crates/providers/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,18 @@ license.workspace = true
name = "qiskit_providers"

[dependencies]
anyhow.workspace = true
rustworkx-core.workspace = true
num-complex.workspace = true
thiserror.workspace = true

[dependencies.hashbrown]
workspace = true
features = ["rayon", "serde"]

[lints]
workspace = true

[dependencies.ndarray]
workspace = true
features = ["rayon", "approx"]
27 changes: 27 additions & 0 deletions crates/providers/src/data_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,33 @@ impl<T> DataTree<T> {
}
}

/// Iterate over direct children, yielding `(optional_key, child)` pairs in index order.
///
/// # Example
/// ```rust
/// use qiskit_providers::DataTree;
/// let mut tree = DataTree::new();
/// tree.push_leaf(10); // unnamed
/// tree.insert_leaf("b", 20); // named
/// tree.push_leaf(30); // unnamed
/// let children: Vec<_> = tree.iter_children().collect();
/// assert_eq!(children[0], (None, &DataTree::Leaf(10)));
/// assert_eq!(children[1], (Some("b"), &DataTree::Leaf(20)));
/// assert_eq!(children[2], (None, &DataTree::Leaf(30)));
/// ```
pub fn iter_children(&self) -> impl Iterator<Item = (Option<&str>, &DataTree<T>)> + '_ {
let branch = match self {
Self::Branch(branch) => branch,
Self::Leaf(_) => panic!("called iter_children() on a leaf node"),
};
let rev: HashMap<usize, &str> = branch.keys.iter().map(|(k, &v)| (v, k.as_str())).collect();
branch
.data
.iter()
.enumerate()
.map(move |(i, child)| (rev.get(&i).copied(), child))
}

/// Insert a new leaf node with an associated string key
///
/// If a key is provided that is already in the tree the new value will be associated with
Expand Down
5 changes: 5 additions & 0 deletions crates/providers/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,10 @@
// that they have been altered from the originals.

mod data_tree;
mod program_node;
mod store;
pub mod tensor;

pub use data_tree::{DataTree, PathEntry};
pub use program_node::ProgramNode;
pub use store::Store;
40 changes: 40 additions & 0 deletions crates/providers/src/program_node.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// This code is part of Qiskit.
//
// (C) Copyright IBM 2026
//
// This code is licensed under the Apache License, Version 2.0. You may
// obtain a copy of this license in the LICENSE.txt file in the root directory
// of this source tree or at https://www.apache.org/licenses/LICENSE-2.0.
//
// Any modifications or derivative works of this code must retain this
// copyright notice, and modified files need to carry a notice indicating
// that they have been altered from the originals.

use crate::data_tree::DataTree;
use crate::tensor::{Tensor, TensorType};

/// A node in a quantum program graph that transforms tensors.
pub trait ProgramNode {
/// The name of this program node.
fn name(&self) -> &str;

/// The namespace this program node belongs to.
fn namespace(&self) -> &str;

/// The namespace and name as one string.
fn full_name(&self) -> String {
format!("{}.{}", self.namespace(), self.name())
}

/// The inputs expected at `call` time.
fn input_types(&self) -> &DataTree<TensorType>;

/// The outputs promised on `call` return.
fn output_types(&self) -> &DataTree<TensorType>;

/// Whether this program node implements the call method.
fn implements_call(&self) -> bool;

/// The action of this program node.
fn call(&self, args: &DataTree<Tensor>) -> anyhow::Result<DataTree<Tensor>>;
}
159 changes: 159 additions & 0 deletions crates/providers/src/store.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// This code is part of Qiskit.
//
// (C) Copyright IBM 2026
//
// This code is licensed under the Apache License, Version 2.0. You may
// obtain a copy of this license in the LICENSE.txt file in the root directory
// of this source tree or at https://www.apache.org/licenses/LICENSE-2.0.
//
// Any modifications or derivative works of this code must retain this
// copyright notice, and modified files need to carry a notice indicating
// that they have been altered from the originals.

use crate::data_tree::DataTree;
use crate::program_node::ProgramNode;
use crate::tensor::{Tensor, TensorType};
use std::sync::LazyLock;

/// A program node that owns constant data and outputs it unconditionally.
///
/// `Store` takes no inputs; its `call()` always returns the data it was constructed with.
/// In a data-flow graph, `Store` nodes play the role of constants — they are wired to
/// the input ports of computation nodes to supply fixed values.
pub struct Store {
data: DataTree<Tensor>,
output_types: DataTree<TensorType>,
}

impl Store {
/// Construct a new `Store` holding the given data.
pub fn new(data: DataTree<Tensor>) -> Self {
let output_types = derive_output_types(&data);
Self { data, output_types }
}

/// Return a reference to the stored data.
pub fn data(&self) -> &DataTree<Tensor> {
&self.data
}
}

/// Recursively derive output types from concrete tensor data.
fn derive_output_types(data: &DataTree<Tensor>) -> DataTree<TensorType> {
match data {
DataTree::Leaf(tensor) => DataTree::new_leaf(tensor.tensor_type()),
DataTree::Branch(_) => {
let mut result = DataTree::with_capacity(data.len());
for (key, child) in data.iter_children() {
let child_type = derive_output_types(child);
if let Some(k) = key {
result.insert_branch(k, child_type);
} else {
result.push_branch(child_type);
}
}
result
}
}
}

impl ProgramNode for Store {
fn name(&self) -> &str {
"store"
}

fn namespace(&self) -> &str {
"core"
}

fn input_types(&self) -> &DataTree<TensorType> {
// Stores never have inputs; use a static to avoid per-instance storage
static EMPTY: LazyLock<DataTree<TensorType>> = LazyLock::new(DataTree::new);
&EMPTY
}

fn output_types(&self) -> &DataTree<TensorType> {
&self.output_types
}

fn implements_call(&self) -> bool {
true
}

fn call(&self, _args: &DataTree<Tensor>) -> anyhow::Result<DataTree<Tensor>> {
Ok(self.data.clone())
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::tensor::{DType, DTypeLike, Dim, Tensor};

#[test]
fn test_store_leaf_call() {
let data = DataTree::new_leaf(Tensor::from([1.0_f64, 2.0, 3.0]));
let store = Store::new(data);
let result = store.call(&DataTree::new()).unwrap();
let DataTree::Leaf(Tensor::F64(arr)) = result else {
panic!("expected f64 leaf");
};
assert_eq!(arr.as_slice().unwrap(), &[1.0, 2.0, 3.0]);
}

#[test]
fn test_store_output_types_leaf() {
let data = DataTree::new_leaf(Tensor::from([1.0_f64, 2.0, 3.0]));
let store = Store::new(data);
let DataTree::Leaf(tt) = store.output_types() else {
panic!("expected leaf output type");
};
assert!(matches!(tt.dtype, DTypeLike::Concrete(DType::F64)));
assert_eq!(tt.shape, vec![Dim::Fixed(3)]);
assert!(!tt.broadcastable);
}

#[test]
fn test_store_output_types_2d() {
use ndarray::arr2;
let data = DataTree::new_leaf(Tensor::F64(arr2(&[[1.0_f64, 2.0], [3.0, 4.0]]).into_dyn()));
let store = Store::new(data);
let DataTree::Leaf(tt) = store.output_types() else {
panic!("expected leaf output type");
};
assert_eq!(tt.shape, vec![Dim::Fixed(2), Dim::Fixed(2)]);
}

#[test]
fn test_store_branched() {
let mut data = DataTree::new();
data.insert_leaf("a", Tensor::from([1.0_f64, 2.0]));
data.insert_leaf("b", Tensor::from([10_i32, 20, 30]));
let store = Store::new(data);

assert!(store.input_types().is_empty());
assert_eq!(store.name(), "store");
assert_eq!(store.namespace(), "core");
assert_eq!(store.full_name(), "core.store");

let out_types = store.output_types();
let DataTree::Leaf(tt_a) = out_types.get_by_str_key("a").unwrap() else {
panic!("expected leaf at a");
};
assert!(matches!(tt_a.dtype, DTypeLike::Concrete(DType::F64)));
assert_eq!(tt_a.shape, vec![Dim::Fixed(2)]);

let DataTree::Leaf(tt_b) = out_types.get_by_str_key("b").unwrap() else {
panic!("expected leaf at b");
};
assert!(matches!(tt_b.dtype, DTypeLike::Concrete(DType::I32)));
assert_eq!(tt_b.shape, vec![Dim::Fixed(3)]);
}

#[test]
fn test_store_no_inputs() {
let store = Store::new(DataTree::new_leaf(Tensor::from([42.0_f64])));
assert!(store.input_types().is_empty());
assert!(store.implements_call());
}
}
Loading
Loading