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

time extension via SequencePT #830

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from
49 changes: 46 additions & 3 deletions qupulse/program/linspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from qupulse.parameter_scope import Scope, MappedScope, FrozenDict
from qupulse.program import (ProgramBuilder, HardwareTime, HardwareVoltage, Waveform, RepetitionCount, TimeType,
SimpleExpression)
from qupulse.program.waveforms import MultiChannelWaveform
from qupulse.program.waveforms import MultiChannelWaveform, TransformingWaveform, ConstantWaveform, SequenceWaveform

# this resolution is used to unify increments
# the increments themselves remain floats
Expand Down Expand Up @@ -194,9 +194,26 @@ def with_repetition(self, repetition_count: RepetitionCount,
def with_sequence(self,
measurements: Optional[Sequence[MeasurementWindow]] = None) -> ContextManager['ProgramBuilder']:
yield self


@contextlib.contextmanager
def new_subprogram(self, global_transformation: 'Transformation' = None) -> ContextManager['ProgramBuilder']:
raise NotImplementedError('Not implemented yet (postponed)')

inner_builder = LinSpaceBuilder(self._idx_to_name)
yield inner_builder
inner_program = inner_builder.to_program()

if inner_program is not None:
#measurements not yet included
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The linspace builder ignores measurements. The idea is to have a dedicated MeasurementBuilder to extract them.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

# measurements = [(name, begin, length)
# for name, (begins, lengths) in inner_program.get_measurement_windows().items()
# for begin, length in zip(begins, lengths)]
# self._top.add_measurements(measurements)
waveform = to_waveform(inner_program,self._idx_to_name)
if global_transformation is not None:
waveform = TransformingWaveform.from_transformation(waveform, global_transformation)
self.play_arbitrary_waveform(waveform)

# raise NotImplementedError('Not implemented yet (postponed)')

def with_iteration(self, index_name: str, rng: range,
measurements: Optional[Sequence[MeasurementWindow]] = None) -> Iterable['ProgramBuilder']:
Expand Down Expand Up @@ -408,3 +425,29 @@ def to_increment_commands(linspace_nodes: Sequence[LinSpaceNode]) -> List[Comman
state.add_node(linspace_nodes)
return state.commands


def to_waveform(program: Sequence[LinSpaceNode], channels: Tuple[ChannelID]) -> Waveform:

SUPPORTED_NODES = {LinSpaceArbitraryWaveform,LinSpaceHold}
SUPPORTED_INITIAL_NODES = {LinSpaceArbitraryWaveform,LinSpaceHold}

assert all(type(node) in SUPPORTED_NODES for node in program), 'some node not (yet) supported for single waveform'
assert type(program[0]) in SUPPORTED_INITIAL_NODES, 'initial node not (yet) supported for single waveform'

sequence = []
for node in program:
if type(node)==LinSpaceArbitraryWaveform:
sequence += [node.waveform]
Nomos11 marked this conversation as resolved.
Show resolved Hide resolved
elif type(node)==LinSpaceHold:
assert node.duration_factors is None, 'NotImplemented'
assert all(factor is None for factor in node.factors), 'NotImplemented'
#the channels should be sorted accordingly so we can do this.
sequence += [MultiChannelWaveform(
[ConstantWaveform(node.duration_base,base,ch) for base,ch in zip(node.bases,channels)])]
terrorfisch marked this conversation as resolved.
Show resolved Hide resolved
else:
raise NotImplementedError()

sequenced_waveform = SequenceWaveform.from_sequence(
sequence
)
return sequenced_waveform
4 changes: 3 additions & 1 deletion qupulse/pulses/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
from qupulse.pulses.arithmetic_pulse_template import ArithmeticPulseTemplate as ArithmeticPT,\
ArithmeticAtomicPulseTemplate as ArithmeticAtomicPT
from qupulse.pulses.time_reversal_pulse_template import TimeReversalPulseTemplate as TimeReversalPT
from qupulse.pulses.time_manipulation_pulse_template import TimeExtensionPulseTemplate as TimeExtensionPT,\
SingleWFTimeExtensionPulseTemplate as SingleWFTimeExtensionPT

import warnings
with warnings.catch_warnings():
Expand All @@ -31,4 +33,4 @@

__all__ = ["FunctionPT", "ForLoopPT", "AtomicMultiChannelPT", "MappingPT", "RepetitionPT", "SequencePT", "TablePT",
"PointPT", "ConstantPT", "AbstractPT", "ParallelConstantChannelPT", "ArithmeticPT", "ArithmeticAtomicPT",
"TimeReversalPT", "ParallelChannelPT"]
"TimeReversalPT", "ParallelChannelPT", "TimeExtensionPT", "SingleWFTimeExtensionPT"]
12 changes: 6 additions & 6 deletions qupulse/pulses/pulse_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ def with_appended(self, *appended: 'PulseTemplate'):
return self

def pad_to(self, to_new_duration: Union[ExpressionLike, Callable[[Expression], ExpressionLike]],
as_single_wf: bool = True,
pt_kwargs: Mapping[str, Any] = None) -> 'PulseTemplate':
"""Pad this pulse template to the given duration.
The target duration can be numeric, symbolic or a callable that returns a new duration from the current
Expand All @@ -392,13 +393,14 @@ def pad_to(self, to_new_duration: Union[ExpressionLike, Callable[[Expression], E
>>> padded_4 = my_pt.pad_to(to_next_multiple(1, 16))
Args:
to_new_duration: Duration or callable that maps the current duration to the new duration
as_single_wf: if the pt is intended to be a single waveform padded to adhere to hardware constraints
pt_kwargs: Keyword arguments for the newly created sequence pulse template.

Returns:
A pulse template that has the duration given by ``to_new_duration``. It can be ``self`` if the duration is
already as required. It is never ``self`` if ``pt_kwargs`` is non-empty.
"""
from qupulse.pulses import ConstantPT, SequencePT
from qupulse.pulses import SingleWFTimeExtensionPT, TimeExtensionPT
current_duration = self.duration
if callable(to_new_duration):
new_duration = to_new_duration(current_duration)
Expand All @@ -407,11 +409,9 @@ def pad_to(self, to_new_duration: Union[ExpressionLike, Callable[[Expression], E
pad_duration = new_duration - current_duration
if not pt_kwargs and pad_duration == 0:
return self
pad_pt = ConstantPT(pad_duration, self.final_values)
if pt_kwargs:
return SequencePT(self, pad_pt, **pt_kwargs)
else:
return self @ pad_pt
if as_single_wf:
return SingleWFTimeExtensionPT(self,0.,pad_duration,**pt_kwargs if pt_kwargs is not None else {})
return TimeExtensionPT(self,0.,pad_duration,**pt_kwargs if pt_kwargs is not None else {})

def __format__(self, format_spec: str):
if format_spec == '':
Expand Down
7 changes: 5 additions & 2 deletions qupulse/pulses/sequence_pulse_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ def __init__(self,
identifier: Optional[str]=None,
parameter_constraints: Optional[Iterable[ConstraintLike]]=None,
measurements: Optional[List[MeasurementDeclaration]]=None,
registry: PulseRegistryType=None) -> None:
registry: PulseRegistryType=None,
allow_subtemplate_concatenation: bool = True) -> None:
"""Create a new SequencePulseTemplate instance.

Requires a (correctly ordered) list of subtemplates in the form
Expand Down Expand Up @@ -75,6 +76,7 @@ def __init__(self,
+ f' defined {defined_channels} vs. subtemplate {subtemplate.defined_channels}')

self._register(registry=registry)
self._allow_subtemplate_concatenation = allow_subtemplate_concatenation

@classmethod
def concatenate(cls, *pulse_templates: Union[PulseTemplate, MappingTuple], **kwargs) -> 'SequencePulseTemplate':
Expand All @@ -92,7 +94,8 @@ def concatenate(cls, *pulse_templates: Union[PulseTemplate, MappingTuple], **kwa
if (isinstance(pt, SequencePulseTemplate)
and pt.identifier is None
and not pt.measurement_declarations
and not pt.parameter_constraints):
and not pt.parameter_constraints
and pt._allow_subtemplate_concatenation):
parsed.extend(pt.subtemplates)
else:
parsed.append(pt)
Expand Down
99 changes: 99 additions & 0 deletions qupulse/pulses/time_manipulation_pulse_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import numbers
from typing import Dict, Optional, Set, Union, List, Iterable

from qupulse import ChannelID
from qupulse._program.transformation import Transformation
from qupulse.parameter_scope import Scope
from qupulse.pulses.pulse_template import PulseTemplate
from qupulse.pulses import ConstantPT, SequencePT
from qupulse.expressions import ExpressionLike, ExpressionScalar
from qupulse._program.waveforms import ConstantWaveform
from qupulse.program import ProgramBuilder
from qupulse.pulses.parameters import ConstraintLike
from qupulse.pulses.measurement import MeasurementDeclaration
from qupulse.serialization import PulseRegistryType


def _evaluate_expression_dict(expression_dict: Dict[str, ExpressionScalar], scope: Scope) -> Dict[str, float]:
return {ch: value.evaluate_in_scope(scope)
for ch, value in expression_dict.items()}


class TimeExtensionPulseTemplate(SequencePT):
"""Extend the given pulse template with a constant(?) prefix and/or suffix.
Both start and stop are defined as positive quantities.
"""

@property
def parameter_names(self) -> Set[str]:
return self._extend_inner.parameter_names | set(self._extend_stop.variables) | set(self._extend_start.variables)

def __init__(self, inner: PulseTemplate, start: ExpressionLike, stop: ExpressionLike,
*,
parameter_constraints: Optional[Iterable[ConstraintLike]]=None,
measurements: Optional[List[MeasurementDeclaration]]=None,
identifier: Optional[str] = None,
registry: PulseRegistryType = None):

self._extend_inner = inner
self._extend_start = ExpressionScalar(start)
self._extend_stop = ExpressionScalar(stop)

id_base = identifier if identifier is not None else ""

start_pt = ConstantPT(self._extend_start,self._extend_inner.initial_values,identifier=id_base+f"__prepend_{id(self)}")
stop_pt = ConstantPT(self._extend_stop,self._extend_inner.final_values,identifier=id_base+f"__postpend_{id(self)}")

super().__init__(start_pt,self._extend_inner,stop_pt,identifier=identifier,
parameter_constraints=parameter_constraints,
measurements=measurements,
registry=registry)


class SingleWFTimeExtensionPulseTemplate(SequencePT):
"""Extend the given pulse template with a constant(?) prefix and/or suffix.
Both start and stop are defined as positive quantities.
"""

@property
def parameter_names(self) -> Set[str]:
return self._extend_inner.parameter_names | set(self._extend_stop.variables) | set(self._extend_start.variables)

def _create_program(self, *,
scope: Scope,
measurement_mapping: Dict[str, Optional[str]],
channel_mapping: Dict[ChannelID, Optional[ChannelID]],
global_transformation: Optional[Transformation],
to_single_waveform: Set[Union[str, 'PulseTemplate']],
program_builder: ProgramBuilder):

super()._create_program(scope=scope,
measurement_mapping=measurement_mapping,
channel_mapping=channel_mapping,
global_transformation=global_transformation,
to_single_waveform=to_single_waveform | {self},
program_builder=program_builder)

def __init__(self, inner: PulseTemplate, start: ExpressionLike, stop: ExpressionLike,
*,
parameter_constraints: Optional[Iterable[ConstraintLike]]=None,
measurements: Optional[List[MeasurementDeclaration]]=None,
identifier: Optional[str] = None,
registry: PulseRegistryType = None):

self._extend_inner = inner
self._extend_start = ExpressionScalar(start)
self._extend_stop = ExpressionScalar(stop)

id_base = identifier if identifier is not None else ""

start_pt = ConstantPT(self._extend_start,self._extend_inner.initial_values,identifier=id_base+f"__prepend_{id(self)}")
stop_pt = ConstantPT(self._extend_stop,self._extend_inner.final_values,identifier=id_base+f"__postpend_{id(self)}")

super().__init__(start_pt,self._extend_inner,stop_pt,identifier=identifier,
parameter_constraints=parameter_constraints,
measurements=measurements,
registry=registry,
allow_subtemplate_concatenation=False)


9 changes: 7 additions & 2 deletions qupulse/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@


__all__ = ["checked_int_cast", "is_integer", "isclose", "pairwise", "replace_multiple", "cached_property",
"forced_hash", "to_next_multiple"]
"forced_hash", "to_next_multiple", "next_multiple_of"]


def checked_int_cast(x: Union[float, int, numpy.ndarray], epsilon: float=1e-6) -> int:
Expand Down Expand Up @@ -149,4 +149,9 @@ def to_next_multiple(sample_rate: ExpressionLike, quantum: int,
else:
#still return 0 if duration==0
return lambda duration: ExpressionScalar(f'{quantum}/({sample_rate})*Max({min_quanta},-(-{duration}*{sample_rate}//{quantum}))*Max(0, sign({duration}))')



def next_multiple_of(duration: ExpressionLike, sample_rate: ExpressionLike, quantum: int,
min_quanta: Optional[int] = None) -> ExpressionScalar:
"""thin wrapper around to_next_multiple to directly call the function"""
return to_next_multiple(sample_rate,quantum,min_quanta)(ExpressionScalar(duration))
Loading
Loading