Skip to content

Feat/linspace measurements reversed registers divisor #901

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

Draft
wants to merge 97 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 85 commits
Commits
Show all changes
97 commits
Select commit Hold shift + click to select a range
1132ee1
linspace time sweep barebone test
Nomos11 May 31, 2024
4d2fdb9
barebone test for pulse metadata
Nomos11 May 31, 2024
7cfcb74
equality handling SimpleExpression; file refactor
Nomos11 May 31, 2024
74eb4d7
supply __lt__ as well to be logically consistent
Nomos11 May 31, 2024
f595806
implement dummywaveform abstract compare subset
Nomos11 Jun 3, 2024
bc985cd
tests
Nomos11 Jun 3, 2024
627a27f
Update setup.cfg
Nomos11 Jun 3, 2024
06aa1b3
delete wrong decorator
Nomos11 Jun 3, 2024
f87b1ea
somewhat fix the tests
Nomos11 Jun 3, 2024
e6b1e9e
uncomment imports
Nomos11 Jun 4, 2024
3c65a9c
forward voltage increment resolution
Nomos11 Jun 5, 2024
634cd30
wrongly assumed hardware resolution
Nomos11 Jun 5, 2024
7d6b01a
draft dependent waits & dependency domains
Nomos11 Jun 6, 2024
1ea5176
first syntactic debug
Nomos11 Jun 6, 2024
37ee51d
fix definition of iterations
Nomos11 Jun 6, 2024
b37b510
see if one can replace int->ChannelID
Nomos11 Jun 7, 2024
c796b61
fix channel trafo call
Nomos11 Jun 7, 2024
6d4e835
fix transform commands
Nomos11 Jun 8, 2024
9fd050c
resolution dependent set/increment
Nomos11 Jun 8, 2024
051e8b9
remove outdates resolution handling attempt
Nomos11 Jun 8, 2024
e58277c
math methods for resolution class
Nomos11 Jun 8, 2024
cf74051
fix domain check
Nomos11 Jun 8, 2024
2447e23
fix __mul__
Nomos11 Jun 8, 2024
a9f1710
test wf amp sweep
Nomos11 Jun 11, 2024
d9dad64
bugfix
Nomos11 Jun 11, 2024
5f22a52
not sure if correct (depstate comparison)
Nomos11 Jun 11, 2024
b04cbcf
fix some of the depkey confusion
Nomos11 Jun 12, 2024
e4c2366
more flexible repetition in sequence structure
Nomos11 Jun 13, 2024
f250226
dependency_key -> key for consistency
Nomos11 Jun 13, 2024
e84ecf8
always emit incr/set before wait
Nomos11 Jun 13, 2024
73513eb
dirty stepped play node in LinSpaceBuilder
Nomos11 Jun 15, 2024
468a46c
further bugfixes
Nomos11 Jun 16, 2024
a7eacf4
further bug patching
Nomos11 Jun 16, 2024
550a31c
hash Commands
Nomos11 Jun 17, 2024
fc8aee4
only modify commands that affect the current awg
Nomos11 Jul 5, 2024
b3176f0
re-commit P.S.' initial changes
Nomos11 Jul 10, 2024
def9369
re-commit P.S.' bugfixes
Nomos11 Jul 10, 2024
66dc3b5
hacky linspace measurements
Nomos11 Sep 6, 2024
419f91f
omit trailing zeros remove. EXPERIMENTAL
Sep 6, 2024
417c6c2
no repetition SimpleExpression scope
Sep 6, 2024
e939b5d
more testcases for linspace
Sep 6, 2024
640672e
meas queue pop fix
Sep 6, 2024
87dc2db
boldly delete assert
Sep 6, 2024
c320fe5
fix tests
Nomos11 Sep 6, 2024
caadef8
nicer command print
Nomos11 Sep 6, 2024
d762797
RDP repr, eq
Nomos11 Sep 7, 2024
eb917b7
test fix
Nomos11 Sep 7, 2024
6c6abb1
some measurement window test
Nomos11 Sep 7, 2024
7c55850
hash RDP
Nomos11 Sep 11, 2024
4000f06
__str__ typo
Nomos11 Sep 11, 2024
090c0a3
np.int64 ugly
Nomos11 Sep 11, 2024
245de3b
breaking: no LinSpaceSequence anymore
Nomos11 Sep 14, 2024
4f29e2b
dummy drop measurements
Nomos11 Sep 14, 2024
fa03049
don't need to provide channels to linspaceprograms
Nomos11 Sep 14, 2024
31fb3ea
bugfix default argument
Nomos11 Sep 14, 2024
fcd6510
optionally omit redundant plotting samples for faster pyplot
Nomos11 Sep 14, 2024
bb1a508
style change
Nomos11 Sep 14, 2024
56db9d7
needs further testing in repetition node
Nomos11 Sep 15, 2024
9293abc
measure_program default channel arg
Nomos11 Sep 19, 2024
4325ea3
demo script to check awg output
Nomos11 Sep 20, 2024
74d5dc8
pad_to as single waveform
Nomos11 Sep 21, 2024
cb8d1bd
Merge pull request #875 from qutech/feat/linspace_measurements_timeex…
Nomos11 Sep 21, 2024
1e48d85
change example
Nomos11 Oct 14, 2024
6011ab3
bug example channel mapping
Nomos11 Oct 14, 2024
4d89e4a
file io in expectation_checker
Nomos11 Oct 25, 2024
de1295a
build-waveform channel_mapping in loopPT consistency
Nomos11 Oct 27, 2024
feafac0
revert option to split pad_to to more waveforms
Nomos11 Oct 27, 2024
b1c55b4
pad all atomic subtemplates
Nomos11 Oct 27, 2024
4922d71
correct hash function
Nomos11 Nov 7, 2024
82bd04c
quick hack AtomicTimeReversalPT
Nomos11 Dec 13, 2024
5050d92
error message for TimeReversal
Nomos11 Dec 13, 2024
55a4e6b
also on creation
Nomos11 Dec 13, 2024
f93e02f
simon's reversed loopbuilder
Nomos11 Dec 19, 2024
6d78bec
potentially working LinSpaceBuilder reversal
Nomos11 Dec 19, 2024
ec18543
measurement reversed (without t-sweep)
Nomos11 Dec 19, 2024
60d493f
include loop idx in depkey
Nomos11 Feb 24, 2025
0d377c1
include loop idx at which depkey **should** be freed
Nomos11 Feb 24, 2025
30dfecb
disable print
Nomos11 Feb 24, 2025
2e8c9c9
wrong indent level; remove print
Nomos11 Feb 28, 2025
3d2c634
revert comparison yet again
Nomos11 Feb 28, 2025
224907e
need to pop
Nomos11 Feb 28, 2025
88f64c2
waveformcollection duration
Nomos11 Mar 3, 2025
a0b1194
why was this not noticed before
Nomos11 Mar 13, 2025
4cbc158
power 2 hack
Nomos11 Jun 7, 2025
88faf7b
SH's sympy fix from main
Nomos11 Jun 25, 2025
8ed592c
update typehint for change in qupulse-hdawg
Nomos11 Jul 3, 2025
1b299e7
further explanation
Nomos11 Jul 3, 2025
d88f45d
ooon
Nomos11 Jul 3, 2025
461202b
fix(?)/hack in measurements in rep/iter/sequence
Nomos11 Jul 17, 2025
2fbb63a
? git missed something
Nomos11 Jul 17, 2025
e8337e2
interestingly not seen before
Nomos11 Jul 18, 2025
88740ba
roll out time sweeps in linspacebuilder
Nomos11 Jul 18, 2025
f6e66c5
optionally roll out other vars
Nomos11 Jul 18, 2025
0d25a97
propagation
Nomos11 Jul 18, 2025
457a7d0
optionally concatenate sequential wfs based on duration
Nomos11 Jul 18, 2025
a9822d7
bracket required - Chil Pollins
Nomos11 Jul 19, 2025
c539dad
mutable default
Nomos11 Jul 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
481 changes: 481 additions & 0 deletions qupulse/examples/expectation_checker.py
Copy link
Member

Choose a reason for hiding this comment

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

Move to root examples directory

Large diffs are not rendered by default.

142 changes: 142 additions & 0 deletions qupulse/examples/expectation_checker_example_script.py
Copy link
Member

Choose a reason for hiding this comment

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

Move to root examples directory

Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# -*- coding: utf-8 -*-
import numpy as np

from qupulse.pulses import PointPT, ConstantPT, RepetitionPT, ForLoopPT, TablePT,\
FunctionPT, AtomicMultiChannelPT, SequencePT, MappingPT, ParallelConstantChannelPT
from qupulse.program.linspace import LinSpaceBuilder

from qupulse.plotting import plot
from qupulse.utils import to_next_multiple

from expectation_checker import ExpectationChecker, HDAWGAlazar

#%% Get Devices

# Get the Alazar and the HDAWG in hardcoded configuration (2V p-p range)
# Connect any Marker from the HDAWG to Trig in of the Alazar
# Connect one dummy channel from HDAWG to the external clock of the Alazar,
# then set 0.5V-10MHz-oscillator on this channel (e.g. in HDAWG-Webinterface)

ha = HDAWGAlazar("DEVXXXX","USB",)

#%% Example pulse definitions

# PulseTemplates must be defined on channels named as 'ZI0_X', X in A to H
# (ensure correct mapping to Alazar)
# Markers will be overwritten to play a marker on each channel to trigger the Alazar
# identifiers of the final PT will be the names of the plotted object


class ShortSingleRampTest():
def __init__(self, base_time=1e3):
hold = ConstantPT(base_time, {'a': '-1. + idx * 0.01'})
pt = hold.with_iteration('idx', 200)
self.pulse_template = MappingPT(pt,
channel_mapping={'a':'ZI0_A',},
identifier=self.__class__.__name__
)

class ShortSingleRampTestWithPlay():
def __init__(self,base_time=1e3+8):
# init = PointPT([(1.0,1e4)],channel_names=('ZI0_MARKER_FRONT',))
init = FunctionPT('1.0+1e-9*t',base_time,channel='ZI0_A_MARKER_FRONT')#.pad_to(to_next_multiple(1.0,16,4),)

hold = ConstantPT(base_time, {'a': '-1. + idx * 0.01'})#.pad_to(to_next_multiple(1.0,16,4))
pt = ParallelConstantChannelPT(init,dict(ZI0_A=0.))@(ParallelConstantChannelPT(hold,dict(ZI0_A_MARKER_FRONT=0.)).with_iteration('idx', 200))
self.pulse_template = MappingPT(pt,
channel_mapping={'a':'ZI0_A',},
identifier=self.__class__.__name__
)

class SequencedRepetitionTest():
def __init__(self,base_time=1e2,rep_factor=2):
wait = AtomicMultiChannelPT(
ConstantPT(f'64*{base_time}', {'a': '-1. + idx_a * 0.01 + y_gain', }),
ConstantPT(f'64*{base_time}', {'b': '-0.5 + idx_b * 0.02'})
)

dependent_constant = AtomicMultiChannelPT(
ConstantPT(64*base_time, {'a': '-1.0 + y_gain'}),
ConstantPT(64*base_time, {'b': '-0.5 + idx_b*0.02',}),
)

dependent_constant2 = AtomicMultiChannelPT(
ConstantPT(64*base_time, {'a': '-0.5 + y_gain'}),
ConstantPT(64*base_time, {'b': '-0.3 + idx_b*0.02',}),
)


pt = (dependent_constant @ dependent_constant2.with_repetition(rep_factor) @ (wait.with_iteration('idx_a', rep_factor))).with_iteration('idx_b', rep_factor)\

self.pulse_template = MappingPT(pt,parameter_mapping=dict(y_gain=0.3,),
channel_mapping={'a':'ZI0_A','b':'ZI0_C'},
identifier=self.__class__.__name__
)


class SteppedRepetitionTest():
def __init__(self,base_time=1e2,rep_factor=2):

wait = ConstantPT(f'64*{base_time}*(1+idx_t)', {'a': '-0.5 + idx_a * 0.15', 'b': '-.5 + idx_a * 0.3'})
normal_pt = ParallelConstantChannelPT(FunctionPT("sin(t/1000)","t_sin",channel='a'),{'b':-0.2})
amp_pt = ParallelConstantChannelPT("amp*1/8"*FunctionPT("sin(t/2000)","t_sin",channel='a'),{'b':-0.5})
# amp_pt2 = ParallelConstantChannelPT("amp2*1/8"*FunctionPT("sin(t/1000)","t_sin",channel='a'),{'b':-0.5})
amp_inner = ParallelConstantChannelPT(FunctionPT(f"(1+amp)*1/(2*{rep_factor})*sin(4*pi*t/t_sin)","t_sin",channel='a'),{'b':-0.5})
amp_inner2 = ParallelConstantChannelPT(FunctionPT(f"(1+amp2)*1/(2*{rep_factor})*sin((1*freq)*4*pi*t/t_sin)+off/(2*{rep_factor})","t_sin",channel='a'),{'b':-0.3})

pt = ((((normal_pt@amp_inner2).with_iteration('off', rep_factor)@normal_pt@wait)\
.with_repetition(rep_factor))@amp_inner.with_iteration('amp', rep_factor))\
.with_iteration('amp2', rep_factor).with_iteration('freq', rep_factor).with_iteration('idx_a',rep_factor)

self.pulse_template = MappingPT(pt,parameter_mapping=dict(t_sin=64*base_time,idx_t=1,
#idx_a=1,#freq=1,#amp2=1
),
channel_mapping={'a':'ZI0_A','b':'ZI0_C'},
identifier=self.__class__.__name__)

class TimeSweepTest():
def __init__(self,base_time=1e2,rep_factor=3):
wait = ConstantPT(f'64*{base_time}*(1+idx_t)',
{'a': '-1. + idx_a * 0.01', 'b': '-.5 + idx_b * 0.02'})

random_constant = ConstantPT(64*base_time, {'a': -.4, 'b': -.3})
meas = ConstantPT(64*base_time, {'a': 0.05, 'b': 0.06})

singlet_scan = (SequencePT(random_constant,wait,meas,identifier='s')).with_iteration('idx_a', rep_factor)\
.with_iteration('idx_b', rep_factor)\
.with_iteration('idx_t', rep_factor)

self.pulse_template = MappingPT(singlet_scan,channel_mapping={'a':'ZI0_A','b':'ZI0_C'},
identifier=self.__class__.__name__)


#%% Instantiate Checker

# select exemplary pulse
pulse = ShortSingleRampTest(1e3+8)
# pulse = ShortSingleRampTestWithPlay()
# pulse = SequencedRepetitionTest(1e3,4)
# pulse = SteppedRepetitionTest(1e2,3)
# pulse = TimeSweepTest(1e2,3)

# Define a program builder to test program with:
program_builder = LinSpaceBuilder(
#set to True to ensure triggering at Program start if program starts with constant pulse
play_marker_when_constant=True,
#in case stepped repetitions are needed, insert variables here:
to_stepping_repeat={'example',},
)

# Data will be saved as xr.Dataset in save_path
# data_offsets corrects for offsets in Alazar (not in saved data, only in plotting)
checker = ExpectationChecker(ha, pulse.pulse_template,
program_builder=program_builder,
save_path=SAVE_HERE,
data_offsets={'t_offset':-100.,'v_offset':0.008,'v_scale':0.9975}
)

assert float(pulse.pulse_template.duration) < 1e7, "Ensure you know what you're doing when recording long data"

#%% Run the checker

checker.run()
141 changes: 141 additions & 0 deletions qupulse/expressions/simple.py
Copy link
Member

Choose a reason for hiding this comment

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

Looks good.

Copy link
Member

Choose a reason for hiding this comment

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

I reconsidered and remembered I did not put them there for a reason. The idea is that SimpleExpressions are more like context sensitive numbers than expressions.

Copy link
Collaborator Author

@Nomos11 Nomos11 Jul 2, 2025

Choose a reason for hiding this comment

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

I only needed to move them due to some circular import otherwise i believe. The name might indeed be misleading

Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import numpy as np
from numbers import Real, Number
from typing import Optional, Union, Sequence, ContextManager, Mapping, Tuple, Generic, TypeVar, Iterable, Dict, List
from dataclasses import dataclass

from functools import total_ordering
from qupulse.utils.sympy import _lambdify_modules
from qupulse.expressions import sympy as sym_expr, Expression
from qupulse.utils.types import MeasurementWindow, TimeType, FrozenMapping


NumVal = TypeVar('NumVal', bound=Real)


@total_ordering
@dataclass
class SimpleExpression(Generic[NumVal]):
"""This is a potential hardware evaluable expression of the form

C + C1*R1 + C2*R2 + ...
where R1, R2, ... are potential runtime parameters.

The main use case is the expression of for loop dependent variables where the Rs are loop indices. There the
expressions can be calculated via simple increments.
"""

base: NumVal
offsets: Mapping[str, NumVal]

def __post_init__(self):
assert isinstance(self.offsets, Mapping)

def value(self, scope: Mapping[str, NumVal]) -> NumVal:
value = self.base
for name, factor in self.offsets.items():
value += scope[name] * factor
return value

def __abs__(self):
return abs(self.base)+sum([abs(o) for o in self.offsets.values()])

def __eq__(self, other):
#there is no good way to compare it without having a value,
#but cannot require more parameters in magic method?
#so have this weird full equality for now which doesn logically make sense
#in most cases to catch unintended consequences

if isinstance(other, (float, int, TimeType)):
return self.base==other and all([o==other for o in self.offsets])

if type(other) == type(self):
if len(self.offsets)!=len(other.offsets): return False
return self.base==other.base and all([o1==o2 for o1,o2 in zip(self.offsets,other.offsets)])

return NotImplemented

def __gt__(self, other):
return all([b for b in self._return_greater_comparison_bools(other)])

def __lt__(self, other):
return all([not b for b in self._return_greater_comparison_bools(other)])

def _return_greater_comparison_bools(self, other) -> List[bool]:
#there is no good way to compare it without having a value,
#but cannot require more parameters in magic method?
#so have this weird full equality for now which doesn logically make sense
#in most cases to catch unintended consequences
if isinstance(other, (float, int, TimeType)):
return [self.base>other] + [o>other for o in self.offsets.values()]

if type(other) == type(self):
if len(self.offsets)!=len(other.offsets): return [False]
return [self.base>other.base] + [o1>o2 for o1,o2 in zip(self.offsets.values(),other.offsets.values())]

return NotImplemented

def __add__(self, other):
if isinstance(other, (float, int, TimeType)):
return SimpleExpression(self.base + other, self.offsets)

if type(other) == type(self):
offsets = self.offsets.copy()
for name, value in other.offsets.items():
offsets[name] = value + offsets.get(name, 0)
return SimpleExpression(self.base + other.base, offsets)

return NotImplemented

def __radd__(self, other):
return self.__add__(other)

def __sub__(self, other):
return self.__add__(-other)

def __rsub__(self, other):
return (-self).__add__(other)

def __neg__(self):
return SimpleExpression(-self.base, {name: -value for name, value in self.offsets.items()})

def __mul__(self, other: NumVal):
if isinstance(other, (float, int, TimeType)):
return SimpleExpression(self.base * other, {name: other * value for name, value in self.offsets.items()})

return NotImplemented

def __rmul__(self, other):
return self.__mul__(other)

def __truediv__(self, other):
inv = 1 / other
return self.__mul__(inv)

def __hash__(self):
return hash((self.base,frozenset(sorted(self.offsets.items()))))

@property
def free_symbols(self):
return ()

def _sympy_(self):
return self

def replace(self, r, s):
return self

def evaluate_in_scope_(self, *args, **kwargs):
# TODO: remove. It is currently required to avoid nesting this class in an expression for the MappedScope
# We can maybe replace is with a HardwareScope or something along those lines
return self


#alibi class to allow instance check?
@dataclass
class SimpleExpressionStepped(SimpleExpression):
step_nesting_level: int
rng: range
reverse: int|bool


_lambdify_modules.append({'SimpleExpression': SimpleExpression, 'SimpleExpressionStepped': SimpleExpressionStepped})
Loading