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>