Public Access
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
801e31e84f | ||
|
|
5f558e4f03 | ||
|
|
f562255c75 | ||
|
|
42a4f9e2b9 | ||
|
|
ed5ddde2e3 | ||
|
|
f86dafeda7 | ||
|
|
9e42205a4d | ||
|
|
1f6763f9de | ||
|
|
6668ac5b2b | ||
|
|
ee453c03a7 | ||
|
|
1fb56e20f9 | ||
|
|
ef11824f8b |
No files matched your search
Vendored
+13
@@ -1,3 +1,16 @@
|
|||||||
|
python3-damaris (0.33) stable; urgency=medium
|
||||||
|
|
||||||
|
* restore numpy scalar job XML serialization, broken since v0.28: lin_range and log_range values were written as unparsable np.float64(...)
|
||||||
|
* make temperature proxy host and port configurable in the configuration tab
|
||||||
|
|
||||||
|
-- Markus Rosenstihl <markus.rosenstihl@pkm.tu-darmstadt.de> Fri, 23 Oct 2026 11:13:39 +0200
|
||||||
|
|
||||||
|
python3-damaris (0.32) stable; urgency=medium
|
||||||
|
|
||||||
|
* fix removal of plot artists: Axes.lines/collections are read-only ArtistLists since matplotlib 3.5
|
||||||
|
|
||||||
|
-- Markus Rosenstihl <markus.rosenstihl@pkm.tu-darmstadt.de> Thu, 22 Oct 2026 16:51:50 +0200
|
||||||
|
|
||||||
python3-damaris (0.31) stable; urgency=medium
|
python3-damaris (0.31) stable; urgency=medium
|
||||||
|
|
||||||
* added phasing slider
|
* added phasing slider
|
||||||
|
|||||||
+3
-2
@@ -51,12 +51,13 @@ Following is a short description of the options:
|
|||||||
10. Filename of the data pool file
|
10. Filename of the data pool file
|
||||||
11. Write interval after which the data pool is rewritten
|
11. Write interval after which the data pool is rewritten
|
||||||
12. Compression settings
|
12. Compression settings
|
||||||
13. Authors, License and version numbers of used components
|
13. Host and port of the temperature proxy service
|
||||||
|
14. Authors, License and version numbers of used components
|
||||||
|
|
||||||
Temperature History
|
Temperature History
|
||||||
===================
|
===================
|
||||||
|
|
||||||
DAMARIS automatically retrieves one temperature history for the complete experiment from the local ``tempproxy`` service at ``http://localhost:8000/v1/readings`` after the experiment finishes. The proxy controls the temperature device; no controller, serial device, or polling interval needs to be configured in DAMARIS. Successful readings are stored under the ``temperature`` DataPool key with elapsed seconds on the x-axis and the proxy's ``pv`` values unchanged on the y-axis, so units depend on the configured controller. If the proxy is unavailable or has no readings for the experiment, the history is omitted and the experiment's other data is still saved.
|
DAMARIS automatically retrieves one temperature history for the complete experiment from the ``tempproxy`` service at ``http://<host>:<port>/v1/readings`` after the experiment finishes. Host and port are configured in the Configuration tab, the defaults are ``127.0.0.1`` and ``18007``. The proxy controls the temperature device; no controller, serial device, or polling interval needs to be configured in DAMARIS. Successful readings are stored under the ``temperature`` DataPool key with elapsed seconds on the x-axis and the proxy's ``pv`` values unchanged on the y-axis, so units depend on the configured controller. If the proxy is unavailable or has no readings for the experiment, the history is omitted and the experiment's other data is still saved.
|
||||||
|
|
||||||
Experiment Script
|
Experiment Script
|
||||||
=================
|
=================
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
## module contents
|
## module contents
|
||||||
#
|
#
|
||||||
__version__ = "0.31"
|
__version__ = "0.33"
|
||||||
__all__=["experiments", "data", "gui"]
|
__all__=["experiments", "data", "gui"]
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class StateSimple(StateBase):
|
|||||||
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 = float(time)
|
||||||
self.content = content
|
self.content = content
|
||||||
|
|
||||||
def to_xml(self, indent: str = "") -> str:
|
def to_xml(self, indent: str = "") -> str:
|
||||||
@@ -191,7 +191,7 @@ class Experiment:
|
|||||||
|
|
||||||
Typically only used in very special cases.
|
Typically only used in very special cases.
|
||||||
"""
|
"""
|
||||||
self.state_list.append('<state time="%s">\n' % repr(time))
|
self.state_list.append('<state time="%s">\n' % repr(float(time)))
|
||||||
self.total_time[-1] += time
|
self.total_time[-1] += time
|
||||||
|
|
||||||
## End of *state_start*
|
## End of *state_start*
|
||||||
@@ -404,10 +404,10 @@ class Experiment:
|
|||||||
The state length is 2 µs. Stabilisation time depends on RF source.
|
The state length is 2 µs. Stabilisation time depends on RF source.
|
||||||
|
|
||||||
:param float frequency: frequency to set in Hz
|
:param float frequency: frequency to set in Hz
|
||||||
:param float phase: phase to set
|
:param float phase: phase to set, normalized to the range [0, 360)
|
||||||
:param int ttls: default=0, additional ttl lines to set (integer)
|
:param int ttls: default=0, additional ttl lines to set (integer)
|
||||||
"""
|
"""
|
||||||
s_content = '<analogout id="0" f="%f" phase="%f"/>' % (frequency, phase)
|
s_content = '<analogout id="0" f="%f" phase="%f"/>' % (frequency, phase % 360)
|
||||||
if ttls != 0:
|
if ttls != 0:
|
||||||
s_content += '<ttlout value="0x%06x"/>' % ttls
|
s_content += '<ttlout value="0x%06x"/>' % ttls
|
||||||
self.state_list.append(StateSimple(2e-6, s_content))
|
self.state_list.append(StateSimple(2e-6, s_content))
|
||||||
@@ -582,8 +582,9 @@ class Experiment:
|
|||||||
- The state length for this command is 0.5 µs.
|
- The state length for this command is 0.5 µs.
|
||||||
- The stabilization time for the phase change is RF source dependent. A typical value for PTS310 is 2 µs.
|
- The stabilization time for the phase change is RF source dependent. A typical value for PTS310 is 2 µs.
|
||||||
- The phase is relative to the initial phase set at start of the experiment.
|
- The phase is relative to the initial phase set at start of the experiment.
|
||||||
|
- Negative phases are normalized to the range [0, 360) before writing the job file.
|
||||||
"""
|
"""
|
||||||
s_content = '<analogout phase="%f" />' % (phase)
|
s_content = '<analogout phase="%f" />' % (phase % 360)
|
||||||
if ttls!=0:
|
if ttls!=0:
|
||||||
s_content += '<ttlout value="%d"/>' % ttls
|
s_content += '<ttlout value="%d"/>' % ttls
|
||||||
self.state_list.append(StateSimple(0.5e-6, s_content))
|
self.state_list.append(StateSimple(0.5e-6, s_content))
|
||||||
@@ -680,19 +681,19 @@ class Experiment:
|
|||||||
value=""
|
value=""
|
||||||
elif type(value) is float or isinstance(value, numpy.floating):
|
elif type(value) is float or isinstance(value, numpy.floating):
|
||||||
type_string="Float"
|
type_string="Float"
|
||||||
value=repr(value)
|
value=repr(float(value))
|
||||||
elif type(value) is int or isinstance(value, numpy.integer):
|
elif type(value) is int or isinstance(value, numpy.integer):
|
||||||
type_string="Int"
|
type_string="Int"
|
||||||
value=repr(value)
|
value=repr(int(value))
|
||||||
elif type(value) is int:
|
elif type(value) is int:
|
||||||
type_string="Long"
|
type_string="Long"
|
||||||
value=repr(value)
|
value=repr(value)
|
||||||
elif type(value) is complex or isinstance(value, numpy.complexfloating):
|
elif type(value) is complex or isinstance(value, numpy.complexfloating):
|
||||||
type_string="Complex"
|
type_string="Complex"
|
||||||
value=repr(value)
|
value=repr(complex(value))
|
||||||
elif type(value) is bool or isinstance(value, numpy.bool_):
|
elif type(value) is bool or isinstance(value, numpy.bool_):
|
||||||
type_string="Boolean"
|
type_string="Boolean"
|
||||||
value=repr(value)
|
value=repr(bool(value))
|
||||||
elif type(value) in (str,):
|
elif type(value) in (str,):
|
||||||
type_string="String"
|
type_string="String"
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -871,7 +871,10 @@ class DamarisGUI:
|
|||||||
temperature_history_start = self.temperature_history_start
|
temperature_history_start = self.temperature_history_start
|
||||||
self.temperature_history_start = None
|
self.temperature_history_start = None
|
||||||
self.data.pop("temperature", None)
|
self.data.pop("temperature", None)
|
||||||
temperature = TemperatureProxyClient().get_history(
|
proxy_config = self.config.get( )
|
||||||
|
temperature = TemperatureProxyClient(
|
||||||
|
host=proxy_config["tempproxy_host"],
|
||||||
|
port=proxy_config["tempproxy_port"]).get_history(
|
||||||
temperature_history_start, time.time())
|
temperature_history_start, time.time())
|
||||||
if temperature is not None:
|
if temperature is not None:
|
||||||
self.data["temperature"] = temperature
|
self.data["temperature"] = temperature
|
||||||
@@ -2379,6 +2382,27 @@ class ConfigTab:
|
|||||||
backend_vbox.pack_start(self.config_adc_bit_depth_hbox, False, False, 0)
|
backend_vbox.pack_start(self.config_adc_bit_depth_hbox, False, False, 0)
|
||||||
self.config_adc_bit_depth_hbox.show_all()
|
self.config_adc_bit_depth_hbox.show_all()
|
||||||
|
|
||||||
|
# temperature proxy settings
|
||||||
|
self.config_tempproxy_hbox = gtk.Box(orientation=gtk.Orientation.HORIZONTAL, spacing=6)
|
||||||
|
self.config_tempproxy_host_label = gtk.Label(label="Temp proxy host:")
|
||||||
|
self.config_tempproxy_host_entry = gtk.Entry()
|
||||||
|
self.config_tempproxy_host_entry.set_text("127.0.0.1")
|
||||||
|
self.config_tempproxy_host_entry.set_tooltip_text("Host of the tempproxy service reading the temperature controller")
|
||||||
|
self.config_tempproxy_host_entry.connect("key-press-event", self.config_keypress)
|
||||||
|
self.config_tempproxy_port_label = gtk.Label(label="port:")
|
||||||
|
self.config_tempproxy_port_spinbutton = gtk.SpinButton.new_with_range(1, 65535, 1)
|
||||||
|
self.config_tempproxy_port_spinbutton.set_value(18007)
|
||||||
|
self.config_tempproxy_port_spinbutton.set_tooltip_text("Port of the tempproxy service, /v1/readings is requested there")
|
||||||
|
self.config_tempproxy_port_spinbutton.connect("key-press-event", self.config_keypress)
|
||||||
|
self.config_tempproxy_hbox.pack_start(self.config_tempproxy_host_label, False, False, 0)
|
||||||
|
self.config_tempproxy_hbox.pack_start(self.config_tempproxy_host_entry, False, False, 0)
|
||||||
|
self.config_tempproxy_hbox.pack_start(self.config_tempproxy_port_label, False, False, 0)
|
||||||
|
self.config_tempproxy_hbox.pack_start(self.config_tempproxy_port_spinbutton, False, False, 0)
|
||||||
|
frontend_vbox = self.xml_gui.get_object("vbox7")
|
||||||
|
if frontend_vbox:
|
||||||
|
frontend_vbox.pack_start(self.config_tempproxy_hbox, False, False, 0)
|
||||||
|
self.config_tempproxy_hbox.show_all()
|
||||||
|
|
||||||
# insert version informations
|
# insert version informations
|
||||||
components_text = """
|
components_text = """
|
||||||
operating system %(os)s
|
operating system %(os)s
|
||||||
@@ -2485,7 +2509,9 @@ pygobject version %(pygobject)s
|
|||||||
"data_pool_complib": complib,
|
"data_pool_complib": complib,
|
||||||
"data_pool_comprate": self.config_data_pool_comprate.get_value_as_int( ),
|
"data_pool_comprate": self.config_data_pool_comprate.get_value_as_int( ),
|
||||||
"script_font": self.config_script_font_button.get_font_name( ),
|
"script_font": self.config_script_font_button.get_font_name( ),
|
||||||
"adc_bit_depth": self.config_adc_bit_depth_spinbutton.get_value_as_int()
|
"adc_bit_depth": self.config_adc_bit_depth_spinbutton.get_value_as_int(),
|
||||||
|
"tempproxy_host": self.config_tempproxy_host_entry.get_text( ),
|
||||||
|
"tempproxy_port": self.config_tempproxy_port_spinbutton.get_value_as_int( )
|
||||||
}
|
}
|
||||||
ADC_Result.default_bit_depth = actual_config["adc_bit_depth"]
|
ADC_Result.default_bit_depth = actual_config["adc_bit_depth"]
|
||||||
return actual_config
|
return actual_config
|
||||||
@@ -2520,6 +2546,10 @@ pygobject version %(pygobject)s
|
|||||||
ADC_Result.default_bit_depth = int(config["adc_bit_depth"])
|
ADC_Result.default_bit_depth = int(config["adc_bit_depth"])
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
if "tempproxy_host" in config:
|
||||||
|
self.config_tempproxy_host_entry.set_text( str( config[ "tempproxy_host" ] ) )
|
||||||
|
if "tempproxy_port" in config:
|
||||||
|
self.config_tempproxy_port_spinbutton.set_value( float( config[ "tempproxy_port" ] ) )
|
||||||
|
|
||||||
if "data_pool_complib" in config:
|
if "data_pool_complib" in config:
|
||||||
# find combo-box entry and make it active...
|
# find combo-box entry and make it active...
|
||||||
@@ -3849,7 +3879,7 @@ class MonitorWidgets:
|
|||||||
self.measurementresultline = None
|
self.measurementresultline = None
|
||||||
elif self.measurementresultline is not None:
|
elif self.measurementresultline is not None:
|
||||||
# clear line plot
|
# clear line plot
|
||||||
self.matplot_axes.lines.remove( self.measurementresultline[ 0 ] )
|
self.measurementresultline[ 0 ].remove( )
|
||||||
self.measurementresultline = None
|
self.measurementresultline = None
|
||||||
if hasattr(self, 'matplot_cursor') and self.matplot_cursor is not None:
|
if hasattr(self, 'matplot_cursor') and self.matplot_cursor is not None:
|
||||||
self.matplot_cursor.disconnect_events()
|
self.matplot_cursor.disconnect_events()
|
||||||
@@ -4146,17 +4176,17 @@ class MonitorWidgets:
|
|||||||
|
|
||||||
# remove lines and error bars
|
# remove lines and error bars
|
||||||
if self.measurementresultgraph is not None:
|
if self.measurementresultgraph is not None:
|
||||||
self.matplot_axes.lines.remove( self.measurementresultgraph[ 0 ] )
|
self.measurementresultgraph[ 0 ].remove( )
|
||||||
# remove caps
|
# remove caps
|
||||||
for l in self.measurementresultgraph[ 1 ]:
|
for l in self.measurementresultgraph[ 1 ]:
|
||||||
self.matplot_axes.lines.remove( l )
|
l.remove( )
|
||||||
# and columns
|
# and columns
|
||||||
for l in self.measurementresultgraph[ 2 ]:
|
for l in self.measurementresultgraph[ 2 ]:
|
||||||
self.matplot_axes.collections.remove( l )
|
l.remove( )
|
||||||
self.measurementresultgraph = None
|
self.measurementresultgraph = None
|
||||||
|
|
||||||
if self.measurementresultline is not None:
|
if self.measurementresultline is not None:
|
||||||
self.matplot_axes.lines.remove( self.measurementresultline[ 0 ] )
|
self.measurementresultline[ 0 ].remove( )
|
||||||
self.measurementresultline = None
|
self.measurementresultline = None
|
||||||
|
|
||||||
# remove fit overlay lines
|
# remove fit overlay lines
|
||||||
@@ -4244,13 +4274,13 @@ class MonitorWidgets:
|
|||||||
|
|
||||||
# finally decide about x log plot
|
# finally decide about x log plot
|
||||||
if x_scale == "log10" and xmin > 0:
|
if x_scale == "log10" and xmin > 0:
|
||||||
self.matplot_axes.set_xscale( "log", basex=10.0 )
|
self.matplot_axes.set_xscale( "log", base=10.0 )
|
||||||
self.matplot_axes.fmt_xdata = lambda x: "%g" % x
|
self.matplot_axes.fmt_xdata = lambda x: "%g" % x
|
||||||
#elif x_scale=="log" and xmin>0:
|
#elif x_scale=="log" and xmin>0:
|
||||||
# e scaling implementation not really useful
|
# e scaling implementation not really useful
|
||||||
# self.matplot_axes.set_xscale("log", basex=numpy.e)
|
# self.matplot_axes.set_xscale("log", basex=numpy.e)
|
||||||
if y_scale == "log10" and ymin > 0:
|
if y_scale == "log10" and ymin > 0:
|
||||||
self.matplot_axes.set_yscale( "log", basex=10.0 )
|
self.matplot_axes.set_yscale( "log", base=10.0 )
|
||||||
self.matplot_axes.fmt_ydata = lambda x: "%g" % x
|
self.matplot_axes.fmt_ydata = lambda x: "%g" % x
|
||||||
|
|
||||||
self.__rescale = False
|
self.__rescale = False
|
||||||
|
|||||||
@@ -135,7 +135,8 @@ class ResultReader:
|
|||||||
elif t == "Complex":
|
elif t == "Complex":
|
||||||
self.result_description[k]=complex(v)
|
self.result_description[k]=complex(v)
|
||||||
elif t == "Boolean":
|
elif t == "Boolean":
|
||||||
self.result_description[k]=bool(v)
|
# bool("False") would be True, compare the text instead
|
||||||
|
self.result_description[k]=(v == "True")
|
||||||
elif t == "String":
|
elif t == "String":
|
||||||
self.result_description[k]=v
|
self.result_description[k]=v
|
||||||
else:
|
else:
|
||||||
@@ -502,7 +503,8 @@ class ResultReader:
|
|||||||
elif t == "Complex":
|
elif t == "Complex":
|
||||||
self.result_description[k]=complex(v)
|
self.result_description[k]=complex(v)
|
||||||
elif t == "Boolean":
|
elif t == "Boolean":
|
||||||
self.result_description[k]=bool(v)
|
# bool("False") would be True, compare the text instead
|
||||||
|
self.result_description[k]=(v == "True")
|
||||||
elif t == "String":
|
elif t == "String":
|
||||||
self.result_description[k]=v
|
self.result_description[k]=v
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""Read experiment temperature history from the local tempproxy service.
|
"""Read experiment temperature history from the tempproxy service.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
client = TemperatureProxyClient()
|
client = TemperatureProxyClient() # defaults to http://127.0.0.1:18007
|
||||||
temperature = client.get_history(start_epoch, end_epoch)
|
temperature = client.get_history(start_epoch, end_epoch)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -18,11 +18,18 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class TemperatureProxyClient:
|
class TemperatureProxyClient:
|
||||||
"""Retrieve experiment-bounded readings from ``localhost:8000``."""
|
"""Retrieve experiment-bounded readings from a configurable tempproxy host/port."""
|
||||||
|
|
||||||
URL = "http://localhost:8000/v1/readings"
|
|
||||||
TIMEOUT = 5
|
TIMEOUT = 5
|
||||||
|
|
||||||
|
def __init__(self, host: str = "127.0.0.1", port: int = 18007):
|
||||||
|
"""Create a client for ``http://<host>:<port>/v1/readings``.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
client = TemperatureProxyClient(host="192.168.1.5", port=18007)
|
||||||
|
"""
|
||||||
|
self.url = f"http://{host}:{port}/v1/readings"
|
||||||
|
|
||||||
def get_history(self, start_epoch: float, end_epoch: float) -> TemperatureResult | None:
|
def get_history(self, start_epoch: float, end_epoch: float) -> TemperatureResult | None:
|
||||||
"""Return temperature history relative to the experiment start, or ``None``.
|
"""Return temperature history relative to the experiment start, or ``None``.
|
||||||
|
|
||||||
@@ -33,7 +40,7 @@ class TemperatureProxyClient:
|
|||||||
temperature = TemperatureProxyClient().get_history(1000.0, 1060.0)
|
temperature = TemperatureProxyClient().get_history(1000.0, 1060.0)
|
||||||
"""
|
"""
|
||||||
query = urlencode({"start": start_epoch, "end": end_epoch})
|
query = urlencode({"start": start_epoch, "end": end_epoch})
|
||||||
url = f"{self.URL}?{query}"
|
url = f"{self.url}?{query}"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with urlopen(url, timeout=self.TIMEOUT) as response:
|
with urlopen(url, timeout=self.TIMEOUT) as response:
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import re
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from damaris.experiments.Experiment import Experiment
|
||||||
|
|
||||||
|
|
||||||
|
def phase_of_state(state):
|
||||||
|
"extracts the phase value from a state's XML content"
|
||||||
|
match = re.search(r'phase="([-0-9.]+)"', state.content)
|
||||||
|
assert match, "no phase in state: %s" % state.content
|
||||||
|
return float(match.group(1))
|
||||||
|
|
||||||
|
|
||||||
|
class TestExperimentPhase(unittest.TestCase):
|
||||||
|
def test_set_phase_negative_normalized(self):
|
||||||
|
"""Test that set_phase() normalizes negative phases to [0, 360)."""
|
||||||
|
e = Experiment()
|
||||||
|
for degrees, expected in ((-90, 270.0), (-45.5, 314.5),
|
||||||
|
(-270, 90.0), (-360, 0.0)):
|
||||||
|
e.set_phase(degrees)
|
||||||
|
self.assertEqual(phase_of_state(e.state_list.list[-1]), expected)
|
||||||
|
|
||||||
|
def test_set_phase_positive_unchanged(self):
|
||||||
|
"""Test that set_phase() passes positive phases through unchanged."""
|
||||||
|
e = Experiment()
|
||||||
|
for degrees, expected in ((0, 0.0), (90, 90.0), (270, 270.0),
|
||||||
|
(45.5, 45.5)):
|
||||||
|
e.set_phase(degrees)
|
||||||
|
self.assertEqual(phase_of_state(e.state_list.list[-1]), expected)
|
||||||
|
|
||||||
|
def test_set_frequency_negative_phase_normalized(self):
|
||||||
|
"""Test that set_frequency() normalizes a negative phase."""
|
||||||
|
e = Experiment()
|
||||||
|
e.set_frequency(85e6, -90)
|
||||||
|
state = e.state_list.list[-1]
|
||||||
|
self.assertEqual(phase_of_state(state), 270.0)
|
||||||
|
self.assertIn('f="85000000.000000"', state.content)
|
||||||
|
|
||||||
|
def test_rf_pulse_negative_phase_normalized(self):
|
||||||
|
"""Test that rf_pulse() normalizes a negative pulse phase."""
|
||||||
|
e = Experiment(gating=1e-6, rf_sources=[1], rf_gates=[2])
|
||||||
|
e.rf_pulse(length=1e-6, phase=-90, source=0)
|
||||||
|
self.assertEqual(phase_of_state(e.state_list.list[0]), 270.0)
|
||||||
|
|
||||||
|
def test_numpy_scalars_in_description(self):
|
||||||
|
"""Test that description items with numpy scalars (e.g. from lin_range)
|
||||||
|
are written as plain numbers so the result script can parse them again."""
|
||||||
|
import numpy as np
|
||||||
|
from damaris.tools.ranges import lin_range
|
||||||
|
e = Experiment()
|
||||||
|
e.set_description("tau", lin_range(1e-6, 3e-6, 1e-6)[0])
|
||||||
|
e.set_description("n", np.int64(42))
|
||||||
|
xml = e.write_xml_string()
|
||||||
|
items = {key: (typ, text)
|
||||||
|
for key, typ, text in
|
||||||
|
re.findall(r'<item key="([^"]+)" type="([^"]+)">([^<]*)</item>', xml)}
|
||||||
|
self.assertEqual(items["tau"][0], "Float")
|
||||||
|
self.assertEqual(items["n"][0], "Int")
|
||||||
|
self.assertEqual(float(items["tau"][1]), 1e-6)
|
||||||
|
self.assertEqual(int(items["n"][1]), 42)
|
||||||
|
|
||||||
|
def test_numpy_scalar_state_times(self):
|
||||||
|
"""Test that numpy scalar times do not end up as np.float64(...) in the XML."""
|
||||||
|
import numpy as np
|
||||||
|
e = Experiment()
|
||||||
|
e.wait(np.float64(1e-6))
|
||||||
|
e.state_start(np.float64(2e-6))
|
||||||
|
xml = e.write_xml_string()
|
||||||
|
self.assertNotIn("np.float64", xml)
|
||||||
|
self.assertIn('<state time="1e-06"/>', xml)
|
||||||
|
self.assertIn('<state time="2e-06">', xml)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -22,7 +22,7 @@ class TestTemperatureProxyClient(unittest.TestCase):
|
|||||||
return io.BytesIO(json.dumps(payload).encode("utf-8"))
|
return io.BytesIO(json.dumps(payload).encode("utf-8"))
|
||||||
|
|
||||||
@patch("damaris.gui.TemperatureProxyClient.urlopen")
|
@patch("damaris.gui.TemperatureProxyClient.urlopen")
|
||||||
def test_get_history_uses_fixed_endpoint_and_converts_readings(self, mock_urlopen):
|
def test_get_history_uses_default_endpoint_and_converts_readings(self, mock_urlopen):
|
||||||
mock_urlopen.return_value = self.response({
|
mock_urlopen.return_value = self.response({
|
||||||
"readings": [
|
"readings": [
|
||||||
{"t": 101.5, "pv": 293.15},
|
{"t": 101.5, "pv": 293.15},
|
||||||
@@ -35,13 +35,25 @@ class TestTemperatureProxyClient(unittest.TestCase):
|
|||||||
args, kwargs = mock_urlopen.call_args
|
args, kwargs = mock_urlopen.call_args
|
||||||
parsed_url = urlparse(args[0])
|
parsed_url = urlparse(args[0])
|
||||||
self.assertEqual(parsed_url.scheme, "http")
|
self.assertEqual(parsed_url.scheme, "http")
|
||||||
self.assertEqual(parsed_url.netloc, "localhost:8000")
|
self.assertEqual(parsed_url.netloc, "127.0.0.1:18007")
|
||||||
self.assertEqual(parsed_url.path, "/v1/readings")
|
self.assertEqual(parsed_url.path, "/v1/readings")
|
||||||
self.assertEqual(parse_qs(parsed_url.query), {"start": ["100.0"], "end": ["110.0"]})
|
self.assertEqual(parse_qs(parsed_url.query), {"start": ["100.0"], "end": ["110.0"]})
|
||||||
self.assertEqual(kwargs, {"timeout": TemperatureProxyClient.TIMEOUT})
|
self.assertEqual(kwargs, {"timeout": TemperatureProxyClient.TIMEOUT})
|
||||||
self.assertEqual(result.x, [1.5, 4.0])
|
self.assertEqual(result.x, [1.5, 4.0])
|
||||||
self.assertEqual(result.y, [293.15, 294.25])
|
self.assertEqual(result.y, [293.15, 294.25])
|
||||||
|
|
||||||
|
@patch("damaris.gui.TemperatureProxyClient.urlopen")
|
||||||
|
def test_custom_host_and_port_are_used(self, mock_urlopen):
|
||||||
|
mock_urlopen.return_value = self.response({"readings": [{"t": 101.0, "pv": 293.0}]})
|
||||||
|
|
||||||
|
client = TemperatureProxyClient(host="proxy.example.org", port=9000)
|
||||||
|
client.get_history(100.0, 110.0)
|
||||||
|
|
||||||
|
args, _ = mock_urlopen.call_args
|
||||||
|
parsed_url = urlparse(args[0])
|
||||||
|
self.assertEqual(parsed_url.netloc, "proxy.example.org:9000")
|
||||||
|
self.assertEqual(parsed_url.path, "/v1/readings")
|
||||||
|
|
||||||
@patch("damaris.gui.TemperatureProxyClient.urlopen")
|
@patch("damaris.gui.TemperatureProxyClient.urlopen")
|
||||||
def test_empty_readings_return_none(self, mock_urlopen):
|
def test_empty_readings_return_none(self, mock_urlopen):
|
||||||
mock_urlopen.return_value = self.response({"readings": []})
|
mock_urlopen.return_value = self.response({"readings": []})
|
||||||
@@ -62,7 +74,7 @@ class TestTemperatureProxyClient(unittest.TestCase):
|
|||||||
OSError("connection refused"),
|
OSError("connection refused"),
|
||||||
TimeoutError("request timed out"),
|
TimeoutError("request timed out"),
|
||||||
http.client.BadStatusLine("not an HTTP response"),
|
http.client.BadStatusLine("not an HTTP response"),
|
||||||
HTTPError(TemperatureProxyClient.URL, 500, "server error", {}, None),
|
HTTPError(self.client.url, 500, "server error", {}, None),
|
||||||
)
|
)
|
||||||
for failure in failures:
|
for failure in failures:
|
||||||
with self.subTest(failure=failure):
|
with self.subTest(failure=failure):
|
||||||
|
|||||||
Reference in new issue
Block a user