Skip to content

Commit

Permalink
Merge branch 'master' of github.com:qutech/qc-toolkit
Browse files Browse the repository at this point in the history
Conflicts:
	qctoolkit/pulses/function_pulse_template.py
  • Loading branch information
lumip committed Dec 7, 2015
2 parents d971fe8 + f763202 commit 0182a02
Show file tree
Hide file tree
Showing 11 changed files with 190 additions and 70 deletions.
34 changes: 34 additions & 0 deletions MATLAB/convert_qctoolkit.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
function pulse_group = convert_qctoolkit(qct_output)
% pulse_group = convert_qctoolkit(qct_output)
%
% Registers pulses and converts pulse group data obtained from the qc-toolkit.
%
% qct_output: The output tuple of the
% PulseControlInterface.create_pulse_group() method.

qct_pulses = qct_output{2};

% Convert Python dicts of pulses to pulse control waveform pulse structs
% and register them using plsreg. Remember index in pulse database.
pulse_indices = zeros(size(qct_pulses, 2));
for i = 1:size(qct_pulses, 2)
pulse = struct(qct_pulses{i});
pulse.name = arrayfun(@char, pulse.name);
pulse.data = struct(pulse.data);
pulse.data.marker = cell2mat(cell(pulse.data.marker));
pulse.data.wf = cell2mat(cell(pulse.data.wf));
pulse_indices(i) = plsreg(pulse);
end

% Convert Python dict of pulse group to pulse control struct.
% Replace pulse indices in pulse_group.pulses with the indices of the
% pulses in the pulse database (plsdata).
pulse_group = struct(qct_output{1});
pulse_group.chan = double(pulse_group.chan);
pulse_group.name = arrayfun(@char, pulse_group.name);
pulse_group.ctrl = arrayfun(@char, pulse_group.ctrl);
pulse_group.nrep = cellfun(@double, cell(pulse_group.nrep));
pulse_group.pulses = cellfun(@double, cell(pulse_group.pulses));
for i = 1:size(pulse_group.pulses, 2)
pulse_group.pulses(i) = pulse_indices(pulse_group.pulses(i) + 1);
end
46 changes: 46 additions & 0 deletions MATLAB/qctoolkitExample.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
clear

% Setup temporary plsdata
global plsdata;
plsdata = [];
plsdata.datafile = [tempdir 'hardwaretest\plsdata_hw'];
plsdata.grpdir = [tempdir 'hardwaretest\plsdef\plsgrp'];
try
rmdir([tempdir 'hardwaretest'],'s');
end
mkdir(plsdata.grpdir);

plsdata.pulses = struct('data', {}, 'name', {}, 'xval',{}, 'taurc',{}, 'pardef',{},'trafofn',{},'format',{});
plsdata.tbase = 1000;

% Define some TablePulseTemplates
table_pulse_1 = py.qctoolkit.pulses.TablePulseTemplate();
table_pulse_1.add_entry('foo', 10);
table_pulse_1.add_entry(100, 0);

table_pulse_2 = py.qctoolkit.pulses.TablePulseTemplate();
table_pulse_2.add_entry(25, -10);
table_pulse_2.add_entry(50, 0);


% Build a sequence of TablePulseTemplates with given parameters
sequencer = py.qctoolkit.pulses.Sequencer();

clear parameters;
parameters.foo = 45;

sequencer.push(table_pulse_1, parameters);
sequencer.push(table_pulse_2);
parameters.foo = 80;
sequencer.push(table_pulse_1, parameters);
sequencer.push(table_pulse_1, parameters);

block = sequencer.build();
sequence = block.compile_sequence();

% Convert the instruction sequence to pulse_control pulses and a
% pulse_group
pci = py.qctoolkit.qcmatlab.pulse_control.PulseControlInterface(1e3);
qct_output = pci.create_pulse_group(sequence, 'foo');

pulse_group = convert_qctoolkit(qct_output);
21 changes: 13 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
# qc-toolkit: Quantum Computing Toolkit
[![Coverage Status](https://coveralls.io/repos/qutech/qc-toolkit/badge.svg?branch=master&service=github)](https://coveralls.io/github/qutech/qc-toolkit?branch=master)
[![Build Status](https://travis-ci.org/qutech/qc-toolkit.svg?branch=master)](https://travis-ci.org/qutech/qc-toolkit)
[![Documentation Status](https://readthedocs.org/projects/qc-toolkit/badge/?version=latest)](http://qc-toolkit.readthedocs.org/en/latest/?badge=latest)


The qc-toolkit project aims to produce a software toolkit facilitating experiments (or productive use of) involving pulse driven state manipulation of physical qubits.
The qc-toolkit project aims to produce a software toolkit facilitating experiments involving pulse driven state manipulation of physical qubits.
It will provide a hardware independent object representation of pulses and pulse templates as well as means to translate this representation to hardware instructions, execute these instructions and perform corresponding measurements.
Pulses may be as complex as specifying conditional branching/looping and the object representation features easy reuse of previously defined pulse templates.

## Status
Note that the project is in early development and thus neither feature-complete nor necessarily bug free. Additionally, interfaces and design decisions are subject to change.

## Installation
qc-toolkit is developed using Python 3.5 but should also run on previous 3.x versions (without guarantee) if the [typing module](https://github.com/JukkaL/typing) is installed.
Currently, there are no other required dependencies on external modules.
The optional script tests/utils/syntax_check.py invokes pyflakes to perform a static code analysis, so pyflakes should be installed if its usage is intended.
qc-toolkit is developed using Python 3.5 but should also run on previous 3.3+ versions.

The package is installed with:
```
python3 setup.py install
```

The optional script *tests/utils/syntax_check.py* invokes pyflakes to perform a static code analysis, so pyflakes should be installed if its usage is intended.

## Folder Structure
The repository primarily consists of the folders *src* and *tests*.
The repository primarily consists of the folders *qctoolkit* and *tests*.

*src* contains the entire source code of the project and is further partitioned into packages of related modules (i.e. a package folder *pulses* which contains all modules related to pulse representation and translation).
*qctoolkit* contains the entire source code of the project and is further partitioned into packages of related modules (i.e. a package folder *pulses* which contains all modules related to pulse representation and translation).

Contents of *tests* mirror the structure of *src*. For every *<module>.py* somewhere in *src* there should exist a *<module>Tests.py* in the corresponding subdirectory of *tests*.
Contents of *tests* mirror the structure of *qctoolkit*. For every *<module>* somewhere in *qctoolkit* there should exist a *<module>Tests.py* in the corresponding subdirectory of *tests*.
7 changes: 3 additions & 4 deletions qctoolkit/expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from typing import Any, Dict, Iterable, Optional

from qctoolkit.serialization import Serializable
from qctoolkit.pulses.parameters import Parameter

__all__ = ["Expression"]

Expand All @@ -25,11 +24,11 @@ def string(self) -> str:
def variables(self) -> Iterable[str]:
return self.__expression.variables()

def evaluate(self, parameters: Dict[str, Parameter]) -> float:
def evaluate(self, **kwargs) -> float:
if USE_NUMEXPR:
return numexpr.evaluate(self.__string, global_dict={}, local_dict=parameters)
return numexpr.evaluate(self.__string, global_dict={}, local_dict=kwargs)
else:
return self.__expression.evaluate(parameters)
return self.__expression.evaluate(kwargs)

def get_serialization_data(self, serializer: 'Serializer') -> Dict[str, Any]:
return dict(type='Expression', expression=self.__string)
Expand Down
25 changes: 12 additions & 13 deletions qctoolkit/pulses/function_pulse_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def get_pulse_length(self, parameters) -> float:
missing = self.__parameter_names - set(parameters.keys())
for m in missing:
raise ParameterNotProvidedException(m)
return self.__duration_expression.evaluate(parameters)
return self.__duration_expression.evaluate(**parameters)

def get_measurement_windows(self, parameters: Optional[Dict[str, Parameter]] = {}) -> List[MeasurementWindow]:
"""Return all measurement windows defined in this PulseTemplate.
Expand Down Expand Up @@ -103,26 +103,25 @@ class FunctionWaveform(Waveform):
def __init__(self, parameters: Dict[str, Parameter], expression: Expression, duration_expression: Expression) -> None:
super().__init__()
self.__expression = expression
self.__duration_expression = duration_expression
self.__parameters = parameters

self.__duration_expression = duration_expression
self.__partial_expression = self.__partial

def __partial (self,t):
params = self.__parameters.copy()
params.update({"t":t})
return self.__expression.evaluate(**params)

@property
def _compare_key(self) -> Any:
return self.__expression

@property
def duration(self) -> float:
return self.__duration_expression.evaluate(self.__parameters)
return self.__duration_expression.evaluate(**self.__parameters)

def sample(self, sample_times: np.ndarray, first_offset: float=0) -> np.ndarray:
# TODO: implement this

voltages = np.empty_like(sample_times)
sample_times -= (sample_times[0] - first_offset)

#voltages[sample_times] = self.__expression(self.__parameters.update("t":sample_times))

#for entry1, entry2 in zip(self.__table[:-1], self.__table[1:]): # iterate over interpolated areas
# indices = np.logical_and(sample_times >= entry1.t, sample_times <= entry2.t)
# voltages[indices] = entry2.interp((entry1.t, entry1.v), (entry2.t, entry2.v), sample_times[indices]) # evaluate interpolation at each time
func = np.vectorize(self.__partial_expression)
voltages = func(sample_times)
return voltages
7 changes: 0 additions & 7 deletions qctoolkit/qcmatlab/manager.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import matlab.engine
""" Start a MATLAB session or connect to a connect to an existing one. """

from .pulse_control import PulseControlInterface

__all__ = ["Manager",
"EngineNotFound",
"NoConnectionSupported"]
Expand Down Expand Up @@ -82,8 +80,3 @@ def get_engine() -> matlab.engine.matlabengine.MatlabEngine:

assert( isinstance(Manager.__engine_store, matlab.engine.matlabengine.MatlabEngine) )
return Manager.__engine_store

@staticmethod
def create_pulse_control_interface(sample_rate: float, time_scaling: float=0.001) -> PulseControlInterface:
register_pulse_callback = lambda x: Manager.getEngine().plsreg(x)
return PulseControlInterface(register_pulse_callback, sample_rate, time_scaling)
41 changes: 26 additions & 15 deletions qctoolkit/qcmatlab/pulse_control.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from math import floor
import numpy
from typing import Dict, Any, Callable
from typing import Any, Dict, List, Tuple

from qctoolkit.pulses.instructions import Waveform, EXECInstruction, STOPInstruction, InstructionSequence

Expand All @@ -9,25 +9,26 @@

class PulseControlInterface:

def __init__(self, register_pulse_callback: Callable[[Dict[str, Any]], int], sample_rate: float, time_scaling: float=0.001) -> None:
Pulse = Dict[str, Any]
PulseGroup = Dict[str, Any]

def __init__(self, sample_rate: float, time_scaling: float=0.001) -> None:
"""Initialize PulseControlInterface.
Arguments:
pulse_registration_function -- A function which registers the pulse in pulse control and returns its id.
sample_rate -- The rate in Hz at which waveforms are sampled.
time_scaling -- A factor that scales the time domain defined in PulseTemplates. Defaults to 0.001, meaning
that one unit of time in a PulseTemplate corresponds to one microsecond.
"""
super().__init__()
self.__sample_rate = sample_rate
self.__time_scaling = time_scaling
self.__register_pulse_callback = register_pulse_callback

@staticmethod
def __get_waveform_name(waveform: Waveform) -> str:
return 'wf_{}'.format(hash(waveform))

def create_waveform_struct(self, waveform: Waveform, name: str) -> Dict[str, Any]:
def create_waveform_struct(self, waveform: Waveform, name: str) -> 'PulseControlInterface.Pulse':
"""Construct a dictionary adhering to the waveform struct definition in pulse control.
Arguments:
Expand All @@ -43,12 +44,17 @@ def create_waveform_struct(self, waveform: Waveform, name: str) -> Dict[str, Any
# TODO: how to deal with the second channel expected in waveform structs in pulse control?
return struct

def create_pulse_group(self, sequence: InstructionSequence, name: str) -> Dict[str, Any]:
def create_pulse_group(self, sequence: InstructionSequence, name: str)\
-> Tuple['PulseControlInterface.PulseGroup', List['PulseControlInterface.Pulse']]:
"""Construct a dictionary adhering to the pulse group struct definition in pulse control.
All waveforms in the given InstructionBlock are converted to waveform pulse structs and registered in
pulse control with the pulse registration function held by the class. create_pulse_group detects
multiple use of waveforms and sets up the pulse group dictionary accordingly.
All waveforms in the given InstructionSequence are converted to waveform pulse structs and returned as a list
in the second component of the returned tuple. The first component of the result is a pulse group dictionary
denoting the sequence of waveforms using their indices in the returned list. create_pulse_group detects multiple
use of waveforms and sets up the pulse group dictionary accordingly.
Note that pulses are not registered in pulse control. To achieve this and update the pulse group struct
accordingly, the dedicated MATLAB script has to be invoked.
The function will raise an Exception if the given InstructionBlock does contain branching instructions,
which are not supported by pulse control.
Expand All @@ -68,17 +74,22 @@ def create_pulse_group(self, sequence: InstructionSequence, name: str) -> Dict[s
chan=1,
ctrl='notrig')

registered_waveforms = dict()
waveform_ids = dict()
waveform_structs = list()

for waveform in waveforms:
if waveform not in registered_waveforms:
if waveform not in waveform_ids:
name = self.__get_waveform_name(waveform)
waveform_struct = self.create_waveform_struct(waveform, name)
registered_waveforms[waveform] = self.__register_pulse_callback(waveform_struct)
if pulse_group['pulses'] and pulse_group['pulses'][-1] == registered_waveforms[waveform]:
waveform_structs.append(waveform_struct)
index = len(waveform_structs) - 1
waveform_ids[waveform] = index
else:
index = waveform_ids[waveform]
if pulse_group['pulses'] and pulse_group['pulses'][-1] == index:
pulse_group['nrep'][-1] += 1
else:
pulse_group['pulses'].append(registered_waveforms[waveform])
pulse_group['pulses'].append(index)
pulse_group['nrep'].append(1)

return pulse_group
return (pulse_group, waveform_structs)
21 changes: 12 additions & 9 deletions tests/format_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ def _test_attribute(self,package,name):
return bool

def test_annotations(self):
whitelist = ["__init__"]
for root, dirs, files in os.walk(srcPath):
for filename in files:
methods = {}
Expand All @@ -141,16 +142,18 @@ def test_annotations(self):
package = (".".join(os.path.join(root,filename.split(".")[0])[len(srcPath)+1:].split(os.path.sep)))
for name in methods:
if name == "None":
for method in methods[name]:
if self._test_attribute(package,method):
imported = getattr(__import__(package, fromlist=[method]), method)
self.assertIsNotNone(imported.__annotations__,"No Annotation found. Module: {}, Method: {}".format(package,method))
for method in methods[name] :
if method not in whitelist:
if self._test_attribute(package,method):
imported = getattr(__import__(package, fromlist=[method]), method)
self.assertIsNotNone(imported.__annotations__,"No Annotation found. Module: {}, Method: {}".format(package,method))
else:
if self._test_attribute(package,name):
imported = getattr(__import__(package, fromlist=[name]), name)
for method in methods[name]:
if hasattr(imported, method):
loaded_method = getattr(imported, method)
if hasattr(loaded_method,"__call__"):
self.assertIn("return",loaded_method.__annotations__,"No Return annotation found for Module: {}, Class: {}, Method: {}".format(package,name,method))
self.assertNotEqual(len(loaded_method.__annotations__.keys()),0,"No Annotation found. Module: {}, Class: {}, Method: {}".format(package,name,method))
if method not in whitelist:
if hasattr(imported, method):
loaded_method = getattr(imported, method)
if hasattr(loaded_method,"__call__"):
self.assertIn("return",loaded_method.__annotations__,"No Return annotation found for Module: {}, Class: {}, Method: {}".format(package,name,method))
self.assertNotEqual(len(loaded_method.__annotations__.keys()),0,"No Annotation found. Module: {}, Class: {}, Method: {}".format(package,name,method))
34 changes: 30 additions & 4 deletions tests/pulses/function_pulse_tests.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import unittest

from qctoolkit.pulses.function_pulse_template import FunctionPulseTemplate
from qctoolkit.pulses.function_pulse_template import FunctionPulseTemplate,\
FunctionWaveform
from qctoolkit.pulses.sequencing import Sequencer
from qctoolkit.pulses.instructions import InstructionBlock

from tests.serialization_dummies import DummySerializer
from qctoolkit.expressions import Expression

import numpy as np

class FunctionPulseTest(unittest.TestCase):
def setUp(self):
Expand All @@ -28,6 +34,26 @@ def test_serialization_data(self):
measurement=False)
self.assertEqual(expected_data, self.fpt.get_serialization_data(DummySerializer()))




class FunctionPulseSequencingTest(unittest.TestCase):
def setUp(self):
unittest.TestCase.setUp(self)
self.f = "a * t"
self.duration = "y"
self.args = dict(a=3,y=1)
self.fpt = FunctionPulseTemplate(self.f, self.duration)

def test_build_sequence(self):
ib = InstructionBlock()
seq = Sequencer()
cond = None
self.fpt.build_sequence(seq, self.args, cond, ib)

class FunctionWaveformTest(unittest.TestCase):
def test_sample(self):
f = Expression("(t+1)**b")
length = Expression("c**b")
par = {"b":2,"c":10}
fw = FunctionWaveform(par,f,length)
a = np.arange(4)
self.assertEqual(list(fw.sample(a)), [1,4,9,16])

5 changes: 4 additions & 1 deletion tests/pulses/sequencing_dummies.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,10 @@ def __init__(self, duration: float=0, sample_output: numpy.ndarray=None) -> None

@property
def _compare_key(self) -> Any:
return id(self)
if self.sample_output is not None:
return tuple(self.sample_output.tolist())
else:
return id(self)

@property
def duration(self) -> float:
Expand Down

0 comments on commit 0182a02

Please sign in to comment.