Releases: qilimanjaro-tech/qililab
0.20.3
Hackathon/hotfixes (#413) * fix save results * empty programs hotfix * clean sequenes * circuit_to_pulses fix * black formatting * unittest for execute * updated changelog * black formatting * changelog updated * isort test * cleaning unittest * black reformat * fixing unittest for mocking all functions called * another fix * add save_results flag * fix * fix tests --------- Co-authored-by: AlbertMitjans <a.mitjanscoma@gmail.com>
0.20.2
- Set the
repetition_duration
to 200k in theql.execute
function.
0.20.1
Fixed some issues.
0.20.0
New features since last release
-
Added to the
draw()
method the option of specifying if you want:- modulation or not,
- plot the real, iamginary, absolute or any combination of these options
- specify the type of plotline to use, such as "-", ".", "--", ":"...
for the classesExperimental
andExecutionManager
, like:
# Option 1 (Default) figure = sample_experiment.draw( real=True, imag=True, abs=False, modulation=True, linestyle="-" ) # Option 2 (Equivalent to option 1) figure = sample_experiment.draw() # Option 3 (something different, only plotting the envelopes without modulation) figure = sample_experiment.draw(modulation=False, linestyle=".") plt.show()
-
Added
lambda_2
attribute to thecosine.py
module containing theCosine
pulse_shape, modifying the previous A/2*(1-cos(x)).
Into a more general A/2*(1-lambda_1cos(phi)-lambda_2cos(2phi)), giving a modified sinusoidal-gaussian.-
lambda_1 cosine A/2*(1-cos(x)): Starts at height 0 (phase=0), maximum height A (phase=pi)
and ends at height 0 (phase=2pi). Which is a sinusoidal like gaussian. -
lambda_2 cosine A/2*(1-cos(2x)): Starts at height 0 (phase=0), maximum height A (phase=pi/2)
then another height 0 in the middle at phase=pi, then another maximum height A (phase=3/2pi)
and ends at height 0 (phase=2pi).
For more info check the docstring and the following references:
- Supplemental material B. "Flux pulse parametrization" at [https://arxiv.org/abs/1903.02492%5C%5D],
- OPTIMAL SOLUTION: SMALL CHANGE IN θ at [https://arxiv.org/abs/1402.5467%5C%5D]
-
-
Added user integration for
pulse_distortions
. Now they can be used writing them in the Buses of the runcards:buses: - id_: 0 category: bus alias: feedline_bus system_control: id_: 0 name: readout_system_control category: system_control instruments: [QRM1, rs_1] port: 100 distortions: # <-- new line - name: bias_tee # <-- new line tau_bias_tee: 1.0 # <-- new line - name: lfilter # <-- new line a: [0.1, 1.1] # <-- new line b: [1.1, 1.3] # <-- new line - id_: 10 category: bus alias: drive_line_q0_bus system_control: id_: 10 name: system_control category: system_control instruments: [QCM-RF1] port: 10 distortions: [] # <-- new line
-
Added CZ gate support, 2 qubit gate support to
circuit_to_pulse
and corresponding definitions to the runcard.CZ implements a Sudden Net Zero (SNZ) pulse through the flux line as well as a parking gate (if defined in the runcard)
to adjacent qubits with lower frequency than CZ's target qubit.
For the parking gate, if the time is greater than the CZ pulse, the extra time is added as padding at the beginning/end
of the pulse.
The parameters for the CZ in the runcard are amplitude, duration of the halfpulse; and for the CZ's snz pulse b
(impulse between halfpulses) and t_phi (time between halfpulses without accounting for b)Example:
gates: 1: - name: Park amplitude: 1.0 phase: 0 duration: 103 shape: name: rectangular (0,2): - name: CZ amplitude: 1.0 phase: duration: 40 shape: name: snz b: 0.5 t_phi: 1
In the example above, if qubit 1 is connected to 2 and has lower frequency, there will be an attempt to apply a parking
pulse. If a Park gate definition is found for qubit 1, then a parking pulse will be applied.
The total duration of the CZ gate above will be 2*duration + t_phi + 2 = 83 (each b has 1ns duration and there are 2 bs).
Thus the parking gate lasts for some extra 20ns which will result in 10ns 'pad time' in the parking gate before and after
the SNZ pulse.
Note that the order of the qubits in the CZ is important even if the gate is symmetric, because the second qubit will be
the target for the SNZ pulse.
#369 -
Added
cosine.py
module containing aCosine
child class ofpulse_shape
, which gives a sinusoidal like gaussian A/2*(1-cos(x)).
The shape starts at height 0 (phase=0), maximum height A (phase=pi) and ends at height 0 (phase=2pi)pulse = Pulse( amplitude=..., phase=..., duration=..., frequency=..., pulse_shape=Cosine(), )
-
Added
pulse.pulse_distortion.lfilter_correction.py
module, which is another child class for thepulse.pulse_distortion
package.distorted_envelope = LFilter(norm_factor=1.2, a=[0.7, 1.3], b=[0.5, 0.6]).apply( original_envelopes )
Also adds a phase property to
PulseEvent
and implementsFactory.get
directly in thefrom_dict()
method of the parent classPulseDistortion
. -
Added
get_port_from_qubit_idx
method toChip
class. This method takes the qubit index and the line type as arguments and returns the associated port.
#362 -
Added
pulse.pulse_distortion
package, which contains a modulepulse_distortion.py
with the base class to distort envelopes inPulseEvent
, and two modulesbias_tee_correction.py
andexponential_decay_correction.py
, each containing examples of distortion child classes to apply. This new feature can be used in two ways, directly from the class itself:distorted_envelope = BiasTeeCorrection(tau_bias_tee=1.0).apply(original_envelope)
or from the class PulseEvent (which ends up calling the previous one):
pulse_event = PulseEvent( pulse="example_pulse", start_time="example_start", distortions=[ BiasTeeCorrection(tau_bias_tee=1.0), BiasTeeCorrection(tau_bias_tee=0.8), ], ) distorted_envelope = pulse_event.envelope()
This would apply them like: BiasTeeCorrection_0.8(BiasTeeCorrection_1.0(original_pulse)), so the first one gets applied first and so on...
(If you write their composition, it will be in reverse order respect the list)Also along the way modified/refactored the to_dict() and from_dict() and envelope() methods of PulseEvent, Pulse, PulseShape... since they had some bugs, such as:
- the dict() methods edited the external dictionaries making them unusable two times.
- the maximum of the envelopes didn't correspond to the given amplitude.
-
The
QbloxQCMRF
module has been added. To use it, please use theQCM-RF
name inside the runcard:- name: QCM-RF alias: QCM-RF0 id_: 2 category: awg firmware: 0.7.0 num_sequencers: 1 out0_lo_freq: 3700000000 # <-- new line out0_lo_en: true # <-- new line out0_att: 10 # <-- new line out0_offset_path0: 0.2 # <-- new line out0_offset_path1: 0.07 # <-- new line out1_lo_freq: 3900000000 # <-- new line out1_lo_en: true # <-- new line out1_att: 6 # <-- new line out1_offset_path0: 0.1 # <-- new line out1_offset_path1: 0.6 # <-- new line awg_sequencers: ...
-
The
QbloxQRMRF
module has been added. To use it, please use theQRM-RF
name inside the runcard:- name: QRM-RF alias: QRM-RF0 id_: 0 category: awg firmware: 0.7.0 num_sequencers: 1 out0_in0_lo_freq: 3000000000 # <-- new line out0_in0_lo_en: true # <-- new line out0_att: 34 # <-- new line in0_att: 28 # <-- new line out0_offset_path0: 0.123 # <-- new line out0_offset_path1: 1.234 # <-- new line acquisition_delay_time: 100 awg_sequencers: ...
Improvements
-
The
get_bus_by_qubit_index
method ofPlatform
class now returns a tuple of three buses:flux_bus, control_bux, readout_bus
.
#362 -
Arbitrary mapping of I/Q channels to outputs is now possible with the Qblox driver. When using a mapping that is not
possible in hardware, the waveforms of the corresponding paths are swapped (in software) to allow it. For example,
when loading a runcard with the following sequencer mapping a warning should be raised:awg_sequencers: - identifier: 0 output_i: 1 output_q: 0
>>> platform = build_platform(name=runcard_name) [qililab] [0.16.1|WARNING|2023-05-09 17:18:51]: Cannot set `output_i=1` and `output_q=0` in hardware. The I/Q signals sent to sequencer 0 will be swapped to allow this setting.
Under the hood, the driver maps
path0 -> output0
andpath1 -> output1
.
When applying an I/Q pulse, it sends the I signal throughpath1
and the Q signal throughpath0
.
#324 -
The versions of the
qblox-instruments
andqpysequence
requirements have been updated to0.9.0
#337 -
Allow uploading negative envelopes on the
QbloxModule
class.
#356 -
The parameter
sync_en
of the Qblox sequencers is now updated automatically...
0.19.0
New features since last release
-
Added
experiment/portfolio/
submodule, which will contain pre-defined experiments and their analysis.
#189 -
Added
ExperimentAnalysis
class, which contains methods used to analyze the results of an experiment.
#189 -
Added
Rabi
portfolio experiment. Here is a usage example:loop_range = np.linspace(start=0, stop=1, step=0.1) rabi = Rabi(platform=platform, qubit=0, range=loop_range) rabi.turn_on_instruments() bus_parameters = {Parameter.GAIN: 0.8, Parameter.FREQUENCY: 5e9} rabi.bus_setup(bus_parameters, control=True) # set parameters of control bus x_parameters = {Parameter.DURATION: 40} rabi.gate_setup(x_parameters, gate="X") # set parameters of X gate rabi.build_execution() results = rabi.run() # all the returned values are also saved inside the `Rabi` class! post_processed_results = rabi.post_process_results() fitted_parameters = rabi.fit() fig = rabi.plot()
-
Added
FlippingSequence
portfolio experiment.
#245 -
Added
get_bus_by_qubit_index
method to thePlatform
class.
#189 -
Added
circuit
module, which contains everything related to Qililab's internal circuit representation.The module contains the following submodules:
circuit/converters
: Contains classes that can convert from external circuit representation to Qililab's internal circuit representation and vice versa.circuit/nodes
: Contains classes representing graph nodes that are used in circuit's internal graph data structure.circuit/operations
: Contains classes representing operations that can be added to the circuit.
#175
-
Added
Circuit
class for representing quantum circuits. The class stores the quantum circuit as a directed acyclic graph (DAG) using therustworkx
library for manipulation. It offers methods to add operations to the circuit, calculate the circuit's depth, and visualize the circuit. Example usage:# create a Circuit with two qubits circuit = Circuit(2) # add operations for qubit in [0, 1]: circuit.add(qubit, X()) circuit.add(0, Wait(t=100)) circuit.add(0, X()) circuit.add((0, 1), Measure()) # print depth of circuit print(f"Depth: {circuit.depth}") # print circuit circuit.print() # draw circuit's graph circuit.draw()
-
Added
OperationFactory
class to register and retrieve operation classes based on their names.
#175 -
Added
CircuitTranspiler
class for calculating operation timings and transpiling quantum circuits into pulse operations. Thecalculate_timings()
method annotates operations in the circuit with timing information by evaluating start and end times for each operation. Theremove_special_operations()
method removes special operations (Barrier, Wait, Passive Reset) from the circuit after the timings have been calculated. Thetranspile_to_pulse_operations()
method then transpiles the quantum circuit operations into pulse operations, taking into account the calculated timings. Example usage:# create the transpiler transpiler = CircuitTranspiler(settings=platform.settings) # calculate timings circuit_ir1 = transpiler.calculate_timings(circuit) # remove special operations circuit_ir2 = transpiler.remove_special_operations(circuit_ir1) # transpile operations to pulse operations circuit_ir3 = transpiler.transpile_to_pulse_operations(circuit_ir2)
-
Added
QiliQasmConverter
class to convert a circuit from/to QiliQASM, an over-simplified QASM version. Example usage:# Convert to QiliQASM qasm = QiliQasmConverter.to_qasm(circuit) print(qasm) # Parse from QiliQASM parsed_circuit = QiliQasmConverter.from_qasm(qasm)
-
Pulses with different frequencies will be automatically sent and read by different sequencers (multiplexed readout).
#242 -
Added an optional parameter "frequency" to the "modulated_waveforms" method of the Pulse and PulseEvent classes, allowing for specification of a modulation frequency different from that of the Pulse.
#242 -
Added
values
andchannel_id
attribute to theLoop
class.
Here is an example on how a loop is created now:new_loop = Loop(alias="loop", parameter=Parameter.POWER, values=np.linspace(1, 10, 10))
-
Gate settings can be set for each qubit individually, or tuple of qubits in case of two-qubit gates.
Example of updated runcard schema:gates: 0: - name: M amplitude: 1.0 phase: 0 duration: 6000 shape: name: rectangular - name: X amplitude: 1.0 phase: 0 duration: 100 shape: name: gaussian num_sigmas: 4 1: - name: M amplitude: 1.0 phase: 0 duration: 6000 shape: name: rectangular - name: X amplitude: 1.0 phase: 0 duration: 100 shape: name: gaussian num_sigmas: 4 (0,1): - name: CPhase amplitude: 1.0 phase: 0 duration: 6000 shape: name: rectangular
To change settings with set_parameter methods, use the alias format "GATE(QUBITs)". For example:
platform.set_parameter(alias="X(0)", parameter=Parameter.DURATION, value=40) platform.set_parameter(alias="CPhase(0,1)", parameter=Parameter.DURATION, value=80)
-
Weighted acquisition is supported. The weight arrays are set as sequencer parameters
weights_path0
andweights_path1
, and the weighed acquisition can be enabled setting the sequencer parameterweighed_acq_enabled
totrue
. Note: theintegration_length
parameter will be ignored ifweighed_acq_enabled
is set totrue
, and the length of the weights arrays will be used instead.awg_sequencers: - identifier: 0 chip_port_id: 1 intermediate_frequency: 1.e+08 weights_path0: [0.98, ...] # <-- new line weights_path1: [0.72, ...] # <-- new line weighed_acq_enabled: true # <-- new line threshold: 0.5 # <-- new line
-
Result
,Results
andAcquisitions
classes implement thecounts
method, which returns a dictionary-like object containing the counts of each measurement based in the discretization of the instrument via thethreshold
sequencer parameter. Alternatively, theprobabilities
method can also be used, which returns a normalized version of the same counts object.>>> counts = result.counts() Counts: {'00': 502, '01': 23, '10': 19, '11': 480} >>> probabilities = result.probabilities() Probabilities: {'00': 0.49023438, '01': 0.02246094, '10': 0.01855469, '11': 0.46875}
Improvements
-
Return an integer (instead of the
Port
class) when callingChip.get_port
. This is to avoid using the private
id_
attribute of thePort
class to obtain the port index.
#189 -
The asynchronous data handling used to save results and send data to the live plotting has been improved. Now we are
saving the results in a queue, and there is only ONE thread which retrieves the results from the queue, sends them to
the live plotting and saves them to a file.
#282 -
The asynchronous data handling used to save results and send data to the live plotting has been improved.
Previously we only had ONE active thread retrieving the results and saving them but was created and killed after processing one result of the totalResults
object. Now we are creating the thread just ONCE, so threading is handled at theExperiment
level instead of what was done previously at theExution_Manager
level.
#298
Breaking changes
-
draw()
method ofCircuit
uses Graphviz internally. To be able to call the method Graphviz must be installed. In Ubuntu-based distros a simplesudo apt-get install graphviz
is sufficient. For detailed installation information for your OS please consult Graphviz's installation page.
#175 -
gates
property of runcard must be updated to provide a list of gate settings for each qubit individually.
#292
Deprecations / Removals
-
The
Execution
class has been removed. Its functionality is now added to theExecutionManager
class.
Please useExecutionManager
instead. TheExecutionBuilder
returns now an instance ofExecutionManager
.
#246 -
The
LoopOptions
class has been removed. It was used to cr...
0.18.0
New features since last release
-
Added
Experiment.compile
method, which return the compiled experiment for debugging purposes.
#225>>> sequences = experiment.compile()
This method returns a list of dictionaries (one dictionary per circuit executed in the experiment). Each dictionary
contains the sequences uploaded for each bus:>>> sequences[0] {'drive_line_bus': [qpysequence_1], 'feedline_input_output_bus': [qpysequence_2]}
This experiment is using two buses (
drive_line_bus
andfeedling_input_output_bus
), which have a list of the uploaded sequences
(in this case only 1).We can then obtain the program of such sequences:
>>> sequences[0]["drive_line_bus"][0]._program setup: move 1000, R0 wait_sync 4 average: reset_ph play 0, 1, 4 long_wait_1: move 3, R1 long_wait_1_loop: wait 65532 loop R1, @long_wait_1_loop wait 3400 loop R0, @average stop: stop
-
Added support for setting output offsets of a qblox module.
#199
Improvements
-
Added support for the execution of pulses with durations that are not multiples of 4. For this, a new
minimum_clock_time
attribute has been added to the runcard:settings: id_: 0 category: platform name: spectro_v_flux device_id: 9 minimum_clock_time: 4 # <-- new line!
When a pulse has a duration that is not a multiple of the
minimum_clock_time
, a padding of 0s is added after the pulse to make sure the next pulse falls within a multiple of 4.
#227 -
Change name
PulseScheduledBus
toBusExecution
.
#225 -
Separate
generate_program_and_upload
into two methods:compile
andupload
. From now on, when executing a
single circuit, all the pulses of each bus will be compiled first, and then uploaded to the instruments.
#225 -
Make
nshots
andrepetition_duration
dynamic attributes of theQbloxModule
class. When any of these two settings
changes, the cache is emptied to make sure new programs are compiled.
#225 -
Added methods to
PulseBusSchedule
for multiplexing: #236frequencies()
returns a list of the frequencies in the schedule.with_frequency(frequency: float)
returns a newPulseBusSchedule
containing only those events at that frequency.
-
Pulse
,PulseEvent
,PulseShapes
and child classes are now immutable.
#236
Breaking changes
-
An
out_offsets
attribute has been added to the settings of aQbloxModule
object. This attribute contains a list
of the offsets applied to each output. The runcard should be updated to contain this new information:instruments: - name: QRM alias: QRM id_: 1 category: awg firmware: 0.7.0 num_sequencers: 1 acquisition_delay_time: 100 out_offsets: [0.3, 0, 0, 0] # <-- this new line needs to be added to the runcard! awg_sequencers: ...
Deprecations / Removals
- Removed the
AWG.frequency
attribute because it was not used.
#225 - Removed
ReadoutPulse
andReadoutEvent
.Pulse
andPulseEvent
have to be used instead.
#236 - Removed
add(Pulse)
method fromPulseBusSchedule
andPulseSchedule
.add_event(PulseEvent)
has to be used instead.
#236
Bug fixes
0.17.0
New features since last release
Improvements
-
Cast
chip
dictionary into theChipSchema
class and remove unusedInstrumentControllerSchema
class.
#187 -
Upload sequence directly to Qblox instruments, without having to save & load a
json
file.
#197 -
Changed
schedule_index_to_load
argument toidx
for more readability.
#192 -
Refactored the
Experiment
class, creating a method for each step of the workflow. TheExperiment.execute
method will run all these methods in order:
#192connect
: Connect to the instruments and block the device.initial_setup
: Apply runcard settings to the instruments.build_execution
:- Translate the circuit into pulses.
- Create the
Execution
class (which contains all the buses with the pulses to execute). - Initialize the live plotting.
- Create the
Results
class and theresults.yml
file (where the results will be stored).
turn_on_instruments
: Turn on the instruments (if necessary).run
: Iterate over all the loop values, and for each step:- Generate the program.
- Upload the program.
- Execute the program.
- Save the result to the
results.yml
file. - Send data to live plotting.
- Save the result to the
Experiment.results
attribute.
turn_off_instruments
: Turn off the instruments (if necessary).disconnect
: Disconnect from the platform and release the device.remote_save_experiment
: Ifremote_save = True
, save the experiment and the results to the database.
-
When translating a Circuit into pulses, the target qubit/resonator frequency is now used to initialize the
corresponding pulse.
#209
Breaking changes
-
Removed context manager from
Execution
class. Users will be responsible for turning off and disconnecting the
instruments when not using theexecute
method directly!
#192 -
Removed the
ExecutionOptions
class. Now the user can freely choose which steps of the workflow to execute.
#192 -
Removed the
Platform.connect_and_initial_setup
method.
#192 -
Move
connection
anddevice_id
information into thePlatform
class. Now users should adddevice_id
inside
the runcard and add aconnection
argument when callingbuild_platform
:
#211platform = build_platform(name=runcard_name, connection=connection) platform.connect(manual_override=False)
-
Removed the frequency argument from the
Pulse.modulated_waveforms
method (and all the methods that uses this method
internally). Removefrequency
property from certain buses.
#209 -
The
Pulse.frequency
argument is now mandatory to initialize a pulse.
#209
Deprecations / Removals
-
Removed the
ExecutionPreparation
class and theresults_data_management.py
file, and replace it with a
prepare_results
method inside theExperiment
class.
#192 -
Removed unused
connect
,disconnect
andsetup
methods from theExecution
class. These are used in the
Experiment
class, which call the corresponding methods of thePlatform
class.
#192 -
Removed the
RemoteAPI
class. This class didn't add any functionality.
#192 -
Removed all the
Bus
andSystemControl
types. Now there is only a genericBus
, that can contain a
SystemControl
,ReadoutSystemControl
(which contain a list of instruments to control) orSimulatedSystemControl
,
which is used to control simulated quantum systems.
#210
Bug fixes
-
Fixed wrong timing calculation in Q1ASM generation
#186 -
Fix bug where calling
set_parameter
withParameter.DRAG_COEFFICIENT
would raise an error.
#187 -
The
qibo
version has been downgraded to0.1.10
to allow installation on Mac laptops.
#185 -
Fixed the
Platform.get_element
method:
#192- Now calling
get_element
of a gate returns aGateSettings
instance, instead of aPlatformSettings
instance. - Add try/except over the
chip.get_node_from_alias
method to avoid getting an error if the node doesn't exist.
- Now calling