Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements to ActionBuilder #271

Merged
merged 34 commits into from
May 16, 2023
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
6306c30
Moving and simplyfing methods
andrea-pasquale Mar 7, 2023
5a3bf03
Fix tests
andrea-pasquale Mar 8, 2023
cf33c76
Add forgotten file
andrea-pasquale Mar 8, 2023
37f683d
Remove try-except block
andrea-pasquale Mar 8, 2023
e8f3358
Fix plotting functions
andrea-pasquale Mar 8, 2023
00e5392
Minor fixes and enable qq-live for RB
andrea-pasquale Mar 9, 2023
6618fc8
Fix tests
andrea-pasquale Mar 10, 2023
cd71901
Merge branch 'main' into simplify_builder
andrea-pasquale Apr 11, 2023
af29b7b
vodovozovaliza Apr 28, 2023
0b075d5
Minor changes
vodovozovaliza Apr 28, 2023
52d3b13
Create fitting_report tables
vodovozovaliza Apr 28, 2023
8b1402e
Merge branch 'main' into simplify_builder
andrea-pasquale Apr 28, 2023
b85fdb6
Fix tests
vodovozovaliza Apr 29, 2023
6ed1edb
Merge branch 'simplify_builder' into nigsc_changes
vodovozovaliza May 1, 2023
17bd410
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 1, 2023
ea94088
nigsc
vodovozovaliza May 1, 2023
499a2af
Fix tests
vodovozovaliza May 1, 2023
d926f1d
Add suggestions
vodovozovaliza May 1, 2023
9391972
Fix warnings.
vodovozovaliza May 1, 2023
f6bcd1c
Minor changes
vodovozovaliza May 1, 2023
b028c51
main runcard | embed circuit better
wilkensJ May 1, 2023
5f4952d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 1, 2023
3c3823a
Improving nigsc plotting
wilkensJ May 2, 2023
59a93b5
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 2, 2023
7588c4d
improve plot | fix coverage
vodovozovaliza May 3, 2023
6c5612c
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 3, 2023
b21a5c3
Update src/qibocal/calibrations/niGSC/basics/experiment.py
vodovozovaliza May 3, 2023
02df578
Update src/qibocal/calibrations/niGSC/basics/experiment.py
vodovozovaliza May 3, 2023
1a2cf1c
Update src/qibocal/calibrations/niGSC/basics/experiment.py
vodovozovaliza May 3, 2023
f4416ec
Update src/qibocal/calibrations/niGSC/basics/experiment.py
vodovozovaliza May 3, 2023
12e587b
reverse table format
May 3, 2023
4277bad
Merge pull request #318 from qiboteam/nigsc_changes
andrea-pasquale May 3, 2023
cc265c8
Apply Liza' suggestions
andrea-pasquale May 8, 2023
1cdd8dd
Merge branch 'main' into simplify_builder
vodovozovaliza May 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions runcards/niGSC.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ format: pickle
actions:
standardrb:
nqubits: 1
depths: [1,3,5,7,10]
runs: 2
depths: [1,3,5,7,10,20,30,50]
runs: 15
nshots: 1024
noise_model: PauliErrorOnUnitary
noise_model: PauliErrorOnAll
noise_params: [0.01, 0.01, 0.01]
XIdrb:
nqubits: 1
Expand All @@ -24,5 +24,5 @@ actions:
depths: [1,3,5,7,10]
runs: 2
nshots: 1024
noise_model: PauliErrorOnUnitary
noise_model: PauliErrorOnAll
noise_params: [0.01, 0.01, 0.01]
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from qibolab.sweeper import Parameter, Sweeper

from qibocal import plots
from qibocal.config import raise_error
from qibocal.data import DataUnits
from qibocal.decorators import plot
from qibocal.fitting.methods import lorentzian_fit
Expand Down
95 changes: 37 additions & 58 deletions src/qibocal/calibrations/niGSC/XIdrb.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from qibocal.calibrations.niGSC.basics.circuitfactory import CircuitFactory
from qibocal.calibrations.niGSC.basics.experiment import Experiment
from qibocal.calibrations.niGSC.basics.plot import Report, scatter_fit_fig
from qibocal.calibrations.niGSC.basics.utils import number_to_str
from qibocal.config import raise_error


Expand All @@ -40,22 +41,16 @@ def __init__(self, nqubits: int, depths: list, qubits: list = []) -> None:
if not len(self.qubits) == 1:
raise_error(
ValueError,
"This class is written for gates acting on only one qubit, not {} qubits.".format(
len(self.qubits)
),
f"This class is written for gates acting on only one qubit, not {len(self.qubits)} qubits.",
)
self.name = "XId"

def build_circuit(self, depth: int):
# Initiate the empty circuit from qibo with 'self.nqubits'
# many qubits.
circuit = Circuit(1, density_matrix=True)
# There are only two gates to choose from for every qubit.
a = [gates.I(0), gates.X(0)]
# Draw sequence length many zeros and ones.
random_ints = np.random.randint(0, 2, size=depth)
# Get the Xs and Ids with random_ints as indices.
gate_lists = np.take(a, random_ints)
circuit = Circuit(1)
# Draw sequence length many I and X gates.
gate_lists = np.random.choice([gates.I(0), gates.X(0)], size=depth)
# Add gates to circuit.
circuit.add(gate_lists)
circuit.add(gates.M(0))
Expand All @@ -72,18 +67,20 @@ def __init__(
noise_model: NoiseModel = None,
) -> None:
super().__init__(circuitfactory, data, nshots, noise_model)
# Make the circuitfactory a list be able to store them using save_circuits method.
self.prebuild()
self.name = "XIdRB"

def execute(self, circuit: Circuit, datarow: dict) -> dict:
datadict = super().execute(circuit, datarow)
datadict["depth"] = circuit.ngates - 1
# TODO change that.
datadict["countX"] = circuit.gate_types["x"]
# TODO change to circuit.gate_types["x"] for next Qibo version
datadict["countX"] = len(circuit.gates_of_type("x"))
return datadict


# Define the result class for this specific module.
class moduleReport(Report):
class ModuleReport(Report):
def __init__(self) -> None:
super().__init__()
self.title = "X-Id Benchmarking"
Expand Down Expand Up @@ -137,13 +134,14 @@ def post_processing_sequential(experiment: Experiment):
# After the row by row execution of tasks comes the aggregational task. Something like calculation
# of means, deviations, fitting data, tasks where the whole data as to be looked at, and not just
# one instance of circuit + other information.
def get_aggregational_data(experiment: Experiment) -> pd.DataFrame:
def get_aggregational_data(experiment: Experiment, ndecays: int = 2) -> pd.DataFrame:
"""Computes aggregational tasks, fits data and stores the results in a data frame.

No data is manipulated in the ``experiment`` object.

Args:
experiment (Experiment): After sequential postprocessing of the experiment data.
ndecays (int): Number of decays to be fitted. Default is 2.

Returns:
pd.DataFrame: The summarized data.
Expand All @@ -152,39 +150,26 @@ def get_aggregational_data(experiment: Experiment) -> pd.DataFrame:
depths, ydata = experiment.extract("filter", "depth", "mean")
_, ydata_std = experiment.extract("filter", "depth", "std")
# Fit the filtered signal for each depth, there could be two overlaying exponential functions.
popt, perr = fitting_methods.fit_exp2_func(depths, ydata)
popt, perr = fitting_methods.fit_expn_func(depths, ydata, n=ndecays)
# Create dictionaries with fitting parameters and estimated errors
popt_keys = [f"A{k+1}" for k in range(ndecays)]
popt_keys += [f"p{k+1}" for k in range(ndecays)]
popt_dict = dict(zip(popt_keys, popt))
perr_keys = [f"A{k+1}_err" for k in range(ndecays)]
perr_keys += [f"p{k+1}_err" for k in range(ndecays)]
perr_dict = dict(zip(perr_keys, perr))
# Build a list of dictionaries with the aggregational information.
data = [
{
"depth": depths, # The x-axis.
"data": ydata, # The filtred signal.
"2sigma": 2 * ydata_std, # The 2 * standard deviation error for each depth.
"fit_func": "exp2_func", # Which function was used to fit.
"popt_real": {
"A1_real": np.real(
popt[0]
), # The complex prefactors would lead to imaginary data.
"A2_real": np.real(
popt[1]
), # That's why they have to be stored seperatly.
"p1": popt[2],
"p2": popt[3],
}, # The real fitting parameters.
"perr": {
"A1_err": perr[0],
"A2_err": perr[1],
"p1_err": perr[2],
"p2_err": perr[3],
}, # The estimated errors.
"fit_func": "expn_func", # Which function was used to fit.
"popt": popt_dict, # The real fitting parameters.
"perr": perr_dict, # The estimated errors.
}
]
if np.iscomplex(popt[0]) or np.iscomplex(popt[1]):
data[0]["popt_imag"] = {
"A1_imag": np.imag(popt[0]),
"A2_imag": np.imag(popt[1]),
"p1": popt[2],
"p2": popt[3],
} # The imaginary fitting parameters.

df = pd.DataFrame(data, index=["filter"])
return df

Expand All @@ -204,30 +189,24 @@ def build_report(experiment: Experiment, df_aggr: pd.DataFrame) -> Figure:
"""

# Initiate a report object.
report = moduleReport()
# Add general information to the object.
report = ModuleReport()
# Add general information to the table.
report.info_dict["Number of qubits"] = len(experiment.data[0]["samples"][0])
report.info_dict["Number of shots"] = len(experiment.data[0]["samples"])
report.info_dict["runs"] = experiment.extract("samples", "depth", "count")[1][0]
report.info_dict["Fitting daviations"] = "".join(
[
"{}:{:.3f} ".format(key, df_aggr.loc["filter"]["perr"][key])
for key in df_aggr.loc["filter"]["perr"]
]
dfrow = df_aggr.loc["filter"]
popt_pairs = list(dfrow["popt"].items())[::2] + list(dfrow["popt"].items())[1::2]
report.info_dict["Fit"] = "".join(
[f"{key}={number_to_str(value)} " for key, value in popt_pairs]
)
perr_pairs = list(dfrow["perr"].items())[::2] + list(dfrow["perr"].items())[1::2]
report.info_dict["Fitting deviations"] = "".join(
[f"{key}={number_to_str(value)} " for key, value in perr_pairs]
)
# Use the predefined ``scatter_fit_fig`` function from ``basics.utils`` to build the wanted
# plotly figure with the scattered filtered data along with the mean for
# each depth and the exponential fit for the means.
report.all_figures.append(
scatter_fit_fig(
experiment, df_aggr, "depth", "filter", fittingparam_label="popt_real"
)
)
if "popt_imag" in df_aggr:
report.all_figures.append(
scatter_fit_fig(
experiment, df_aggr, "depth", "filter", fittingparam_label="popt_imag"
)
)
# Return the figure the report object builds out of all figures added to the report.
report.all_figures.append(scatter_fit_fig(experiment, df_aggr, "depth", "filter"))

# Return the figure of the report object and the corresponding table.
return report.build()
17 changes: 10 additions & 7 deletions src/qibocal/calibrations/niGSC/basics/circuitfactory.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import abc
from copy import deepcopy

import numpy as np
from qibo import gates
Expand Down Expand Up @@ -49,13 +50,15 @@ def __next__(self) -> Circuit:
# Check if the stop critarion is met.
if self.n >= len(self.depths):
raise StopIteration
else:
circuit = self.build_circuit(self.depths[self.n])
self.n += 1
# Distribute the circuit onto the given support.
bigcircuit = Circuit(self.nqubits)
bigcircuit.add(circuit.on_qubits(*self.qubits))
return bigcircuit

circuit = self.build_circuit(self.depths[self.n])
self.n += 1
# Distribute the circuit onto the given support.
circuit_init_kwargs = deepcopy(circuit.init_kwargs)
circuit_init_kwargs["nqubits"] = self.nqubits
bigcircuit = Circuit(**circuit_init_kwargs)
bigcircuit.add(circuit.on_qubits(*self.qubits))
return bigcircuit

def build_circuit(self, depth: int) -> Circuit:
"""Initiate a ``qibo.models.Circuit`` object and fill it with the wanted gates.
Expand Down
54 changes: 29 additions & 25 deletions src/qibocal/calibrations/niGSC/basics/experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from qibo.models import Circuit
from qibo.noise import NoiseModel

from qibocal.calibrations.niGSC.basics.utils import experiment_directory
from qibocal.cli.utils import generate_output_folder
from qibocal.config import raise_error


Expand Down Expand Up @@ -39,33 +39,25 @@ def __init__(
if circuitfactory is not None and not isinstance(circuitfactory, Iterable):
raise_error(
TypeError,
"given circuit factory has wrong type {}, must be Iterable | None.".format(
type(circuitfactory)
),
f"Invalid type {type(circuitfactory)} for CircuitFactory, must be Iterable or None.",
)
self.circuitfactory = circuitfactory
if data is not None and not isinstance(data, Iterable):
raise_error(
TypeError,
"given data has wrong type {}, must be Iterable | None ".format(
type(data)
),
f"Invalid data type {type(data)}. Data must be Iterable or None.",
)
self.data = data
if nshots is not None and not isinstance(nshots, int):
raise_error(
TypeError,
"given nshots has wrong type {}, must be int | None".format(
type(nshots)
),
f"Invalid nshots type {type(nshots)}. Nshots must be int or None.",
)
self.nshots = nshots
if noise_model is not None and not isinstance(noise_model, NoiseModel):
raise_error(
TypeError,
"given circuit factory has wrong type {}, must be qibo NoiseModel | None .".format(
type(noise_model)
),
f"NoiseModel has wrong type {type(noise_model)}. NoiseModel must be qibo NoiseModel or None .",
)
self.__noise_model = noise_model
self.name = "Abstract"
Expand All @@ -88,8 +80,8 @@ def load(cls, path: str) -> Experiment:
Returns:
Experiment: The object with data (and circuitfactory).
"""
datapath = f"{path}experiment_data.pkl"
circuitspath = f"{path}circuits.pkl"
datapath = f"{path}/experiment_data.pkl"
circuitspath = f"{path}/circuits.pkl"
if isfile(datapath):
with open(datapath, "rb") as f:
data = pickle.load(f)
Expand All @@ -107,28 +99,40 @@ def load(cls, path: str) -> Experiment:
obj = cls(circuitfactory, data=data, nshots=nshots)
return obj

def save(self, path: str | None = None) -> str:
"""Creates a path if None given and pickles relevant data from ``self.data``
and if ``self.circuitfactory`` is a list that one too.
def save_circuits(self, path: str | None = None, force: bool = False) -> str:
"""Creates a path if None given and pickles ``self.circuitfactory``
if it is a list.

Returns:
(str): The path of stored experiment.
"""

# Check if path to store is given, if not create one. If yes check if the last character
# is a /, if not add it.
# Check if path to store is given, if not create one.
if path is None:
self.path = experiment_directory("rb")
self.path = generate_output_folder(path, force)
else:
self.path = path if path[-1] == "/" else f"{path}/"
# Only if the circuit factory is a list it will be stored.
self.path = path
if isinstance(self.circuitfactory, list):
with open(f"{self.path}circuits.pkl", "wb") as f:
with open(f"{self.path}/circuits.pkl", "wb") as f:
pickle.dump(self.circuitfactory, f)
return self.path

def save(self, path: str | None = None, force: bool = False) -> str:
"""Creates a path if None given and pickles relevant data from ``self.data``.

Returns:
(str): The path of stored experiment.
"""

# Check if path to store is given, if not create one.
if path is None:
self.path = generate_output_folder(path, force)
else:
self.path = path
# And only if data is not None the data list (full of dicionaries) will be
# stored.
if self.data is not None:
with open(f"{self.path}experiment_data.pkl", "wb") as f:
with open(f"{self.path}/experiment_data.pkl", "wb") as f:
pickle.dump(self.data, f)
# It is convenient to know the path after storing, so return it.
return self.path
Expand Down
Loading