add type hints to Experiment.py and dac.py, set up pyrefly as dev dependency
Build Debian Packages / build (bookworm, debian12) (push) Successful in 9m3s
Build Debian Packages / build (trixie, debian13) (push) Successful in 9m9s

This commit is contained in:
markusro committed 2026-10-17 21:52:10 +02:00
1 parent f9d8de7697
commit efb01250c8
4 files changed
+85 -44

No files matched your search

+12
View File
@@ -39,6 +39,9 @@ maintainers = [
[project.scripts] [project.scripts]
DAMARIS3 = "damaris.__main__:main" DAMARIS3 = "damaris.__main__:main"
[dependency-groups]
dev = ["pyrefly"]
[tool.setuptools.packages.find] [tool.setuptools.packages.find]
where = ["src"] where = ["src"]
@@ -50,6 +53,15 @@ version = {attr = "damaris.__version__"}
[tool.setuptools.data-files] [tool.setuptools.data-files]
"share/python3-damaris/images" = ["src/damaris/gui/DAMARIS3.png", "src/damaris/gui/DAMARIS3.ico"] "share/python3-damaris/images" = ["src/damaris/gui/DAMARIS3.png", "src/damaris/gui/DAMARIS3.ico"]
[tool.pyrefly]
project-includes = [
"src/damaris/experiments/Experiment.py",
"src/damaris/experiments/dac.py",
]
search-path = ["src"]
python-version = "3.11"
python-platform = "linux"
#"share/python3-damaris/doc" = ["doc/index.html"] #"share/python3-damaris/doc" = ["doc/index.html"]
#"share/python3-damaris/doc/reference-html" = ["doc/reference-html/*"] #"share/python3-damaris/doc/reference-html" = ["doc/reference-html/*"]
#"share/python3-damaris/doc/tutorial-html" = ["doc/tutorial-html/*"] #"share/python3-damaris/doc/tutorial-html" = ["doc/tutorial-html/*"]
+45 -43
View File
@@ -3,20 +3,20 @@ import types
import numpy import numpy
class StateBase(object): class StateBase(object):
def __init__(self): def __init__(self) -> None:
pass pass
def to_xml(self, indent = ""): def to_xml(self, indent: str = "") -> str:
return indent + "<!-- " + repr(self) + " -->" return indent + "<!-- " + repr(self) + " -->"
class StateSimple(StateBase): class StateSimple(StateBase):
def __init__(self, time, content=None): def __init__(self, time: float, content: object | None = None) -> None:
super(StateSimple, self).__init__() super(StateSimple, self).__init__()
if time < 0: if time < 0:
raise AssertionError("time for state is negative!") raise AssertionError("time for state is negative!")
self.time = time self.time = time
self.content = content self.content = content
def to_xml(self, indent = ""): def to_xml(self, indent: str = "") -> str:
s = indent + '<state time="%s"' % repr(self.time) s = indent + '<state time="%s"' % repr(self.time)
if self.content is None: if self.content is None:
return s + '/>\n' return s + '/>\n'
@@ -24,14 +24,14 @@ class StateSimple(StateBase):
s += indent + ' ' + str(self.content) + '\n' s += indent + ' ' + str(self.content) + '\n'
s += indent + '</state>\n' s += indent + '</state>\n'
return s return s
def __repr__(self): def __repr__(self) -> str:
return 'StateSimple(%s, %s)' % (self.time, repr(self.content)) return 'StateSimple(%s, %s)' % (self.time, repr(self.content))
class StateList(StateBase): class StateList(StateBase):
def __init__(self): def __init__(self) -> None:
super(StateList, self).__init__() super(StateList, self).__init__()
self.list = [] self.list: list[object] = []
def to_xml(self, indent = " "): def to_xml(self, indent: str = " ") -> str:
s = "" s = ""
for k in self.list: for k in self.list:
if hasattr(k, "to_xml"): if hasattr(k, "to_xml"):
@@ -39,21 +39,21 @@ class StateList(StateBase):
else: else:
s += indent + str(k) s += indent + str(k)
return s return s
def append(self, val): def append(self, val: object) -> None:
self.list.append(val) self.list.append(val)
class StateLoop(StateList): class StateLoop(StateList):
"""Represents a loop in the state tree""" """Represents a loop in the state tree"""
def __init__(self, repeat): def __init__(self, repeat: int) -> None:
super(StateLoop, self).__init__() super(StateLoop, self).__init__()
self.repeat = repeat self.repeat = repeat
def to_xml(self, indent = ""): def to_xml(self, indent: str = "") -> str:
s = indent + ('<sequent repeat="%d">\n' % self.repeat) s = indent + ('<sequent repeat="%d">\n' % self.repeat)
s += super(StateLoop, self).to_xml(indent + " ") s += super(StateLoop, self).to_xml(indent + " ")
s += indent + '</sequent>\n' s += indent + '</sequent>\n'
return s return s
def __repr__(self): def __repr__(self) -> str:
return 'StateLoop(repeat=%d, %s)' \ return 'StateLoop(repeat=%d, %s)' \
% (self.repeat, repr(self.list)) % (self.repeat, repr(self.list))
@@ -78,32 +78,32 @@ class Experiment:
:param list rf_gates: list of rf gates ttl channels len(rf_gates) == len(rf_sources) :param list rf_gates: list of rf gates ttl channels len(rf_gates) == len(rf_sources)
""" """
job_id = 0 job_id: int = 0
def __init__(self, gating=None, rf_sources=[], rf_gates=[]): def __init__(self, gating: float | None = None, rf_sources: list[int] = [], rf_gates: list[int] = []) -> None:
self.job_id = Experiment.job_id self.job_id: int = Experiment.job_id
Experiment.job_id += 1 Experiment.job_id += 1
self.state_list = StateList() self.state_list: StateList = StateList()
self.list_stack = [] self.list_stack: list[StateList] = []
self.description = { } self.description: dict[str, object] = { }
self.gating = gating self.gating: float | None = gating
assert type(rf_sources) == type(list()), "rf_sources needs to be a list with the channels" assert type(rf_sources) == type(list()), "rf_sources needs to be a list with the channels"
assert type(rf_gates) == type(list()), "rf_gates needs to be a list with the channels" assert type(rf_gates) == type(list()), "rf_gates needs to be a list with the channels"
assert len(rf_gates) == len(rf_sources), "rf_sources and rf_gates must have equal number of entries" assert len(rf_gates) == len(rf_sources), "rf_sources and rf_gates must have equal number of entries"
self.rf_sources = rf_sources self.rf_sources: list[int] = rf_sources
self.rf_gates = rf_gates self.rf_gates: list[int] = rf_gates
#for tracking the experiment length: #for tracking the experiment length:
#because loops are possible we need to track the length for each loop level #because loops are possible we need to track the length for each loop level
self.total_time=[0.0] self.total_time: list[float] = [0.0]
#and we need to know the number of iterations of the loops to multiply the state. #and we need to know the number of iterations of the loops to multiply the state.
self.loop_iterations=[1] self.loop_iterations: list[int] = [1]
# Commands ------------------------------------------------------------------------------------- # Commands -------------------------------------------------------------------------------------
def ttl_pulse(self, length, channel = None, value = None): def ttl_pulse(self, length: float, channel: int | None = None, value: int | None = None) -> None:
""" """
Creates a state with length **length** and switches requested bits of the pulse programmer to HIGH. Creates a state with length **length** and switches requested bits of the pulse programmer to HIGH.
@@ -151,7 +151,7 @@ class Experiment:
self.total_time[-1] += length self.total_time[-1] += length
## Same as ttl_pulse, but no *channel* keyword ## Same as ttl_pulse, but no *channel* keyword
def ttls(self, length = None, value = None): def ttls(self, length: float | None = None, value: int | None = None) -> None:
""" """
Same as ttl_pulse, but no *channel* keyword Same as ttl_pulse, but no *channel* keyword
@@ -159,6 +159,7 @@ class Experiment:
:param int value: lines to set (integer) :param int value: lines to set (integer)
:return: :return:
""" """
assert value is not None, "ttls needs an integer value"
the_value=int(value) the_value=int(value)
s_content = '<ttlout value="0x%06x"/>' % the_value s_content = '<ttlout value="0x%06x"/>' % the_value
if length is not None: if length is not None:
@@ -168,7 +169,7 @@ class Experiment:
self.state_list.append(s_content) self.state_list.append(s_content)
def rf_pulse(self, length=None, phase=0, source=0): def rf_pulse(self, length: float | None = None, phase: float = 0, source: int = 0) -> None:
""" """
Make an rf pulse, including gating and phase switching. Only possible if Experiment is configured. Make an rf pulse, including gating and phase switching. Only possible if Experiment is configured.
@@ -183,7 +184,7 @@ class Experiment:
self.ttls(length=length, value=self.rf_gates[source]+self.rf_sources[source]) self.ttls(length=length, value=self.rf_gates[source]+self.rf_sources[source])
## Beginning of a new state ## Beginning of a new state
def state_start(self, time): def state_start(self, time: float) -> None:
""" """
Starts a state in the pulse programs with duration *time*. Starts a state in the pulse programs with duration *time*.
This must be closed with :func:`state_end`. This must be closed with :func:`state_end`.
@@ -194,13 +195,13 @@ class Experiment:
self.total_time[-1] += time self.total_time[-1] += time
## End of *state_start* ## End of *state_start*
def state_end(self): def state_end(self) -> None:
""" """
Closes a previous :func:`state_start` Closes a previous :func:`state_start`
""" """
self.state_list.append('</state>\n') self.state_list.append('</state>\n')
def wait(self, time, ttls=None, gating=False): def wait(self, time: float, ttls: int | None = None, gating: bool = False) -> None:
""" """
Waits for a specified amount of time without performing any actions. Waits for a specified amount of time without performing any actions.
@@ -241,6 +242,7 @@ class Experiment:
""" """
if gating: if gating:
assert self.gating is not None, "gating time must be set for gating=True"
time -= self.gating time -= self.gating
if ttls is not None: if ttls is not None:
s_content = '<ttlout value="0x%06x"/>' % ttls s_content = '<ttlout value="0x%06x"/>' % ttls
@@ -251,7 +253,7 @@ class Experiment:
self.total_time[-1] += time self.total_time[-1] += time
def record(self, samples, frequency, timelength=None, sensitivity = None, ttls=None, channels = 3, offset = None, impedance = None): def record(self, samples: int, frequency: float, timelength: float | None = None, sensitivity: float | list[float] | None = None, ttls: int | None = None, channels: int = 3, offset: int | list[int] | None = None, impedance: float | list[float] | None = None) -> None:
""" """
Records data with a given number of samples, sampling frequency, and sensitivity. Records data with a given number of samples, sampling frequency, and sensitivity.
This command starts data acquisition from the ADC (Analog-to-Digital Converter). This command starts data acquisition from the ADC (Analog-to-Digital Converter).
@@ -365,7 +367,7 @@ class Experiment:
## Create a loop on the pulse programmer. Loop contents can not change inside the loop. ## Create a loop on the pulse programmer. Loop contents can not change inside the loop.
# @params iterations Number of loop iterations # @params iterations Number of loop iterations
def loop_start(self, iterations): def loop_start(self, iterations: int) -> None:
""" """
creates a loop of given number of iterations and has to be closed by loop_end(). creates a loop of given number of iterations and has to be closed by loop_end().
Commands inside the loop can not change, i.e., the parameters are the same for each loop run. Commands inside the loop can not change, i.e., the parameters are the same for each loop run.
@@ -384,7 +386,7 @@ class Experiment:
self.loop_iterations.append(iterations) self.loop_iterations.append(iterations)
## End loop state ## End loop state
def loop_end(self): def loop_end(self) -> None:
""" """
Closes a l:func:`loop_start` statement. Closes a l:func:`loop_start` statement.
""" """
@@ -396,7 +398,7 @@ class Experiment:
self.total_time[-1] += looptime*loopiterations self.total_time[-1] += looptime*loopiterations
def set_frequency(self, frequency, phase, ttls=0): def set_frequency(self, frequency: float, phase: float, ttls: int = 0) -> None:
""" """
Sets the frequency and phase of the frequency source and optionally further channels. Sets the frequency and phase of the frequency source and optionally further channels.
The state length is 2 µs. Stabilisation time depends on RF source. The state length is 2 µs. Stabilisation time depends on RF source.
@@ -412,7 +414,7 @@ class Experiment:
self.total_time[-1] += 2e-6 self.total_time[-1] += 2e-6
def set_pfg(self, dac_value=None, length=None, shape=('rec', 0), trigger=4, is_seq=False): def set_pfg(self, dac_value: int | None = None, length: float | None = None, shape: tuple[str, float] = ('rec', 0), trigger: int = 4, is_seq: bool = False) -> None:
""" """
This sets the value for the PFG, it also sets it back to dac_value=0 automatically. This sets the value for the PFG, it also sets it back to dac_value=0 automatically.
If you don't wish to do so (i.e. line shapes) set is_seq=1 If you don't wish to do so (i.e. line shapes) set is_seq=1
@@ -480,7 +482,7 @@ class Experiment:
else: # don't know what to do else: # don't know what to do
raise SyntaxError("form is unknown: %s"%form) raise SyntaxError("form is unknown: %s"%form)
def set_dac(self, dac_value, dac_id=1, length=None, is_seq=False, ttls=0): def set_dac(self, dac_value: int, dac_id: int = 1, length: float | None = None, is_seq: bool = False, ttls: int = 0) -> None:
""" """
Sets the value for the DAC (Digital-to-Analog Converter) and optionally additional TTL lines. Sets the value for the DAC (Digital-to-Analog Converter) and optionally additional TTL lines.
This command is used to control analog output signals, such as pulsed field gradients. This command is used to control analog output signals, such as pulsed field gradients.
@@ -544,7 +546,7 @@ class Experiment:
self.total_time[-1] += 42*9e-8 self.total_time[-1] += 42*9e-8
def set_phase(self, phase, ttls=0): def set_phase(self, phase: float, ttls: int = 0) -> None:
""" """
Sets the phase of the RF source to the specified value. Sets the phase of the RF source to the specified value.
@@ -589,7 +591,7 @@ class Experiment:
self.total_time[-1] += 0.5e-6 self.total_time[-1] += 0.5e-6
def set_description(self, key, value): def set_description(self, key: str, value: object) -> None:
""" """
Sets a description key-value pair in the experiment description dictionary. Sets a description key-value pair in the experiment description dictionary.
@@ -629,7 +631,7 @@ class Experiment:
print('Warning: Overwriting existing description "%s" = "%s" with "%s"' % (key, self.description[key], value)) print('Warning: Overwriting existing description "%s" = "%s" with "%s"' % (key, self.description[key], value))
self.description[key] = value self.description[key] = value
def set_pts_local(self): def set_pts_local(self) -> None:
""" """
Sets the PTS310/PTS500 frequency source to local mode. Sets the PTS310/PTS500 frequency source to local mode.
State length is 2 µs. State length is 2 µs.
@@ -653,7 +655,7 @@ class Experiment:
return self.job_id return self.job_id
def write_xml_string(self): def write_xml_string(self) -> str:
""" """
Returns the current program as a string Returns the current program as a string
@@ -706,14 +708,14 @@ class Experiment:
return xml_string return xml_string
def write_quit_job(self): def write_quit_job(self) -> str:
""" """
Returns a xml quit-job Returns a xml quit-job
:returns: XML quit-job :returns: XML quit-job
""" """
return '<?xml version="1.0" encoding="ISO-8859-1"?>\n<quit/>' return '<?xml version="1.0" encoding="ISO-8859-1"?>\n<quit/>'
def get_length(self): def get_length(self) -> float:
timelength = 0.0 timelength = 0.0
#calculate the correct timelength also for unclosed loops, if there is no unclosed loop #calculate the correct timelength also for unclosed loops, if there is no unclosed loop
#the timelength of this experiment is self.total_time[-1]. #the timelength of this experiment is self.total_time[-1].
@@ -723,13 +725,13 @@ class Experiment:
return timelength return timelength
class Quit(Experiment): class Quit(Experiment):
def write_xml_string(self): def write_xml_string(self) -> str:
return '<?xml version="1.0" encoding="ISO-8859-1"?>\n<quit no="%d"/>'%self.job_id return '<?xml version="1.0" encoding="ISO-8859-1"?>\n<quit no="%d"/>'%self.job_id
# /Public Methods ------------------------------------------------------------------------------ # /Public Methods ------------------------------------------------------------------------------
def self_test(): def self_test() -> None:
e = Experiment() e = Experiment()
e.set_description("key", "value") e.set_description("key", "value")
e.set_frequency(85e6, 90, ttls=16) e.set_frequency(85e6, 90, ttls=16)
+1 -1
View File
@@ -2,7 +2,7 @@
""" """
This module holds everything connected with the DAC and PFG This module holds everything connected with the DAC and PFG
""" """
def conv(I_out=0): def conv(I_out: float = 0) -> int:
""" """
converts the demanded Output current in Integer converts the demanded Output current in Integer
""" """
Generated
+27
View File
@@ -938,6 +938,25 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" },
] ]
[[package]]
name = "pyrefly"
version = "1.4.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b3/3e/ea6893be167fa14307d7f9a924bed9ecbbbd91b01953890b9b3c58ed5359/pyrefly-1.4.0.tar.gz", hash = "sha256:bb57c004a4842cfe93f93aebb5203ca6209a5f0c8bda47e8bc289e70940fd910", size = 6962062, upload-time = "2026-10-15T16:54:11.819Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6a/17/dfce6611acc00cb26667e5e1011438cc510f3067f961e0f07f7a0c68f416/pyrefly-1.4.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:460cec39bc8743915e9c2c8c10ba926ad0b29b099d89c1859fb97e0059f42fa7", size = 15705624, upload-time = "2026-10-15T16:53:43.412Z" },
{ url = "https://files.pythonhosted.org/packages/a6/78/a7b96442123fa6ed733de0d1ea933e35d6fc6b7c9cf16e05279abb493493/pyrefly-1.4.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5a6370d686b6cbbac8869832f09a52012888371956fbc68d446a349bc85ccd43", size = 14978669, upload-time = "2026-10-15T16:53:46.546Z" },
{ url = "https://files.pythonhosted.org/packages/23/97/dfcfec6a9d8948ab4b2746f7e87558a82bffabb7a54c22e7d3ce4bdc1932/pyrefly-1.4.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f1b16923a6583a6764e8aec07787e2ef4df644c7030294cba71339783a9451c", size = 15438858, upload-time = "2026-10-15T16:53:48.904Z" },
{ url = "https://files.pythonhosted.org/packages/31/12/6fa414aa87e398d6ddc7145a5d993b0975fc3511198dbb72c47873653ab6/pyrefly-1.4.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:688ad4cb4d6e711d67026fdcfb37b599f86061efe11ba5704287330e7041a69e", size = 16852241, upload-time = "2026-10-15T16:53:51.496Z" },
{ url = "https://files.pythonhosted.org/packages/a8/73/2d7312262cf6481d6ad46eed55b8f2592c3cff60f95cc2f30a114cd1484e/pyrefly-1.4.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f5f3c9dddb6f3128beb49cf4e0f5cb88d4d17f039008d8e0c2be653966acd234", size = 16751917, upload-time = "2026-10-15T16:53:54.39Z" },
{ url = "https://files.pythonhosted.org/packages/2c/65/b44a1cb23667bcbcdc88558cbef79fa9e07a1152c2a1da148036b1a2e0d1/pyrefly-1.4.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6951f51687304caebfedf13dd1a943aeafdbc95abcc4e24a683b97467a77007", size = 16066345, upload-time = "2026-10-15T16:53:57.436Z" },
{ url = "https://files.pythonhosted.org/packages/a6/07/7ca6ff0c53e3a90ecdab05790d33f430fca5c48687613a0b182744166d98/pyrefly-1.4.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:838de4d4a8b9b8ed39bedd0b8b55629c69661d73a6fa3a3a4adff24b9dbeb399", size = 15461223, upload-time = "2026-10-15T16:54:00.364Z" },
{ url = "https://files.pythonhosted.org/packages/61/be/ec7b2f38a898ce5b2219dda730e87d31fc8cfb2108a3d5844f9d590f1281/pyrefly-1.4.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:afb9ce2e0ae3e6106fb45f6d47536ac92b6423e601e08401cdefa850066444f5", size = 16119099, upload-time = "2026-10-15T16:54:02.664Z" },
{ url = "https://files.pythonhosted.org/packages/6e/16/1b9549168b38a575d9c168e17b4b4ee30028d602ec0f7b07c198ef265dbe/pyrefly-1.4.0-py3-none-win32.whl", hash = "sha256:839db536d0ac29f1b23e2d974fb911075c2283e3e623755c45d826aafac175bc", size = 14858880, upload-time = "2026-10-15T16:54:05.114Z" },
{ url = "https://files.pythonhosted.org/packages/03/02/18f075d4de0e255f184f42fc28c1f0ecc9f49dd528678479fc90cf12246c/pyrefly-1.4.0-py3-none-win_amd64.whl", hash = "sha256:7aff37c2b711c8240ed650da0c6148237b327bf7e5dfacee65884f1adea22305", size = 15726220, upload-time = "2026-10-15T16:54:07.452Z" },
{ url = "https://files.pythonhosted.org/packages/92/62/92947076681550904c72a9dd03b24e92694ede9a48548f5e487c849e66ea/pyrefly-1.4.0-py3-none-win_arm64.whl", hash = "sha256:88879f0cb749e5fea81d9ff4c2d5431a70349c458bcdf0b61de72c79214d90a5", size = 14943690, upload-time = "2026-10-15T16:54:09.689Z" },
]
[[package]] [[package]]
name = "pyserial" name = "pyserial"
version = "3.5" version = "3.5"
@@ -976,6 +995,11 @@ dependencies = [
{ name = "tables" }, { name = "tables" },
] ]
[package.dev-dependencies]
dev = [
{ name = "pyrefly" },
]
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "matplotlib", specifier = ">=1.4.1,<=3.6" }, { name = "matplotlib", specifier = ">=1.4.1,<=3.6" },
@@ -990,6 +1014,9 @@ requires-dist = [
{ name = "tables", specifier = ">=3.1.1" }, { name = "tables", specifier = ">=3.1.1" },
] ]
[package.metadata.requires-dev]
dev = [{ name = "pyrefly" }]
[[package]] [[package]]
name = "pyxdg" name = "pyxdg"
version = "0.28" version = "0.28"