from abc import ABCMeta, abstractmethod
import copy
import time
import inspect
import os
import re
import shelve
import sys
import types
import numpy as np
from .integrator import IntegratorCallback
from .utility.text import colour_text as coloured
from .utility import struct_allclose
[docs]class Measurement(object):
'''
A Measurement instance is an object which encodes the logic required to extract
information from a QuantumSystem object. It specifies the algorithm
to be performed to extract the measurement outcome, the type of the measurement
results, and also provides methods to initialise and add results to storage
when performing the same measurement iteratively. Measurement is an
abstract class, and should be subclassed for each new class of measurements.
While Measurement objects can be used directly, they are typically used in conjunction
with Measurements and MeasurementWrapper, as documented in those classes.
Any arguments and/or keyword arguments passed to the Measurement constructor
are passed to Measurement.init.
Subclassing Measurement:
A subclass of Measurement must implement the following methods:
- init
- measure
- result_type
- result_shape
A subclass *may* also override the following methods to further
customise behaviour:
- is_independent
- iterate_results_init
- iterate_results_add
- iterate_is_complete
- iterate_continue_mask
Documentation for these methods is provided below.
Applying Measurement instances:
Although not normally used directly, you can use a Measurement instance
directly on the results of an QuantumSystem integration, for example:
>>> measurement(data=system.integrate(...))
Calling a Measurement instance is an alias for the Measurement.measure
method. If the measurement instance is configured to perform its own integration:
>>> measurement(times=..., initial=..., ...)
Note that if the measurement instance needs access to the QuantumSystem
instance, you can setup the reference using:
>>> measurement.system = system
'''
__metaclass__ = ABCMeta
def __init__(self, *args, **kwargs):
self.__system = None
self.init(*args, **kwargs)
def __call__(self, *args, **kwargs):
return self.measure(*args, **kwargs)
@property
def system(self):
'''
A reference to a QuantumSystem instance. If a system instance is not provided,
and an attempt to access this property is made, a RuntimeException is raised.
You can specify a QuantumSystem using:
>>> measurement.system = system
'''
if self.__system is None:
raise ValueError("QuantumSystem instance required by Measurement object, but a QuantumSystem object has not been configured.")
return self.__system
@system.setter
def system(self, value):
if not isinstance(value, QuantumSystem):
raise ValueError("Specified value must be a `QuantumSystem` instance.")
self.__system = value
@abstractmethod
[docs] def init(self):
'''
This method should initialise the Measurement instance in whatever way
is necessary to prepare the instance for use. Note that any arguments
passed to the Measurement constructor will also be passed to this method.
There is no restriction on the method signature for the init method.
'''
raise NotImplementedError("Measurement.init has not been implemented.")
@abstractmethod
[docs] def measure(self, data=None, params={}, int_kwargs={}, **kwargs):
'''
This method should return the value of a measurement as a numpy array with
data type and shape as specified in `result_type` and `result_shape` respectively.
.. note:: It is possible to return types other than numpy array and still
be compatible with iteration (see MeasurementWrapper) provided you overload
the `iterate_results_init` and `iterate_results_add` methods.
Implementations of `measure` will typically be provided by integration data
by a `MeasurementWrapper` instance (which will be a structured numpy array
as returned by `Integrator.integrate) as the value for the `data` keyword.
A consistent set of values for `times` and `initial` will also be passed
as keywords inside `int_kwargs`.
.. note:: If an implementation of `measure` omits the `data` keyword, QuBricks
assumes that all integration required by the `measure` operator will be
performed internally. It can use the reference to a QuantumSystem
instance at `Measurement.system` for this purpose. If the `data` keyword
is present (for testing/etc), but pre-computed integration data is undesired,
override the `is_independent` method to return `True`. If external data
is *required*, then simply remove the default value of `data`.
Apart from the required keywords of `data` and `params`; any additional
keywords can be specified. Refer to the documentation of `MeasurementWrapper` to
see how their values will filter through to the various methods of QuBricks.
:param data: Data from a QuantumSystem.integrate call, or None.
:type data: numpy.ndarray or None
:param params: Parameter context to use during this measurement. Parameter types can be anything supported by Parameters.
:type params: dict
:param int_kwargs: Keyword arguments to be passed on to any integrator instances,
which includes the times and initial states provided to `MeasurementWrapper.integrate`.
:type int_kwargs: dict
:param kwargs: Any other keyword arguments not collected explicitly.
:type kwargs: dict
'''
raise NotImplementedError("Measurement.measure has not been implemented.")
@abstractmethod
[docs] def result_type(self, *args, **kwargs):
'''
This method should return an object suitable for use as the dtype
argument in a numpy array constructor. Otherwise, no restrictions; other than that it must also
agree with the data-type returned by `Measurement.measure`.
This method will receive all arguments and keyword arguments passed to
`iterate_results_init`, where it is used to initialise the storage of
measurement results.
'''
raise NotImplementedError("Measurement.result_type has not been implemented.")
@abstractmethod
[docs] def result_shape(self, *args, **kwargs):
'''
This method should return a tuple describing the shape of the numpy array to be returned
by Measurement.measure.
This method will receive all arguments and keyword arguments passed to
`iterate_results_init`, where it is used to initialise the storage of
measurement results.
'''
raise NotImplementedError("Measurement.result_shape has not been implemented.")
[docs] def iterate_results_init(self, ranges=[], shape=None, params={}, *args, **kwargs):
'''
This method is called by `MeasurementWrapper.iterate_yielder` to initialise
the storage of the measurement results returned by this object. By default, this
method returns a numpy array with dtype as specified by `result_type` and shape
returned by `result_shape`, with all entries set to np.nan objects. If necessary,
you can overload this method to provide a different storage container
This is a generic initialisation for the Measurement object. It can be overloaded
if necessary.
:param ranges: The range specifications provided to MeasurementWrapper.iterate_yielder.
:type range: list of dict
:param shape: The shape of the resulting evaluated ranges.
:type shape: tuple
:param params: The parameter context of the ranges.
:type params: dict
:param args: Any additional arguments passed to MeasurementWrapper.iterate_yielder.
:type args: tuple
:param kwargs: Any additional keyword arguments passed to MeasurementWrapper.iterate_yielder.
:type kwargs: dict
'''
# Initialise empty array to store results
dtype = self.result_type(ranges=ranges, shape=shape, params=params, *args, **kwargs)
rshape = self.result_shape(ranges=ranges, shape=shape, params=params, *args, **kwargs)
if rshape is not None:
shape = shape + rshape
a = np.empty(shape, dtype=dtype)
a.fill(np.nan)
return a
[docs] def iterate_results_add(self, results=None, result=None, indices=None, params={}):
'''
This method adds a measurement result `result` from `Measurement.measure` to the `results` object
initialised in `Measurement.iterate_results_init`. It should put this result into storage at the
appropriate location for the provided `indices`.
:param results: The storage object in which to place the result (as from `Measurement.iterate_results_init`).
:type results: object
:param result: The result to be stored (as from `Measurement.measure`).
:type result: object
:param indices: The indices at which to store this result.
:type indices: tuple
:param params: The parameter context for this measurement result.
:type params: dict
'''
results[indices] = result
[docs] def iterate_continue_mask(self, results):
'''
This method returns a mask function (see `MeasurementWrapper` documentation), which
in turn based on the `results` object (as initialised by `iterate_results_init`) returns
True or False for a given set of indices indicating whether there already exists data
for those indices.
:param results: The results storage object (see Measurement.iterate_results_init).
:type results: object
'''
def continue_mask(indices, ranges=None, params={}):
if np.any(np.isnan(results[indices].view('float'))):
return True
return False
return continue_mask
[docs] def iterate_is_complete(self, results):
'''
This method returns `True` when the results object is completely specified (results
have been added for all indices; and `False` otherwise.
:param results: The results storage object (see Measurement.iterate_results_init).
:type results: object
'''
if len(np.where(np.isnan(results.view('float')))[0]) != 0:
return False
return True
@property
def is_independent(self):
'''
`True` if this Measurement instance does all required integration internally (and so should
not receive pre-computed integration data). `False` otherwise. The default implementation is
`False`.
'''
return False
[docs]class MeasurementIterationResults(object):
'''
MeasurementIterationResults is class designed to store the results of measurements applied iteratively
over a range of different values (see `MeasurementWrapper.iterate_yielder`). Apart from its role as a
data structure, it also provides methods for saving and loading the data to/from disk.
:param ranges: The specification of ranges passed ultimately to the Parameters instance.
:type ranges: dict or list of dict
:param ranges_eval: The values of the parameters after evaluation from the above specification.
:type ranges_eval: numpy.ndarray
:param results: A dictionary of measurement results, with keys of measurement names.
:type results: dict
:param runtime: An optional number indicating in seconds the time taken to generate the results.
:type runtime: float
:param path: The location to use as a storage location by default.
:type path: str
:param samplers: A dictionary of named samplers (see `parampy.Parameters.range`) for future use with `ranges`, since functions cannot be serialised.
:type samplers: dict of callables
Constructing a MeasurementIterationResults object:
Manually constructing a MeasurementIterationResults instance is unusual, since this is handled
for you by the MeasurementWrapper iteration methods. However, this is possible using:
>>> results = MeasurementIterationResults(ranges=..., ranges_eval=..., results=..., runtime=1.2, path='data.dat', samplers=...
Accessing results:
To access the data stored in a MeasurementIterationResults instance, simply access the relevant
attributes. The available attributes are:
- ranges
- ranges_eval
- results
- runtime
- path
- samplers
Each of these attributes corresponds to the documented parameters described above.
For example:
>>> mresults = results.results['measurement_name']
Note that all of these attributes can be freely overridden. Check out the
`MeasurementIterationResults.update` method for an alternative to updating these
results.
'''
def __init__(self, ranges, ranges_eval, results, params={}, runtime=None, path=None, samplers={}):
self.ranges = ranges
self.ranges_eval = ranges_eval
self.results = results
self.runtime = 0 if runtime is None else runtime
self.path = path
self.samplers = samplers
self.params = params
self.__check_sanity()
def __check_sanity(self):
if type(self.results) is None:
raise ValueError("Improperly initialised result data.")
if type(self.results) is not dict:
new_name = raw_input("Old data format detect. Upgrading: Please enter a name for the current result data (should match the measurement name): ")
self.results = {new_name: self.results}
self.save() # Commit changes to new format
[docs] def update(self, **kwargs):
'''
This method allows you to update the stored data of this `MeasurementIterationResults` instance.
Simply call this method with keyword arguments of the relevant attributes. For example:
>>> results.update(results=..., path=..., ...)
Note that you can update multiple attributes at once. The one special argument is
"runtime", which will increment that attribute with the specified value, rather than
replacing it. For example:
>>> results.runtime
231.211311
>>> results.update(runtime=2.2)
>>> results.runtime
233.411311
'''
for key, value in kwargs.items():
if key not in ['ranges', 'ranges_eval', 'data', 'runtime', 'path', 'samplers', 'params']:
raise ValueError("Invalid update key: %s" % key)
if key is "runtime":
self.runtime += value
else:
setattr(self, key, value)
return self
[docs] def is_complete(self, measurements={}):
'''
This method calls the `Measurement.iterate_is_complete` method with the appropriate
results for each of the measurements provided. If False for any of these
measurements, False is returned.
:param measurements: A dictionary of measurement objects with keys indicating their names.
:type measurements: dict
'''
for name, measurement in measurements.items():
if self.results.get(name,None) is None:
return False
if not measurement.iterate_is_complete(self.results[name]):
return False
return True
[docs] def continue_mask(self, measurements={}):
'''
This method provides a mask for the `parampy.iteration.RangesIterator` instance called in
`MeasurementWrapper.iterate_yielder`. The provided mask calls the `Measurement.iterate_continue_mask`
method with the appropriate results for each of the measurements provided in `measurements`.
:param measurements: A dictionary of measurement objects with keys indicating their names.
:type measurements: dict
'''
def continue_mask(indices, ranges=None, params={}):
for name, measurement in measurements.items():
data = self.results.get(name,None)
if data is None:
return True
if measurement.iterate_continue_mask(data)(indices, ranges=ranges, params=params):
return True
return False
return continue_mask
@staticmethod
def _process_ranges(ranges, defunc=False, samplers={}):
if ranges is None:
return ranges
if type(ranges) is dict:
ranges = [copy.deepcopy(ranges)]
else:
ranges = copy.deepcopy(ranges)
for range in ranges:
for param, spec in range.items():
if defunc and type(spec[-1]) == types.FunctionType:
spec = list(spec)
sampler = spec[-1]
for name, fn in samplers.items():
if sampler == fn:
sampler = name
break
if type(sampler) is not str:
sampler = sampler.__name__
spec[-1] = sampler
spec = tuple(spec)
range[param] = spec
if not defunc and len(spec) > 3 and type(spec[-1]) == str and spec[-1] in samplers:
spec = list(spec)
spec[-1] = samplers[spec[-1]]
spec = tuple(spec)
range[param] = spec
return ranges
[docs] def save(self, path=None, samplers=None):
'''
This method will save this MeasurementIterationResults object to disk at the specified
path, trading the sampler functions in the ranges attribute with their names extract from samplers
(if possible), or by using their inspected name (using the `__name__` attribute). The
resulting file is a "shelf" object from the python `shelve` module.
:param path: A path to the file's destination. If not provided, the earlier provided path is used.
:type path: str
:param samplers: A dictionary of functions (or callables) indexed by string names.
:type samplers: str
For example:
>>> results.save('data.dat')
'''
if path is None:
path = self.path
if samplers is None:
samplers = self.samplers
else:
self.samplers = samplers
if path is None:
raise ValueError("Output file was not specified.")
s = shelve.open(path)
s['ranges'] = MeasurementIterationResults._process_ranges(self.ranges, defunc=True, samplers=samplers)
s['ranges_eval'] = self.ranges_eval
s['results'] = self.results
s['runtime'] = self.runtime
s.close()
@classmethod
[docs] def load(cls, path, samplers={}):
'''
This classmethod will load and populate a new MeasurementIterationResults object from previously
saved data. If provided, `samplers` will be used to convert string names of samplers in the
ranges to functions.
:param path: A path to the file's destination. If not provided, the earlier provided path is used.
:type path: str
:param samplers: A dictionary of functions (or callables) indexed by string names.
:type samplers: str
For example:
>>> results = MeasurementIterationResults.load('data.dat')
'''
s = shelve.open(path)
ranges = MeasurementIterationResults._process_ranges(s.get('ranges'), defunc=False, samplers=samplers)
ranges_eval = s.get('ranges_eval')
results = s.get('results')
runtime = s.get('runtime')
s.close()
return cls(ranges=ranges, ranges_eval=ranges_eval, results=results, runtime=runtime, path=path, samplers=samplers)
@classmethod
[docs] def merge(cls, *paths, **kwargs):
'''
This classmethod will load the results from multiple MeasurementIterationResults objects and merge them
into one; appending data along the axis specified.
:param paths: A sequence of paths which can be loaded using `MeasurementIterationResults.load`, and which
will then be merged along the specified axis into one `MeasurementIterationResults`.
:type paths: tuple
:param kwargs: A dictionary of options; in particular it must contain the axis along which to merge
the data. It can also contain 'samplers' which is then passed to the `MeasurementIterationResults.load`
method and `MeasurementIterationResults` constructor.
:type kwargs: dict
Example:
>>> MeasurementIterationResults.merge('data1.shelf', 'data2.shelf', 'data3.shelf', axis=2)
'''
assert 'axis' in kwargs, "Axis along which to merge MeasurementIterationResults must be specified."
axis = kwargs['axis']
ranges = None
ranges_eval = None
results = None
runtime = 0
for path in paths:
m = MeasurementIterationResults.load(path, samplers=kwargs.get('samplers', {}))
if ranges_eval is None:
ranges_eval = m.ranges_eval
results = m.results
runtime = m.runtime
else:
np.append(ranges_eval, m.ranges_eval, axis=axis)
for key in results.keys():
results[key] = np.append(results[key], m.results[key], axis=axis)
runtime += m.runtime
return cls(ranges, ranges_eval, results, runtime=time, samplers=kwargs.get('samplers', {}))
[docs]class Measurements(object):
'''
Measurements is a designed to simplify the Measurement evaluation process
by acting as a host for multiple named Measurement objects. This object
is used as the `measure` attribute of `QuantumSystem` objects.
:param system: A QuantumSystem instance.
:type system: QuantumSystem
Constructing a Measurements object:
If you want to create a Measurements instance separate from the one hosted
by `QuantumSystem` objects, use the following:
>>> measurements = Measurements(system)
Adding and removing Measurement objects:
To add a Measurement object to a Measurements instance, you simply provide it
a string name, and use (for example):
>>> measurements._add("name", NamedMeasurement)
where `NamedMeasurement` is a subclass of `Measurement`.
To remove a `Measurement` from `Measurements`, use:
>>> measurements._remove("name")
The underscores preceding these methods' names are designed to prevent
name clashes with potential Measurement names.
When a `Measurement` instance is added to `Measurements`, its
internal "system" attribute is updated to point to the `QuantumSystem`
used by `Measurements`.
Extracting a Measurement object:
Once added to the `Measurements` object, a `Measurement` object can be
accessed using attribute notation, or by calling the `Measurements`
instance. For example:
>>> system.measure.name
Or to bundle multiple measurements up into the same evaluation:
>>> system.measure("measurement_1", "measurement_2", ...)
In both cases, the return type is **not** a `Measurement` instance,
but rather a `MeasurementWrapper` instance, which can be used to
perform the `Measurement.measure` operations in a simplified
and consistent manner. See the `MeasurementWrapper` documentation for
more information.
Inspecting a Measurements instance:
To see a list of the names of measurements stored in a `Measurements`
instance, you can use:
>>> measurements._names
To get a reference of the dictionary internally used by `Measurements`
to store and retrieve hosted `Measurement` objects, use:
>>> measurements._measurements
'''
def __init__(self, system):
self.__system = system
self.__measurements = {}
def _add(self, name, measurement):
if not re.match('^[a-zA-Z][a-zA-Z0-9\_]*$', name):
raise ValueError("'%s' Is an invalid name for a measurement." % name)
if not isinstance(measurement, Measurement):
raise ValueError("Supplied measurement must be an instance of Measurement.")
self.__measurements[name] = measurement
measurement.system = self.__system
def _remove(self, name):
return self.__measurements.pop(name)
@property
def _names(self):
'''
A list of strings corresponding to the names of the measurements hosted in this
Measurements instances.
'''
return self.__measurements.keys()
@property
def _measurements(self):
return self.__measurements
# User interaction
def __getattr__(self, name):
return MeasurementWrapper(self.__system, {name: self.__measurements[name]})
def __call__(self, *names):
meas = {}
for name in names:
meas[name] = self.__measurements[name]
return MeasurementWrapper(self.__system, meas)
[docs]class MeasurementWrapper(object):
'''
The `MeasurementWrapper` class wraps around one or more `Measurement` objects
to provide a consistent API for performing (potentially) multiple measurements
at once. There are also performance benefits to be had, since wherever possible
integration results are shared between the `Measurement` instances.
:param system: A QuantumSystem instance.
:type system: QuantumSystem
:param measurements: A dictionary of `Measurement` objects indexed by a string name.
:type measurements: dict
Constructing MeasurementWrapper objects:
The syntax for creating a `MeasurementWrapper` object is:
>>> wrapper = MeasurementWrapper(system, {'name': NamedMeasurement, ...})
where `NamedMeasurement` is a `Measurement` instance.
Adding Measurement objects:
If you want to add additional `Measurement` objects after creating the
`MeasurementWrapper` instance, use the `add_measurements` method. Refer
to the documentation below for more information.
Performing Measurements:
There are three basic procedures which you can use to perform measurements
on the reference "system" `QuantumSystem` instance.
The first of these is `MeasurementWrapper.on`, which applies the measurement(s)
to a pre-computed data. The second is `MeasurementWrapper.integrate`, which
applies the measurement(s) to data computed on the fly. And the last is
the iteration procedures: `MeasurementWrapper.iterate`, `MeasurementWrapper.iterate_yielder`,
and `MeasurementWrapper.iterate_to_file`; each of which allows you to perform
measurements over a range of parameter contexts.
Each of these methods is documented in more detail below.
'''
def __init__(self, system, measurements={}):
self.measurements = {}
self.system = system
self.add_measurements(**measurements)
@property
def __integration_needed(self):
for meas in self.measurements.values():
if not meas.is_independent and 'data' in inspect.getargspec(meas.measure)[0]:
return True
return False
def __split_kwargs(self, kwargs, prefix):
kwargs = kwargs.copy()
if '%skwargs'%prefix in kwargs:
prefixed_kwargs = kwargs.pop('%skwargs'%prefix)
else:
prefixed_kwargs = {}
for kwarg in kwargs.keys():
if kwarg.startswith(prefix):
prefixed_kwargs[kwarg[len(prefix):]] = kwargs.pop(kwarg)
return kwargs, prefixed_kwargs
[docs] def add_measurements(self, **measurements):
'''
This method adds named measurements to the `MeasurementWrapper`. The
syntax for this is:
>>> wrapper.add_measurements(name=NamedMeasurement, ...)
where "name" is any valid measurement name, and `NamedMeasurement` is a
`Measurement` instance.
'''
self.measurements.update(measurements)
[docs] def on(self, data, **kwargs):
'''
This method applies the `Measurement.measure` method to `data` for
every `Measurement` stored in this object. If there are two or more
`Measurement` objects, this method returns a dictionary of `Measurement.measure` results; with
keys being the measurement names. If there is only one `Measurement` object,
the return value of `Measurement.measure` is returned.
:param data: Data from an `QuantumSystem` integration.
:type data: numpy.ndarray
:param kwargs: Additional kwargs to pass to `Measurement.measure`.
:type kwargs: dict
For example:
>>> wrapper.on(data, mykey=myvalue)
Note that if `data` is not `None`, then `initial` and `times` are
extracted from `data`, and passed to `Measurement.measure` in `int_kwargs`.
Also note that if a measurement has `Measurement.is_independent` being `True`,
only the `initial` and `times` will be forwarded from `data`.
'''
kwargs, int_kwargs = self.__split_kwargs(kwargs,'int_')
if data is not None:
if int_kwargs.get('initial') is None:
int_kwargs['initial'] = data['state'][:, 0]
if int_kwargs.get('times') is None:
int_kwargs['times'] = data['time'][0, :]
if len(self.measurements) == 1:
measurement = self.measurements.values()[0]
if measurement.is_independent or 'data' not in inspect.getargspec(measurement.measure)[0]:
return measurement.measure(int_kwargs=int_kwargs, **kwargs)
else:
return measurement.measure(data=data, int_kwargs=int_kwargs, **kwargs)
res = {}
for name, measurement in self.measurements.items():
if measurement.is_independent or 'data' not in inspect.getargspec(measurement.measure)[0]:
res[name] = measurement.measure(int_kwargs=int_kwargs, **kwargs)
else:
res[name] = measurement.measure(data=data, int_kwargs=int_kwargs, **kwargs)
return res
[docs] def integrate(self, params={}, **kwargs):
'''
This method performs an integration of the `QuantumSystem` referenced when
this object was constructed, and then calls `Measurement.on` on that data.
If all `Measurement` objects hosted are "independent" (have `Measurement.is_independent`
as `True`), then no integration is performed.
:param times: Times for which to report the state during integration.
:type times: iterable
:param initial: Initial state vectors / ensembles for the integration. (See `QuantumSystem.state`.
:type initial: list
:param params: Parameter overrides to use during integration. (See `parampy.Parameters` documentation).
:type param: dict
:param kwargs: Additional keyword arguments to pass to `QuantumSystem.integrate` and `Measurement.measure`.
:type kwargs: dict
.. note:: Only keyword arguments prepended with 'int_' are forwarded to
`QuantumSystem.integrate`, with the prefix removed. These keywords are also
also passed to `Measurement.measure` in the `int_kwargs` dictionary.
For example:
>>> wrapper.integrate(times=['T'], initial=['logical0'])
'''
kwargs, int_kwargs = self.__split_kwargs(kwargs,'int_')
if self.__integration_needed:
return self.on(self.system.integrate(params=params, **int_kwargs), params=params, int_kwargs=int_kwargs, **kwargs)
else:
return self.on(None, params=params, int_kwargs=int_kwargs, **kwargs)
[docs] def iterate_yielder(self, ranges, yield_every=0, results=None, params={}, **kwargs):
'''
This method iterates over the possible Cartesian products of the parameter ranges provided,
at each step running the `MeasurementWrapper.integrate` in the resulting parameter context.
After every `yield_every` seconds, this method will flag that it needs to yield the results currently accumulated (as a
`MeasurementIterationResults` object) when the next measurement result has finished computing. This means that
you can, for example, progressively save (or plot) the results as they are taken. Note that if
the processing of the results is slow, this can greatly increase the time it takes to finish
the iteration.
:param ranges: A valid ranges specification (see `parampy.iteration.RangesIterator`)
:type ranges: list or dict
:param yield_every: Minimum number of seconds to go without returning the next result. To yield the value after
every successful computation, use yield_every=0 . If yield_every is None, results are returned
only after every computation has succeeded. By default, yield_every = 0.
:type yield_every: number or None
:param results: Previously computed MeasurementIterationResults object to extend.
:type results: MeasurementIterationResults
:param params: Parameter overrides to use (see `parampy.Parameters.range`)
:type params: dict
:param kwargs: Additional keyword arguments to be passed to `MeasurementWrapper.integrate` (and also to
`Measurement.iterate_results_init`.
:type kwargs: dict
Note that kwargs prefixed with "iter_" will be split out and passed as arguments to `parampy.iteration.RangeIterator`.
'''
kwargs, iter_kwargs = self.__split_kwargs(kwargs, 'iter_')
## Prepare iterator kwargs
if iter_kwargs.get('distributed',False) is True:
def integrate(*args, **kwargs):
if isinstance(system, Exception):
raise system
r = system.measure(*args).integrate(**kwargs)
return r
iter_kwargs['distributed'] = self.system._dispy()
iter_kwargs['function'] = integrate
iter_kwargs['function_args'] = self.measurements.keys()
else:
iter_kwargs['distributed'] = False
iter_kwargs['function'] = self.integrate
iter_kwargs['function_args'] = ()
iter_kwargs['function_kwargs'] = kwargs
iterator = self.system.p.ranges_iterator(ranges,
ranges_eval=None if results is None else results.ranges_eval,
params=params,
**iter_kwargs)
ranges_eval, indices = iterator.ranges_expand()
if results is None:
data = {}
for name, meas in self.measurements.items():
data[name] = meas.iterate_results_init(ranges=ranges, shape=ranges_eval.shape, params=params, **kwargs)
results = MeasurementIterationResults(ranges, ranges_eval, data, params=params)
else:
if not struct_allclose(ranges_eval, results.ranges_eval, rtol=1e-15, atol=1e-15):
if not raw_input("Attempted to resume measurement collection on a result set with different parameter ranges. Continue anyway? (y/N) ").lower().startswith('y'):
raise ValueError("Stopping.")
if type(results.results) != dict:
results.update(ranges=ranges, ranges_eval=ranges_eval, data={self.measurements.keys()[0]: results.results}, params=params)
def splitlist(l, length=None):
if length is None:
yield l
else:
for i in xrange(0, len(l), length):
yield l[i:i + length]
t_start = time.time()
data = results.results
for i, (indices, result) in enumerate(iterator):
if type(result) is not dict:
self.measurements[self.measurements.keys()[0]].iterate_results_add(results=data[self.measurements.keys()[0]], result=result, indices=indices)
else:
for name, value in result.items():
self.measurements[name].iterate_results_add(results=data[name], result=value, indices=indices)
if yield_every is not None and time.time() - t_start >= yield_every:
yield results.update(data=data, runtime=time.time() - t_start)
t_start = time.time()
yield results.update(data=data, runtime=time.time() - t_start)
[docs] def iterate(self, *args, **kwargs):
'''
This method is a wrapper around the `Measurement.iterate_yielder` method in the event that
one only cares about the final result, and does not want to deal with interim results. This
method simply waits until the iteration process is complete, and returns the last result.
All arguments and keyword arguments are passed to `MeasurementWrapper.iterate_yielder`.
'''
from collections import deque
return deque(self.iterate_yielder(*args, yield_every=None, **kwargs), maxlen=1).pop()
[docs] def iterate_to_file(self, path, samplers={}, yield_every=60, *args, **kwargs):
'''
This method wraps around `Measurement.iterate_yielder` in order to continue a previous
measurement collection process (if it did not finish successfully) and to iteratively write the
most recent results to a file. This method modifies the default `yield_every` of the
`iterate_yielder` method to 60 seconds, so that file IO is not the limiting factor of performance,
and so that at most around a minute's worth of processing is lost in the event that something
goes wrong.
:param path: The path at which to save results. If this file exists, attempts are made to continue
the measurement acquisition.
:type path: str
:param samplers: A dictionary of samplers to be used when loading and saving the `MeasurementIterationResults`
object. (see `MeasurementIterationResults.load` and `MeasurementIterationResults.save`)
:type samplers: dict
:param yield_every: The minimum time between attempts to save the results. (see `iterate_yielder`)
:type yield_every: number or None
:param args: Additional arguments to pass to `iterate_yielder`.
:type args: tuple
:param kwargs: Additional keyword arguments to pass to `iterate_yielder`.
:type kwargs: dict
Measurement.iterate_to_file saves the results of the Measurement.iterate method
to a python shelve file at `path`; all other arguments are passed through to the
Measurement.iterate method.
'''
if os.path.dirname(path) is not "" and not os.path.exists(os.path.dirname(path)):
os.makedirs(os.path.dirname(path))
elif os.path.exists(os.path.dirname(path)) and os.path.isfile(os.path.dirname(path)):
raise RuntimeError("Destination path '%s' is a file." % os.path.dirname(path))
results = None
if os.path.isfile(path):
results = MeasurementIterationResults.load(path=path, samplers=samplers)
if results.results is not None:
if results.is_complete(self.measurements):
return results
masks = kwargs.get('iter_masks', [])
masks.append(results.continue_mask(self.measurements))
kwargs['iter_masks'] = masks
print coloured("Attempting to continue data collection...", "YELLOW", True)
else:
results = None
for results in self.iterate_yielder(*args, results=results, yield_every=yield_every, **kwargs):
results.save(path=path, samplers=samplers)
return results
from .system import QuantumSystem