Q-Alchemy Python SDK
This is the Python SDK for using the data cybernetics Q-Alchemy API. It provides state preparation, hosted sparse simulation, circuit compression, feasibility assessment, and provider-neutral Quantum I/O execution through PineXQ. The state-preparation API helps quantum computing researchers put classical data into a quantum computer (the loading/encoding problem, sometimes described as a form of QRAM).
Under the hood, Q-Alchemy runs on PineXQ, the hypermedia (Siren) API platform of data cybernetics. You do not need to know anything about it to use this SDK; if you want to work with the API directly, see the PineXQ documentation.
Installation
The SDK is published on PyPI, so you can install it with pip (or poetry, uv, pdm, ...):
pip install q-alchemy-sdk-py
# or
uv add q-alchemy-sdk-py
# or
pdm add q-alchemy-sdk-py
If you want to use the qiskit-integration, please use
pip install q-alchemy-sdk-py[qiskit]
And if you want the PennyLane-integration, please use
pip install q-alchemy-sdk-py[pennylane]
If you would like to run our examples, please use
pip install q-alchemy-sdk-py[examples]
We use uv and have tested this all with Python 3.11 through 3.14. So the way to install it after cloning is simply
uv sync --locked
The SDK requires pinexq-client>=2.1.0,<3. Client 2.1.0 expects PineXQ
JobManagement API 10.1. The client checks its protocol major/minor against the
server and warns when they differ; the SDK preserves these warnings. A server
still running API 10.0 therefore produces a mismatch warning with client 2.1.0.
Client package versions and API protocol versions are separate.
This client recognizes the server's TenantId and IsMaterialized fields.
API-version and schema warnings are no longer suppressed by the SDK; a future
mismatch remains visible. Q_ALCHEMY_API_VERSION_WARNING is no longer needed.
Again, for qiskit- or PennyLane-integrations, please add the groups
uv sync --locked --extra qiskit --extra pennylane
And for running our examples,
uv sync --locked --extra examples
Usage
There are examples under the /examples folder, but for those that are eager to find out, here it is.
First, you will want to get an API key from the Q-Alchemy Portal. You
need to sign up for this, sorry, but this is necessary. Once you have the API key (free of charge of course)
you can test it!
Direct Example
import numpy as np
import os
from sklearn.datasets import fetch_openml
from q_alchemy.initialize import q_alchemy_as_qasm
mnist = fetch_openml('mnist_784', version=1, parser="auto")
zero: np.ndarray = mnist.data[mnist.target == "0"].iloc[0].to_numpy()
filler = np.empty(2 ** 10 - zero.shape[0])
filler.fill(0)
zero = np.hstack([zero, filler])
zero = zero / np.linalg.norm(zero)
qasm, summary = q_alchemy_as_qasm(zero, max_fidelity_loss=0.2,
api_key=os.environ["Q_ALCHEMY_API_KEY"], return_summary=True)
print(summary)
Qiskit Example
import numpy as np
from sklearn.datasets import fetch_openml
import os
from q_alchemy.qiskit_integration import QAlchemyInitialize, OptParams
mnist = fetch_openml('mnist_784', version=1, parser="auto")
zero: np.ndarray = mnist.data[mnist.target == "0"].iloc[0].to_numpy()
filler = np.empty(2 ** 10 - zero.shape[0])
filler.fill(0)
zero = np.hstack([zero, filler])
zero = zero / np.linalg.norm(zero)
instr = QAlchemyInitialize(
params=zero.tolist(),
opt_params=OptParams(
max_fidelity_loss=0.1,
basis_gates=["id", "rx", "ry", "rz", "cx"],
api_key=os.environ["Q_ALCHEMY_API_KEY"]
)
)
instr.definition.draw(fold=-1)
PennyLane Example
import numpy as np
import pennylane as qml
from sklearn.datasets import fetch_openml
import os
from q_alchemy.pennylane_integration import QAlchemyStatePreparation, OptParams
mnist = fetch_openml('mnist_784', version=1, parser="auto")
zero: np.ndarray = mnist.data[mnist.target == "0"].iloc[0].to_numpy()
filler = np.empty(2 ** 10 - zero.shape[0])
filler.fill(0)
zero = np.hstack([zero, filler])
zero = zero / np.linalg.norm(zero)
dev = qml.device('lightning.qubit', wires=10)
@qml.qnode(dev)
def circuit(state=None):
QAlchemyStatePreparation(
state,
wires=range(10),
opt_params=OptParams(
max_fidelity_loss=0.1,
basis_gates=["id", "rx", "ry", "rz", "cx"],
api_key=os.environ["Q_ALCHEMY_API_KEY"]
)
)
return qml.state()
print(qml.draw(circuit, level="device", max_length=100)(zero.tolist()))
Broadcasting with PennyLane
PennyLane provides native support for broadcasting, which allows quantum nodes to process batches of inputs efficiently. This is particularly useful in machine learning applications where inputs often come in batches. When broadcasting is used in conjunction with Q-Alchemy, each state in the batch is individually prepared using Q-Alchemy's circuit synthesis capabilities.
⚠️ Note: For simulators or backends that support native state initialization using the
StatePrepgate—such asdefault.qubit, andlightning.qubit—the state vector is injected directly without any decomposition into quantum gates. In this case, Q-Alchemy is not used. This behavior is ideal for rapid prototyping and testing. Switching to a hardware backend (or one without native state prep) will automatically invoke Q-Alchemy for state preparation.
Broadcasting Example
import numpy as np
import pennylane as qml
import os
import torch
from q_alchemy.pennylane_integration import AmplitudeEmbedding, OptParams
from sklearn.datasets import make_moons
# Sample data
X, _ = make_moons(n_samples=5, noise=0.1)
X = X / np.linalg.norm(X, axis=1, keepdims=True) # Normalize each row for amplitude embedding
# Create PennyLane device
dev = qml.device("qiskit.aer", wires=1)
@qml.qnode(dev, interface="torch")
def circuit(x):
AmplitudeEmbedding(
x,
wires=[0],
opt_params=OptParams(
max_fidelity_loss=0.0,
api_key=os.environ["Q_ALCHEMY_API_KEY"]
)
)
return qml.expval(qml.PauliZ(0))
# Run the circuit on a batch of inputs
X_tensor = torch.tensor(X, dtype=torch.float64)
print(qml.draw(circuit, level="device", max_length=100)(X_tensor))
This example demonstrates how batched data can be processed using broadcasting with AmplitudeEmbedding, and how Q-Alchemy is triggered on simulators like qiskit.aer. When moving to real hardware or gate-based backends that lack StatePrep gate, Q-Alchemy will transparently handle the state preparation.
Advanced options
Every entry point (q_alchemy_as_qasm, QAlchemyInitialize,
QAlchemyStatePreparation, AmplitudeEmbedding) takes its settings as an
OptParams object, or as a plain dict with the same keys. q_alchemy_as_qasm
also accepts them as keyword arguments. The state itself can be a list, a numpy
array, a scipy sparse array or, for Qiskit, a Statevector.
Dense inputs are checked for sparsity once before serialization. The SDK uses
QTucker's blockwise, early-exit density rule: a vector is sent as sparse when its
number of nonzero amplitudes is at most ceil(0.1 * vector_length). Unlike
QTucker's numerical thresholding, the transport check counts exact nonzeros and
never discards tiny amplitudes or renormalizes the state. This keeps transport
lossless and leaves the approximation budget with QTucker.
- Dense vectors use NumPy
.npyserialization (numpy_loadfor inline input). - Sparse vectors use Parquet with basis indices and complex amplitudes. Inputs already supplied as SciPy sparse matrices/arrays skip the density check and remain sparse, even when fully populated.
- Mixed batches are split into at most two service jobs, one per representation; returned circuits and summaries follow the original input order. Each job uses the usual cleanup policy and timeout. If either job fails, the call raises. Uniform batches still use one job; parallel option sets share one serialization.
The updated ProCon preserves these dense/sparse paths into QTucker. Both formats are supported by older services too, but avoiding server-side conversions requires the updated ProCon deployment. No private QTucker dependency is added to the SDK.
The fields you are most likely to touch:
| Field | Default | Meaning |
|---|---|---|
max_fidelity_loss |
0.0 |
How much fidelity you are willing to give up for a shallower circuit. 0.0 asks for an exact preparation. |
basis_gates |
["u", "cx"] |
Gate names used to transpile the returned circuit for all three methods; under AUTO, also used to compare candidates. |
api_key |
$Q_ALCHEMY_API_KEY |
Your Q-Alchemy API key. Keep it safe! |
initialization_method |
InitializationMethods.AUTO |
Which algorithm builds the circuit (see below). |
extra_kwargs |
{} |
Method-specific options, as a dict (see below). |
use_qasm3 |
False |
Experimental: return OpenQASM 3 instead of OpenQASM 2. |
remove_data |
True |
Delete the job and its uploaded data once the result is fetched. |
job_completion_timeout_sec |
300 |
How long to wait for the job before giving up. |
q_alchemy_as_qasm_parallel(state, option_sets, max_workers=4) compares option
sets concurrently. Results follow the order of option_sets; a failed worker
raises an exception instead of returning an incomplete list. Input serialization
is shared, and max_workers bounds concurrent jobs. Already-running jobs finish
their normal cleanup if another worker fails. A supplied HTTP client remains
caller-owned. Processing-step lookups are cached per client for five minutes;
create a new client when changing accounts.
QAlchemyInitialize owns a copy of the input amplitudes, so later changes to the
caller's array cannot change the pending initialization. Dense inputs remain
NumPy arrays internally. Upload hashing is controlled by assign_data_hash;
the instruction no longer computes the unused param_hash attribute.
InitializationMethods lives in q_alchemy.initialize:
AUTO(default) runs several Tucker candidates (iterative and hierarchical, as resources allow) at your fidelity budget and keeps the cheapest realized circuit. Trivial inputs such as single-qubit and single-basis states take an exact fast path.ITERATIVE_TUCKERandHIERARCHICAL_TUCKERpin one Tucker variant.
Method-specific options (extra_kwargs)
extra_kwargs is passed through to the chosen method. Pass a dict, and the SDK
serializes it for you:
from q_alchemy.initialize import OptParams, InitializationMethods
opt_params = OptParams(
max_fidelity_loss=0.05,
initialization_method=InitializationMethods.ITERATIVE_TUCKER,
extra_kwargs={"max_iterations": 10, "factors_size": 4},
)
⚠️ Options are validated per method. A key that the chosen method does not accept fails the job with
Could not build the initialization circuit: ...instead of being silently ignored. In particular, the iterative-Tucker keys below are not valid underAUTO. Pin the method to use them.
| Method | Accepted extra_kwargs keys |
|---|---|
AUTO |
cost_function ("two_qubit_then_depth" default, "cx_then_depth", "depth_then_cx", "cx+depth", "cx", "depth"), transpile_optimization_level (1), seed_transpiler (0), dominant_basis_fast_path (True), fidelity_tolerance, geometric_entanglement, check_normalization (True) |
ITERATIVE_TUCKER |
max_iterations (≤ 0 picks one from the qubit count), factors_size (0 = automatic), max_stepup (0), fallback (True), perturbation (None), geometric_entanglement (0.0), check_normalization (True), barriers (False; debugging only, hurts transpilation) |
HIERARCHICAL_TUCKER |
geometric_entanglement, check_normalization |
Two interactions worth knowing:
- Set the fidelity with
max_fidelity_lossonOptParams, not inextra_kwargs. The service rejects a conflicting duplicate instead of silently replacing the top-level budget. An equivalent duplicate is accepted. - Set the gate basis with
OptParams.basis_gatesfor all three methods. UnderAUTO, the hosted QAlchemy initializer also uses it when comparing candidates. Do not duplicatebasis_gatesinextra_kwargs.
All three initialization methods return logical circuits transpiled to the
supplied gate basis, without physical placement or routing. Under AUTO, QTucker
transpiles candidates to that basis before comparison and returns the selected
circuit. For explicit HIERARCHICAL_TUCKER and ITERATIVE_TUCKER requests, the
ProCon transpiles the synthesized circuit to that basis at optimization level 1.
This applies to single requests and batches; reported circuit costs precede
physical placement and routing.
The SDK accepts gate names through OptParams.basis_gates, not Qiskit backend or
Target objects. To use a backend's gate basis, supply its supported unitary gate
names explicitly. Compile the complete application for the actual backend later.
For AUTO's optional transpile_options, QTucker ignores physical layout,
coupling-map and scheduling settings and rejects lossy transpilation
(approximation_degree must be 1.0).
Request return_summary=True from q_alchemy_as_qasm or
q_alchemy_as_qasm_parallel_states to inspect the preparation outcome:
status="OK" means execution completed; fidelity_requirement_met=False
means AUTO returned its best-effort circuit without meeting the requested budget.
The decision is supplied by QTucker. It is None for pinned methods and may be
absent in older services. fidelity_loss remains an unsimulated estimate;
fidelity_loss_source identifies its source and selection_reason explains the
AUTO outcome. These fields pass through unchanged. Batch functions always return
lists, including a one-state batch. A synthesis failure stops a batch with the
original error and the failing state's zero-based index.
Compressing circuits through PineXQ
CircuitCompressionService calls the compress_circuit ProcessingStep from
q-alchemy-circuit-compressor-pinexq. The SDK accepts a native Qiskit circuit, the
existing SDK Circuit type, or a schema-1 quantum-circuit envelope (including
feasibility_report.raw["quantum_circuit"]). It returns a typed report with the
compressed circuit, exactness semantics, input/baseline/output metrics, accepted
regions, and optional diagnostics. The local SDK does not install or run the
compression engine or a simulator.
from qiskit import QuantumCircuit
from q_alchemy import CircuitCompressionRequest, CircuitCompressionService
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.cx(0, 1)
with CircuitCompressionService() as service:
report = service.compress(
circuit, CircuitCompressionRequest(options={"collect_report": True})
).result()
print(report.format_summary())
compressed = report.to_qiskit()
The default assumes the complete circuit starts in |0...0>
(equivalence="reachable_subspace"). For a subroutine with arbitrary input states,
use CircuitCompressionRequest(options={"equivalence": "operator"}).
report.exact applies to that declared equivalence; it does not imply that a
reachable-subspace result preserves every possible input. Qiskit input conversion
and output transport preserve the top-level global phase, which matters for
subsequent controlled-subroutine use. Bind symbolic global phases before export.
Compression option names and resource limits are validated by the deployed core.
Omitting options sends {} and retains its defaults. The SDK adds no duplicate
memory or synthesis ceilings. Transport admission checks are owned by the PineXQ
adapter; region indices in the report refer to its normalized baseline circuit.
CircuitCompressionParams follows the other service clients: api_key defaults
to Q_ALCHEMY_API_KEY (then PINEXQ_API_KEY), and host defaults to
Q_ALCHEMY_HOST or jobs.api.q-alchemy.com. Set step_version="0.1.7" to pin a
deployed step, or omit it to discover the visible version. Successful result
retrieval removes SDK-created jobs and WorkData by default. Set remove_data=False
to retain them. A failure or malformed report preserves the job for diagnosis;
CircuitCompressionExecutionError exposes the remote error and its original
exception. Repeating job.result() retries retrieval, or returns the cached report
and retries any unfinished cleanup. job.raw_job remains available until deletion.
job_completion_timeout_sec (default 300) and job.result(timeout=...) only limit
the client's wait. They do not cancel the job or set a server computation deadline.
Use the service context manager to close SDK-owned HTTP connections; injected
clients remain owned by the caller.
Install the qiskit extra for native circuit conversion. Portable usage needs no
Qiskit: pass Circuit.qasm3(program) and consume report.circuit.payload or
report.to_dict(). See the runnable example.
For the 12-qubit chemistry workload, run:
python examples/circuit_compression_quantum_chemistry.py
This example reuses
build_experiment() from feasibility_quantum_chemistry.py. It builds the full
Hartree--Fock preparation plus correlated evolution circuit (71 one-qubit gates
and 34 CX gates), submits it directly to the compression service, and prints the
input/baseline/compressed metrics and two-qubit gate reduction. The complete
circuit starts in |0...0>, so default reachable-subspace compression is used.
It requires the SDK's qiskit extra and a Q-Alchemy API key; it does not run the
Feasibility workflow, select a quantum backend, or require IBM credentials.
Live circuit-compression tests
tests/test_circuit_compression.py uses mocked PineXQ transport. Its optional
ProCon check invokes a locally installed adapter; it does not contact PineXQ.
The separate live suite submits two real jobs to the deployed service. With
Q_ALCHEMY_API_KEY (or PINEXQ_API_KEY) already exported and the SDK's qiskit
extra installed, run:
Q_ALCHEMY_RUN_LIVE_CIRCUIT_COMPRESSION=1 \
pytest -v -s tests/test_circuit_compression_live_integration.py
Optionally set Q_ALCHEMY_CIRCUIT_COMPRESSION_STEP_VERSION=0.1.1 to pin the
ProcessingStep, and Q_ALCHEMY_HOST to select a different PineXQ host. Both a key
and the explicit live-test flag are required; otherwise these tests skip.
The first test sends a two-qubit circuit with two CX gates, checks that compression reduces its two-qubit gate count below both the input and the O0 baseline, and compares the returned statevector with the original. The current compressor reduces this example from two CX gates to one. The second test checks operator equivalence and global phase, including reuse as a controlled subroutine. Both exercise report retrieval, cached results and cleanup of their own jobs/WorkData. The small local statevector/operator calculations verify the results; compression itself runs on PineXQ. Successful jobs are removed, while remote execution or retrieval failures preserve their jobs and data for diagnosis.
Running experiments with Quantum I/O
For a complete molecular example, run the 12-qubit LiH reconstruction notebook. It includes the original sparse CISD target and offers direct ideal simulation, Aer simulation with an imported IBM device noise model, or an IBM QPU, followed by reconstruction and sparse-result export in one service request.
For a system initialized in an arbitrary state with a bath starting in its ground state, see the system–bath example. It evolves both groups and evaluates one observable on the system, without state reconstruction.
State widths, sparse indices, qubit selections and shot counts require integers; booleans and fractional values are rejected locally. Sparse indices in JSON remain decimal strings so large basis indices retain their full precision.
Quantum I/O uses typed Python objects throughout the public SDK. Users construct
State, Circuit, MeasurementPlan, QuantumExperiment, Runtime, and
ExecutionPlan objects and receive a typed ExperimentReport. The SDK handles the
schema-3 JSON serialization used by PineXQ internally; application code does not need
to assemble contract dictionaries or encode complex amplitudes manually.
ExperimentReport.format_summary() uses the same human-readable section layout and
number formatting as the Quantum I/O runtime report, so local/service output remains
consistent at the client boundary.
Quantum I/O removes its SDK-created PineXQ Job and input/output WorkData by default
once result() has successfully returned a report. Provider credentials are uploaded
separately as Secret WorkData before they are attached to a Job and never become part of
QuantumExperiment or ExecutionPlan. Set QuantumIOParams(remove_data=False) when
you explicitly want to preserve the complete PineXQ execution lineage.
Execution-lineage cleanup happens after successful result retrieval. If the job fails, times out, or returns a report the SDK cannot parse, the Job and its WorkData are left in place so the failure can be diagnosed and
result()retried. If submission fails before PineXQ creates a Job, unreferenced WorkData created by that attempt is removed whenremove_data=True. If cleanup itself fails after the report was cached, anotherresult()call retries the remaining cleanup. After a successful automatic cleanup the PineXQ resources are gone:QuantumIOJob.raw_jobthen raises, andQuantumIOJob.removedisTrue. Useremove_data=Falseif you need to inspect the Job afterwards.
Preflight
Preflight is the simplest Quantum I/O workflow. It prepares the target state and, by default, independently verifies the preparation with Q-Alchemy's sparse simulator. It does not run a reference experiment, noisy simulation, or QPU acquisition.
from math import sqrt
from q_alchemy import QuantumExperiment, QuantumIOService, State
a = 1 / sqrt(2)
experiment = QuantumExperiment(
target=State.dense([a, 0, 0, a]),
)
report = QuantumIOService().preflight(experiment).result()
print(report.mode) # preflight
print(report.preparation.method)
print(report.preparation.metrics.cx_count)
print(report.preparation_preflight.target_to_prepared_fidelity)
See examples/quantum_io_preflight.py for the complete preflight example.
Ideal simulation
With no explicit execution plan, run() uses the deployed Q-Alchemy sparse simulator
as an ideal acquisition source:
from math import sqrt
from q_alchemy import (
BasisMeasurement,
MeasurementPlan,
QuantumExperiment,
QuantumIOService,
State,
)
a = 1 / sqrt(2)
experiment = QuantumExperiment(
target=State.dense([a, 0, 0, a]),
measurement_plan=MeasurementPlan(
basis_measurements=(BasisMeasurement("q0-q1", (0, 1)),),
),
)
report = QuantumIOService().run(experiment, shots=256).result()
print(report.mode) # ideal-simulation
print(report.execution.source_kind) # ideal-simulator
print(report.execution.basis_distributions[0].probabilities)
See examples/quantum_io_service.py for a runnable version.
Noisy backend simulation
A noisy simulation can use the topology and calibration of a real IBM backend without
submitting a QPU job. The PineXQ runtime constructs AerSimulator.from_backend(...)
and reports source_kind="noisy-simulator". An ideal sparse reference can be run in
the same experiment so the report includes observable and distribution error metrics.
import os
from qiskit import QuantumCircuit
from q_alchemy import (
BasisMeasurement,
Circuit,
IBMQuantumCredentials,
MeasurementPlan,
PauliObservable,
QuantumExperiment,
QuantumIOService,
State,
noisy_backend_execution_plan,
)
credentials = IBMQuantumCredentials(token=os.environ["IBM_QUANTUM_TOKEN"])
service = QuantumIOService(ibm_credentials=credentials)
backends = service.backends(provider="ibm", min_num_qubits=2)
backend = min(
backends,
key=lambda item: (item.num_qubits, item.pending_jobs or 0, item.name),
)
evolution = QuantumCircuit(2)
evolution.h(0)
evolution.cx(0, 1)
experiment = QuantumExperiment(
target=State.dense([1, 0, 0, 0]),
evolution=Circuit.from_qiskit(evolution),
measurement_plan=MeasurementPlan(
training=(
PauliObservable.pauli("ZI", "ZI"),
PauliObservable.pauli("IZ", "IZ"),
),
validation=(
PauliObservable.pauli("XX", "XX"),
PauliObservable.pauli("ZZ", "ZZ"),
),
basis_measurements=(BasisMeasurement("computational", (0, 1)),),
),
)
plan = noisy_backend_execution_plan(
provider="ibm",
backend=backend.name,
shots=4096,
ideal_reference=True,
estimator=True,
execution_options={"transpile": True},
)
report = service.run(experiment, execution_plan=plan).result()
print(report.mode) # noisy-simulation
print(report.execution.source_kind) # noisy-simulator
print(report.observable_error)
print(report.distribution_errors)
print(report.held_out_verification_error)
No QPU execution occurs in this workflow. IBM credentials are used only to discover the
backend and read the device information needed to construct the calibrated simulator.
The explicit execution_options={"transpile": True} compiles the logical preparation,
evolution and measurement circuits to the selected device's native gates and
connectivity. Omit it only when the circuits already satisfy that Target.
examples/quantum_io_noisy_simulation.py demonstrates the full typed report, including
preparation diagnostics, complete-circuit metrics, ideal reference, noisy observations,
basis distributions, error metrics, state-estimation output, held-out verification, and
warnings.
Quantum hardware
When hardware is needed, backend discovery and execution follow the familiar service/backend pattern. The experiment object is exactly the same one used for simulation:
backend = service.backends(provider="ibm", min_num_qubits=2)[0]
report = backend.run(experiment, shots=1024).result()
print(report.mode) # qpu
print(report.execution.source_kind) # qpu
For advanced workflows, construct a typed ExecutionPlan explicitly. Runtime.resource
selects PineXQ runtime resources without exposing credentials in the experiment contract.
Compact sparse reconstruction
SDK 0.5.0 can submit preparation, reconstruction-observable generation,
acquisition, fitting, and sparse model export in one Quantum I/O job. Requires
deployed q-alchemy-quantum-io-pinexq 0.8.0 with Quantum I/O core 0.11.0.
from q_alchemy import (
ExecutionPlan, MeasurementPlan, QuantumExperiment, QuantumIOParams,
QuantumIOService, Runtime, State,
)
target = State.sparse(num_qubits=2, indices=[0, 3], amplitudes=[2**-.5, 2**-.5])
experiment = QuantumExperiment(
target,
measurement_plan=MeasurementPlan.qtucker_reconstruction(block_size=2, rank=1),
)
plan = ExecutionPlan(
preparation_method="iterative_tucker",
preparation_options={"basis_gates": ["u", "cx"], "max_fidelity_loss": 1e-3},
acquisition=Runtime.qalchemy_sparse(),
estimator=Runtime.qtucker(
block_size=2, rank=1, n_restarts=4,
fit_config={"epochs": 600, "learning_rate": .03, "stderr_floor": .01,
"seed": 123, "verbose": False},
),
estimation_output={"support": "target", "target_fidelity": True},
)
with QuantumIOService(QuantumIOParams(step_version="0.8.0")) as service:
report = service.run(experiment, plan).result()
print(report.format_summary())
report.estimate.sparse_state.save_npz("reconstructed.npz")
preparation_method accepts auto (default), hierarchical_tucker, and
iterative_tucker. QTucker/core own initialization and logical basis
compilation; the SDK forwards the request. Inspect preparation found,
claimed fidelity loss, and preflight diagnostics when using options such as
fallback=False.
qtucker_reconstruction uses the complete native training family. Defaults
allow training-only fitting; validation=True requests an independent disjoint
validation family (optionally validation_edges and validation_paulis).
No held-out RMSE is claimed when validation is absent.
Direct Runtime.qalchemy_sparse() acquisition evaluates expectations without
sampling, uses zero pruning by default, reuses matching preflight/reference
simulation, and reports capping/pruning/exactness. Its configurable
stderr_floor (default 1e-12) is a numerical fitting floor, not sampling
uncertainty or a bound on approximation bias. The plan's positive shots
value is recorded but unused for sampling.
Sparse export queries the implicit fitted QTucker model without dense
materialization. estimation_output also accepts explicit
support="indices", indices=[...], threshold (default 0), and
max_entries (default None). It does not renormalize the subset:
sparse_state.norm_squared and metadata support_probability /
returned_probability reveal retained mass. Zero survivors are valid.
target_fidelity uses the entire original input target support before
filtering/capping, so export options cannot inflate it. It compares the input
target with a pure fitted surrogate, rather than certifying a noisy mixed state
or an evolved target. Existing reference-to-estimated surrogate metrics remain
separate. Both wire JSON and NPZ preserve basis indices above 64 bits.
Leave materialize_statevector_max_qubits unset to keep fitting output implicit.
Molecule/CISD construction stays local. Supply its sparse amplitudes as the target; the hosted workflow replaces manual QASM/observable/fit handoffs. The SDK requires no private Quantum I/O or QTucker package locally.
Choosing classical or quantum execution with feasibility
The feasibility API is a separate hosted service. The SDK is only its remote client: it
uploads a QuantumExperiment and a portable FeasibilityRequest, submits one PineXQ
assess_feasibility Job, and returns the final FeasibilityReport. The classical-first
decision loop, execution of the configured quantum target (a simulator or a physical
QPU), and evidence collection happen on the service side rather than
in the SDK process.
from q_alchemy import (
FeasibilityRequest,
FeasibilityService,
QuantumExperiment,
SolutionCriteria,
State,
)
experiment = QuantumExperiment(target=State.dense([1, 0]))
request = FeasibilityRequest(
criteria=SolutionCriteria.common(max_observable_rmse=0.01),
)
report = FeasibilityService().analyze(experiment, request).result()
print(report.format_summary())
FeasibilityReport.format_summary() mirrors the canonical human-readable formatter in
q-alchemy-feasibility, including classical and quantum status, resource/quality
criteria, backend/model evidence, recommendation, and next-evidence requests.
The SDK formatter is aligned with Feasibility 0.6.50, the PineXQ adapter 0.2.11, and
Quantum I/O 0.11.0. Serialized core reports and their canonical summaries are kept
as regression fixtures so later core changes can be checked without installing
the server runtime in the SDK environment.
Classical Status describes resource feasibility; Quality is a separate assessment.
Quality based on quantum-io:exact-reference is explicitly labeled as relative to
the simulated circuit reference, not necessarily the intended target state.
When Quantum I/O reports a contradicted preparation estimate, the summary displays
its warning. The raw evidence and report.warnings remain available as well.
The embedded computation's preparation_preflight.claim_contradicted is tri-state:
True means a certified comparison found more preparation loss than the initializer
estimated; False means the comparison passed; None (including absent legacy fields)
means no certified comparison is available. preparation.claimed_fidelity_loss is
the initializer's estimate, target_to_prepared_fidelity is the measured fidelity,
and preparation_approximation_infidelity is the measured loss. Quantum I/O owns
the exactness and comparison-tolerance checks. The SDK preserves these fields;
it does not simulate circuits, recompute the verdict, or infer target-relative
energy error from a fidelity discrepancy.
Execution settings use EvidenceCollectionConfig(execution_options=...), separate
from backend_options. The default is {"transpile": True}. For example:
from q_alchemy import EvidenceCollectionConfig
evidence = EvidenceCollectionConfig(
provider="aer",
execution_options={
"transpile": True,
"transpile_options": {"optimization_level": 3},
"estimator_options": {"seed_simulator": 7},
},
)
Use execution_options={"transpile": False} only for circuits already compatible
with the selected backend. Legacy execution keys inside backend_options are
moved into execution_options; conflicting values are rejected locally.
Use estimator_options for Pauli-observable execution. backend_run_options
(and its legacy alias run_options) applies to basis-measurement/count execution.
Retired resource-sweep and attribution controls are no longer constructor
parameters. Their keys in old request JSON are ignored, matching the service.
classical_first=False is rejected locally; use quantum_execution=COMPARE
when both classical and quantum paths are wanted.
To check formatter parity after a core upgrade, run
tests/generate_feasibility_fixtures.py --check using a Python environment with
q-alchemy-feasibility installed and without the SDK on its import path. If the
core intentionally changes, rerun without --check, review the fixture diff,
and run pytest tests/test_feasibility_contract_alignment.py in the SDK environment.
The fixture generator only analyzes synthetic evidence; it submits no PineXQ jobs.
Full-circuit compression is enabled by default by the feasibility service. The typed SDK contract exposes the same control when callers need to disable it or pass compressor options:
from q_alchemy import CircuitCompressionConfig, EvidenceCollectionConfig
evidence = EvidenceCollectionConfig(
circuit_compression=CircuitCompressionConfig(
enabled=True,
options={"optimization_level": 2},
)
)
When a criterion requires QTucker state-estimation evidence and the experiment does not
supply an explicit estimator training plan, the service can generate the reconstruction
family automatically. qtucker_config controls the fitted Tucker model, while
qtucker_observable_config controls observable generation and independent validation:
evidence = EvidenceCollectionConfig(
qtucker_config={
"blocks": [[0, 1], [2, 3]],
"ranks": [2, 2],
},
qtucker_observable_config={
"within_block_mode": "all_paulis",
"cross_block_mode": None,
"extra_edges": [[0, 2]],
"validation_edges": [[1, 3]],
"validation_paulis": "XYZ",
},
)
Every observable returned by QTucker's native reconstruction-observable generator remains
in the training set. Validation is generated separately and is never carved out of that
training family. Direct application diagnostics belong in MeasurementPlan.observables;
training and validation should be used only for an explicit estimator plan.
The default QuantumExecutionPolicy.WHEN_NEEDED stops once the classical path is already
sufficient. Estimator-only criteria do not force quantum execution merely so they can be
evaluated. Use QuantumExecutionPolicy.COMPARE when the quantum path should run even when
classical execution is feasible. The SDK only serializes this policy; the feasibility
service owns the routing decision.
For an opt-in end-to-end check of the SDK -> PineXQ -> feasibility transport, the repository includes a live integration test that submits two small Bell-state jobs: one with compression explicitly disabled and one using the default enabled compression path. Ordinary test runs skip it. Run it explicitly with:
Q_ALCHEMY_RUN_LIVE_FEASIBILITY=1 \
Q_ALCHEMY_API_KEY=... \
pytest -v tests/test_feasibility_live_integration.py
Set Q_ALCHEMY_FEASIBILITY_STEP_VERSION as well when a specific published ProcessingStep
version should be tested; otherwise the SDK selects the latest available version. The live
test does not require IBM credentials because the two-qubit workload is intentionally
classical-first.
The SDK preserves the complete portable report returned by the service.
report.computation_result is the feasibility computation-result envelope: it records
which compute path ran, execution mode/source, whether the requested criteria were met,
and whether the result was selected by the recommendation. If feasibility ran a
classical, simulated, or QPU computation, report.computation_experiment_report
deserializes the embedded Quantum I/O ExperimentReport; the SDK does not rerun that
computation. The nested report uses the same formatter as a directly submitted Quantum
I/O experiment:
computation = report.computation_experiment_report
if computation is not None:
print(computation.format_summary())
Quantum circuits are not returned by default because generated state-preparation circuits can
be large. Request the logical experiment circuit (P + U) explicitly on the SDK call:
report = FeasibilityService().analyze(
experiment,
request,
include_quantum_circuits=True,
).result()
circuit = report.quantum_circuit
if circuit is not None:
print(circuit.draw(output="text"))
The service returns the selected logical circuit as portable QASM 3. The SDK reconstructs the native Qiskit circuit and delegates parsing/rendering to Qiskit; it does not implement a second circuit parser or drawer. Internal measurement and backend-transpiled circuits are not returned.
Feasibility core also returns the renderer-neutral experiment graph that describes which
steps ran, were skipped, were unavailable, or were not needed. The SDK never reconstructs
that graph from statuses. With the optional visualization extra installed,
report.experiment_diagram deserializes the service representation into the shared
q-alchemy-visualization ExperimentDiagram type:
diagram = report.experiment_diagram
if diagram is not None:
print(diagram.draw(output="text"))
The SDK similarly exposes a requested logical circuit as a native Qiskit
QuantumCircuit through report.quantum_circuit. Raw diagram and circuit payloads are
transport details retained by report.to_dict() rather than separate user-facing APIs.
The visualization package is optional so the public SDK remains installable without the
private Q-Alchemy package feed. Graphical Matplotlib output additionally requires the
mpl extra of q-alchemy-visualization.
IBM credentials are optional at submission time because the server may prove that the problem is classically feasible and stop before any quantum-backend evidence is needed. When credentials are supplied, the SDK transports them as separate Secret WorkData; they are never serialized into the experiment or feasibility request.
Noisy simulation is not an automatic pre-QPU stage. When simulation is the desired
quantum target, select a simulator explicitly in EvidenceCollectionConfig, for example
provider="aer", backend="aer_simulator", optionally with portable backend_options.
When IBM is selected, feasibility proceeds from static target checks to physical-QPU
execution when the policy requires quantum evidence.
Use QuantumIOService when you explicitly want to execute a particular Quantum I/O plan.
Use FeasibilityService when the question is which compute path is sufficient under the
stated solution criteria.
A complete hosted example is available in examples/feasibility_service.py. It submits a
small Bell-state workload, prints the canonical feasibility summary and embedded Quantum I/O
report, draws the returned logical experiment circuit with Qiskit when available, and renders
the server-provided experiment diagram when the optional visualization extra is installed.
For a notebook-oriented introduction, see
examples/feasibility_h2_dynamics.ipynb. It uses
the published two-qubit parity-reduced H2 electronic Hamiltonian at 0.735 Angstrom in STO-3G,
starts from the Hartree-Fock molecular reference state, applies one first-order Trotter step,
and runs feasibility in COMPARE mode against an Aer target. The final cells display the
canonical format_summary(), the server-provided experiment diagram, and the returned logical
P + U circuit.
A quantum-chemistry example is available in
examples/feasibility_quantum_chemistry.py.
It uses an illustrative 12-spin-orbital correlated active-space workload and selects the
quantum target from the environment:
- with
IBM_QUANTUM_TOKEN, it uses a physical IBM Quantum device; - with both
IBM_QUANTUM_TOKENandIBM_QUANTUM_BACKEND, it uses that backend; - with an IBM token but no backend name, it requests the least-busy suitable backend;
- without an IBM token, it uses
provider="aer",backend="aer_simulator"with an illustrative portable noise model.
IBM_QUANTUM_INSTANCE is optional and is forwarded when present. The example uses
QuantumExecutionPolicy.COMPARE deliberately so the selected quantum target is actually
executed even if the small classical reference is feasible. Normal feasibility requests can
keep the default when-needed policy. Run it with:
# Required for the hosted Q-Alchemy service.
export Q_ALCHEMY_API_KEY=...
# Optional: physical IBM execution. Omit IBM_QUANTUM_TOKEN for noisy Aer.
export IBM_QUANTUM_TOKEN=...
export IBM_QUANTUM_BACKEND=ibm_example # optional; otherwise least busy
export IBM_QUANTUM_INSTANCE=... # optional
python examples/feasibility_quantum_chemistry.py
Verifying preparation circuits with the sparse simulator
Simulator jobs and their WorkData are preserved on execution, timeout or result
download failure for diagnosis. After a successful download, remove_data=True
requests cleanup; a cleanup failure logs a warning and still returns the result.
Q-Alchemy also hosts a sparse state-vector simulator so you can verify that a preparation circuit really produces your target state. The typical loop is prepare → simulate → verify:
from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector, state_fidelity
from q_alchemy import q_alchemy_as_qasm, SparseSimulator
qasm = q_alchemy_as_qasm(target, max_fidelity_loss=0.0) # prepare
prep = QuantumCircuit.from_qasm_str(qasm)
sim = SparseSimulator() # verify
sv = sim.sparse_statevector(prep)
print("fidelity:", state_fidelity(Statevector(sv.to_dense()), Statevector(target)))
SparseSimulator also exposes .counts(...) and .tomography(...), and it
auto-selects your resource tier from your plan (Standard/Medium for everyone,
XLarge for enterprise).
⚠️ The free plan is strongly limited — state preparation is capped (currently ~12 qubits, batches up to 100) and runs on the Medium simulator tier. Enterprise plans get larger circuits and the XLarge tier. See the limits table in the guide.
📖 Full guide: docs/initialize-and-verify.md · 🧪 Runnable notebook: examples/simulator_vs_initializer.ipynb
Using the simulator as a Qiskit backend
The hosted simulator is also exposed through Qiskit's standard BackendV2
interface — the same way IBM backends are used — so it drops straight into the
Qiskit ecosystem (transpiler, Sampler, etc.):
from qiskit import QuantumCircuit, transpile
from q_alchemy import QAlchemyBackend
backend = QAlchemyBackend() # reads Q_ALCHEMY_API_KEY from env
qc = QuantumCircuit(2, 2)
qc.h(0); qc.cx(0, 1); qc.measure([0, 1], [0, 1])
job = backend.run(transpile(qc, backend), shots=4096)
print(job.result().get_counts()) # {'00': ~2048, '11': ~2048}
backend.run(...) accepts Aer-style options (shots, seed_simulator,
save_sparse_statevector, save_statevector, sparse_index_format, ...), and
there's an IBM-style QAlchemyProvider().get_backend() for discovery. The
resource tier (Medium vs the enterprise XLarge) is selected automatically from
your plan — see the guide.
save_statevector vs save_sparse_statevector
Both ask for the state the circuit prepares, and both are served by the same remote call — the difference is what you get back, and how big it is.
save_statevector is the option you already know from Aer. It hands you the
familiar dense 2**n vector, so Statevector, state_fidelity and friends all
work unchanged:
qc = QuantumCircuit(2)
qc.h(0); qc.cx(0, 1) # no measurement: this is a state export
result = backend.run(qc, save_statevector=True).result()
print(result.data(0)["statevector"])
# [0.70710678+0.j 0. +0.j 0. +0.j 0.70710678+0.j] -> length 2**n
Dense exports default to max_dense_qubits=26 (up to 1 GiB of amplitudes).
Larger requests fail before submission. SparseStatevectorResult.to_dense()
enforces the same default; pass a larger max_dense_qubits explicitly only when
sufficient memory is available. Backend run() options apply only to that job;
use backend.set_options() to change defaults for future jobs.
save_sparse_statevector is Q-Alchemy's own, and it is the one that scales. It
returns only the amplitudes the circuit actually populates, so nothing of
size 2**n is ever built:
result = backend.run(qc, save_sparse_statevector=True).result()
print(result.data(0)["sparse_statevector"])
# {'format': 'sparse_statevector_v1', 'num_qubits': 2, 'nnz': 2,
# 'index_format': 'hex', 'index_convention': 'little_endian',
# 'indices': ['0x0', '0x3'],
# 'amplitudes': [[0.7071067811865476, 0.0], [0.7071067811865476, 0.0]]}
Note the amplitudes are [real, imag] pairs: this entry is the simulator's own
JSON payload, passed through untouched. If you would rather have parsed
complex values — plus to_coo(), to_arrow() and amplitudes_dict() — go
through the client instead of the backend, which returns a typed
SparseStatevectorResult:
from q_alchemy import SparseSimulator
sv = SparseSimulator().sparse_statevector(qc)
print(sv.amplitudes) # [(0.7071067811865476+0j), (0.7071067811865476+0j)]
print(sv.amplitudes_dict()) # {'0x0': (0.707...+0j), '0x3': (0.707...+0j)}
indices and amplitudes line up element by element, and nnz is how many
were stored — two here, not four. That gap is the whole point, and it widens
fast: a 40-qubit state-preparation circuit populating a thousand basis states
returns a thousand amplitudes, while the dense form would need 2**40 complex
numbers, or roughly 17 TB. A dense export is impossible in that regime; a
sparse one is a few hundred kilobytes. Standard Qiskit backends offer no
equivalent.
That only helps if the state is sparse, and nothing forces it to be. By
default the simulator keeps every amplitude above 1e-10, so the result is
exact, but a circuit that populates most of its basis states needs as much
memory as the dense form and can exhaust the simulator. Pass max_nnz=N (to
backend.run or SparseSimulator.sparse_statevector) to cap it: after every
gate only the N largest amplitudes are kept and renormalised. The result is
then approximate, and nothing in it marks that it was truncated.
Ask for both together and you pay for one simulation:
result = backend.run(qc, save_statevector=True, save_sparse_statevector=True).result()
dense = result.data(0)["statevector"] # 2**n numpy array
sparse = result.data(0)["sparse_statevector"] # nnz entries
Use the dense form for small circuits you want to compare against Qiskit
directly; use the sparse form whenever 2**n would not fit — which is exactly
the regime this simulator exists for. Reach for sparse_index_format
(hex/bitstring) and sparse_index_convention
(little_endian/big_endian) to control how the indices are written, and see
the guide for feeding a sparse result straight
back into the loader via to_coo().
From PennyLane
Because it's a standard Qiskit backend, you can use it as a PennyLane device via the PennyLane–Qiskit plugin (whose original version was written by this SDK's author, Carsten Blank):
import pennylane as qml
from q_alchemy import QAlchemyBackend
dev = qml.device("qiskit.remote", wires=2, backend=QAlchemyBackend(), shots=4096)
@qml.qnode(dev)
def circuit():
qml.Hadamard(0)
qml.CNOT([0, 1])
return qml.counts()
Contributions
We welcome contributions - simply fork the repository of this plugin, and then make a pull request containing your contribution. All contributers to this plugin will be listed as authors on the releases.
We also encourage bug reports, suggestions for new features and enhancements!
Authors
Carsten Blank
License
The q-alchemy-sdk-py is free and open source, released under the Apache License, Version 2.0.
Optional Quantum I/O circuit compression
Enable full logical P+U compression through the execution plan. The public SDK transports these settings and requires no private compressor or Quantum I/O runtime installation:
from dataclasses import replace
from q_alchemy.quantum_io import CircuitCompressionConfig, local_simulator_execution_plan
plan = replace(local_simulator_execution_plan(),
circuit_compression=CircuitCompressionConfig(enabled=True))
# report = service.run(experiment, plan).result()
# print(report.circuit_compression)
# print(report.format_summary())
Quantum I/O compression defaults to disabled; Feasibility keeps its enabled default. Reports include exactness semantics and input/output metrics, and older reports remain readable. Preparation claim diagnostics still describe original P.
Quantum I/O validation and reporting
Execution-plan helpers require positive integer shots; booleans and fractional
values are rejected instead of truncated. The qiskit-aer provider alias, like
aer, is local simulation and does not require remote-provider credentials.
State serialization rejects zero and nonfinite amplitudes before upload, during
the existing serialization pass. Wire schema versions must be integers, excluding
booleans. Training and held-out Pauli operators must be distinct even when their
labels differ.
Quantum I/O and Feasibility compression summaries use baseline_metrics when
available, comparing the compiled baseline with the compressed circuit. Original
logical input_metrics remain available; reports from older deployments still use
those metrics when a baseline is absent.
The unused initialization helpers hash_state_vector and encode_statevector
remain callable for compatibility but emit DeprecationWarning. Use the regular
SDK state-upload/initialization API, which manages serialization and identity.
Feasibility request validation
Feasibility request schema versions must be integer 1; boolean options and
criterion required must be actual booleans, and classical resource counts must be
non-negative integers. Strings such as "false" and fractional counts are rejected
instead of silently changing the request. Zero remains a valid explicit memory
budget. These checks are local and require no private Feasibility dependency.
The SDK displays the returned resource and quality conclusions separately.
Initialization transport and failure recovery
Initialization inline transport is selected by serialized/base64 payload size (at most 1 MiB), not qubit count; larger payloads use WorkData. Recoverable wait/download/validation failures preserve jobs and data. Exceptions expose initialization_job and, when available, initialization_job_url. With a caller-owned open client, retry run_job on that job; after an SDK-owned client closes, reconstruct Job.from_url using a fresh authenticated client and the saved URL. Supply expected_states for batches. Validated results are cached for cleanup retries; cleanup errors do not discard a successful result. Compression reports validate metrics and accepted-region fields before cleanup. The SDK continues to use public Qiskit.
Optional logical circuit return
Request the logical experiment circuit with
QuantumIOService.run(experiment, plan, include_quantum_circuits=True).
The default is False, with no circuit export cost. The service exports the
existing logical preparation-plus-evolution circuit after compression, as
report.quantum_circuit in the portable report's inner JSON object. Its envelope
uses kind="quantum-circuit", schema_version=1,
role="logical-experiment-circuit", format="qasm3", and a qasm program.
It preserves global phase and excludes physical routing and measurement circuits.
No preparation, compression or simulation is repeated to export the circuit.
In the SDK, report.quantum_circuit reconstructs a Qiskit QuantumCircuit on
access; install the SDK's Qiskit extra. Use
print(report.quantum_circuit.draw(output="text")) to print it. The downloaded
payload remains available after job cleanup. Reports without a circuit return
None, and ordinary report parsing does not require Qiskit.
PineXQ consumes service_options.include_quantum_circuits from the execution-plan
input before parsing the core plan. This is a service option, not an
ExecutionPlan field. Direct core callers can use
run_quantum_experiment(..., include_quantum_circuits=True) for portable export;
run_quantum_experiment_report(..., include_quantum_circuits=True) continues to
retain runtime circuit objects locally without exporting them automatically.
Metadata
Release files for q-alchemy-sdk-py 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| q_alchemy_sdk_py-0.4.0.tar.gz | 182.4 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| q_alchemy_sdk_py-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 287.3 kB
Release files / q_alchemy_sdk_py-0.4.0.tar.gz
| Download URL | q_alchemy_sdk_py-0.4.0.tar.gz |
|---|---|
| Size | 182.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c55bab651c537a513492ed2f7ab9a8d384946baa62f555b03de50f3396ff09ad
|
|
BLAKE2b-256 checksum How to use checksums |
04519809b787ae71434ad5f92c261eafa498e9e452f1650bd191ce730bc057c6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.
Transparency logRelease files / q_alchemy_sdk_py-0.4.0-py3-none-any.whl
| Download URL | q_alchemy_sdk_py-0.4.0-py3-none-any.whl |
|---|---|
| Size | 104.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7f749d7d4d1b8f1a1fab48d285aa4d7d88ee50f228abc7bf8aefdc009c100305
|
|
BLAKE2b-256 checksum How to use checksums |
2ad2878d7b2bb52cd2c0bc45d14d16bd24193ee7a46bf65696e076842e0d6fd3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.
Transparency logSecurity for q-alchemy-sdk-py 0.4.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for q_alchemy_sdk_py-0.4.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.