diff --git a/c3/generator/devices.py b/c3/generator/devices.py index 6e16e724..d3393c2f 100755 --- a/c3/generator/devices.py +++ b/c3/generator/devices.py @@ -1005,19 +1005,18 @@ def __init__(self, **props): super().__init__(**props) self.inputs = props.pop("inputs", 1) self.outputs = props.pop("outputs", 1) - self.signal = None - if ( - self.params["offset_amp"] is None - ): # i.e. it was not set in the general params already - self.params["offset_amp"] = props.pop("offset_amp") - def process(self, instr, chan, signal: List[Dict[str, Any]]) -> Dict[str, Any]: - """Distort signal by adding noise.""" - offset_amp = self.params["offset_amp"].get_value() - out_signal = {} - for k, sig in signal[0].items(): - out_signal[k] = sig + offset_amp - self.signal = out_signal + def process( + self, instr: Instruction, chan: str, signal: List[Dict[str, Any]] + ) -> Dict[str, Any]: + """Distort signal by adding constant noise.""" + components = instr.comps + for comp in components[chan].values(): + if isinstance(comp, Envelope): + offset_amp = comp.params["offset_amp"].get_value() + self.signal["values"] = signal[0]["values"] + offset_amp + self.signal["ts"] = signal[0]["ts"] + self.signal["name"] = instr.name return self.signal diff --git a/c3/libraries/chip.py b/c3/libraries/chip.py index e55c3c60..c7de081e 100755 --- a/c3/libraries/chip.py +++ b/c3/libraries/chip.py @@ -1017,6 +1017,162 @@ def get_potential_function( # pass +@dev_reg_deco +class CooperPairBox(PhysicalComponent): + """ + The CooperPairBox is a basic model of a modifiable Charge qubit. Which is defined by the Hamiltonian: + H=4*EC*(N-NG)^2 + E_J*cos(Phi) + Parameters + ---------- + EC: Quantity + Charge energy of the qubit + EJ: Quantity + Josephon energy of the qubit + NG: Quantity + Initial value of NG in the hamiltonian from above + Asym: Quantity + Parameter that can modify the effective Josephon energy + Reduced Flux: Quantity + Parameter that can modify the effective Josephon energy + hilbert_dim: Quantity + Dimension of the hilbert space we want to look at + calc_dim: Quantity + Number which defines how many charge levels are included in calculating the hamiltonian + """ + + def __init__( + self, + name: str, + desc: str = None, + comment: str = None, + hilbert_dim: int = None, + calc_dim: Quantity = None, + EC: Quantity = None, + EJ: Quantity = None, + NG: Quantity = None, + Asym: Quantity = None, + Reduced_Flux: Quantity = None, + use_FR: bool = True, + params=None, + ): + super().__init__( + name=name, + desc=desc, + comment=comment, + hilbert_dim=hilbert_dim, + params=params, + ) + if not self.hilbert_dim % 2: + raise Exception("Hilbert dimension has to be odd.") + if EC: + self.params["EC"] = EC + if EJ: + self.params["EJ"] = EJ + if Asym: + self.params["Asym"] = Asym + if Reduced_Flux: + self.params["Reduced_Flux"] = Reduced_Flux + if NG: + self.params["NG"] = NG + if calc_dim: + self.params["calc_dim"] = calc_dim + self.init_Hamiltonian() + + def init_Hs(self, ann_oper): + pass + + def init_Ls(self, ann_oper): + pass + + def init_Hamiltonian(self): + """ + initialize Hamiltonian for initial NG + """ + ec = tf.cast(self.params["EC"].get_value(), tf.complex128) + ej = tf.cast(self.params["EJ"].get_value(), tf.complex128) + asym = tf.cast(self.params["Asym"].get_value(), tf.complex128) + reduced_flux = tf.cast(self.params["Reduced_Flux"].get_value(), tf.complex128) + self.calc_dim = tf.cast(self.params["calc_dim"].get_value(), tf.int32) + ng_mat = tf.linalg.diag( + tf.ones(self.calc_dim - 1, tf.complex128), k=1 + ) + tf.linalg.diag(tf.ones(self.calc_dim - 1, tf.complex128), k=-1) + ng = tf.cast(self.params["NG"].get_value(), tf.float64) + EJphi = ej * tf.sqrt( + asym**2 + (1 - asym**2) * tf.math.cos(np.pi * reduced_flux) ** 2 + ) + h = ( + 4 + * ec + * tf.linalg.diag( + tf.cast( + tf.range( + -(self.calc_dim - 1) / 2 - ng, + (self.calc_dim - 1) / 2 - ng + 1, + ) + ** 2, + tf.complex128, + ) + ) + - EJphi / 2 * ng_mat + ) + e, v = tf.linalg.eigh(h) + self.transform = tf.expand_dims(v, axis=0) + self.static_h = tf.linalg.diag(e)[: self.hilbert_dim, : self.hilbert_dim] + self.transform_inv = tf.expand_dims(tf.linalg.inv(v), axis=0) + + def get_Hamiltonian( + self, + signal=None, + transform=None, + ): + """ + Calculate Hamiltonians for a given signal, i.e. at each time step. + Parameters + ---------- + signal : dictionary + Dictionary containing times and values of NG + Returns + ------- + tf.Tensor + Hamiltonians + """ + ec = tf.cast(self.params["EC"].get_value(), tf.complex128) + ej = tf.cast(self.params["EJ"].get_value(), tf.complex128) + asym = tf.cast(self.params["Asym"].get_value(), tf.complex128) + ng = tf.cast(self.params["NG"].get_value(), tf.float64) + reduced_flux = tf.cast(self.params["Reduced_Flux"].get_value(), tf.complex128) + EJphi = ej * tf.sqrt( + asym**2 + (1 - asym**2) * tf.math.cos(np.pi * reduced_flux) ** 2 + ) + ng_mat = ( + EJphi + / 2 + * ( + tf.linalg.diag(tf.ones(self.calc_dim - 1, tf.complex128), k=1) + + tf.linalg.diag(tf.ones(self.calc_dim - 1, tf.complex128), k=-1) + ) + ) + diag_mat_list = tf.range(-(self.calc_dim - 1) / 2, (self.calc_dim - 1) / 2 + 1) + diag_mat = ( + 2 + * tf.sqrt(ec) + * tf.cast(tf.linalg.diag(diag_mat_list), dtype=tf.complex128) + ) + ec_identity = 2 * tf.sqrt(ec) * tf.eye(self.calc_dim, dtype=tf.complex128) + ec_identity = tf.expand_dims(ec_identity, axis=0) + diag_mat = tf.expand_dims(diag_mat, axis=0) + ng_mat = tf.expand_dims(ng_mat, axis=0) + + if signal: + ng_of_t = tf.cast(signal["values"] + ng, dtype=tf.complex128) + ng_of_t = tf.expand_dims(tf.expand_dims(ng_of_t, axis=1), axis=2) + h = (diag_mat - ng_of_t * ec_identity) ** 2 - ng_mat + h = self.transform_inv @ h @ self.transform + return h + else: + return self.static_h + + @dev_reg_deco class SNAIL(Qubit): """ diff --git a/test/CooperPairBox.pickle b/test/CooperPairBox.pickle new file mode 100644 index 00000000..6d9bed06 Binary files /dev/null and b/test/CooperPairBox.pickle differ diff --git a/test/test_CooperPairBox.py b/test/test_CooperPairBox.py new file mode 100644 index 00000000..bdb72456 --- /dev/null +++ b/test/test_CooperPairBox.py @@ -0,0 +1,250 @@ +# System imports +import copy +import numpy as np +import pickle +import pytest +import hjson + +# Main C3 objects +from c3.c3objs import Quantity as Qty, hjson_decode, hjson_encode +from c3.parametermap import ParameterMap as PMap +from c3.experiment import Experiment as Exp +from c3.model import Model as Mdl +from c3.generator.generator import Generator as Gnr + +# Building blocks +import c3.generator.devices as devices +import c3.signal.gates as gates +import c3.libraries.chip as chip +import c3.signal.pulse as pulse + +# Libs and helpers +import c3.libraries.envelopes as envelopes + + +EC = 330e6 +EJ = 14.67e9 + +# define qubit +q1 = chip.CooperPairBox( + name="Q1", + desc="Qubit 1", + EC=Qty(value=EC, min_val=0, max_val=400e9, unit="Hz 2pi"), + EJ=Qty(value=EJ, min_val=0, max_val=30e9, unit="Hz 2pi"), + hilbert_dim=3, + NG=Qty(value=0, min_val=-5, max_val=5, unit=""), + Asym=Qty(value=0, min_val=-1, max_val=1, unit=""), + Reduced_Flux=Qty(value=0, min_val=-1, max_val=1, unit=""), + calc_dim=Qty(value=21, min_val=1, max_val=41, unit=""), +) + +model = Mdl([q1], [], []) +model.use_FR = False +model.lindbladian = False +model.dressed = False + +# define control signals +sim_res = 30e9 +awg_res = 10e9 +lo = devices.LO(name="lo", resolution=sim_res) +awg = devices.AWG(name="awg", resolution=awg_res) +mixer = devices.Mixer(name="mixer") +dig_to_an = devices.DigitalToAnalog(name="dac", resolution=sim_res) + +generator = Gnr( + devices={ + "LO": devices.LO(name="lo", resolution=sim_res, outputs=1), + "AWG": devices.AWG(name="awg", resolution=awg_res, outputs=1), + "DigitalToAnalog": devices.DigitalToAnalog( + name="dac", resolution=sim_res, inputs=1, outputs=1 + ), + "Mixer": devices.Mixer(name="mixer", inputs=2, outputs=1), + "DC-Offset": devices.DC_Offset(name="DC-Offset", inputs=1, outputs=1), + }, + chains={ + "Q1": { + "LO": [], + "AWG": [], + "DigitalToAnalog": ["AWG"], + "Mixer": ["LO", "DigitalToAnalog"], + "DC-Offset": ["Mixer"], + }, + }, +) + +carrier_parameters = { + "freq": Qty( + value=5381.790179180943e6, + min_val=4.5e9, + max_val=7.5e9, + unit="Hz 2pi", + ), + "framechange": Qty(value=0, min_val=-np.pi, max_val=3 * np.pi, unit="rad"), +} +carr = pulse.Carrier( + name="carrier", desc="Frequency of the local oscillator", params=carrier_parameters +) + + +t_final = 100e-9 +gauss_params_single = { + "amp": Qty(value=0.00149758282986231 / 2, min_val=-0.01, max_val=0.01, unit="V"), + "t_final": Qty( + value=t_final, min_val=0.5 * t_final, max_val=1.5 * t_final, unit="s" + ), + "sigma": Qty(value=t_final / 4, min_val=t_final / 8, max_val=t_final / 2, unit="s"), + "xy_angle": Qty(value=0.04, min_val=-0.5 * np.pi, max_val=2.5 * np.pi, unit="rad"), + "freq_offset": Qty(value=-1e1, min_val=-50 * 1e6, max_val=50 * 1e6, unit="Hz 2pi"), + "delta": Qty( + value=480, min_val=-1.2e5, max_val=1.2e5, unit="" # ,10,#10e9/(g12-f12), + ), + "offset_amp": Qty(value=0, min_val=0, max_val=1, unit="V"), + "t_rise": Qty(value=1e-9, min_val=0, max_val=t_final / 2.0, unit="s"), + "t_bin_start": Qty(value=0.0, min_val=0, max_val=1, unit=""), + "t_bin_end": Qty(value=1499, min_val=-1, max_val=1e9, unit=""), + "inphase": Qty(value=0, min_val=-1, max_val=1, unit=""), +} + + +gauss_env_single = pulse.Envelope( + name="gauss", + desc="Gaussian comp for single-qubit gates", + params=gauss_params_single, + shape=envelopes.gaussian_nonorm, +) + + +instr1 = gates.Instruction( + name="instr1", + targets=[0], + t_start=0.0, + t_end=t_final, + channels=["Q1"], + ideal=np.array([[1, 0, 0], [0, 0, 1], [0, 1, 0]]), +) +instr1.add_component(copy.deepcopy(gauss_env_single), "Q1") +instr1.add_component(carr, "Q1") + +instr2 = gates.Instruction( + name="instr2", + targets=[0], + t_start=0.0, + t_end=t_final, + channels=["Q1"], + ideal=np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]), +) +instr2.add_component(copy.deepcopy(gauss_env_single), "Q1") +instr2.add_component(copy.deepcopy(carr), "Q1") + +instr2.comps["Q1"]["gauss"].params["offset_amp"].set_value(0.5) + +# Make Experiment +parameter_map = PMap(instructions=[instr1, instr2], model=model, generator=generator) +exp = Exp(pmap=parameter_map, sim_res=sim_res) +exp.use_control_fields = False +exp.stop_partial_propagator_gradient = False + +gen_signal1 = generator.generate_signals(instr1) +gen_signal2 = generator.generate_signals(instr2) + +test_data = {} + +with open("test/CooperPairBox.pickle", "rb") as filename: + data = pickle.load(filename) + + +@pytest.mark.integration +def test_signals(): + test_data["instr1"] = gen_signal1["Q1"] + test_data["instr2"] = gen_signal2["Q1"] + np.testing.assert_allclose( + actual=test_data["instr1"]["ts"], + desired=data["instr1"]["ts"], + atol=1e-11 * np.max(data["instr1"]["ts"]), + ) + np.testing.assert_allclose( + actual=test_data["instr1"]["values"], + desired=data["instr1"]["values"], + atol=1e-11 * np.max(data["instr1"]["values"]), + ) + np.testing.assert_allclose( + actual=test_data["instr2"]["ts"], + desired=data["instr2"]["ts"], + atol=1e-11 * np.max(data["instr2"]["ts"]), + ) + np.testing.assert_allclose( + actual=test_data["instr2"]["values"].numpy(), + desired=data["instr2"]["values"].numpy(), + atol=1e-11 * np.max(data["instr"]["values"]), + ) + + +@pytest.mark.integration +def test_static_hamiltonian(): + test_data["static_h"] = model.get_Hamiltonian() + np.testing.assert_allclose(actual=test_data["static_h"], desired=data["static_h"]) + + +@pytest.mark.integration +def test_hamiltonians(): + test_data["hamiltonians_instr1"] = model.get_Hamiltonian(gen_signal1) + test_data["hamiltonians_instr2"] = model.get_Hamiltonian(gen_signal2) + + np.testing.assert_allclose( + actual=test_data["hamiltonians_instr1"], + desired=data["hamiltonians_instr1"], + atol=1e-9 * np.max(data["hamiltonians_instr1"]), + ) + + np.testing.assert_allclose( + actual=test_data["hamiltonians_instr2"], + desired=data["hamiltonians_instr2"], + atol=1e-11 * np.max(data["hamiltonians_instr2"]), + ) + + +@pytest.mark.integration +def test_propagation(): + exp.set_opt_gates("instr1") + exp.compute_propagators() + test_data["propagators_1"] = exp.propagators["instr1"] + test_data["partial_propagators_1"] = exp.partial_propagators["instr1"] + exp.set_opt_gates("instr2") + exp.compute_propagators() + test_data["propagators_2"] = exp.propagators["instr2"] + test_data["partial_propagators_2"] = exp.partial_propagators["instr2"] + + np.testing.assert_allclose( + actual=test_data["propagators_1"], + desired=data["propagators_1"], + atol=1e-11 * np.max(data["propagators_1"]), + ) + np.testing.assert_allclose( + actual=test_data["partial_propagators_1"], + desired=data["partial_propagators_1"], + atol=1e-11 * np.max(data["partial_propagators_1"]), + ) + + np.testing.assert_allclose( + actual=test_data["propagators_2"], + desired=data["propagators_2"], + atol=1e-11 * np.max(data["propagators_2"]), + ) + np.testing.assert_allclose( + actual=test_data["partial_propagators_2"], + desired=data["partial_propagators_2"], + atol=1e-11 * np.max(data["partial_propagators_2"]), + ) + + +@pytest.mark.unit +def test_save_and_load(): + exp.compute_propagators() + propagators = exp.propagators + cfg_str = hjson.dumpsJSON(exp.asdict(), default=hjson_encode) + cfg_dct = hjson.loads(cfg_str, object_pairs_hook=hjson_decode) + exp2 = Exp(sim_res=sim_res) + exp2.from_dict(cfg_dct) + exp2.compute_propagators() + for k in propagators: + np.testing.assert_allclose(exp2.propagators[k], propagators[k])