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
4 changes: 2 additions & 2 deletions docs/qdp/python-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ Builder; chain methods then call `run_throughput()` or `run_latency()`.
| Method | Description |
|--------|-------------|
| `qubits(n)` | Number of qubits. |
| `encoding(method)` | `"amplitude"` \| `"angle"` \| `"basis"`. |
| `encoding(method)` | `"amplitude"` \| `"angle"` \| `"basis"` \| `"iqp"` \| `"iqp-z"`. |
| `batches(total, size=64)` | Total batches and batch size. |
| `prefetch(n)` | No-op (API compatibility). |
| `warmup(n)` | Warmup batch count. |
Expand Down Expand Up @@ -147,7 +147,7 @@ Builder for a synthetic-data loader. Calling `iter(loader)` (or `for qt in loade
| Method | Description |
|--------|-------------|
| `qubits(n)` | Number of qubits. |
| `encoding(method)` | `"amplitude"` \| `"angle"` \| `"basis"`. |
| `encoding(method)` | `"amplitude"` \| `"angle"` \| `"basis"` \| `"iqp"` \| `"iqp-z"`. |
| `batches(total, size=64)` | Total batches and batch size. |
| `source_synthetic(total_batches=None)` | Synthetic data (default); optional override for total batches. |
| `seed(s)` | RNG seed for reproducibility. |
Expand Down
67 changes: 66 additions & 1 deletion qdp/qdp-core/src/pipeline_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -537,11 +537,13 @@ pub fn vector_len(num_qubits: u32, encoding_method: &str) -> usize {
match encoding_method.to_lowercase().as_str() {
"angle" => n,
"basis" => 1,
"iqp-z" => n,
"iqp" => n + n.saturating_mul(n.saturating_sub(1)) / 2,
_ => 1 << n, // amplitude
}
}

/// Deterministic sample generation matching Python utils.build_sample (amplitude/angle/basis).
/// Deterministic sample generation matching Python benchmark helpers.
fn fill_sample(seed: u64, out: &mut [f64], encoding_method: &str, num_qubits: usize) -> Result<()> {
let len = out.len();
if len == 0 {
Expand All @@ -562,6 +564,13 @@ fn fill_sample(seed: u64, out: &mut [f64], encoding_method: &str, num_qubits: us
*v = mixed as f64 * scale;
}
}
"iqp-z" | "iqp" => {
let scale = (2.0 * PI) / len as f64;
for (i, v) in out.iter_mut().enumerate() {
let mixed = (i as u64 + seed) % (len as u64);
*v = mixed as f64 * scale;
}
}
_ => {
// amplitude
let mask = (len - 1) as u64;
Expand Down Expand Up @@ -631,6 +640,13 @@ fn fill_sample_f32(
*v = mixed as f32 * scale;
}
}
"iqp-z" | "iqp" => {
let scale = (2.0 * std::f32::consts::PI) / len as f32;
for (i, v) in out.iter_mut().enumerate() {
let mixed = (i as u64 + seed) % (len as u64);
*v = mixed as f32 * scale;
}
}
_ => {
// amplitude
let mask = (len - 1) as u64;
Expand Down Expand Up @@ -829,6 +845,16 @@ mod tests {
assert_generate_and_inplace_match("basis");
}

#[test]
fn generate_batch_matches_fill_batch_inplace_iqp_z() {
assert_generate_and_inplace_match("iqp-z");
}

#[test]
fn generate_batch_matches_fill_batch_inplace_iqp() {
assert_generate_and_inplace_match("iqp");
}

#[test]
fn adjacent_batches_differ_amplitude() {
assert_adjacent_batches_differ("amplitude");
Expand All @@ -844,6 +870,16 @@ mod tests {
assert_adjacent_batches_differ("basis");
}

#[test]
fn adjacent_batches_differ_iqp_z() {
assert_adjacent_batches_differ("iqp-z");
}

#[test]
fn adjacent_batches_differ_iqp() {
assert_adjacent_batches_differ("iqp");
}

#[test]
fn test_seed_none() {
let config = PipelineConfig {
Expand Down Expand Up @@ -1165,6 +1201,35 @@ mod tests {
assert!(super::encoding_supports_f32("AMPLITUDE"));
assert!(!super::encoding_supports_f32("angle"));
assert!(!super::encoding_supports_f32("basis"));
assert!(!super::encoding_supports_f32("iqp-z"));
assert!(!super::encoding_supports_f32("iqp"));
}

#[test]
fn test_vector_len_for_iqp_variants() {
assert_eq!(super::vector_len(4, "iqp-z"), 4);
assert_eq!(super::vector_len(4, "iqp"), 10);
}

#[test]
fn test_iqp_samples_in_angle_range() {
let config = PipelineConfig {
num_qubits: 4,
batch_size: 3,
encoding_method: "iqp".to_string(),
seed: Some(7),
..Default::default()
};

let vector_len = super::vector_len(config.num_qubits, &config.encoding_method);
let batch = generate_batch(&config, 0, vector_len);
let upper = 2.0 * PI;
for &value in &batch {
assert!(
(0.0..upper).contains(&value),
"iqp value should be in [0, 2pi), got {}",
value
);
}
}
}
4 changes: 2 additions & 2 deletions qdp/qdp-python/benchmark/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ Notes:

- `--frameworks` is a comma-separated list or `all`.
Options: `mahout`, `pennylane`, `qiskit-init`, `qiskit-statevector`.
- `--encoding-method` selects the encoding method: `amplitude` (default) or `basis`.
- `--encoding-method` selects the encoding method: `amplitude` (default), `angle`, `basis`, `iqp`, or `iqp-z`.
- The latency test reports average milliseconds per vector.
- Flags:
- `--qubits`: controls vector length (`2^qubits`).
Expand Down Expand Up @@ -115,7 +115,7 @@ Notes:

- `--frameworks` is a comma-separated list or `all`.
Options: `mahout`, `pennylane`, `qiskit`.
- `--encoding-method` selects the encoding method: `amplitude` (default) or `basis`.
- `--encoding-method` selects the encoding method: `amplitude` (default), `angle`, `basis`, `iqp`, or `iqp-z`.
- Throughput is reported in vectors/sec (higher is better).

## Dependency Notes
Expand Down
27 changes: 24 additions & 3 deletions qdp/qdp-python/benchmark/benchmark_latency.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,16 @@ def run_mahout(
return result.duration_sec, result.latency_ms_per_vector


def _sample_dim(num_qubits: int, encoding_method: str) -> int:
if encoding_method == "basis":
return 1
if encoding_method in {"angle", "iqp-z"}:
return num_qubits
if encoding_method == "iqp":
return num_qubits + num_qubits * (num_qubits - 1) // 2
return 1 << num_qubits


def run_pennylane(num_qubits: int, total_batches: int, batch_size: int, prefetch: int):
if not HAS_PENNYLANE:
print("[PennyLane] Not installed, skipping.")
Expand Down Expand Up @@ -236,8 +246,8 @@ def main() -> None:
"--encoding-method",
type=str,
default="amplitude",
choices=["amplitude", "angle", "basis"],
help="Encoding method to use for Mahout (amplitude, angle, or basis).",
choices=["amplitude", "angle", "basis", "iqp", "iqp-z"],
help="Encoding method to use for Mahout (amplitude, angle, basis, iqp, or iqp-z).",
)
args = parser.parse_args()

Expand All @@ -249,8 +259,19 @@ def main() -> None:
except ValueError as exc:
parser.error(str(exc))

# TODO: fix this with #1252 in the future.
if args.encoding_method in {"iqp", "iqp-z"}:
unsupported = [name for name in frameworks if name != "mahout"]
if unsupported:
print(
"Warning: IQP benchmarks in this script currently support only "
"framework 'mahout'; skipping unsupported frameworks: "
f"{', '.join(unsupported)}."
)
frameworks = ["mahout"]

total_vectors = args.batches * args.batch_size
vector_len = 1 << args.qubits
vector_len = _sample_dim(args.qubits, args.encoding_method)

print(f"Generating {total_vectors} samples of {args.qubits} qubits...")
print(f" Batch size : {args.batch_size}")
Expand Down
4 changes: 3 additions & 1 deletion qdp/qdp-python/benchmark/benchmark_pytorch_ref.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ def _sample_dim(encoding_method: str, num_qubits: int) -> int:
return 1
if encoding_method == "angle":
return num_qubits
if encoding_method == "iqp-z":
return num_qubits
if encoding_method == "iqp":
return num_qubits + num_qubits * (num_qubits - 1) // 2
return 1 << num_qubits
Expand Down Expand Up @@ -270,7 +272,7 @@ def main() -> None:
parser.add_argument(
"--encoding-method",
default="amplitude",
choices=["amplitude", "angle", "basis", "iqp"],
choices=["amplitude", "angle", "basis", "iqp", "iqp-z"],
)
parser.add_argument("--warmup", type=int, default=5)
parser.add_argument("--trials", type=int, default=3)
Expand Down
27 changes: 24 additions & 3 deletions qdp/qdp-python/benchmark/benchmark_throughput.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,16 @@ def run_mahout(
return result.duration_sec, result.vectors_per_sec


def _sample_dim(num_qubits: int, encoding_method: str) -> int:
if encoding_method == "basis":
return 1
if encoding_method in {"angle", "iqp-z"}:
return num_qubits
if encoding_method == "iqp":
return num_qubits + num_qubits * (num_qubits - 1) // 2
return 1 << num_qubits


def run_pennylane(num_qubits: int, total_batches: int, batch_size: int, prefetch: int):
if not HAS_PENNYLANE:
print("[PennyLane] Not installed, skipping.")
Expand Down Expand Up @@ -209,8 +219,8 @@ def main() -> None:
"--encoding-method",
type=str,
default="amplitude",
choices=["amplitude", "angle", "basis"],
help="Encoding method to use for Mahout (amplitude, angle, or basis).",
choices=["amplitude", "angle", "basis", "iqp", "iqp-z"],
help="Encoding method to use for Mahout (amplitude, angle, basis, iqp, or iqp-z).",
)
args = parser.parse_args()

Expand All @@ -219,8 +229,19 @@ def main() -> None:
except ValueError as exc:
parser.error(str(exc))

# TODO: fix this with #1252 in the future.
if args.encoding_method in {"iqp", "iqp-z"}:
unsupported = [name for name in frameworks if name != "mahout"]
if unsupported:
print(
"Warning: IQP benchmarks in this script currently support only "
"framework 'mahout'; skipping unsupported frameworks: "
f"{', '.join(unsupported)}."
)
frameworks = ["mahout"]

total_vectors = args.batches * args.batch_size
vector_len = 1 << args.qubits
vector_len = _sample_dim(args.qubits, args.encoding_method)

print(f"Generating {total_vectors} samples of {args.qubits} qubits...")
print(f" Batch size : {args.batch_size}")
Expand Down
62 changes: 32 additions & 30 deletions qdp/qdp-python/benchmark/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ def build_sample(

Args:
seed: Seed value used to generate deterministic data.
vector_len: Length of the vector (2^num_qubits for amplitude, num_qubits for angle).
encoding_method: "amplitude", "angle", or "basis".
vector_len: Input length for the selected encoding.
encoding_method: "amplitude", "angle", "basis", "iqp", or "iqp-z".

Returns:
NumPy array containing the sample data.
Expand All @@ -50,21 +50,22 @@ def build_sample(
mask = np.uint64(vector_len - 1)
idx = np.uint64(seed) & mask
return np.array([idx], dtype=np.float64)
if encoding_method == "angle":
# Angle encoding: one angle per qubit, scaled to [0, 2*pi)

if encoding_method in ("angle", "iqp", "iqp-z"):
# Angle/IQP-family encodings: deterministic phase parameters in [0, 2*pi)
if vector_len == 0:
return np.array([], dtype=np.float64)
scale = (2.0 * np.pi) / vector_len
idx = np.arange(vector_len, dtype=np.uint64)
mixed = (idx + np.uint64(seed)) % np.uint64(vector_len)
return mixed.astype(np.float64) * scale
else:
# Amplitude encoding: full vector
mask = np.uint64(vector_len - 1)
scale = 1.0 / vector_len
idx = np.arange(vector_len, dtype=np.uint64)
mixed = (idx + np.uint64(seed)) & mask
return mixed.astype(np.float64) * scale

# Amplitude encoding: full vector
mask = np.uint64(vector_len - 1)
scale = 1.0 / vector_len
idx = np.arange(vector_len, dtype=np.uint64)
mixed = (idx + np.uint64(seed)) & mask
return mixed.astype(np.float64) * scale


def generate_batch_data(
Expand All @@ -78,8 +79,8 @@ def generate_batch_data(

Args:
n_samples: Number of samples to generate.
dim: Dimension of each sample (2^num_qubits for amplitude encoding).
encoding_method: "amplitude", "angle", or "basis".
dim: Input dimension for the selected encoding.
encoding_method: "amplitude", "angle", "basis", "iqp", or "iqp-z".
seed: Random seed for reproducibility.

Returns:
Expand All @@ -90,12 +91,13 @@ def generate_batch_data(
if encoding_method == "basis":
# Basis encoding: single index per sample
return np.random.randint(0, dim, size=(n_samples, 1)).astype(np.float64)
if encoding_method == "angle":
# Angle encoding: per-qubit angles in [0, 2*pi)

if encoding_method in ("angle", "iqp", "iqp-z"):
# Angle/IQP-family encodings: phase parameters in [0, 2*pi)
return (np.random.rand(n_samples, dim) * (2.0 * np.pi)).astype(np.float64)
else:
# Amplitude encoding: full vectors
return np.random.rand(n_samples, dim).astype(np.float64)

# Amplitude encoding: full vectors
return np.random.rand(n_samples, dim).astype(np.float64)


def normalize_batch(
Expand All @@ -106,13 +108,13 @@ def normalize_batch(

Args:
batch: NumPy array of shape (batch_size, vector_len).
encoding_method: "amplitude", "angle", or "basis".
encoding_method: "amplitude", "angle", "basis", "iqp", or "iqp-z".

Returns:
Normalized batch. For basis/angle encoding, returns the input unchanged.
Normalized batch. For basis/angle/IQP-family encodings, returns the input unchanged.
"""
if encoding_method in ("basis", "angle"):
# Basis/angle encodings don't need normalization
if encoding_method in ("basis", "angle", "iqp", "iqp-z"):
# Basis/angle/IQP-family encodings don't need normalization
return batch
# Amplitude encoding: normalize vectors
norms = np.linalg.norm(batch, axis=1, keepdims=True)
Expand All @@ -128,13 +130,13 @@ def normalize_batch_torch(

Args:
batch: PyTorch tensor of shape (batch_size, vector_len).
encoding_method: "amplitude", "angle", or "basis".
encoding_method: "amplitude", "angle", "basis", "iqp", or "iqp-z".

Returns:
Normalized batch. For basis/angle encoding, returns the input unchanged.
Normalized batch. For basis/angle/IQP-family encodings, returns the input unchanged.
"""
if encoding_method in ("basis", "angle"):
# Basis/angle encodings don't need normalization
if encoding_method in ("basis", "angle", "iqp", "iqp-z"):
# Basis/angle/IQP-family encodings don't need normalization
return batch
# Amplitude encoding: normalize vectors
norms = torch.norm(batch, dim=1, keepdim=True)
Expand All @@ -157,9 +159,9 @@ def prefetched_batches(
Args:
total_batches: Total number of batches to generate.
batch_size: Number of samples per batch.
vector_len: Length of each vector (2^num_qubits for amplitude, num_qubits for angle).
vector_len: Input length for the selected encoding.
prefetch: Number of batches to prefetch.
encoding_method: "amplitude", "angle", or "basis".
encoding_method: "amplitude", "angle", "basis", "iqp", or "iqp-z".

Yields:
NumPy arrays of shape (batch_size, vector_len) or (batch_size, 1).
Expand Down Expand Up @@ -200,9 +202,9 @@ def prefetched_batches_torch(
Args:
total_batches: Total number of batches to generate.
batch_size: Number of samples per batch.
vector_len: Length of each vector (2^num_qubits for amplitude, num_qubits for angle).
vector_len: Input length for the selected encoding.
prefetch: Number of batches to prefetch.
encoding_method: "amplitude", "angle", or "basis".
encoding_method: "amplitude", "angle", "basis", "iqp", or "iqp-z".

Yields:
PyTorch tensors of shape (batch_size, vector_len) or (batch_size, 1).
Expand Down
Loading
Loading