use crate::quantum::{Amp, Circuit, Gateset, MAX_QUBITS, ONE, QuantumError, StateVector};
use crate::quantum_energy::EnergyClass;
use crate::quantum_fabric::lanes::{self, GateWords, Lane};
use crate::quantum_fabric::opencl::{self, DeviceIdentity, DeviceKind};
use crate::quantum_fabric::plan::{self, Config, LaneRun, Program, Step};
use crate::quantum_fabric::{
Certificate, Certification, Corpus, EnergyCalibration, FabricClass, KernelEntry, Member, MemberError, Presence, RunFuture, RunMeter, Runner, Unmetered, Window,
apply_kernel, pair_updates,
};
use opencl3::command_queue::CommandQueue;
use opencl3::context::Context;
use opencl3::device::{CL_DEVICE_TYPE_ALL, CL_DEVICE_TYPE_CPU, CL_DEVICE_TYPE_GPU, Device};
use opencl3::error_codes::ClError;
use opencl3::kernel::{ExecuteKernel, Kernel as ClKernel};
use opencl3::memory::{Buffer, CL_MEM_READ_WRITE};
use opencl3::platform::{Platform, get_platforms};
use opencl3::program::Program as ClProgram;
use opencl3::types::{CL_BLOCKING, cl_device_id, cl_int, cl_uint, cl_ulong};
use std::sync::{Arc, Mutex, MutexGuard};
pub type ProbeOutput = ((u32, u32), (u32, u32));
type MeteredRun = (LaneRun, (u64, Option<EnergyClass>));
const OUT_OF_MEMORY: [cl_int; 3] = [-4, -5, -6];
const LOADER_MISSING: cl_int = -2000;
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum OpenClError {
Unavailable(String),
Build(String),
Device(String),
Memory(String),
Circuit(QuantumError),
TooWide { n_qubits: u8, max: u8 },
OutOfRange { gate: u32 },
Internal(&'static str),
Probe { a: i32, b: i32 },
Unsupported(String),
}
impl std::fmt::Display for OpenClError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
OpenClError::Unavailable(why) => write!(f, "OpenCL is unavailable: {why}"),
OpenClError::Build(log) => write!(f, "the kernel did not build: {log}"),
OpenClError::Device(e) => write!(f, "device: {e}"),
OpenClError::Memory(e) => write!(f, "out of memory: {e}"),
OpenClError::Circuit(e) => write!(f, "{e}"),
OpenClError::TooWide { n_qubits, max } => write!(f, "{n_qubits} qubits; this member holds at most {max}"),
OpenClError::OutOfRange { gate } => write!(f, "the result of op {gate} leaves 32 bits"),
OpenClError::Internal(why) => write!(f, "host defect: {why}"),
OpenClError::Probe { a, b } => write!(f, "the device's arithmetic differs from the CPU's at ({a}, {b})"),
OpenClError::Unsupported(why) => write!(f, "not a member: {why}"),
}
}
}
impl std::error::Error for OpenClError {}
impl From<OpenClError> for MemberError {
fn from(e: OpenClError) -> MemberError {
match e {
OpenClError::TooWide { n_qubits, max } => MemberError::TooWide { n_qubits, max },
OpenClError::OutOfRange { gate } => MemberError::OutOfRange { gate },
OpenClError::Memory(why) => MemberError::Memory(why),
OpenClError::Device(why) => MemberError::Device(why),
OpenClError::Circuit(e) => MemberError::Circuit(e),
other => MemberError::Other(other.to_string()),
}
}
}
fn cl(e: ClError) -> OpenClError {
if OUT_OF_MEMORY.contains(&e.0) { OpenClError::Memory(e.to_string()) } else { OpenClError::Device(e.to_string()) }
}
#[derive(Debug)]
pub struct Refusal {
pub device: String,
pub key: String,
pub error: OpenClError,
}
#[derive(Debug)]
pub struct Discovery {
pub found: Vec<OpenClMember>,
pub refused: Vec<Refusal>,
pub unavailable: Option<String>,
}
fn platforms() -> Result<Vec<Platform>, String> {
match get_platforms() {
Err(e) if e.0 == LOADER_MISSING => Err("no OpenCL ICD loader on this host".into()),
Err(e) => Err(format!("the ICD loader lists no platform: {e}")),
Ok(p) if p.is_empty() => Err("the ICD loader lists no platform".into()),
Ok(p) => Ok(p),
}
}
pub fn available() -> Result<(), String> {
platforms().map(|_| ())
}
pub fn discover() -> Discovery {
let mut d = Discovery { found: Vec::new(), refused: Vec::new(), unavailable: None };
let platforms = match platforms() {
Ok(p) => p,
Err(why) => {
d.unavailable = Some(why);
return d;
}
};
for platform in platforms {
let info = PlatformInfo {
name: platform.name().unwrap_or_default(),
version: platform.version().unwrap_or_default(),
vendor: platform.vendor().unwrap_or_default(),
};
let ids = match platform.get_devices(CL_DEVICE_TYPE_ALL) {
Ok(ids) => ids,
Err(e) => {
d.refused.push(Refusal { device: format!("platform {:?}", info.name), key: String::new(), error: cl(e) });
continue;
}
};
for (k, id) in ids.into_iter().enumerate() {
match open(&info, k, id) {
Ok(m) => d.found.push(m),
Err(r) => d.refused.push(r),
}
}
}
d
}
struct PlatformInfo {
name: String,
version: String,
vendor: String,
}
fn qubits_for(bytes: u64) -> u8 {
let lanes = bytes / 8;
if lanes < 2 { 0 } else { (lanes.ilog2() as u8).min(MAX_QUBITS) }
}
fn hex(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{b:02x}")).collect()
}
fn open(platform: &PlatformInfo, position: usize, id: cl_device_id) -> Result<OpenClMember, Refusal> {
let unread = |error: OpenClError| Refusal { device: format!("{:?} device {position}", platform.name), key: String::new(), error };
let device = Device::new(id);
let name = device.name().map_err(|e| unread(cl(e)))?;
let kind = match device.dev_type().map_err(|e| unread(cl(e)))? {
t if t & CL_DEVICE_TYPE_GPU != 0 => DeviceKind::Gpu,
t if t & CL_DEVICE_TYPE_CPU != 0 => DeviceKind::Cpu,
_ => DeviceKind::Accelerator,
};
let extensions = device.extensions().unwrap_or_default();
let uuid = if extensions.split_whitespace().any(|x| x == "cl_khr_device_uuid") { device.uuid_khr().map(|u| hex(&u)).unwrap_or_default() } else { String::new() };
let memory = device.global_mem_size().map_err(|e| unread(cl(e)))?;
let identity = DeviceIdentity {
platform: platform.name.clone(),
platform_version: platform.version.clone(),
vendor: platform.vendor.clone(),
name: name.clone(),
version: device.version().unwrap_or_default(),
driver: device.driver_version().unwrap_or_default(),
kind,
uuid,
compute_units: device.max_compute_units().unwrap_or(0),
memory,
};
let key = identity.key();
let refuse = |error: OpenClError| Refusal { device: name.clone(), key: key.clone(), error };
let Some(class) = identity.class() else {
return Err(refuse(OpenClError::Unsupported("an OpenCL accelerator, which no fabric class names".into())));
};
if !device.available().unwrap_or(false) {
return Err(refuse(OpenClError::Unsupported("the device reports itself unavailable".into())));
}
let context = Context::from_device(&device).map_err(|e| refuse(cl(e)))?;
#[allow(deprecated)] let queue = CommandQueue::create_default(&context, 0).map_err(|e| refuse(cl(e)))?;
let kernel = Kernel::build(&context, opencl::SOURCE).map_err(refuse)?;
let max_alloc = device.max_mem_alloc_size().unwrap_or(0);
let max_qubits = qubits_for(max_alloc.min(memory / 2));
let detail = format!(
"opencl; platform {:?} ({}); device {name:?}, {}; {} compute units; {}; driver {}; uuid {}; memory {memory} bytes, {max_alloc} per allocation; up to {max_qubits} qubits; a metered run's window runs from the first upload to the last readback",
identity.platform,
identity.platform_version,
kind_label(kind),
identity.compute_units,
identity.version,
identity.driver,
if identity.uuid.is_empty() { "not reported" } else { &identity.uuid },
);
let member = OpenClMember {
context: Arc::new(context),
queue: Arc::new(queue),
kernel: Arc::new(Mutex::new(kernel)),
id: identity.member_id(),
key: key.clone(),
class,
implementation: opencl::implementation_id(),
detail,
max_qubits,
binary64: extensions.split_whitespace().any(|x| x == "cl_khr_fp64"),
identity,
};
let pairs = lanes::probe_pairs(4096, 0x5eed);
match member.probe(&pairs) {
Ok(out) => match lanes::first_mismatch(&pairs, &out) {
None => Ok(member),
Some((a, b)) => Err(refuse(OpenClError::Probe { a, b })),
},
Err(e) => Err(refuse(e)),
}
}
fn kind_label(kind: DeviceKind) -> &'static str {
match kind {
DeviceKind::Gpu => "a GPU",
DeviceKind::Cpu => "a CPU",
DeviceKind::Accelerator => "an accelerator",
}
}
struct Kernel {
apply: ClKernel,
probe: ClKernel,
}
impl Kernel {
fn build(context: &Context, source: &str) -> Result<Kernel, OpenClError> {
let program = ClProgram::create_and_build_from_source(context, source, opencl::BUILD_OPTIONS).map_err(OpenClError::Build)?;
Ok(Kernel { apply: ClKernel::create(&program, "apply_gate").map_err(cl)?, probe: ClKernel::create(&program, "probe").map_err(cl)? })
}
}
fn global(work: u64) -> usize {
work.clamp(1, opencl::GLOBAL_MAX) as usize
}
#[derive(Clone)]
pub struct OpenClMember {
context: Arc<Context>,
queue: Arc<CommandQueue>,
kernel: Arc<Mutex<Kernel>>,
identity: DeviceIdentity,
id: String,
key: String,
class: FabricClass,
implementation: String,
detail: String,
max_qubits: u8,
binary64: bool,
}
unsafe impl Send for OpenClMember {}
unsafe impl Sync for OpenClMember {}
impl std::fmt::Debug for OpenClMember {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("OpenClMember").field("id", &self.id).field("key", &self.key).field("implementation", &self.implementation).field("max_qubits", &self.max_qubits).finish()
}
}
impl OpenClMember {
pub fn id(&self) -> &str {
&self.id
}
pub fn key(&self) -> &str {
&self.key
}
pub fn class(&self) -> FabricClass {
self.class
}
pub fn detail(&self) -> &str {
&self.detail
}
pub fn max_qubits(&self) -> u8 {
self.max_qubits
}
pub fn implementation(&self) -> &str {
&self.implementation
}
pub fn identity(&self) -> &DeviceIdentity {
&self.identity
}
pub fn has_binary64(&self) -> bool {
self.binary64
}
fn kernel(&self) -> MutexGuard<'_, Kernel> {
self.kernel.lock().unwrap_or_else(|p| p.into_inner())
}
fn upload<T: Copy>(&self, data: &[T]) -> Result<Buffer<T>, OpenClError> {
let mut b = unsafe { Buffer::<T>::create(&self.context, CL_MEM_READ_WRITE, data.len(), std::ptr::null_mut()) }.map_err(cl)?;
unsafe { self.queue.enqueue_write_buffer(&mut b, CL_BLOCKING, 0, data, &[]) }.map_err(cl)?;
Ok(b)
}
fn download<T: Copy + Default>(&self, b: &Buffer<T>, len: usize) -> Result<Vec<T>, OpenClError> {
let mut out: Vec<T> = Vec::new();
out.try_reserve_exact(len).map_err(|e| OpenClError::Memory(e.to_string()))?;
out.resize(len, T::default());
unsafe { self.queue.enqueue_read_buffer(b, CL_BLOCKING, 0, &mut out, &[]) }.map_err(cl)?;
Ok(out)
}
pub fn probe(&self, pairs: &[(i32, i32)]) -> Result<Vec<ProbeOutput>, OpenClError> {
if pairs.is_empty() {
return Ok(Vec::new());
}
let flat: Vec<cl_int> = pairs.iter().flat_map(|&(a, b)| [a, b]).collect();
let cases = self.upload(&flat)?;
let out = self.upload(&vec![0 as cl_uint; pairs.len() * 4])?;
let count = pairs.len() as cl_ulong;
{
let k = self.kernel();
unsafe { ExecuteKernel::new(&k.probe).set_arg(&cases).set_arg(&out).set_arg(&count).set_global_work_size(global(count)).enqueue_nd_range(&self.queue) }
.map_err(cl)?;
}
let words = self.download(&out, pairs.len() * 4)?;
Ok(words.as_chunks::<4>().0.iter().map(|w| ((w[0], w[1]), (w[2], w[3]))).collect())
}
pub fn config(&self) -> Config {
Config::PER_GATE
}
pub fn run_program(&self, p: &Program) -> Result<LaneRun, OpenClError> {
Ok(self.run_program_metered(p, &mut Unmetered)?.0)
}
fn run_program_metered<M: RunMeter + ?Sized>(&self, p: &Program, meter: &mut M) -> Result<MeteredRun, OpenClError> {
let n = p.n_qubits;
if n == 0 {
return Ok((LaneRun { amps: vec![[ONE as i32, 0]], out_of_range: None }, (0, None)));
}
if n > self.max_qubits {
return Err(OpenClError::TooWide { n_qubits: n, max: self.max_qubits });
}
if n > MAX_QUBITS || p.start >= 1usize << n {
return Err(OpenClError::Internal("the start is outside the state"));
}
let len = 1usize << n;
let mut gates = Vec::with_capacity(p.steps.len());
for step in &p.steps {
let Step::Gate { gate } = *step else { return Err(OpenClError::Internal("a tile step; this member runs gate steps only")) };
let g = p.gates.get(gate as usize).ok_or(OpenClError::Internal("a step names a gate the program does not hold"))?;
if !g.is_well_formed() {
return Err(OpenClError::Internal("malformed gate words for this state"));
}
gates.push((&g.words, g.index));
}
let mut init: Vec<i32> = Vec::new();
init.try_reserve_exact(2 * len).map_err(|e| OpenClError::Memory(e.to_string()))?;
init.resize(2 * len, 0);
init[2 * p.start] = ONE as i32;
self.execute(init, &gates, meter)
}
pub fn run_lanes(&self, state: &[Lane], gates: &[GateWords]) -> Result<LaneRun, OpenClError> {
let len = state.len();
if len < 2 || !len.is_power_of_two() {
return Err(OpenClError::Internal("a state that is not 2^n lanes, n at least 1"));
}
let n = len.trailing_zeros() as u8;
if n > self.max_qubits {
return Err(OpenClError::TooWide { n_qubits: n, max: self.max_qubits });
}
let planned: Vec<(&GateWords, u32)> = gates.iter().zip(0u32..).collect();
let mut init: Vec<i32> = Vec::new();
init.try_reserve_exact(2 * len).map_err(|e| OpenClError::Memory(e.to_string()))?;
init.extend(state.iter().flat_map(|l| [l[0], l[1]]));
Ok(self.execute(init, &planned, &mut Unmetered)?.0)
}
fn execute<M: RunMeter + ?Sized>(&self, init: Vec<i32>, gates: &[(&GateWords, u32)], meter: &mut M) -> Result<MeteredRun, OpenClError> {
let len = init.len() / 2;
for (w, _) in gates {
let (tbit, ctrl) = (w.tbit as usize, w.ctrl as usize);
if tbit == 0 || !tbit.is_power_of_two() || tbit > len / 2 || ctrl & tbit != 0 || ctrl & !(len - 1) != 0 {
return Err(OpenClError::Internal("malformed gate words for this state"));
}
}
let pairs = (len / 2) as cl_ulong;
let mut window = Window::new(meter);
window.open();
let amps = self.upload(&init)?;
drop(init);
let report = self.upload(&[u32::MAX as cl_uint])?;
{
let k = self.kernel();
for &(w, index) in gates {
let m: [cl_int; 8] = w.m.map(|x| x as i32);
let mut launch = ExecuteKernel::new(&k.apply);
unsafe {
launch.set_arg(&s).set_arg(&pairs).set_arg(&w.tbit).set_arg(&w.ctrl);
for v in &m {
launch.set_arg(v);
}
launch.set_arg(&index).set_arg(&report).set_global_work_size(global(pairs)).enqueue_nd_range(&self.queue)
}
.map_err(cl)?;
}
}
let bad = self.download(&report, 1)?;
let words = self.download(&s, 2 * len)?;
let reading = window.close();
let out_of_range = bad.first().copied().filter(|&k| k != u32::MAX);
let mut lanes_out: Vec<Lane> = Vec::new();
lanes_out.try_reserve_exact(len).map_err(|e| OpenClError::Memory(e.to_string()))?;
lanes_out.extend(words.as_chunks::<2>().0.iter().copied());
Ok((LaneRun { amps: lanes_out, out_of_range }, reading))
}
pub fn simulate_from(&self, c: &Circuit, start: usize) -> Result<StateVector, OpenClError> {
Ok(self.simulate_with(c, start, &mut Unmetered)?.0)
}
pub fn simulate_metered(&self, c: &Circuit, start: usize, meter: &mut dyn RunMeter) -> Result<(StateVector, u64, Option<EnergyClass>), OpenClError> {
self.simulate_with(c, start, meter)
}
fn simulate_with<M: RunMeter + ?Sized>(&self, c: &Circuit, start: usize, meter: &mut M) -> Result<(StateVector, u64, Option<EnergyClass>), OpenClError> {
let p = plan::compile_with(c, start, &self.config()).map_err(OpenClError::Circuit)?;
if p.n_qubits > self.max_qubits {
return Err(OpenClError::TooWide { n_qubits: p.n_qubits, max: self.max_qubits });
}
let (run, (joules_micro, class)) = self.run_program_metered(&p, meter)?;
if let Some(gate) = run.out_of_range {
return Err(OpenClError::OutOfRange { gate });
}
let mut amps = Vec::new();
amps.try_reserve_exact(run.amps.len()).map_err(|e| OpenClError::Memory(e.to_string()))?;
amps.extend(run.amps.iter().map(|l| Amp { re: i64::from(l[0]), im: i64::from(l[1]) }));
Ok((StateVector { n_qubits: p.n_qubits, amps }, joules_micro, class))
}
pub fn simulate(&self, c: &Circuit) -> Result<StateVector, OpenClError> {
self.simulate_from(c, 0)
}
pub fn certify(&self, gs: Gateset, corpus: Corpus) -> Result<Certificate, OpenClError> {
Ok(self.certify_metered(gs, corpus, &mut Unmetered)?.0)
}
fn certify_metered<M: RunMeter + ?Sized>(&self, gs: Gateset, corpus: Corpus, meter: &mut M) -> Result<(Certificate, Option<(u64, EnergyClass)>), OpenClError> {
let mut cert = Certification::new();
let mut calibration = EnergyCalibration::new();
for c in corpus.circuits(gs) {
let reference = lanes::reference_output(&c).map_err(OpenClError::Circuit)?;
let (run, (joules_micro, class)) = self.run_program_metered(&plan::compile_with(&c, 0, &self.config()).map_err(OpenClError::Circuit)?, &mut *meter)?;
if let Some(gate) = run.out_of_range {
return Err(OpenClError::OutOfRange { gate });
}
cert.add(&lanes::circuit_bytes(&c), &reference, &lanes::output(&run.amps));
calibration.add(pair_updates(&c), joules_micro, class.as_ref());
}
Ok((cert.finish(apply_kernel(gs), &self.implementation, &self.id, &corpus.generator(gs)), calibration.finish()))
}
pub fn member(&self, corpus: Corpus) -> Result<Member, OpenClError> {
self.member_with(corpus, &mut Unmetered)
}
pub fn member_metered(&self, corpus: Corpus, meter: &mut dyn RunMeter) -> Result<Member, OpenClError> {
self.member_with(corpus, meter)
}
fn member_with<M: RunMeter + ?Sized>(&self, corpus: Corpus, meter: &mut M) -> Result<Member, OpenClError> {
let mut kernels = Vec::new();
for gs in Gateset::ALL {
let (certificate, energy) = self.certify_metered(gs, corpus, &mut *meter)?;
kernels.push(KernelEntry { kernel: apply_kernel(gs).into(), implementation: self.implementation.clone(), certificate, energy });
}
Ok(Member { id: self.id.clone(), class: self.class, presence: Presence::Detected, kernels, detail: self.detail.clone() })
}
#[doc(hidden)]
pub fn with_max_qubits(&self, max_qubits: u8) -> OpenClMember {
OpenClMember { max_qubits: max_qubits.min(self.max_qubits), ..self.clone() }
}
#[doc(hidden)]
pub fn with_source(&self, source: &str) -> Result<OpenClMember, OpenClError> {
let kernel = Kernel::build(&self.context, source)?;
Ok(OpenClMember { kernel: Arc::new(Mutex::new(kernel)), implementation: opencl::implementation_id_of(source), ..self.clone() })
}
}
#[cfg(feature = "quantum_sv")]
#[derive(Clone)]
pub struct OpenClSv {
member: OpenClMember,
kernels: Arc<Mutex<SvKernels>>,
implementation: String,
}
#[cfg(feature = "quantum_sv")]
struct SvKernels {
diag: ClKernel,
one: ClKernel,
many: ClKernel,
}
#[cfg(feature = "quantum_sv")]
unsafe impl Send for OpenClSv {}
#[cfg(feature = "quantum_sv")]
unsafe impl Sync for OpenClSv {}
#[cfg(feature = "quantum_sv")]
impl std::fmt::Debug for OpenClSv {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("OpenClSv").field("member", &self.member.id).field("implementation", &self.implementation).finish()
}
}
#[cfg(feature = "quantum_sv")]
impl OpenClMember {
pub fn sv(&self) -> Result<OpenClSv, OpenClError> {
self.sv_with_source(crate::quantum_fabric::sv64::CL_SOURCE)
}
#[doc(hidden)]
pub fn sv_with_source(&self, source: &str) -> Result<OpenClSv, OpenClError> {
use crate::quantum_fabric::sv64;
if !self.binary64 {
return Err(OpenClError::Unsupported("the device has no binary64 (cl_khr_fp64)".into()));
}
let program = ClProgram::create_and_build_from_source(&self.context, source, sv64::CL_BUILD_OPTIONS).map_err(OpenClError::Build)?;
let kernels = SvKernels {
diag: ClKernel::create(&program, "sv_diag").map_err(cl)?,
one: ClKernel::create(&program, "sv_one").map_err(cl)?,
many: ClKernel::create(&program, "sv_many").map_err(cl)?,
};
Ok(OpenClSv { member: self.clone(), kernels: Arc::new(Mutex::new(kernels)), implementation: sv64::cl_implementation_id_of(source) })
}
}
#[cfg(feature = "quantum_sv")]
impl OpenClSv {
pub fn implementation(&self) -> &str {
&self.implementation
}
pub fn run(&self, n: u32, gates: &[crate::gates::Gate], width: usize) -> Result<(Vec<f64>, Vec<f64>), OpenClError> {
use crate::quantum_fabric::sv64::{Launch, plan};
let launches = plan(n, gates, width).map_err(|_| OpenClError::Internal("a gate the state vector refuses"))?;
let max = self.member.max_qubits.saturating_sub(1);
if n == 0 || n > u32::from(max) {
return Err(OpenClError::TooWide { n_qubits: n.min(255) as u8, max });
}
let len = 1usize << n;
let mut init = vec![0.0f64; len];
init[0] = 1.0;
let re = self.member.upload(&init)?;
init[0] = 0.0;
let im = self.member.upload(&init)?;
drop(init);
let flat = |m: &[crate::linalg::C]| -> Vec<f64> { m.iter().flat_map(|c| [c.re, c.im]).collect() };
{
let k = self.kernels.lock().unwrap_or_else(|p| p.into_inner());
for l in &launches {
match l {
Launch::Diag { bitpos, diag } => {
let bp = self.member.upload(bitpos)?;
let dg = self.member.upload(&flat(diag))?;
let (count, nk): (cl_ulong, cl_uint) = (len as u64, bitpos.len() as u32);
unsafe { ExecuteKernel::new(&k.diag).set_arg(&re).set_arg(&im).set_arg(&count).set_arg(&bp).set_arg(&nk).set_arg(&dg).set_global_work_size(global(count)).enqueue_nd_range(&self.member.queue) }.map_err(cl)?;
}
Launch::One { s, m } => {
let pairs = (len / 2) as cl_ulong;
let v = flat(m);
let mut launch = ExecuteKernel::new(&k.one);
unsafe {
launch.set_arg(&re).set_arg(&im).set_arg(&pairs).set_arg(s);
for x in &v {
launch.set_arg(x);
}
launch.set_global_work_size(global(pairs)).enqueue_nd_range(&self.member.queue)
}
.map_err(cl)?;
}
Launch::Many { sorted, offsets, m } => {
let so = self.member.upload(sorted)?;
let of = self.member.upload(offsets)?;
let mm = self.member.upload(&flat(m))?;
let (groups, nk): (cl_ulong, cl_uint) = ((len >> sorted.len()) as u64, sorted.len() as u32);
unsafe { ExecuteKernel::new(&k.many).set_arg(&re).set_arg(&im).set_arg(&groups).set_arg(&so).set_arg(&nk).set_arg(&of).set_arg(&mm).set_global_work_size(global(groups)).enqueue_nd_range(&self.member.queue) }.map_err(cl)?;
}
}
}
}
Ok((self.member.download(&re, len)?, self.member.download(&im, len)?))
}
pub fn certify(&self, corpus: crate::quantum_fabric::sv64::SvCorpus) -> Result<Certificate, OpenClError> {
use crate::quantum_fabric::sv64::{KERNEL_SV_RUN, state_output};
let mut cert = Certification::new();
for case in corpus.cases() {
let reference = case.reference().map_err(|_| OpenClError::Internal("the corpus holds a gate the state vector refuses"))?;
let (re, im) = self.run(case.n, &case.gates, case.width)?;
cert.add(&case.bytes(), &reference, &state_output(&re, &im));
}
Ok(cert.finish(KERNEL_SV_RUN, &self.implementation, &self.member.id, &corpus.generator()))
}
pub fn entry(&self, corpus: crate::quantum_fabric::sv64::SvCorpus) -> Result<KernelEntry, OpenClError> {
Ok(KernelEntry { kernel: crate::quantum_fabric::sv64::KERNEL_SV_RUN.into(), implementation: self.implementation.clone(), certificate: self.certify(corpus)?, energy: None })
}
}
impl Runner for OpenClMember {
fn member_id(&self) -> String {
self.id.clone()
}
fn run<'r>(&'r self, implementation: &'r str, c: &'r Circuit, start: usize, meter: &'r mut dyn RunMeter) -> RunFuture<'r> {
Box::pin(async move {
if implementation != self.implementation {
return Err(MemberError::Other(format!("member {} runs implementation {}, not {implementation:?}", self.id, self.implementation)));
}
Ok(self.simulate_with(c, start, meter)?)
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn running_out_of_memory_or_resources_is_a_memory_error_and_every_other_a_device_error() {
for code in OUT_OF_MEMORY {
assert!(matches!(cl(ClError(code)), OpenClError::Memory(_)), "{code}");
}
assert!(matches!(cl(ClError(-36)), OpenClError::Device(_)));
assert!(MemberError::from(OpenClError::Memory("x".into())).falls_back());
assert!(MemberError::from(OpenClError::OutOfRange { gate: 3 }).falls_back());
assert!(!MemberError::from(OpenClError::Probe { a: 1, b: 2 }).falls_back());
assert!(!MemberError::from(OpenClError::Internal("x")).falls_back());
}
#[test]
fn the_state_a_member_holds_is_what_one_allocation_holds() {
assert_eq!(qubits_for(0), 0);
assert_eq!(qubits_for(15), 0);
assert_eq!(qubits_for(16), 1);
assert_eq!(qubits_for(1536 << 20), MAX_QUBITS.min(27));
assert_eq!(qubits_for(u64::MAX), MAX_QUBITS);
}
#[test]
fn the_global_size_covers_the_work_and_is_capped() {
assert_eq!(global(0), 1);
assert_eq!(global(7), 7);
assert_eq!(global(1 << 40), opencl::GLOBAL_MAX as usize);
}
}