1.2 Entanglement Entropy by Randomized Measurement


Multiple Experiments

Consider a scenario, you have multiple circuits that you want to run at once.

Call .measure() one by one will be inefficient, no to mention that you also need to call .anlyze() for their post-processing.

Here we provide a more efficient way solve this problem, where the true power of Qurrium as experiment manage toolkit.

a. Import the instances

from qurry import EntropyMeasure

experiment_randomized = EntropyMeasure()
# It's default method. EntropyMeasure(method='randomized') also works

b. Preparing quantum circuit

Prepare and add circuits to the .wave for later usage.

from qiskit import QuantumCircuit
from qurry.recipe import TrivialParamagnet, GHZ


def make_neel_circuit(n):
    qc = QuantumCircuit(n)
    for i in range(0, n, 2):
        qc.x(i)
    return qc


for i in range(2, 13, 2):
    experiment_randomized.add(TrivialParamagnet(i), f"trivial_paramagnet_{i}")
    experiment_randomized.add(GHZ(i), f"ghz_{i}")
    experiment_randomized.add(make_neel_circuit(i), f"neel_{i}")

experiment_randomized.waves
WaveContainer({
  'trivial_paramagnet_2': <qurry.recipe.simple.paramagnet.TrivialParamagnet object at 0x715640108290>,
  'ghz_2': <qurry.recipe.simple.cat.GHZ object at 0x71564010a270>,
  'neel_2': <qiskit.circuit.quantumcircuit.QuantumCircuit object at 0x715738054f20>,
  'trivial_paramagnet_4': <qurry.recipe.simple.paramagnet.TrivialParamagnet object at 0x7156401351f0>,
  'ghz_4': <qurry.recipe.simple.cat.GHZ object at 0x715640109f70>,
  'neel_4': <qiskit.circuit.quantumcircuit.QuantumCircuit object at 0x71564024a4e0>,
  'trivial_paramagnet_6': <qurry.recipe.simple.paramagnet.TrivialParamagnet object at 0x7156401354f0>,
  'ghz_6': <qurry.recipe.simple.cat.GHZ object at 0x715640135580>,
  'neel_6': <qiskit.circuit.quantumcircuit.QuantumCircuit object at 0x715640135790>,
  'trivial_paramagnet_8': <qurry.recipe.simple.paramagnet.TrivialParamagnet object at 0x715640135970>,
  'ghz_8': <qurry.recipe.simple.cat.GHZ object at 0x715640135a00>,
  'neel_8': <qiskit.circuit.quantumcircuit.QuantumCircuit object at 0x7156402e9e80>,
  'trivial_paramagnet_10': <qurry.recipe.simple.paramagnet.TrivialParamagnet object at 0x71571453e960>,
  'ghz_10': <qurry.recipe.simple.cat.GHZ object at 0x715640135f10>,
  'neel_10': <qiskit.circuit.quantumcircuit.QuantumCircuit object at 0x715640136240>,
  'trivial_paramagnet_12': <qurry.recipe.simple.paramagnet.TrivialParamagnet object at 0x71564010b470>,
  'ghz_12': <qurry.recipe.simple.cat.GHZ object at 0x715640136600>,
  'neel_12': <qiskit.circuit.quantumcircuit.QuantumCircuit object at 0x7156401368d0>})

c. Execute multiple experiments at once

Let’s demonstrate the true power of Qurrium.

from qurry.qurrent import EntropyMeasureRandomizedMeasureArgs

Preparing a configuration list for multiple experiments with following parameters:

class EntropyMeasureRandomizedMeasureArgs(total=False):
    """Output arguments for :meth:`output`."""
    shots: int
    """Number of shots."""
    tags: Optional[tuple[str, ...]]
    """The tags to be used for the experiment."""

    wave: Optional[Union[QuantumCircuit, Hashable]]
    """The key or the circuit to execute."""
    times: int
    """The number of random unitary operator.
    It will denote as `N_U` in the experiment name."""
    measure: Optional[Union[tuple[int, int], int, list[int]]]
    """The measure range."""
    unitary_loc: Optional[Union[tuple[int, int], int, list[int]]]
    """The range of the unitary operator."""
    unitary_loc_not_cover_measure: bool
    """Whether the range of the unitary operator is not cover the measure range."""
    random_unitary_seeds: Optional[dict[int, dict[int, int]]]
    """The seeds for all random unitary operator.
    This argument only takes input as type of `dict[int, dict[int, int]]`.
    The first key is the index for the random unitary operator.
    The second key is the index for the qubit.

    .. code-block:: python
        {
            0: {0: 1234, 1: 5678},
            1: {0: 2345, 1: 6789},
            2: {0: 3456, 1: 7890},
        }

    If you want to generate the seeds for all random unitary operator,
    you can use the function :func:`generate_random_unitary_seeds`
    in :mod:`qurry.qurrium.utils.random_unitary`.

    .. code-block:: python
        from qurry.qurrium.utils.random_unitary import generate_random_unitary_seeds

        random_unitary_seeds = generate_random_unitary_seeds(100, 2)
    """
config_list: list[EntropyMeasureRandomizedMeasureArgs] = [
    {
        "shots": 1024,
        "wave": f"{wave_names}_{i}",
        "times": 100,
        "tags": (wave_names, f"size_{i}"),
    }
    for _ in range(10)
    for i in range(2, 13, 2)
    for wave_names in ["trivial_paramagnet", "ghz", "neel"]
]
print(len(config_list))
180

The .multiOutput will return an id of this multimanager instance, which can be used to get the results and post-process them.

Each multimanager will export the experiments in a folder you can specify by setting save_location parameter with default location for current directory where Python executed. It will create a folder with the name of the multimanager instance, and inside it will create a folder for storing each experiment data.

It will do firstly in the building process, but you can skip it by setting skip_build_write=True to save time. After all experiments are executed, it will export secondly, which can also be skipped by setting skip_output_write=True for no files output.

multi_exps1 = experiment_randomized.multiOutput(
    config_list,
    summoner_name="qurrent.randomized_measure",  # you can name it whatever you want
    multiprocess_build=True,
    # Using multiprocessing to build the experiments,
    # it will be faster but take all the CPU
    skip_build_write=True,
    # Skip the writing of the experiment as files during the build,
    save_location=".",
    # Save the experiment as files in the current directory
    multiprocess_write=True,
    # Writing the experiment as files using multiprocessing,
)
multi_exps1
| MultiManager building...
| Write "qurrent.randomized_measure.001", at location "qurrent.randomized_measure.001"
| MultiOutput running...
| Export multimanager...
| No quantity to export.
| Export multi.config.json for 4de3484f-570b-4727-a14b-a363358540ce
| Exporting qurrent.randomized_measure.001/qurryinfo.json...
| Exporting qurrent.randomized_measure.001/qurryinfo.json done.
'4de3484f-570b-4727-a14b-a363358540ce'

You can check the result of multiOutput that we just executed by accessing the .multimanagers

experiment_randomized.multimanagers
MultiManagerContainer(num=1, {
  "4de3484f-570b-4727-a14b-a363358540ce":
    <MultiManager(name="qurrent.randomized_measure.001", jobstype="local", ..., exps_num=180)>,
})
experiment_randomized.multimanagers[multi_exps1]
<MultiManager(id="4de3484f-570b-4727-a14b-a363358540ce",
  name="qurrent.randomized_measure.001",
  tags=(),
  jobstype="local",
  pending_strategy="tags",
  last_events={
    'output.001': '2025-07-08 17:02:59',},
  exps_num=180)>

d. Run post-processing at once

experiment_randomized.multiAnalysis(
    summoner_id=multi_exps1,
    skip_write=True,
    multiprocess_write=False,
    selected_qubits=[0, 1],
)
| "report.001" has been completed.
'4de3484f-570b-4727-a14b-a363358540ce'
print("| Available results:")
for k, v in (
    experiment_randomized.multimanagers[multi_exps1]
    .quantity_container["report.001"]
    .items()
):
    print("| -", k, "with length", len(v))
| Available results:
| - ('trivial_paramagnet', 'size_2') with length 10
| - ('ghz', 'size_2') with length 10
| - ('neel', 'size_2') with length 10
| - ('trivial_paramagnet', 'size_4') with length 10
| - ('ghz', 'size_4') with length 10
| - ('neel', 'size_4') with length 10
| - ('trivial_paramagnet', 'size_6') with length 10
| - ('ghz', 'size_6') with length 10
| - ('neel', 'size_6') with length 10
| - ('trivial_paramagnet', 'size_8') with length 10
| - ('ghz', 'size_8') with length 10
| - ('neel', 'size_8') with length 10
| - ('trivial_paramagnet', 'size_10') with length 10
| - ('ghz', 'size_10') with length 10
| - ('neel', 'size_10') with length 10
| - ('trivial_paramagnet', 'size_12') with length 10
| - ('ghz', 'size_12') with length 10
| - ('neel', 'size_12') with length 10
  • Example of the content of quantity_container

experiment_randomized.multimanagers[multi_exps1].quantity_container["report.001"][
    ("trivial_paramagnet", "size_10")
][:2]
[{'purity': np.float64(1.0211288261413574),
  'entropy': np.float64(-0.03016488885125096),
  'puritySD': np.float64(0.6669314540660862),
  'entropySD': np.float64(0.9422696497854144),
  'num_classical_registers': 10,
  'classical_registers': [0, 1],
  'classical_registers_actually': [0, 1],
  'all_system_source': 'independent',
  'purityAllSys': np.float64(1.857098331451416),
  'entropyAllSys': np.float64(-0.893050206518096),
  'puritySDAllSys': np.float64(1.9115763744450462),
  'entropySDAllSys': np.float64(1.485016549197476),
  'num_classical_registers_all_sys': 10,
  'classical_registers_all_sys': None,
  'classical_registers_actually_all_sys': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
  'errorRate': np.float64(-0.36306131913879974),
  'mitigatedPurity': np.float64(0.6650459218428806),
  'mitigatedEntropy': np.float64(0.5884741318260232),
  'counts_num': 100,
  'taking_time': 0.000638529,
  'taking_time_all_sys': 0.024673163,
  'counts_used': None,
  'input': {'num_qubits': 10,
   'selected_qubits': [0, 1],
   'registers_mapping': {0: 0,
    1: 1,
    2: 2,
    3: 3,
    4: 4,
    5: 5,
    6: 6,
    7: 7,
    8: 8,
    9: 9},
   'bitstring_mapping': {0: 0,
    1: 1,
    2: 2,
    3: 3,
    4: 4,
    5: 5,
    6: 6,
    7: 7,
    8: 8,
    9: 9},
   'shots': 1024,
   'unitary_located': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]},
  'header': {'serial': 0, 'datetime': '2025-07-08 17:03:16', 'log': {}}},
 {'purity': np.float64(1.0049881362915039),
  'entropy': np.float64(-0.007178470743072036),
  'puritySD': np.float64(0.685812211883099),
  'entropySD': np.float64(0.9845070218598585),
  'num_classical_registers': 10,
  'classical_registers': [0, 1],
  'classical_registers_actually': [0, 1],
  'all_system_source': 'independent',
  'purityAllSys': np.float64(1.585183048248291),
  'entropyAllSys': np.float64(-0.6646494444984673),
  'puritySDAllSys': np.float64(0.8368608668283176),
  'entropySDAllSys': np.float64(0.7616376063454757),
  'num_classical_registers_all_sys': 10,
  'classical_registers_all_sys': None,
  'classical_registers_actually_all_sys': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
  'errorRate': np.float64(-0.259267673963092),
  'mitigatedPurity': np.float64(0.7261063977297069),
  'mitigatedEntropy': np.float64(0.4617471303713257),
  'counts_num': 100,
  'taking_time': 0.000610683,
  'taking_time_all_sys': 0.026317439,
  'counts_used': None,
  'input': {'num_qubits': 10,
   'selected_qubits': [0, 1],
   'registers_mapping': {0: 0,
    1: 1,
    2: 2,
    3: 3,
    4: 4,
    5: 5,
    6: 6,
    7: 7,
    8: 8,
    9: 9},
   'bitstring_mapping': {0: 0,
    1: 1,
    2: 2,
    3: 3,
    4: 4,
    5: 5,
    6: 6,
    7: 7,
    8: 8,
    9: 9},
   'shots': 1024,
   'unitary_located': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]},
  'header': {'serial': 0, 'datetime': '2025-07-08 17:03:16', 'log': {}}}]

e. Run post-processing at once with specific analysis arguments

At first, we need to get the each experiment’s id in the multimanager instance.

expkeys_of_multi_exps1 = list(
    experiment_randomized.multimanagers[multi_exps1].exps.keys()
)
print("| The number of exp_id:", len(expkeys_of_multi_exps1))
print("| First 3 experiment keys:")
expkeys_of_multi_exps1[:3]
| The number of exp_id: 180
| First 3 experiment keys:
['089e8136-e117-4a8b-b9a3-8795e1b424d0',
 '3eee202a-1fd5-479e-87f2-861cb096593a',
 '151270be-7876-4b49-9ac0-3c007d8b0a9a']
  1. If you want to run the post-processing for some specific experiments, for example, the first 3 experiments we get for the multimanager instance.

experiment_randomized.multiAnalysis(
    summoner_id=multi_exps1,
    analysis_name="first_3",
    skip_write=True,
    multiprocess_write=False,
    specific_analysis_args={
        k: (
            {
                "selected_qubits": [0, 1],
            }
            if idx < 3
            else False  # Give False to skip analysis for this experiment
        )
        for idx, k in enumerate(expkeys_of_multi_exps1)
    },
)
| "first_3.001" has been completed.
'4de3484f-570b-4727-a14b-a363358540ce'
print("| Available results:")
print(
    "| length:",
    sum(
        len(v)
        for v in experiment_randomized.multimanagers[multi_exps1]
        .quantity_container["first_3.001"]
        .values()
    ),
)
| Available results:
| length: 3
  1. Or manually specify all the analysis arguments for each experiment.

experiment_randomized.multiAnalysis(
    summoner_id=multi_exps1,
    skip_write=False,
    analysis_name="all_manual",
    multiprocess_write=True,
    specific_analysis_args={
        k: {
            "selected_qubits": [0, 1],  # selected qubits for the analysis
        }
        for idx, k in enumerate(expkeys_of_multi_exps1)
    },
)
| "all_manual.001" has been completed.
| Export multimanager...
| Export multi.config.json for 4de3484f-570b-4727-a14b-a363358540ce
| Exporting qurrent.randomized_measure.001/qurryinfo.json...
| Exporting qurrent.randomized_measure.001/qurryinfo.json done.
'4de3484f-570b-4727-a14b-a363358540ce'
print("| Available results:")
print(
    "| length:",
    sum(
        len(v)
        for v in experiment_randomized.multimanagers[multi_exps1]
        .quantity_container["all_manual.001"]
        .values()
    ),
)
| Available results:
| length: 180

All multiAnalysis results

experiment_randomized.multimanagers[multi_exps1].quantity_container.keys()
dict_keys(['report.001', 'first_3.001', 'all_manual.001'])

f. Read exported multimanager data

multi_exps1_reades = experiment_randomized.multiRead(
    save_location=".",
    summoner_name="qurrent.randomized_measure.001",
)
| Retrieve qurrent.randomized_measure.001...
| at: qurrent.randomized_measure.001

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>