3.3 Magnetization Square by Classical Shadow#
Basic Usage#
a. Import the instances#
from qurry import ShadowUnveil
experiment_shadow = ShadowUnveil()
b. Preparing quantum circuit#
from qurry.recipe import Cat, TrivialParamagnet
circuits_dict = {
"trivialPM_2": TrivialParamagnet(2),
"trivialPM_4": TrivialParamagnet(4),
"trivialPM_6": TrivialParamagnet(6),
"trivialPM_8": TrivialParamagnet(8),
"cat_2": Cat(2),
"cat_4": Cat(4),
"cat_6": Cat(6),
"cat_8": Cat(8),
}
print("| trivial paramagnet and cat in 4 qubits:")
print(circuits_dict["trivialPM_4"])
print(circuits_dict["cat_4"])
| trivial paramagnet and cat in 4 qubits:
┌───┐
q_0: ┤ H ├
├───┤
q_1: ┤ H ├
├───┤
q_2: ┤ H ├
├───┤
q_3: ┤ H ├
└───┘
┌───┐
q_0: ┤ H ├──■────────────
└───┘┌─┴─┐
q_1: ─────┤ X ├──■───────
└───┘┌─┴─┐
q_2: ──────────┤ X ├──■──
└───┘┌─┴─┐
q_3: ───────────────┤ X ├
└───┘
c. Execute the circuit#
i. Preparing the operators for post-processing#
import numpy as np
import functools as ft
from itertools import permutations
from qiskit import QuantumCircuit
from qiskit.circuit.library import ZGate, IGate
def operator_preparing(
circuit: QuantumCircuit,
) -> list[np.ndarray[tuple[int, int], np.dtype[np.complex128]]]:
"""Prepare the operator for the circuit.
Args:
circuit (QuantumCircuit): The quantum circuit for which the operator is prepared.
Returns:
list[np.ndarray[tuple[int, int], np.dtype[np.complex128]]]:
A list of numpy arrays representing the operator for each pair of qubits.
"""
z_gate_matrix = ZGate().to_matrix()
i_gate_matrix = IGate().to_matrix()
num_qubits = circuit.num_qubits
return [
ft.reduce(
np.kron,
(
z_gate_matrix.copy() if i in tgt else i_gate_matrix.copy()
for i in range(num_qubits)
),
)
for tgt in permutations(range(num_qubits), 2)
] # type: ignore[return]
operators_for_magnet_sq = operator_preparing(circuits_dict["cat_4"])
print(
"| The number of operators for Magnetization Square:", len(operators_for_magnet_sq)
)
| The number of operators for Magnetization Square: 12
ii. Find a Proper Number of Classical Snapshot for Epsilon Upperbound#
from qurry.process.classical_shadow.expectation_process import (
worst_accuracy_predict_epsilon_calc,
)
num_classical_snapshot_candinates = []
for i in range(1, 6):
num_classical_snapshot_candinates.append(i * 100)
print(
"| The candidate number for classical snapshot:",
num_classical_snapshot_candinates[-1],
)
epsilon_upperbound, shadow_norm_upperbound = worst_accuracy_predict_epsilon_calc(
num_classical_snapshot_candinates[-1], operators_for_magnet_sq
)
print(
"| The epsilon upper bound for the worst accuracy prediction is :\n",
epsilon_upperbound,
)
| The candidate number for classical snapshot: 100
| The epsilon upper bound for the worst accuracy prediction is :
9.329523031752482
| The candidate number for classical snapshot: 200
| The epsilon upper bound for the worst accuracy prediction is :
6.596969000988257
| The candidate number for classical snapshot: 300
| The epsilon upper bound for the worst accuracy prediction is :
5.386402633793108
| The candidate number for classical snapshot: 400
| The epsilon upper bound for the worst accuracy prediction is :
4.664761515876241
| The candidate number for classical snapshot: 500
| The epsilon upper bound for the worst accuracy prediction is :
4.172289539329696
We use 300
as the number of classical snapshot.
iii. Execute experiment and run post-processing#
exp1 = experiment_shadow.measure(circuits_dict["cat_4"], times=300, shots=4096)
exp1
'4763281d-83b9-448c-96c4-d56ca9f5d7e1'
Use the operators we prepare for post-processing.
report01 = experiment_shadow.exps[exp1].analyze(
range(4),
operators_for_magnet_sq,
)
report01
<SUAnalysis(
serial=0,
SUAnalysisInput(shots=4096, num_qubits=4, selected_qubits=[0, 1, 2, 3], registers_mapping={0: 0, 1: 1, 2: 2, 3: 3}, bitstring_mapping={0: 0, 1: 1, 2: 2, 3: 3}, unitary_located=[0, 1, 2, 3]),
SUAnalysisContent(purity=0.6877098447592761, entropy=0.5401280965624338, and others)),
unused_args_num=3
)>
main01, side_product_01 = report01.export()
print(main01.keys())
print(side_product_01.keys())
dict_keys(['classical_registers_actually', 'taking_time', 'mean_of_rho', 'purity', 'entropy', 'estimate_of_given_operators', 'accuracy_prob_comp_delta', 'num_of_estimators_k', 'accuracy_predict_epsilon', 'maximum_shadow_norm', 'input', 'header'])
dict_keys(['average_classical_snapshots_rho', 'corresponding_rhos'])
Now, you can find we have some values in estimate_of_given_operators
, we will need this to calculate magnetization square.
print("| estimate_of_given_operators:")
print(main01["estimate_of_given_operators"])
print("|" + "-" * 50)
for k in [
"accuracy_prob_comp_delta",
"num_of_estimators_k",
"accuracy_predict_epsilon",
"maximum_shadow_norm",
]:
print(f"| {k}:", main01[k])
| estimate_of_given_operators:
[np.complex128(0.5+0j), np.complex128(1+0j), np.complex128(0.75+0j), np.complex128(0.5+0j), np.complex128(0.5+0j), np.complex128(1+0j), np.complex128(1+0j), np.complex128(0.5+0j), np.complex128(1+0j), np.complex128(0.75+0j), np.complex128(1+0j), np.complex128(1+0j)]
|--------------------------------------------------
| accuracy_prob_comp_delta: 0.008051103069660285
| num_of_estimators_k: 16
| accuracy_predict_epsilon: 5.386402633793108
| maximum_shadow_norm: nan
With following functions:
def unveil_magnetization_square(
estimate_of_given_operators: list[np.complex128],
num_qubits: int,
) -> np.float64:
"""Processing Classical Shadows post-processing for MagnetSquare.
Args:
estimate_of_given_operators (list[np.complex128]): The estimates of the given operators.
num_qubits (int): The number of qubits in the circuit.
Returns:
np.float64: The unveiled magnet square value.
"""
return np.complex128(sum(estimate_of_given_operators) + num_qubits).real / (
num_qubits**2
)
unveil_magnetization_square_01 = unveil_magnetization_square(
main01["estimate_of_given_operators"],
main01["input"]["num_qubits"],
)
print(
"| The magnetization square value is:",
unveil_magnetization_square_01,
)
| The magnetization square value is: 0.84375
All main quantities
for key, value in main01.items():
if key in ["mean_of_rho"]:
continue
print(f"| {key} : {value}")
| classical_registers_actually : [3, 2, 1, 0]
| taking_time : 0.1288447380065918
| purity : 0.6877098447592761
| entropy : 0.5401280965624338
| estimate_of_given_operators : [np.complex128(0.5+0j), np.complex128(1+0j), np.complex128(0.75+0j), np.complex128(0.5+0j), np.complex128(0.5+0j), np.complex128(1+0j), np.complex128(1+0j), np.complex128(0.5+0j), np.complex128(1+0j), np.complex128(0.75+0j), np.complex128(1+0j), np.complex128(1+0j)]
| accuracy_prob_comp_delta : 0.008051103069660285
| num_of_estimators_k : 16
| accuracy_predict_epsilon : 5.386402633793108
| maximum_shadow_norm : nan
| input : {'shots': 4096, 'num_qubits': 4, 'selected_qubits': [0, 1, 2, 3], 'registers_mapping': {0: 0, 1: 1, 2: 2, 3: 3}, 'bitstring_mapping': {0: 0, 1: 1, 2: 2, 3: 3}, 'unitary_located': [0, 1, 2, 3]}
| header : {'serial': 0, 'datetime': '2025-06-26 11:45:15', 'log': {}}
print(main01["mean_of_rho"].shape)
(16, 16)
d. Export them after all#
exp1_id, exp1_files_info = experiment_shadow.exps[exp1].write(
save_location=".", # where to save files
)
exp1_files_info
{'folder': 'experiment.N_U_300.qurshady_entropy.001',
'qurryinfo': 'experiment.N_U_300.qurshady_entropy.001/qurryinfo.json',
'args': 'experiment.N_U_300.qurshady_entropy.001/args/experiment.N_U_300.qurshady_entropy.001.id=4763281d-83b9-448c-96c4-d56ca9f5d7e1.args.json',
'advent': 'experiment.N_U_300.qurshady_entropy.001/advent/experiment.N_U_300.qurshady_entropy.001.id=4763281d-83b9-448c-96c4-d56ca9f5d7e1.advent.json',
'legacy': 'experiment.N_U_300.qurshady_entropy.001/legacy/experiment.N_U_300.qurshady_entropy.001.id=4763281d-83b9-448c-96c4-d56ca9f5d7e1.legacy.json',
'tales.random_unitary_ids': 'experiment.N_U_300.qurshady_entropy.001/tales/experiment.N_U_300.qurshady_entropy.001.id=4763281d-83b9-448c-96c4-d56ca9f5d7e1.random_unitary_ids.json',
'reports': 'experiment.N_U_300.qurshady_entropy.001/reports/experiment.N_U_300.qurshady_entropy.001.id=4763281d-83b9-448c-96c4-d56ca9f5d7e1.reports.json',
'reports.tales.average_classical_snapshots_rho': 'experiment.N_U_300.qurshady_entropy.001/tales/experiment.N_U_300.qurshady_entropy.001.id=4763281d-83b9-448c-96c4-d56ca9f5d7e1.average_classical_snapshots_rho.reports.json',
'reports.tales.corresponding_rhos': 'experiment.N_U_300.qurshady_entropy.001/tales/experiment.N_U_300.qurshady_entropy.001.id=4763281d-83b9-448c-96c4-d56ca9f5d7e1.corresponding_rhos.reports.json'}
Post-Process Availablities and Version Info#
from qurry.process import AVAIBILITY_STATESHEET
AVAIBILITY_STATESHEET
| Qurrium version: 0.13.0
---------------------------------------------------------------------------
### Qurrium Post-Processing
- Backend Availability ................... Python Cython Rust JAX
- randomized_measure
- entangled_entropy.entropy_core_2 ....... Yes Depr. Yes No
- entangle_entropy.purity_cell_2 ......... Yes Depr. Yes No
- entangled_entropy_v1.entropy_core ...... Yes Depr. Yes No
- entangle_entropy_v1.purity_cell ........ Yes Depr. Yes No
- wavefunction_overlap.echo_core_2 ....... Yes Depr. Yes No
- wavefunction_overlap.echo_cell_2 ....... Yes Depr. Yes No
- wavefunction_overlap_v1.echo_core ...... Yes Depr. Yes No
- wavefunction_overlap_v1.echo_cell ...... Yes Depr. Yes No
- hadamard_test
- purity_echo_core ....................... Yes No Yes No
- magnet_square
- magnsq_core ............................ Yes No Yes No
- string_operator
- strop_core ............................. Yes No Yes No
- classical_shadow
- rho_m_core ............................. Yes No No Yes
- utils
- randomized ............................. Yes Depr. Yes No
- counts_process ......................... Yes No Yes No
- bit_slice .............................. Yes No Yes No
- dummy .................................. Yes No Yes No
- test ................................... Yes No Yes No
---------------------------------------------------------------------------
+ Yes ...... Working normally.
+ Error .... Exception occurred.
+ No ....... Not supported.
+ Depr. .... Deprecated.
---------------------------------------------------------------------------
by <Hoshi>