From 4d71606fa08672c2e10f0442027ea1cfecfad613 Mon Sep 17 00:00:00 2001 From: Edoardo-Pedicillo Date: Wed, 20 Nov 2024 10:30:03 +0400 Subject: [PATCH 1/8] fix: define PADDING constant --- .../protocols/two_qubit_interaction/mermin/protocol.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/qibocal/protocols/two_qubit_interaction/mermin/protocol.py b/src/qibocal/protocols/two_qubit_interaction/mermin/protocol.py index dceab4bec..a5d3e3598 100644 --- a/src/qibocal/protocols/two_qubit_interaction/mermin/protocol.py +++ b/src/qibocal/protocols/two_qubit_interaction/mermin/protocol.py @@ -20,6 +20,8 @@ get_readout_basis, ) +PLOT_PADDING = 0.2 + @dataclass class MerminParameters(Parameters): @@ -180,7 +182,9 @@ def _plot(data: MerminData, fit: MerminResults, target): classical_bound = 2 ** (n_targets // 2) quantum_bound = 2 ** ((n_targets - 1) / 2) * (2 ** (n_targets // 2)) - fig = go.Figure(layout_yaxis_range=[-quantum_bound - 0.2, quantum_bound + 0.2]) + fig = go.Figure( + layout_yaxis_range=[-quantum_bound - PLOT_PADDING, quantum_bound + PLOT_PADDING] + ) if fit is not None: fig.add_trace( go.Scatter( From 1c8d9e0d7db62599be64838394041922d5750566 Mon Sep 17 00:00:00 2001 From: Edoardo-Pedicillo Date: Wed, 20 Nov 2024 10:31:37 +0400 Subject: [PATCH 2/8] fix: change n_targets --- .../protocols/two_qubit_interaction/mermin/protocol.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/qibocal/protocols/two_qubit_interaction/mermin/protocol.py b/src/qibocal/protocols/two_qubit_interaction/mermin/protocol.py index a5d3e3598..67e6bd8e4 100644 --- a/src/qibocal/protocols/two_qubit_interaction/mermin/protocol.py +++ b/src/qibocal/protocols/two_qubit_interaction/mermin/protocol.py @@ -176,11 +176,10 @@ def _fit(data: MerminData) -> MerminResults: def _plot(data: MerminData, fit: MerminResults, target): """Plotting function for Mermin protocol.""" figures = [] - targets = data.targets - n_targets = len(targets) - classical_bound = 2 ** (n_targets // 2) - quantum_bound = 2 ** ((n_targets - 1) / 2) * (2 ** (n_targets // 2)) + n_qubits = len(target) + classical_bound = 2 ** (n_qubits // 2) + quantum_bound = 2 ** ((n_qubits - 1) / 2) * (2 ** (n_qubits // 2)) fig = go.Figure( layout_yaxis_range=[-quantum_bound - PLOT_PADDING, quantum_bound + PLOT_PADDING] From 87b4ebddcc0aee6c1749218291d84ce08e9aeb3e Mon Sep 17 00:00:00 2001 From: Edoardo-Pedicillo Date: Wed, 20 Nov 2024 18:33:10 +0400 Subject: [PATCH 3/8] refactor: readout mitigation matrix acquisition and post-processing --- src/qibocal/auto/operation.py | 2 +- .../protocols/readout_mitigation_matrix.py | 146 ++++++------------ 2 files changed, 44 insertions(+), 104 deletions(-) diff --git a/src/qibocal/auto/operation.py b/src/qibocal/auto/operation.py index 8b1f602eb..1d7527ae8 100644 --- a/src/qibocal/auto/operation.py +++ b/src/qibocal/auto/operation.py @@ -181,7 +181,7 @@ def load_data(path: Path, filename: str): """Load data stored in a npz file.""" file = path / f"{filename}.npz" if file.is_file(): - raw_data_dict = dict(np.load(file)) + raw_data_dict = dict(np.load(file, allow_pickle=True)) data_dict = {} for data_key, array in raw_data_dict.items(): diff --git a/src/qibocal/protocols/readout_mitigation_matrix.py b/src/qibocal/protocols/readout_mitigation_matrix.py index dc6ed3db8..78cb14c07 100644 --- a/src/qibocal/protocols/readout_mitigation_matrix.py +++ b/src/qibocal/protocols/readout_mitigation_matrix.py @@ -7,24 +7,18 @@ from qibo import gates from qibo.backends import GlobalBackend from qibo.models import Circuit -from qibolab import ExecutionParameters from qibolab.platform import Platform -from qibolab.pulses import PulseSequence from qibolab.qubits import QubitId from qibocal.auto.operation import Data, Parameters, Results, Routine from qibocal.auto.transpile import dummy_transpiler, execute_transpiled_circuit from qibocal.config import log -from .utils import calculate_frequencies - @dataclass class ReadoutMitigationMatrixParameters(Parameters): """ReadoutMitigationMatrix matrix inputs.""" - pulses: Optional[bool] = True - """Get readout mitigation matrix using pulses. If False gates will be used.""" nshots: Optional[int] = None """Number of shots.""" relaxation_time: Optional[int] = None @@ -37,10 +31,14 @@ class ReadoutMitigationMatrixResults(Results): field(default_factory=dict) ) """Readout mitigation matrices (inverse of measurement matrix).""" - measurement_matrix: dict[tuple[QubitId, ...], npt.NDArray[np.float64]] = field( - default_factory=dict - ) - """Matrix containing measurement matrices for each state.""" + + +ReadoutMitigationMatrixType = np.dtype( + [ + ("state", int), + ("frequency", np.float64), + ] +) @dataclass @@ -54,40 +52,6 @@ class ReadoutMitigationMatrixData(Data): data: dict = field(default_factory=dict) """Raw data acquited.""" - def add(self, qubits, state, freqs): - for result_state, freq in freqs.items(): - self.data[ - qubits - + ( - state, - result_state, - ) - ] = freq - - for basis in [format(i, f"0{len(qubits)}b") for i in range(2 ** len(qubits))]: - if ( - qubits - + ( - state, - basis, - ) - not in self.data - ): - self.data[ - qubits - + ( - state, - basis, - ) - ] = 0 - - def __getitem__(self, qubits): - return { - index: value - for index, value in self.data.items() - if qubits == list(index[: len(index) - 2]) - } - def _acquisition( params: ReadoutMitigationMatrixParameters, @@ -105,75 +69,52 @@ def _acquisition( nqubits = len(qubits) for i in range(2**nqubits): state = format(i, f"0{nqubits}b") - if params.pulses: - sequence = PulseSequence() - for q, bit in enumerate(state): - if bit == "1": - sequence.add( - platform.create_RX_pulse( - qubits[q], start=0, relative_phase=0 - ) - ) - measurement_start = sequence.finish - for q in range(len(state)): - MZ_pulse = platform.create_MZ_pulse( - qubits[q], start=measurement_start - ) - sequence.add(MZ_pulse) - results = platform.execute_pulse_sequence( - sequence, ExecutionParameters(nshots=params.nshots) - ) - data.add( - tuple(qubits), state, calculate_frequencies(results, tuple(qubits)) + c = Circuit( + platform.nqubits, + ) + for q, bit in enumerate(state): + if bit == "1": + c.add(gates.X(qubits[q])) + c.add(gates.M(*[qubits[i] for i in range(len(state))])) + _, results = execute_transpiled_circuit( + c, qubit_map, backend, nshots=params.nshots, transpiler=transpiler + ) + frequencies = np.zeros(2 ** len(qubits)) + for state, freq in results.frequencies().items(): + frequencies[int(state, 2)] = freq + for freq in frequencies: # TODO: Remove this loop? + data.register_qubit( + ReadoutMitigationMatrixType, + (qubits), + dict( + state=np.array([int(state, 2)]), + frequency=freq, + ), ) - else: - c = Circuit( - platform.nqubits, - wire_names=[str(i) for i in range(platform.nqubits)], - ) - for q, bit in enumerate(state): - if bit == "1": - c.add(gates.X(qubits[q])) - c.add(gates.M(*[qubits[i] for i in range(len(state))])) - _, results = execute_transpiled_circuit( - c, qubit_map, backend, nshots=params.nshots, transpiler=transpiler - ) - data.add(tuple(qubits), state, dict(results.frequencies())) return data def _fit(data: ReadoutMitigationMatrixData) -> ReadoutMitigationMatrixResults: """Post processing for readout mitigation matrix protocol.""" readout_mitigation_matrix = {} - measurement_matrix = {} - for qubit in data.qubit_list: - qubit_data = data[qubit] - matrix = np.zeros((2 ** len(qubit), 2 ** len(qubit))) - computational_basis = [ - format(i, f"0{len(qubit)}b") for i in range(2 ** len(qubit)) - ] - for state in computational_basis: - column = np.zeros(2 ** len(qubit)) - qubit_state_data = { - index: value - for index, value in qubit_data.items() - if index[-2] == state - } - for index, value in qubit_state_data.items(): - column[(int(index[-1], 2))] = value / data.nshots - matrix[:, int(state, 2)] = np.flip(column) - - measurement_matrix[tuple(qubit)] = matrix.tolist() + for qubits in data.qubit_list: + qubit_data = data.data[tuple(qubits)] + mitigation_matrix = [] + for state in range(2 ** len(qubits)): + mitigation_matrix.append(qubit_data[qubit_data.state == state].frequency) + mitigation_matrix = np.vstack(mitigation_matrix) try: - readout_mitigation_matrix[tuple(qubit)] = np.linalg.inv(matrix).tolist() + readout_mitigation_matrix[tuple(qubits)] = np.linalg.inv( + mitigation_matrix + ).tolist() except np.linalg.LinAlgError as e: log.warning(f"ReadoutMitigationMatrix: the fitting was not succesful. {e}") - - return ReadoutMitigationMatrixResults( + res = ReadoutMitigationMatrixResults( readout_mitigation_matrix=readout_mitigation_matrix, - measurement_matrix=measurement_matrix, ) + return res + def _plot( data: ReadoutMitigationMatrixData, @@ -187,12 +128,11 @@ def _plot( computational_basis = [ format(i, f"0{len(target)}b") for i in range(2 ** len(target)) ] - z = fit.measurement_matrix[tuple(target)] - + z = np.array(fit.readout_mitigation_matrix[tuple(target)]) * data.nshots fig = px.imshow( z, x=computational_basis, - y=computational_basis[::-1], + y=computational_basis, text_auto=True, labels={ "x": "Prepeared States", From 6cd7e29e1c35e85f6df3c594bfe4012b539610b9 Mon Sep 17 00:00:00 2001 From: Edoardo-Pedicillo Date: Wed, 20 Nov 2024 18:48:47 +0400 Subject: [PATCH 4/8] fix: propagate changes in the other routines and fix tests --- .../protocols/two_qubit_interaction/chsh/protocol.py | 4 ++-- .../protocols/two_qubit_interaction/mermin/protocol.py | 4 +--- tests/runcards/protocols.yml | 10 +--------- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/qibocal/protocols/two_qubit_interaction/chsh/protocol.py b/src/qibocal/protocols/two_qubit_interaction/chsh/protocol.py index 6adc6f147..51e6f9add 100644 --- a/src/qibocal/protocols/two_qubit_interaction/chsh/protocol.py +++ b/src/qibocal/protocols/two_qubit_interaction/chsh/protocol.py @@ -174,7 +174,7 @@ def _acquisition_pulses( if params.apply_error_mitigation: mitigation_data = mitigation_acquisition( - mitigation_params(pulses=True, nshots=params.nshots), platform, targets + mitigation_params(nshots=params.nshots), platform, targets ) mitigation_results = mitigation_fit(mitigation_data) @@ -226,7 +226,7 @@ def _acquisition_circuits( qubit_map = [i for i in range(platform.nqubits)] if params.apply_error_mitigation: mitigation_data = mitigation_acquisition( - mitigation_params(pulses=False, nshots=params.nshots), platform, targets + mitigation_params(nshots=params.nshots), platform, targets ) mitigation_results = mitigation_fit(mitigation_data) for pair in targets: diff --git a/src/qibocal/protocols/two_qubit_interaction/mermin/protocol.py b/src/qibocal/protocols/two_qubit_interaction/mermin/protocol.py index 67e6bd8e4..d26c3eacf 100644 --- a/src/qibocal/protocols/two_qubit_interaction/mermin/protocol.py +++ b/src/qibocal/protocols/two_qubit_interaction/mermin/protocol.py @@ -89,9 +89,7 @@ def _acquisition( data = MerminData(thetas=thetas.tolist()) if params.apply_error_mitigation: mitigation_data, _ = readout_mitigation_matrix.acquisition( - readout_mitigation_matrix.parameters_type.load( - dict(pulses=True, nshots=params.nshots) - ), + readout_mitigation_matrix.parameters_type.load(dict(nshots=params.nshots)), platform, targets, ) diff --git a/tests/runcards/protocols.yml b/tests/runcards/protocols.yml index 5577af6a1..ac8ecc407 100644 --- a/tests/runcards/protocols.yml +++ b/tests/runcards/protocols.yml @@ -883,19 +883,11 @@ actions: native: False apply_error_mitigation: True - - id: readout_mitigation_matrix pulses + - id: readout_mitigation_matrix operation: readout_mitigation_matrix targets: [[0,1,2],[1,2]] parameters: nshots: 10 - pulses: True - - - id: readout_mitigation_matrix circuits - operation: readout_mitigation_matrix - targets: [[0,1,2],[1,2]] - parameters: - nshots: 10 - pulses: False - id: twpa frequency operation: twpa_frequency From f35f1668120b0e44ce3bedc456dddace0d79d379 Mon Sep 17 00:00:00 2001 From: Edoardo-Pedicillo Date: Wed, 20 Nov 2024 18:56:35 +0400 Subject: [PATCH 5/8] fix: remove pickle --- src/qibocal/auto/operation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibocal/auto/operation.py b/src/qibocal/auto/operation.py index 1d7527ae8..8b1f602eb 100644 --- a/src/qibocal/auto/operation.py +++ b/src/qibocal/auto/operation.py @@ -181,7 +181,7 @@ def load_data(path: Path, filename: str): """Load data stored in a npz file.""" file = path / f"{filename}.npz" if file.is_file(): - raw_data_dict = dict(np.load(file, allow_pickle=True)) + raw_data_dict = dict(np.load(file)) data_dict = {} for data_key, array in raw_data_dict.items(): From b7a924932a51e99c8dff0490defc778c859e9418 Mon Sep 17 00:00:00 2001 From: Edoardo-Pedicillo Date: Thu, 21 Nov 2024 15:39:17 +0400 Subject: [PATCH 6/8] fix: circuit execution issue --- .../protocols/readout_mitigation_matrix.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/qibocal/protocols/readout_mitigation_matrix.py b/src/qibocal/protocols/readout_mitigation_matrix.py index 78cb14c07..ce11fd314 100644 --- a/src/qibocal/protocols/readout_mitigation_matrix.py +++ b/src/qibocal/protocols/readout_mitigation_matrix.py @@ -70,19 +70,19 @@ def _acquisition( for i in range(2**nqubits): state = format(i, f"0{nqubits}b") c = Circuit( - platform.nqubits, + nqubits, ) for q, bit in enumerate(state): if bit == "1": - c.add(gates.X(qubits[q])) - c.add(gates.M(*[qubits[i] for i in range(len(state))])) + c.add(gates.X(q)) + c.add(gates.M(*range(nqubits))) _, results = execute_transpiled_circuit( - c, qubit_map, backend, nshots=params.nshots, transpiler=transpiler + c, qubits, backend, nshots=params.nshots, transpiler=transpiler ) frequencies = np.zeros(2 ** len(qubits)) - for state, freq in results.frequencies().items(): - frequencies[int(state, 2)] = freq - for freq in frequencies: # TODO: Remove this loop? + for i, freq in results.frequencies().items(): + frequencies[int(i, 2)] = freq + for freq in frequencies: data.register_qubit( ReadoutMitigationMatrixType, (qubits), @@ -102,7 +102,7 @@ def _fit(data: ReadoutMitigationMatrixData) -> ReadoutMitigationMatrixResults: mitigation_matrix = [] for state in range(2 ** len(qubits)): mitigation_matrix.append(qubit_data[qubit_data.state == state].frequency) - mitigation_matrix = np.vstack(mitigation_matrix) + mitigation_matrix = np.vstack(mitigation_matrix) / data.nshots try: readout_mitigation_matrix[tuple(qubits)] = np.linalg.inv( mitigation_matrix @@ -128,7 +128,8 @@ def _plot( computational_basis = [ format(i, f"0{len(target)}b") for i in range(2 ** len(target)) ] - z = np.array(fit.readout_mitigation_matrix[tuple(target)]) * data.nshots + measurement_matrix = np.linalg.inv(fit.readout_mitigation_matrix[tuple(target)]) + z = measurement_matrix fig = px.imshow( z, x=computational_basis, From a5f5dd0902a0f76edcfbda7c27908ecd2f6b4ae4 Mon Sep 17 00:00:00 2001 From: Edoardo-Pedicillo Date: Thu, 21 Nov 2024 17:27:38 +0400 Subject: [PATCH 7/8] fix: use 2q circuits --- .../two_qubit_interaction/chsh/circuits.py | 57 +++++++++---------- .../two_qubit_interaction/chsh/protocol.py | 5 +- 2 files changed, 28 insertions(+), 34 deletions(-) diff --git a/src/qibocal/protocols/two_qubit_interaction/chsh/circuits.py b/src/qibocal/protocols/two_qubit_interaction/chsh/circuits.py index cebe92346..898ab8536 100644 --- a/src/qibocal/protocols/two_qubit_interaction/chsh/circuits.py +++ b/src/qibocal/protocols/two_qubit_interaction/chsh/circuits.py @@ -7,7 +7,7 @@ from .utils import READOUT_BASIS -def create_bell_circuit(nqubits, qubits, theta=np.pi / 4, bell_state=0): +def create_bell_circuit(theta=np.pi / 4, bell_state=0): """Creates the circuit to generate the bell states and with a theta-measurement bell_state chooses the initial bell state for the test: 0 -> |00>+|11> @@ -17,24 +17,24 @@ def create_bell_circuit(nqubits, qubits, theta=np.pi / 4, bell_state=0): Native defaults to only using GPI2 and GPI gates. """ p = [0, 0] - c = Circuit(nqubits) - c.add(gates.H(qubits[0])) - c.add(gates.H(qubits[1])) - c.add(gates.CZ(qubits[0], qubits[1])) - c.add(gates.H(qubits[1])) + c = Circuit(2) + c.add(gates.H(0)) + c.add(gates.H(1)) + c.add(gates.CZ(0, 1)) + c.add(gates.H(1)) if bell_state == 1: - c.add(gates.Z(qubits[0])) + c.add(gates.Z(0)) elif bell_state == 2: - c.add(gates.Z(qubits[0])) - c.add(gates.X(qubits[0])) + c.add(gates.Z(0)) + c.add(gates.X(0)) elif bell_state == 3: - c.add(gates.X(qubits[0])) + c.add(gates.X(0)) - c.add(gates.RY(qubits[0], theta)) + c.add(gates.RY(0, theta)) return c, p -def create_bell_circuit_native(nqubits, qubits, theta=np.pi / 4, bell_state=0): +def create_bell_circuit_native(theta=np.pi / 4, bell_state=0): """Creates the circuit to generate the bell states and with a theta-measurement bell_state chooses the initial bell state for the test: 0 -> |00>+|11> @@ -44,35 +44,33 @@ def create_bell_circuit_native(nqubits, qubits, theta=np.pi / 4, bell_state=0): Native defaults to only using GPI2 and GPI gates. """ - c = Circuit(nqubits) + c = Circuit(2) p = [0, 0] - c.add(gates.GPI2(qubits[0], np.pi / 2)) - c.add(gates.GPI2(qubits[1], np.pi / 2)) - c.add(gates.CZ(qubits[0], qubits[1])) - c.add(gates.GPI2(qubits[1], -np.pi / 2)) + c.add(gates.GPI2(0, np.pi / 2)) + c.add(gates.GPI2(1, np.pi / 2)) + c.add(gates.CZ(0, 1)) + c.add(gates.GPI2(1, -np.pi / 2)) if bell_state == 0: p[0] += np.pi elif bell_state == 1: p[0] += 0 elif bell_state == 2: p[0] += 0 - c.add(gates.GPI2(qubits[0], p[0])) - c.add(gates.GPI2(qubits[0], p[0])) + c.add(gates.GPI2(0, p[0])) + c.add(gates.GPI2(0, p[0])) elif bell_state == 3: p[0] += np.pi - c.add(gates.GPI2(qubits[0], p[0])) - c.add(gates.GPI2(qubits[0], p[0])) + c.add(gates.GPI2(0, p[0])) + c.add(gates.GPI2(0, p[0])) - c.add(gates.GPI2(qubits[0], p[0])) + c.add(gates.GPI2(0, p[0])) p[0] += theta - c.add(gates.GPI2(qubits[0], p[0] + np.pi)) + c.add(gates.GPI2(0, p[0] + np.pi)) return c, p def create_chsh_circuits( - platform, - qubits, theta=np.pi / 4, bell_state=0, native=True, @@ -84,15 +82,14 @@ def create_chsh_circuits( """ create_bell = create_bell_circuit_native if native else create_bell_circuit chsh_circuits = {} - nqubits = platform.nqubits if platform else max(qubits) + 1 for basis in readout_basis: - c, p = create_bell(nqubits, qubits, theta, bell_state) + c, p = create_bell(theta, bell_state) for i, base in enumerate(basis): if base == "X": if native: - c.add(gates.GPI2(qubits[i], p[i] + np.pi / 2)) + c.add(gates.GPI2(i, p[i] + np.pi / 2)) else: - c.add(gates.H(qubits[i])) - c.add(gates.M(*qubits)) + c.add(gates.H(i)) + c.add(gates.M(0, 1)) chsh_circuits[basis] = c return chsh_circuits diff --git a/src/qibocal/protocols/two_qubit_interaction/chsh/protocol.py b/src/qibocal/protocols/two_qubit_interaction/chsh/protocol.py index 51e6f9add..94f877f5e 100644 --- a/src/qibocal/protocols/two_qubit_interaction/chsh/protocol.py +++ b/src/qibocal/protocols/two_qubit_interaction/chsh/protocol.py @@ -223,7 +223,6 @@ def _acquisition_circuits( backend = GlobalBackend() backend.platform = platform transpiler = dummy_transpiler(backend) - qubit_map = [i for i in range(platform.nqubits)] if params.apply_error_mitigation: mitigation_data = mitigation_acquisition( mitigation_params(nshots=params.nshots), platform, targets @@ -242,8 +241,6 @@ def _acquisition_circuits( for bell_state in params.bell_states: for theta in thetas: chsh_circuits = create_chsh_circuits( - platform, - qubits=pair, bell_state=bell_state, theta=theta, native=params.native, @@ -254,7 +251,7 @@ def _acquisition_circuits( nshots=params.nshots, transpiler=transpiler, backend=backend, - qubit_map=qubit_map, + qubit_map=pair, ) frequencies = result.frequencies() data.register_basis(pair, bell_state, basis, frequencies) From 4c0013a6e3221dab8b5bd21f9f42b3036a3671bd Mon Sep 17 00:00:00 2001 From: Edoardo-Pedicillo Date: Thu, 21 Nov 2024 18:37:48 +0400 Subject: [PATCH 8/8] fix: add check before dumping the mitigation matrix --- .../two_qubit_interaction/chsh/protocol.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/qibocal/protocols/two_qubit_interaction/chsh/protocol.py b/src/qibocal/protocols/two_qubit_interaction/chsh/protocol.py index 94f877f5e..f8a64c57b 100644 --- a/src/qibocal/protocols/two_qubit_interaction/chsh/protocol.py +++ b/src/qibocal/protocols/two_qubit_interaction/chsh/protocol.py @@ -76,14 +76,16 @@ class CHSHData(Data): def save(self, path: Path): """Saving data including mitigation matrix.""" - - np.savez( - path / f"{MITIGATION_MATRIX_FILE}.npz", - **{ - json.dumps((control, target)): self.mitigation_matrix[control, target] - for control, target, _, _, _ in self.data - }, - ) + if self.mitigation_matrix: + np.savez( + path / f"{MITIGATION_MATRIX_FILE}.npz", + **{ + json.dumps((control, target)): self.mitigation_matrix[ + control, target + ] + for control, target, _, _, _ in self.data + }, + ) super().save(path=path) @classmethod