def from_tree(cls, tree, ctx): """ Converts basic types representing YAML trees into an 'weldx.core.TimeSeries'. Parameters ---------- tree : An instance of a basic Python type (possibly nested) that corresponds to a YAML subtree. ctx : An instance of the 'AsdfFile' object that is being constructed. Returns ------- weldx.core.TimeSeries : An instance of the 'weldx.core.TimeSeries' type. """ if "value" in tree: # constant values = Q_(np.asarray(tree["value"]), tree["unit"]) return TimeSeries(values) elif "values" in tree: time = tree["time"] interpolation = tree["interpolation"] values = Q_(tree["values"], tree["unit"]) return TimeSeries(values, time, interpolation) return TimeSeries(tree["expression"]) # mathexpression
def test_interp_time_warning(): """Test if a warning is emitted when interpolating already interpolated data.""" ts = TimeSeries(data=Q_([1, 2, 3], "m"), time=Q_([0, 1, 2], "s")) with pytest.warns(None) as recorded_warnings: ts_interp = ts.interp_time(Q_([0.25, 0.5, 0.75, 1], "s")) assert not any(w.category == UserWarning for w in recorded_warnings) with pytest.warns(UserWarning): ts_interp.interp_time(Q_([0.4, 0.6], "s"))
def test_init_data_array(data, dims, coords, exception_type): """Test the `__init__` method with an xarray as data parameter.""" da = xr.DataArray(data=data, dims=dims, coords=coords) if exception_type is not None: with pytest.raises(exception_type): TimeSeries(da) else: ts = TimeSeries(da) assert ts.data_array.dims[0] == "time"
def from_yaml_tree(self, node: dict, tag: str, ctx): """Construct from tree.""" if "value" in node: # constant values = Q_(node["value"], node["units"]) return TimeSeries(values) if "values" in node: time = node["time"] interpolation = node["interpolation"] values = Q_(node["values"], node["units"]) return TimeSeries(values, time, interpolation) return TimeSeries(node["expression"]) # mathexpression
def test_construction_discrete(data: pint.Quantity, time, interpolation, shape_exp): """Test the construction of the TimeSeries class.""" # set expected values time_exp = time if isinstance(time_exp, pint.Quantity): time_exp = pd.TimedeltaIndex(time_exp.m, unit="s") exp_interpolation = interpolation if len(data.shape) == 0 and interpolation is None: exp_interpolation = "step" # create instance ts = TimeSeries(data=data, time=time, interpolation=interpolation) # check assert np.all(ts.data == data) assert np.all(ts.time == time_exp) assert ts.interpolation == exp_interpolation assert ts.shape == shape_exp assert data.is_compatible_with(ts.units) assert np.all(ts.data_array.data == data) assert ts.data_array.attrs["interpolation"] == exp_interpolation if time_exp is None: assert "time" not in ts.data_array else: assert np.all(ts.data_array.time == time_exp)
def test_local_coordinate_system_coords_timeseries(copy_arrays, lazy_load, has_ref_time, has_tdp_orientation): """Test reading and writing a LCS with a `TimeSeries` as coordinates to asdf.""" # create inputs to lcs __init__ me = ME("a*t", dict(a=Q_([[1, 0, 0]], "1/s"))) ts = TimeSeries(data=me) ref_time = None if has_ref_time: ref_time = pd.Timestamp("13:37") time = None orientation = None if has_tdp_orientation: time = Q_([1, 2], "s") orientation = WXRotation.from_euler("x", [0, 90], degrees=True).as_matrix() # create lcs lcs = tf.LocalCoordinateSystem(orientation=orientation, coordinates=ts, time=time, time_ref=ref_time) # round trip and compare lcs_buffer = write_read_buffer({"lcs": lcs}, open_kwargs={ "copy_arrays": copy_arrays, "lazy_load": lazy_load })["lcs"] assert lcs_buffer == lcs
def lcs_coords_from_ts( ts: TimeSeries, time: Union[pd.DatetimeIndex, pint.Quantity] ) -> xr.DataArray: """Create translation coordinates from a TimeSeries at specific timesteps. Parameters ---------- ts: TimeSeries that describes the coordinate motion as a 3D vector. time Timestamps used for interpolation. TODO: add support for pd.DateTimeindex as well Returns ------- xarray.DataArray : A DataArray with correctly labeled dimensions to be used for LCS creation. """ ts_data = ts.interp_time(time=time) # assign vector coordinates and convert to mm ts_data = ts_data.rename({"dim_1": "c"}).assign_coords({"c": ["x", "y", "z"]}) ts_data.data = ts_data.data.to("mm").magnitude ts_data["time"] = pd.TimedeltaIndex(ts_data["time"].data) return ts_data
def sine( f: pint.Quantity, amp: pint.Quantity, bias: pint.Quantity = None, phase: pint.Quantity = Q_(0, "rad"), ) -> TimeSeries: """Create a simple sine TimeSeries from quantity parameters. f(t) = amp*sin(f*t+phase)+bias Parameters ---------- f : pint.Quantity Frequency of the sine (in Hz) amp : pint.Quantity Sine amplitude bias : pint.Quantity function bias phase : pint.Quantity phase shift Returns ------- TimeSeries """ if bias is None: bias = 0.0 * amp.u expr_string = "a*sin(o*t+p)+b" parameters = {"a": amp, "b": bias, "o": Q_(2 * np.pi, "rad") * f, "p": phase} expr = MathematicalExpression(expression=expr_string, parameters=parameters) return TimeSeries(expr)
def __post_init__(self): """Set defaults and convert parameter inputs.""" if self.tag is None: self.tag = "GMAW" self.parameters = { k: (v if isinstance(v, TimeSeries) else TimeSeries(v)) for k, v in self.parameters.items() }
def test_construction_expression(data, shape_exp, unit_exp): """Test the construction of the TimeSeries class.""" ts = TimeSeries(data=data) # check assert ts.data == data assert ts.time is None assert ts.interpolation is None assert ts.shape == shape_exp assert ts.data_array is None assert U_(unit_exp).is_compatible_with(ts.units)
def test_construction_expression(data, shape_exp, unit_exp): """Test the construction of the TimeSeries class.""" ts = TimeSeries(data=data) # check assert ts.data == data assert ts.time is None assert ts.interpolation is None assert ts.shape == shape_exp assert ts.data_array is None assert Q_(1, unit_exp).check(UREG.get_dimensionality(ts.units))
class ShapeValidatorTestClass: """Helper class to test the shape validator""" prop1: np.ndarray = np.ones((1, 2, 3)) prop2: np.ndarray = np.ones((3, 2, 1)) prop3: np.ndarray = np.ones((2, 4, 6, 8, 10)) prop4: np.ndarray = np.ones((1, 3, 5, 7, 9)) prop5: float = 3.141 quantity: pint.Quantity = Q_(10, "m") timeseries: TimeSeries = TimeSeries(Q_(10, "m")) nested_prop: dict = field(default_factory=lambda: { "p1": np.ones((10, 8, 6, 4, 2)), "p2": np.ones((9, 7, 5, 3, 1)), }) time_prop: pd.DatetimeIndex = pd.timedelta_range("0s", freq="s", periods=9) optional_prop: np.ndarray = None
def sine( f: QuantityLike, amp: QuantityLike, bias: QuantityLike = None, phase: QuantityLike = None, ) -> TimeSeries: """Create a simple sine TimeSeries from quantity parameters. f(t) = amp*sin(f*t+phase)+bias Parameters ---------- f : Frequency of the sine (in Hz) amp : Sine amplitude bias : function bias phase : phase shift Returns ------- weldx.TimeSeries : """ if phase is None: phase = Q_(0, "rad") if bias is None: amp = Q_(amp) bias = 0.0 * amp.u expr_string = "a*sin(o*t+p)+b" parameters = { "a": amp, "b": bias, "o": Q_(2 * np.pi, "rad") * Q_(f), "p": phase } expr = MathematicalExpression(expression=expr_string, parameters=parameters) return TimeSeries(expr)
def test_construction_discrete(data, time, interpolation, shape_exp): """Test the construction of the TimeSeries class.""" # set expected values if isinstance(time, pint.Quantity): time_exp = pd.TimedeltaIndex(time.magnitude, unit="s") else: time_exp = time # create instance ts = TimeSeries(data=data, time=time, interpolation=interpolation) # check assert np.all(ts.data == data) assert np.all(ts.time == time_exp) assert ts.interpolation == interpolation assert ts.shape == shape_exp assert data.check(UREG.get_dimensionality(ts.units)) assert np.all(ts.data_array.data == data) assert ts.data_array.attrs["interpolation"] == interpolation if time_exp is None: assert "time" not in ts.data_array else: assert np.all(ts.data_array.time == time_exp)
@pytest.mark.parametrize( "test_input", [ ShapeValidatorTestClass(prop4=np.ones((2, 3, 5, 7, 9))), # mismatch a - prop5 ShapeValidatorTestClass(prop2=np.ones((5, 2, 1))), # mismatch n - prop1 ShapeValidatorTestClass(optional_prop=np.ones((3, 2, 9))), # wrong optional ShapeValidatorTestClass( time_prop=pd.date_range("2020", freq="D", periods=3)), ShapeValidatorTestClass( quantity=Q_([0, 3], "s")), # mismatch shape [1] ShapeValidatorTestClass(timeseries=TimeSeries(Q_( [0, 3], "m"), Q_([0, 1], "s")) # mismatch shape [1] ), ], ) def test_shape_validator_exceptions(test_input): with pytest.raises(ValidationError): write_read_buffer({"root": test_input}) @pytest.mark.parametrize( "test", [ UnitValidatorTestClass(), UnitValidatorTestClass(length_prop=Q_(1, "inch")), ], )
from weldx.asdf.util import write_read_buffer from weldx.constants import Q_ from weldx.core import TimeSeries from weldx.welding.processes import GmawProcess @pytest.mark.parametrize( "inputs", [ GmawProcess( "spray", "Fronius", "TPSi", dict( wire_feedrate=Q_(10, "m/min"), voltage=TimeSeries(Q_(40.0, "V")), impedance=Q_(10.0, "percent"), characteristic=Q_(5, "V/A"), ), ), GmawProcess( "spray", "CLOOS", "Quinto", dict( wire_feedrate=Q_(10, "m/min"), voltage=TimeSeries(Q_([40.0, 20.0], "V"), Q_([0.0, 10.0], "s")), impedance=Q_(10.0, "percent"), characteristic=Q_(5, "V/A"), ),
def single_pass_weld_example( out_file: Optional[Union[str, BytesIO]] = "single_pass_weld_example.asdf", ) -> Optional[tuple[BytesIO, dict]]: """Create ASDF file containing all required fields of the single_pass_weld schema. Parameters ---------- out_file : destination file, if None returns a BytesIO buffer. Returns ------- buff, tree When writing to memory, return the buffer and the tree (as dictionary). """ # Imports import asdf import numpy as np import pandas as pd from asdf.tags.core import Software import weldx.geometry as geo import weldx.measurement as msm # importing the weldx package with prevalent default abbreviations import weldx.transformations as tf from weldx.asdf.util import get_schema_path, write_buffer, write_read_buffer from weldx.constants import META_ATTR, Q_ from weldx.core import MathematicalExpression, TimeSeries from weldx.tags.aws.process.gas_component import GasComponent from weldx.tags.aws.process.shielding_gas_for_procedure import ( ShieldingGasForProcedure, ) from weldx.tags.aws.process.shielding_gas_type import ShieldingGasType from weldx.tags.processes.process import GmawProcess from weldx.transformations.local_cs import LocalCoordinateSystem as lcs from weldx.transformations.rotation import WXRotation from weldx.welding.groove.iso_9692_1 import get_groove from weldx.welding.util import sine # Timestamp reference_timestamp = pd.Timestamp("2020-11-09 12:00:00") # Geometry # groove + trace = geometry groove = get_groove( groove_type="VGroove", workpiece_thickness=Q_(5, "mm"), groove_angle=Q_(50, "deg"), root_face=Q_(1, "mm"), root_gap=Q_(1, "mm"), ) # define the weld seam length in mm seam_length = Q_(300, "mm") # create a linear trace segment a the complete weld seam trace trace_segment = geo.LinearHorizontalTraceSegment(seam_length) trace = geo.Trace(trace_segment) geometry = dict(groove_shape=groove, seam_length=seam_length) base_metal = dict(common_name="S355J2+N", standard="DIN EN 10225-2:2011") workpiece = dict(base_metal=base_metal, geometry=geometry) # Setup the Coordinate System Manager (CSM) # crete a new coordinate system manager with default base coordinate system csm = tf.CoordinateSystemManager("base") # add the workpiece coordinate system csm.add_cs( coordinate_system_name="workpiece", reference_system_name="base", lcs=trace.coordinate_system, ) tcp_start_point = Q_([5.0, 0.0, 2.0], "mm") tcp_end_point = Q_([-5.0, 0.0, 2.0], "mm") + np.append( seam_length, Q_([0, 0], "mm")) v_weld = Q_(10, "mm/s") s_weld = (tcp_end_point - tcp_start_point)[0] # length of the weld t_weld = s_weld / v_weld t_start = pd.Timedelta("0s") t_end = pd.Timedelta(str(t_weld.to_base_units())) rot = WXRotation.from_euler(seq="x", angles=180, degrees=True) coords = [tcp_start_point.magnitude, tcp_end_point.magnitude] tcp_wire = lcs(coordinates=coords, orientation=rot, time=[t_start, t_end]) # add the workpiece coordinate system csm.add_cs( coordinate_system_name="tcp_wire", reference_system_name="workpiece", lcs=tcp_wire, ) tcp_contact = lcs(coordinates=[0, 0, -10]) # add the workpiece coordinate system csm.add_cs( coordinate_system_name="tcp_contact", reference_system_name="tcp_wire", lcs=tcp_contact, ) TCP_reference = csm.get_cs("tcp_contact", "workpiece") # Measurements # time time = pd.timedelta_range(start="0s", end="10s", freq="2s") # current data I_ts = sine(f=Q_(10, "1/s"), amp=Q_(20, "A"), bias=Q_(300, "A")) current_data = TimeSeries(I_ts.interp_time(time).data, time) # voltage data U_ts = sine(f=Q_(10, "1/s"), amp=Q_(3, "V"), bias=Q_(40, "V"), phase=Q_(0.1, "rad")) voltage_data = TimeSeries(U_ts.interp_time(time).data, time) # define current source and transformations src_current = msm.SignalSource( name="Current Sensor", output_signal=msm.Signal(signal_type="analog", units="V", data=None), error=msm.Error(Q_(0.1, "percent")), ) current_AD_transform = msm.SignalTransformation( name="AD conversion current measurement", error=msm.Error(Q_(0.01, "percent")), func=MathematicalExpression( "a * x + b", dict(a=Q_(32768.0 / 10.0, "1/V"), b=Q_(0.0, ""))), type_transformation="AD", ) current_calib_transform = msm.SignalTransformation( name="Calibration current measurement", error=msm.Error(0.0), func=MathematicalExpression( "a * x + b", dict(a=Q_(1000.0 / 32768.0, "A"), b=Q_(0.0, "A"))), ) # define voltage source and transformations src_voltage = msm.SignalSource( name="Voltage Sensor", output_signal=msm.Signal("analog", "V", data=None), error=msm.Error(Q_(0.1, "percent")), ) voltage_AD_transform = msm.SignalTransformation( name="AD conversion voltage measurement", error=msm.Error(Q_(0.01, "percent")), func=MathematicalExpression( "a * x + b", dict(a=Q_(32768.0 / 10.0, "1/V"), b=Q_(0.0, ""))), type_transformation="AD", ) voltage_calib_transform = msm.SignalTransformation( name="Calibration voltage measurement", error=msm.Error(0.0), func=MathematicalExpression( "a * x + b", dict(a=Q_(100.0 / 32768.0, "V"), b=Q_(0.0, "V"))), ) # Define lab equipment HKS_sensor = msm.MeasurementEquipment( name="HKS P1000-S3", sources=[src_current, src_voltage], ) BH_ELM = msm.MeasurementEquipment( name="Beckhoff ELM3002-0000", transformations=[current_AD_transform, voltage_AD_transform], ) twincat_scope = Software(name="Beckhoff TwinCAT ScopeView", version="3.4.3143") setattr(current_calib_transform, META_ATTR, dict(software=twincat_scope)) setattr(voltage_calib_transform, META_ATTR, dict(software=twincat_scope)) # Define current measurement chain welding_current_chain = msm.MeasurementChain.from_equipment( name="welding current measurement chain", equipment=HKS_sensor, source_name="Current Sensor", ) welding_current_chain.add_transformation_from_equipment( equipment=BH_ELM, transformation_name="AD conversion current measurement", ) welding_current_chain.add_transformation( transformation=current_calib_transform, data=current_data, ) # Define voltage measurement chain welding_voltage_chain = msm.MeasurementChain.from_equipment( name="welding voltage measurement chain", equipment=HKS_sensor, source_name="Voltage Sensor", ) welding_voltage_chain.add_transformation_from_equipment( equipment=BH_ELM, transformation_name="AD conversion voltage measurement", ) welding_voltage_chain.add_transformation( transformation=voltage_calib_transform, data=voltage_data, ) # Define measurements welding_current = msm.Measurement( name="welding current measurement", data=[current_data], measurement_chain=welding_current_chain, ) welding_voltage = msm.Measurement( name="welding voltage measurement", data=[voltage_data], measurement_chain=welding_voltage_chain, ) # GMAW Process params_pulse = dict( wire_feedrate=Q_(10.0, "m/min"), pulse_voltage=Q_(40.0, "V"), pulse_duration=Q_(5.0, "ms"), pulse_frequency=Q_(100.0, "Hz"), base_current=Q_(60.0, "A"), ) process_pulse = GmawProcess( "pulse", "CLOOS", "Quinto", params_pulse, tag="CLOOS/pulse", meta={"modulation": "UI"}, ) gas_comp = [ GasComponent("argon", Q_(82, "percent")), GasComponent("carbon dioxide", Q_(18, "percent")), ] gas_type = ShieldingGasType(gas_component=gas_comp, common_name="SG") gas_for_procedure = ShieldingGasForProcedure( use_torch_shielding_gas=True, torch_shielding_gas=gas_type, torch_shielding_gas_flowrate=Q_(20, "l / min"), ) process = dict( welding_process=process_pulse, shielding_gas=gas_for_procedure, weld_speed=TimeSeries(v_weld), welding_wire={"diameter": Q_(1.2, "mm")}, ) # ASDF file tree = dict( reference_timestamp=reference_timestamp, equipment=[HKS_sensor, BH_ELM], measurements=[welding_current, welding_voltage], welding_current=welding_current_chain.get_signal( "Calibration current measurement").data, welding_voltage=welding_voltage_chain.get_signal( "Calibration voltage measurement").data, coordinate_systems=csm, TCP=TCP_reference, workpiece=workpiece, process=process, ) tree[META_ATTR] = {"welder": "A.W. Elder"} model_path = get_schema_path("single_pass_weld-0.1.0.yaml") # pre-validate? write_read_buffer( tree, asdffile_kwargs=dict(custom_schema=str(model_path)), ) if out_file: with asdf.AsdfFile( tree, custom_schema=str(model_path), ) as ff: ff.write_to(out_file, all_array_storage="inline") else: return ( write_buffer( tree, asdffile_kwargs=dict(custom_schema=str(model_path)), write_kwargs=dict(all_array_storage="inline"), ), tree, )
def test_construction_exceptions( data, time, interpolation, exception_type, test_name ): """Test the exceptions of the 'set_parameter' method.""" with pytest.raises(exception_type): TimeSeries(data=data, time=time, interpolation=interpolation)
class TestTime: """Test the time class.""" # test_init helper functions ------------------------------------------------------- @staticmethod def _parse_time_type_test_input( type_input, ) -> tuple[Union[types_time_like, Time], bool]: """Return the time type and a bool that defines if the returned type is a delta. This is mainly used in generalized tests where a type like `Time` itself can represent deltas and absolute times. In this case one can use this function to extract the information from a tuple. """ if isinstance(type_input, tuple): # to avoid wrong test setups due to spelling mistakes assert type_input[1] in ["timedelta", "datetime"] time_type = type_input[0] is_timedelta = type_input[1] == "timedelta" else: time_type = type_input is_timedelta = _is_timedelta(type_input) return time_type, is_timedelta @classmethod def _get_init_exp_values( cls, is_timedelta, time_ref, data_was_scalar, delta_val, abs_val, ): """Get the expected result values for the `__init__` test.""" exp_is_absolute = time_ref is not None or not is_timedelta # expected reference time exp_time_ref = None if exp_is_absolute: exp_time_ref = Timestamp( time_ref if time_ref is not None else abs_val[0]) # expected time delta values val = delta_val if exp_is_absolute: offset = 0 if not is_timedelta: if time_ref is not None: offset = Timestamp(abs_val[0]) - Timestamp(time_ref) offset = offset.total_seconds() offset -= delta_val[0] val = [v + offset for v in delta_val] val = val[0] if data_was_scalar else val exp_timedelta = Timedelta(val, "s") if data_was_scalar else TDI( val, "s") # expected datetime exp_datetime = None if exp_is_absolute: time_ref = Timestamp( time_ref if time_ref is not None else abs_val[0]) exp_datetime = time_ref + exp_timedelta return dict( is_absolute=exp_is_absolute, time_ref=exp_time_ref, timedelta=exp_timedelta, datetime=exp_datetime, ) # test_init ------------------------------------------------------------------------ @pytest.mark.parametrize("scl, arr", [(True, False), (True, True), (False, True)]) @pytest.mark.parametrize("set_time_ref", [False, True]) @pytest.mark.parametrize( "input_vals", [ (str, "timedelta"), (Time, "timedelta"), (Q_, "timedelta"), TDI, Timedelta, np.timedelta64, (str, "datetime"), (Time, "datetime"), (Q_, "datetime"), DTI, Timestamp, np.datetime64, ], ) def test_init( self, input_vals: Union[type, tuple[type, str]], set_time_ref: bool, scl: bool, arr: bool, ): """Test the `__init__` method of the time class. Parameters ---------- input_vals : Either a compatible time type or a tuple of two values. The tuple is needed in case the tested time type can either represent relative time values as well as absolute ones. In this case, the first value is the type. The second value is a string specifying if the type represents absolute ("datetime") or relative ("timedelta") values. set_time_ref : If `True`, a reference time will be passed to the `__init__` method scl : If `True`, the data of the passed type consists of a single value. arr : If `True`, the data of the passed type is an array """ input_type, is_timedelta = self._parse_time_type_test_input(input_vals) # skip matrix cases that do not work -------------------- if arr and input_type in [Timedelta, Timestamp]: return if not arr and input_type in [DTI, TDI]: return # create input values ----------------------------------- delta_val = [1, 2, 3] abs_val = [f"2000-01-01 16:00:0{v}" for v in delta_val] time = _initialize_time_type(input_type, delta_val, abs_val, is_timedelta, arr, scl) time_ref = "2000-01-01 15:00:00" if set_time_ref else None # create `Time` instance -------------------------------- time_class_instance = Time(time, time_ref) # check results ----------------------------------------- exp = self._get_init_exp_values(is_timedelta, time_ref, scl, delta_val, abs_val) assert time_class_instance.is_absolute == exp["is_absolute"] assert time_class_instance.reference_time == exp["time_ref"] assert np.all(time_class_instance.as_timedelta() == exp["timedelta"]) if exp["is_absolute"]: assert np.all(time_class_instance.as_datetime() == exp["datetime"]) else: with pytest.raises(TypeError): time_class_instance.as_datetime() # test_init_from_time_dependent_types ---------------------------------------------- @staticmethod @pytest.mark.parametrize( "time_dep_type", [ LocalCoordinateSystem(coordinates=np.zeros((2, 3)), time=["2s", "3s"]), LocalCoordinateSystem(coordinates=np.zeros((2, 3)), time=["2000", "2001"]), TimeSeries(Q_([2, 4, 1], "m"), TDI([1, 2, 3], "s")), TimeSeries(Q_([2, 4, 1], "m"), ["2001", "2002", "2003"]), ], ) def test_init_from_time_dependent_types(time_dep_type): """Test initialization with types derived from `TimeDependent`.""" t = Time(time_dep_type) assert np.all(t == time_dep_type.time) # test_init_exceptions ------------------------------------------------------------- @staticmethod @pytest.mark.parametrize( "time, time_ref, raises", [ (TDI([3, 2, 1]), None, ValueError), (DTI(["2010", "2000"]), None, ValueError), (["2010", "2000"], None, ValueError), (Q_([3, 2, 1], "s"), None, ValueError), (np.array([3, 2, 1], dtype="timedelta64[s]"), None, ValueError), (None, None, TypeError), (5, None, TypeError), ("string", None, TypeError), (Q_(10, "m"), None, DimensionalityError), ], ) def test_init_exception(time, time_ref, raises): """Test initialization of the `Time` class with all supported types.""" with pytest.raises(raises): Time(time, time_ref) # test_add_timedelta --------------------------------------------------------------- @pytest.mark.parametrize("other_on_rhs", [True, False]) @pytest.mark.parametrize("time_class_is_array", [False, True]) @pytest.mark.parametrize("other_is_array", [False, True]) @pytest.mark.parametrize("unit", ["s", "h"]) @pytest.mark.parametrize( "other_type", [ (str, "timedelta"), (Time, "timedelta"), (Q_, "timedelta"), TDI, Timedelta, np.timedelta64, (str, "datetime"), (Time, "datetime"), (Q_, "datetime"), DTI, Timestamp, np.datetime64, ], ) def test_add_timedelta( self, other_type, other_on_rhs: bool, unit: str, time_class_is_array: bool, other_is_array: bool, ): """Test the `__add__` method if the `Time` class represents a time delta. Parameters ---------- other_type : The type of the other object other_on_rhs : If `True`, the other type is on the rhs of the + sign and on the lhs otherwise unit : The time unit to use time_class_is_array : If `True`, the `Time` instance contains 3 time values and 1 otherwise other_is_array : If `True`, the other time object contains 3 time values and 1 otherwise """ other_type, is_timedelta = self._parse_time_type_test_input(other_type) # skip array cases where the type does not support arrays if other_type in [Timedelta, Timestamp] and other_is_array: return if not other_is_array and other_type in [DTI, TDI]: return # skip __radd__ cases where we got conflicts with the other types' __add__ if not other_on_rhs and other_type in ( Q_, np.ndarray, np.timedelta64, np.datetime64, DTI, TDI, ): return # setup rhs delta_val = [4, 6, 8] if unit == "s": abs_val = [f"2000-01-01 10:00:0{v}" for v in delta_val] else: abs_val = [f"2000-01-01 1{v}:00:00" for v in delta_val] other = _initialize_time_type( other_type, delta_val, abs_val, is_timedelta, other_is_array, not other_is_array, unit, ) # setup lhs time_class_values = [1, 2, 3] if time_class_is_array else [1] time_class = Time(Q_(time_class_values, unit)) # setup expected values add = delta_val if other_is_array else delta_val[0] exp_val = np.array(time_class_values) + add exp_val += 0 if is_timedelta else time_class_values[0] - exp_val[0] exp_time_ref = None if is_timedelta else abs_val[0] exp = Time(Q_(exp_val, unit), exp_time_ref) # calculate and evaluate result res = time_class + other if other_on_rhs else other + time_class assert res.reference_time == exp.reference_time assert np.all(res.as_timedelta() == exp.as_timedelta()) assert np.all(res == exp) # test_add_datetime ---------------------------------------------------------------- @staticmethod @pytest.mark.parametrize("other_on_rhs", [True, False]) @pytest.mark.parametrize("time_class_is_array", [False, True]) @pytest.mark.parametrize("other_is_array", [False, True]) @pytest.mark.parametrize( "other_type", [ str, Time, Q_, TDI, Timedelta, np.timedelta64, ], ) def test_add_datetime( other_type, other_on_rhs: bool, time_class_is_array: bool, other_is_array: bool, ): """Test the `__add__` method if the `Time` class represents a datetime. Parameters ---------- other_type : The type of the other object other_on_rhs : If `True`, the other type is on the rhs of the + sign and on the lhs otherwise time_class_is_array : If `True`, the `Time` instance contains 3 time values and 1 otherwise other_is_array : If `True`, the other time object contains 3 time values and 1 otherwise """ # skip array cases where the type does not support arrays if other_type in [Timedelta, Timestamp] and other_is_array: return if not other_is_array and other_type in [DTI, TDI]: return # skip __radd__ cases where we got conflicts with the other types' __add__ if not other_on_rhs and other_type in (Q_, np.ndarray, np.timedelta64, TDI): return # setup rhs delta_val = [4, 6, 8] other = _initialize_time_type( other_type, delta_val, None, True, other_is_array, not other_is_array, "s", ) # setup lhs time_class_values = [1, 2, 3] if time_class_is_array else [1] time_class = Time(Q_(time_class_values, "s"), "2000-01-01 10:00:00") # setup expected values add = delta_val if other_is_array else delta_val[0] exp_val = np.array(time_class_values) + add exp_time_ref = time_class.reference_time exp = Time(Q_(exp_val, "s"), exp_time_ref) # calculate and evaluate result res = time_class + other if other_on_rhs else other + time_class assert res.reference_time == exp.reference_time assert np.all(res.as_timedelta() == exp.as_timedelta()) assert np.all(res == exp) # test_sub ------------------------------------------------------------------------- @staticmethod def _date_diff(date_1: str, date_2: str, unit: str) -> int: """Calculate the diff between two dates in the specified unit.""" return int( Time(Timestamp(date_1) - Timestamp(date_2)).as_quantity().m_as(unit)) @pytest.mark.parametrize("other_on_rhs", [False, True]) @pytest.mark.parametrize("time_class_is_array", [False, True]) @pytest.mark.parametrize("other_is_array", [False, True]) @pytest.mark.parametrize("unit", ["s", "h"]) @pytest.mark.parametrize("time_class_is_timedelta", [False, True]) @pytest.mark.parametrize( "other_type", [ (str, "timedelta"), (Time, "timedelta"), (Q_, "timedelta"), TDI, Timedelta, np.timedelta64, (str, "datetime"), (Time, "datetime"), (Q_, "datetime"), DTI, Timestamp, np.datetime64, ], ) def test_sub( self, other_type, other_on_rhs: bool, unit: str, time_class_is_array: bool, time_class_is_timedelta: bool, other_is_array: bool, ): """Test the `__sub__` method of the `Time` class. Parameters ---------- other_type : The type of the other object other_on_rhs : If `True`, the other type is on the rhs of the + sign and on the lhs otherwise unit : The time unit to use time_class_is_array : If `True`, the `Time` instance contains 3 time values and 1 otherwise time_class_is_timedelta : If `True`, the `Time` instance represents a time delta and a datetime otherwise other_is_array : If `True`, the other time object contains 3 time values and 1 otherwise """ other_type, other_is_timedelta = self._parse_time_type_test_input( other_type) if other_on_rhs: lhs_is_array = time_class_is_array lhs_is_timedelta = time_class_is_timedelta rhs_is_array = other_is_array rhs_is_timedelta = other_is_timedelta else: lhs_is_array = other_is_array lhs_is_timedelta = other_is_timedelta rhs_is_array = time_class_is_array rhs_is_timedelta = time_class_is_timedelta # skip array cases where the type does not support arrays or scalars if other_type in [Timedelta, Timestamp] and other_is_array: return if not other_is_array and other_type in [DTI, TDI]: return # skip __rsub__ cases where we got conflicts with the other types' __sub__ if not other_on_rhs and other_type in ( Q_, np.ndarray, np.timedelta64, np.datetime64, DTI, TDI, ): return # skip cases where an absolute time is on the rhs, since pandas does # not support this case (and it does not make sense) if lhs_is_timedelta and not rhs_is_timedelta: return # skip cases where the lhs is a scalar and the rhs is an array because it will # always involve non monotonically increasing array values, which is forbidden. if rhs_is_array and not lhs_is_array: return # test values vals_lhs = [3, 5, 9] if lhs_is_array else [3] vals_rhs = [1, 2, 3] if rhs_is_array else [1] # setup rhs other_val = vals_rhs if other_on_rhs else vals_lhs if unit == "s": abs_val = [f"2000-01-01 10:00:0{v}" for v in other_val] else: abs_val = [f"2000-01-01 1{v}:00:00" for v in other_val] other = _initialize_time_type( other_type, other_val, abs_val, other_is_timedelta, other_is_array, not other_is_array, unit, ) # setup lhs time_class_values = vals_lhs if other_on_rhs else vals_rhs time_class_time_ref = None if time_class_is_timedelta else "2000-01-01 11:00:00" time_class = Time(Q_(time_class_values, unit), time_class_time_ref) # setup expected values sub = vals_rhs if other_is_array else vals_rhs[0] exp_val = np.array(vals_lhs) - sub if not other_is_timedelta: if time_class_is_timedelta: exp_val -= time_class_values[0] + exp_val[0] else: d = self._date_diff(time_class_time_ref, abs_val[0], unit) + vals_rhs[0] exp_val += d if other_on_rhs else (d + exp_val[0]) * -1 exp_time_ref = None if not other_is_timedelta and time_class_is_timedelta: exp_time_ref = abs_val[0] elif other_is_timedelta and not time_class_is_timedelta: exp_time_ref = time_class_time_ref exp = Time(Q_(exp_val, unit), exp_time_ref) # calculate and evaluate result res = time_class - other if other_on_rhs else other - time_class assert res.reference_time == exp.reference_time assert np.all(res.as_timedelta() == exp.as_timedelta()) assert np.all(res == exp) # test_pandas_index ---------------------------------------------------------------- @staticmethod @pytest.mark.parametrize( "arg, expected", [ # timedeltas (TDI([42], unit="ns"), TDI([42], unit="ns")), (pd.timedelta_range("0s", "20s", 10), pd.timedelta_range("0s", "20s", 10)), (np.timedelta64(42), TDI([42], unit="ns")), (np.array([-10, 0, 20 ]).astype("timedelta64[ns]"), TDI([-10, 0, 20], "ns")), (Q_(42, "ns"), TDI([42], unit="ns")), ("10s", TDI(["10s"])), (["5ms", "10s", "2D"], TDI(["5 ms", "10s", "2D"])), # datetimes (np.datetime64(50, "Y"), DTI(["2020-01-01"])), ("2020-01-01", DTI(["2020-01-01"])), ( np.array(["2012-10-02", "2012-10-05", "2012-10-11"], dtype="datetime64[ns]"), DTI(["2012-10-02", "2012-10-05", "2012-10-11"]), ), ], ) def test_pandas_index(arg, expected): """Test conversion to appropriate pd.TimedeltaIndex or pd.DatetimeIndex.""" t = Time(arg) assert np.all(t.as_pandas_index() == expected) assert np.all(t.as_pandas_index() == t.index) assert np.all(t.as_timedelta_index() == t.timedelta) # test_as_quantity ----------------------------------------------------------------- @staticmethod @pytest.mark.parametrize( "arg, unit, expected", [ ("1s", "s", 1), ("1s", "ms", 1000), ("1s", "us", 1000000), ("1s", "ns", 1000000000), (TDI([1, 2, 3], "s"), "s", [1, 2, 3]), (TDI([1, 2, 3], "s"), "ms", np.array([1, 2, 3]) * 1e3), (TDI([1, 2, 3], "s"), "us", np.array([1, 2, 3]) * 1e6), (TDI([1, 2, 3], "s"), "ns", np.array([1, 2, 3]) * 1e9), ("2020-01-01", "s", 0), ], ) def test_quantity(arg, unit, expected): """Test conversion to pint.Quantity with different scales.""" t = Time(arg) q = Time(arg).as_quantity(unit) expected = Q_(expected, unit) assert np.allclose(q, expected) if t.is_absolute: assert t.reference_time == q.time_ref if unit == "s": assert np.all(q == t.quantity) # test_convert_util ---------------------------------------------------------------- @staticmethod def test_convert_util(): """Test basic conversion functions from/to xarray/pint.""" t = pd.date_range("2020", periods=10, freq="1s") ts = t[0] arr = xr.DataArray( np.arange(10), dims=["time"], coords={"time": t - ts}, ) arr.time.weldx.time_ref = ts time = Time(arr) assert len(time) == len(t) assert time.equals(Time(t)) time_q = time.as_quantity() assert np.all(time_q == Q_(range(10), "s")) assert time_q.time_ref == ts arr2 = time.as_data_array() assert arr.time.identical(arr2.time) # test_duration -------------------------------------------------------------------- @staticmethod @pytest.mark.parametrize( "values, exp_duration", [ ("1s", "0s"), ("2000-01-01", "0s"), (Q_([3, 5, 7, 8], "s"), "5s"), (["2000-01-03", "2000-01-05", "2000-01-07", "2000-01-08" ], "5days"), ], ) def test_duration(values, exp_duration): """Test the duration property.""" t = Time(values) assert t.duration.all_close(exp_duration) # test_resample -------------------------------------------------------------------- @staticmethod @pytest.mark.parametrize( "number_or_interval, exp_values", [ # test clipping ("10s", [3, 9]), # test edge case (2, [3, 9]), ("6s", [3, 9]), # test even time deltas (4, [3, 5, 7, 9]), ("2s", [3, 5, 7, 9]), # test uneven time deltas ("5s", [3, 8, 9]), ("2.2s", [3, 5.2, 7.4, 9]), ], ) def test_resample(number_or_interval, exp_values): """Test resample method.""" t_ref = "2000-01-01" t_delta = Time(Q_([3, 5, 8, 9], "s")) t_abs = t_delta + t_ref exp_delta = Time(Q_(exp_values, "s")) exp_abs = exp_delta + t_ref result_delta = t_delta.resample(number_or_interval) result_abs = t_abs.resample(number_or_interval) assert result_delta.all_close(exp_delta) assert result_abs.all_close(exp_abs) # test_resample_exceptions --------------------------------------------------------- @staticmethod @pytest.mark.parametrize( "values,number_or_interval, raises", [ ("4s", 2, RuntimeError), ("2000-02-01", 2, RuntimeError), (["4s", "10s"], "no time", TypeError), (["4s", "10s"], 1, ValueError), (["4s", "10s"], "0s", ValueError), (["4s", "10s"], "-2s", ValueError), ], ) def test_resample_exceptions(values, number_or_interval, raises): """Test possible exceptions of the resample method.""" with pytest.raises(raises): Time(values).resample(number_or_interval) # test_union ----------------------------------------------------------------------- @staticmethod @pytest.mark.parametrize( "list_of_objects, time_exp", [ ( [ date_range("2020-02-02", periods=4, freq="2D"), date_range("2020-02-01", periods=4, freq="2D"), date_range("2020-02-03", periods=2, freq="3D"), ], date_range("2020-02-01", periods=8, freq="1D"), ), ([TDI([1, 5]), TDI([2, 6, 7]), TDI([1, 3, 7])], TDI([1, 2, 3, 5, 6, 7])), ], ) @pytest.mark.parametrize("test_instance", [True, False]) def test_union(test_instance, list_of_objects, time_exp): """Test input types for Time.union function. Parameters ---------- list_of_objects: List with input objects time_exp: Expected result time """ if test_instance: instance = Time(list_of_objects[0]) assert np.all(instance.union(list_of_objects[1:]) == time_exp) else: assert np.all(Time.union(list_of_objects) == time_exp)
class TestTimeSeries: """Tests for the TimeSeries class.""" # Fixtures, aliases and shared variables ------------------------------------------- ME = MathematicalExpression DTI = pd.DatetimeIndex TDI = pd.TimedeltaIndex TS = TimeSeries time_discrete = pd.TimedeltaIndex([0, 1, 2, 3, 4], unit="s") value_constant = Q_(1, "m") values_discrete = Q_(np.array([10, 11, 12, 14, 16]), "mm") me_expr_str = "a*t + b" me_params = {"a": Q_(2, "m/s"), "b": Q_(-2, "m")} me_params_vec = {"a": Q_([2, 0, 1], "m/s"), "b": Q_([-2, 3, 0], "m")} ts_constant = TimeSeries(value_constant) ts_disc_step = TimeSeries(values_discrete, time_discrete, "step") ts_disc_linear = TimeSeries(values_discrete, time_discrete, "linear") ts_expr = TimeSeries(ME(me_expr_str, me_params)) ts_expr_vec = TimeSeries(ME(me_expr_str, me_params_vec)) # test_construction_discrete ------------------------------------------------------- @staticmethod @pytest.mark.parametrize( "data, time, interpolation, shape_exp", [ (Q_(1, "m"), None, None, (1,)), (Q_([3, 7, 1], "m"), TDI([0, 1, 2], unit="s"), "step", (3,)), (Q_([3, 7, 1], ""), Q_([0, 1, 2], "s"), "step", (3,)), ], ) def test_construction_discrete(data: pint.Quantity, time, interpolation, shape_exp): """Test the construction of the TimeSeries class.""" # set expected values time_exp = time if isinstance(time_exp, pint.Quantity): time_exp = pd.TimedeltaIndex(time_exp.m, unit="s") exp_interpolation = interpolation if len(data.shape) == 0 and interpolation is None: exp_interpolation = "step" # create instance ts = TimeSeries(data=data, time=time, interpolation=interpolation) # check assert np.all(ts.data == data) assert np.all(ts.time == time_exp) assert ts.interpolation == exp_interpolation assert ts.shape == shape_exp assert data.is_compatible_with(ts.units) assert np.all(ts.data_array.data == data) assert ts.data_array.attrs["interpolation"] == exp_interpolation if time_exp is None: assert "time" not in ts.data_array else: assert np.all(ts.data_array.time == time_exp) # test_construction_expression ----------------------------------------------------- params_scalar = {"a": Q_(2, "1/s"), "b": Q_(-2, "")} params_vec = {"a": Q_([[2, 3, 4]], "m/s"), "b": Q_([[-2, 3, 1]], "m")} @staticmethod @pytest.mark.parametrize( "data, shape_exp, unit_exp", [ (ME("a*t + b", params_scalar), (1,), ""), (ME("a*t + b", params_vec), (1, 3), "m"), ], ) def test_construction_expression(data, shape_exp, unit_exp): """Test the construction of the TimeSeries class.""" ts = TimeSeries(data=data) # check assert ts.data == data assert ts.time is None assert ts.interpolation is None assert ts.shape == shape_exp assert ts.data_array is None assert U_(unit_exp).is_compatible_with(ts.units) # test_init_data_array ------------------------------------------------------------- @staticmethod @pytest.mark.parametrize( "data, dims, coords, exception_type", [ (Q_([1, 2, 3], "m"), "time", dict(time=TDI([1, 2, 3])), None), (Q_([1, 2, 3], "m"), "a", dict(a=TDI([1, 2, 3])), KeyError), (Q_([[1, 2]], "m"), ("a", "time"), dict(a=[2], time=TDI([1, 2])), None), (Q_([1, 2, 3], "m"), "time", None, KeyError), (Q_([1, 2, 3], "m"), "time", dict(time=[1, 2, 3]), TypeError), ([1, 2, 3], "time", dict(time=TDI([1, 2, 3])), TypeError), ], ) def test_init_data_array(data, dims, coords, exception_type): """Test the `__init__` method with an xarray as data parameter.""" da = xr.DataArray(data=data, dims=dims, coords=coords) if exception_type is not None: with pytest.raises(exception_type): TimeSeries(da) else: ts = TimeSeries(da) assert ts.data_array.dims[0] == "time" # test_construction_exceptions ----------------------------------------------------- values_def = Q_([5, 7, 3, 6, 8], "m") time_def = Q_([0, 1, 2, 3, 4], "s") me_too_many_vars = ME("a*t + b", {}) me_param_units = ME("a*t + b", {"a": Q_(2, "1/s"), "b": Q_(-2, "m")}) @staticmethod @pytest.mark.parametrize( "data, time, interpolation, exception_type, test_name", [ (values_def, time_def, "int", ValueError, "# unknown interpolation"), (values_def, time_def.magnitude, "step", TypeError, "# invalid time type"), (me_too_many_vars, None, None, Exception, "# too many free variables"), (me_param_units, None, None, Exception, "# incompatible parameter units"), ("a string", None, None, TypeError, "# wrong data type"), ], ids=get_test_name, ) def test_construction_exceptions( data, time, interpolation, exception_type, test_name ): """Test the exceptions of the 'set_parameter' method.""" with pytest.raises(exception_type): TimeSeries(data=data, time=time, interpolation=interpolation) # test_comparison ------------------------------------- time_wrong_values = TDI([0, 1, 2, 3, 5], unit="s") values_discrete_wrong = Q_(np.array([10, 11, 12, 15, 16]), "mm") values_unit_wrong = Q_(np.array([10, 11, 12, 14, 16]), "s") values_unit_prefix_wrong = Q_(np.array([10, 11, 12, 14, 16]), "m") params_wrong_values = {"a": Q_(2, "1/s"), "b": Q_(-1, "")} params_wrong_unit = {"a": Q_(2, "g/s"), "b": Q_(-2, "g")} params_wrong_unit_prefix = {"a": Q_(2, "m/ms"), "b": Q_(-2, "m")} @staticmethod @pytest.mark.parametrize( "ts, ts_other, result_exp", [ (ts_constant, TS(value_constant), True), (ts_disc_step, TS(values_discrete, time_discrete, "step"), True), (ts_expr, TS(ME(me_expr_str, me_params)), True), (ts_constant, ts_disc_step, False), (ts_constant, ts_expr, False), (ts_disc_step, ts_expr, False), (ts_constant, 1, False), (ts_disc_step, 1, False), (ts_expr, 1, False), (ts_constant, "wrong", False), (ts_disc_step, "wrong", False), (ts_expr, "wrong", False), (ts_constant, TS(Q_(1337, "m")), False), (ts_constant, TS(Q_(1, "mm")), False), (ts_constant, TS(Q_(1, "s")), False), (ts_disc_step, TS(values_discrete, time_wrong_values, "step"), False), (ts_disc_step, TS(values_discrete_wrong, time_discrete, "step"), False), (ts_disc_step, TS(values_unit_prefix_wrong, time_discrete, "step"), False), (ts_disc_step, TS(values_discrete, time_discrete, "linear"), False), (ts_expr, TS(ME("a*t + 2*b", me_params)), False), (ts_expr, TS(ME(me_expr_str, params_wrong_values)), False), (ts_expr, TS(ME(me_expr_str, params_wrong_unit)), False), (ts_expr, TS(ME(me_expr_str, params_wrong_unit_prefix)), False), ], ) def test_comparison(ts, ts_other, result_exp): """Test the TimeSeries comparison methods.""" assert (ts == ts_other) is result_exp assert (ts != ts_other) is not result_exp # test_interp_time ----------------------------------------------------------------- time_single = pd.TimedeltaIndex([2.1], "s") time_single_q = Q_(2.1, "s") time_mul = pd.TimedeltaIndex([-3, 0.7, 1.1, 1.9, 2.5, 3, 4, 7], "s") time_mul_q = Q_([-3, 0.7, 1.1, 1.9, 2.5, 3, 4, 7], "s") results_exp_vec = [ [-8, 3, -3], [-0.6, 3, 0.7], [0.2, 3, 1.1], [1.8, 3, 1.9], [3, 3, 2.5], [4, 3, 3], [6, 3, 4], [12, 3, 7], ] @staticmethod @pytest.mark.parametrize( "ts, time, magnitude_exp, unit_exp", [ (ts_constant, time_single, 1, "m"), (ts_constant, time_single_q, 1, "m"), (ts_constant, time_mul, [1, 1, 1, 1, 1, 1, 1, 1], "m"), ( ts_constant, time_mul + pd.Timestamp("2020"), [1, 1, 1, 1, 1, 1, 1, 1], "m", ), (ts_constant, time_mul_q, [1, 1, 1, 1, 1, 1, 1, 1], "m"), (ts_disc_step, time_single, 12, "mm"), (ts_disc_step, time_single_q, 12, "mm"), (ts_disc_step, time_mul, [10, 10, 11, 11, 12, 14, 16, 16], "mm"), (ts_disc_step, time_mul_q, [10, 10, 11, 11, 12, 14, 16, 16], "mm"), (ts_disc_linear, time_single, 12.2, "mm"), (ts_disc_linear, time_single_q, 12.2, "mm"), (ts_disc_linear, time_mul, [10, 10.7, 11.1, 11.9, 13, 14, 16, 16], "mm"), (ts_disc_linear, time_mul_q, [10, 10.7, 11.1, 11.9, 13, 14, 16, 16], "mm"), (ts_expr, time_single, 2.2, "m"), (ts_expr, time_single_q, 2.2, "m"), (ts_expr, time_mul, [-8, -0.6, 0.2, 1.8, 3, 4, 6, 12], "m"), (ts_expr, time_mul_q, [-8, -0.6, 0.2, 1.8, 3, 4, 6, 12], "m"), (ts_expr_vec, time_single, [[2.2, 3, 2.1]], "m"), (ts_expr_vec, time_single_q, [[2.2, 3, 2.1]], "m"), (ts_expr_vec, time_mul, results_exp_vec, "m"), ], ) def test_interp_time(ts, time, magnitude_exp, unit_exp): """Test the interp_time function.""" result = ts.interp_time(time) assert np.all(np.isclose(result.data.magnitude, magnitude_exp)) assert result.units == U_(unit_exp) time = Time(time) if len(time) == 1: assert result.time is None else: assert np.all(Time(result.time, result._reference_time) == time) # test_interp_time_warning --------------------------------------------------------- @staticmethod def test_interp_time_warning(): """Test if a warning is emitted when interpolating already interpolated data.""" ts = TimeSeries(data=Q_([1, 2, 3], "m"), time=Q_([0, 1, 2], "s")) with pytest.warns(None) as recorded_warnings: ts_interp = ts.interp_time(Q_([0.25, 0.5, 0.75, 1], "s")) assert not any(w.category == UserWarning for w in recorded_warnings) with pytest.warns(UserWarning): ts_interp.interp_time(Q_([0.4, 0.6], "s")) # test_interp_time_exceptions ------------------------------------------------------ @staticmethod @pytest.mark.parametrize("ts", [ts_constant, ts_disc_step, ts_disc_linear, ts_expr]) @pytest.mark.parametrize( "time, exception_type, test_name", [ # (DTI(["2010-10-10"]), ValueError, "# wrong type #1"), ("a string", TypeError, "# wrong type #2"), ([1, 2, 3], TypeError, "# wrong type #3"), (1, TypeError, "# wrong type #4"), (Q_(2, "s/m"), Exception, "# wrong type #5"), ], ids=get_test_name, ) def test_interp_time_exceptions(ts, time, exception_type, test_name): """Test the exceptions of the 'set_parameter' method.""" with pytest.raises(exception_type): ts.interp_time(time)
sd = csm.get_data(data_name) sd_buffer = csm_buffer.get_data(data_name) assert sd == sd_buffer # -------------------------------------------------------------------------------------- # TimeSeries # -------------------------------------------------------------------------------------- @pytest.mark.parametrize("copy_arrays", [True, False]) @pytest.mark.parametrize("lazy_load", [True, False]) @pytest.mark.parametrize( "ts", [ TimeSeries(Q_(42, "m")), TimeSeries(Q_(42.0, "m")), TimeSeries(Q_([42, 23, 12], "m"), time=pd.TimedeltaIndex([0, 2, 4])), TimeSeries(Q_([42, 23, 12], "m"), time=pd.TimedeltaIndex([0, 2, 5])), TimeSeries(ME("a*t+b", parameters={ "a": Q_(2, "1/s"), "b": Q_(5, "") })), TimeSeries( Q_([1, 2, 3], "m"), time=pd.date_range(start="2020-01-01", freq="1D", periods=3), ), TimeSeries( Q_([1, 2, 3], "m"), time=pd.DatetimeIndex(["2020", "2021", "2024"]), ),
class TestTimeSeries: """Tests for the TimeSeries class.""" # Fixtures, aliases and shared variables ------------------------------------------- ME = MathematicalExpression DTI = pd.DatetimeIndex TDI = pd.TimedeltaIndex TS = TimeSeries time_discrete = pd.TimedeltaIndex([0, 1, 2, 3, 4], unit="s") value_constant = Q_(1, "m") values_discrete = Q_(np.array([10, 11, 12, 14, 16]), "mm") me_expr_str = "a*t + b" me_params = {"a": Q_(2, "m/s"), "b": Q_(-2, "m")} me_params_vec = {"a": Q_([[2, 0, 1]], "m/s"), "b": Q_([[-2, 3, 0]], "m")} ts_constant = TimeSeries(value_constant) ts_disc_step = TimeSeries(values_discrete, time_discrete, "step") ts_disc_linear = TimeSeries(values_discrete, time_discrete, "linear") ts_expr = TimeSeries(ME(me_expr_str, me_params)) ts_expr_vec = TimeSeries(ME(me_expr_str, me_params_vec)) # test_construction_discrete ------------------------------------------------------- @staticmethod @pytest.mark.parametrize( "data, time, interpolation, shape_exp", [ (Q_(1, "m"), None, None, (1, )), (Q_([3, 7, 1], "m"), TDI([0, 1, 2], unit="s"), "step", (3, )), (Q_([3, 7, 1], ""), Q_([0, 1, 2], "s"), "step", (3, )), ], ) def test_construction_discrete(data, time, interpolation, shape_exp): """Test the construction of the TimeSeries class.""" # set expected values if isinstance(time, pint.Quantity): time_exp = pd.TimedeltaIndex(time.magnitude, unit="s") else: time_exp = time # create instance ts = TimeSeries(data=data, time=time, interpolation=interpolation) # check assert np.all(ts.data == data) assert np.all(ts.time == time_exp) assert ts.interpolation == interpolation assert ts.shape == shape_exp assert data.check(UREG.get_dimensionality(ts.units)) assert np.all(ts.data_array.data == data) assert ts.data_array.attrs["interpolation"] == interpolation if time_exp is None: assert "time" not in ts.data_array else: assert np.all(ts.data_array.time == time_exp) # test_construction_expression ----------------------------------------------------- params_scalar = {"a": Q_(2, "1/s"), "b": Q_(-2, "")} params_vec = {"a": Q_([[2, 3, 4]], "m/s"), "b": Q_([[-2, 3, 1]], "m")} @staticmethod @pytest.mark.parametrize( "data, shape_exp, unit_exp", [ (ME("a*t + b", params_scalar), (1, ), ""), (ME("a*t + b", params_vec), (1, 3), "m"), ], ) def test_construction_expression(data, shape_exp, unit_exp): """Test the construction of the TimeSeries class.""" ts = TimeSeries(data=data) # check assert ts.data == data assert ts.time is None assert ts.interpolation is None assert ts.shape == shape_exp assert ts.data_array is None assert Q_(1, unit_exp).check(UREG.get_dimensionality(ts.units)) # test_construction_exceptions ----------------------------------------------------- values_def = Q_([5, 7, 3, 6, 8], "m") time_def = Q_([0, 1, 2, 3, 4], "s") me_too_many_vars = ME("a*t + b", {}) me_param_units = ME("a*t + b", {"a": Q_(2, "1/s"), "b": Q_(-2, "m")}) me_time_vec = ME("a*t + b", { "a": Q_([2, 3, 4], "1/s"), "b": Q_([-2, 3, 1], "") }) @staticmethod @pytest.mark.parametrize( "data, time, interpolation, exception_type, test_name", [ (values_def, time_def, "int", ValueError, "# unknown interpolation"), (values_def, time_def, None, ValueError, "# wrong interp. parameter type"), (values_def, time_def.magnitude, "step", ValueError, "# invalid time type"), (me_too_many_vars, None, None, Exception, "# too many free variables"), (me_param_units, None, None, Exception, "# incompatible parameter units"), (me_time_vec, None, None, Exception, "# not compatible with time vectors"), ("a string", None, None, TypeError, "# wrong data type"), ], ids=get_test_name, ) def test_construction_exceptions(data, time, interpolation, exception_type, test_name): """Test the exceptions of the 'set_parameter' method.""" with pytest.raises(exception_type): TimeSeries(data=data, time=time, interpolation=interpolation) # test_comparison ------------------------------------- time_wrong_values = TDI([0, 1, 2, 3, 5], unit="s") values_discrete_wrong = Q_(np.array([10, 11, 12, 15, 16]), "mm") values_unit_wrong = Q_(np.array([10, 11, 12, 14, 16]), "s") values_unit_prefix_wrong = Q_(np.array([10, 11, 12, 14, 16]), "m") params_wrong_values = {"a": Q_(2, "1/s"), "b": Q_(-1, "")} params_wrong_unit = {"a": Q_(2, "g/s"), "b": Q_(-2, "g")} params_wrong_unit_prefix = {"a": Q_(2, "m/ms"), "b": Q_(-2, "m")} @staticmethod @pytest.mark.parametrize( "ts, ts_other, result_exp", [ (ts_constant, TS(value_constant), True), (ts_disc_step, TS(values_discrete, time_discrete, "step"), True), (ts_expr, TS(ME(me_expr_str, me_params)), True), (ts_constant, ts_disc_step, False), (ts_constant, ts_expr, False), (ts_disc_step, ts_expr, False), (ts_constant, 1, False), (ts_disc_step, 1, False), (ts_expr, 1, False), (ts_constant, "wrong", False), (ts_disc_step, "wrong", False), (ts_expr, "wrong", False), (ts_constant, TS(Q_(1337, "m")), False), (ts_constant, TS(Q_(1, "mm")), False), (ts_constant, TS(Q_(1, "s")), False), (ts_disc_step, TS(values_discrete, time_wrong_values, "step"), False), (ts_disc_step, TS(values_discrete_wrong, time_discrete, "step"), False), (ts_disc_step, TS(values_unit_prefix_wrong, time_discrete, "step"), False), (ts_disc_step, TS(values_discrete, time_discrete, "linear"), False), (ts_expr, TS(ME("a*t + 2*b", me_params)), False), (ts_expr, TS(ME(me_expr_str, params_wrong_values)), False), (ts_expr, TS(ME(me_expr_str, params_wrong_unit)), False), (ts_expr, TS(ME(me_expr_str, params_wrong_unit_prefix)), False), ], ) def test_comparison(ts, ts_other, result_exp): """Test the TimeSeries comparison methods.""" assert (ts == ts_other) is result_exp assert (ts != ts_other) is not result_exp # test_interp_time ----------------------------------------------------------------- time_single = pd.TimedeltaIndex([2.1], "s") time_single_q = Q_(2.1, "s") time_mul = pd.TimedeltaIndex([-3, 0.7, 1.1, 1.9, 2.5, 3, 4, 7], "s") time_mul_q = Q_([-3, 0.7, 1.1, 1.9, 2.5, 3, 4, 7], "s") results_exp_vec = [ [-8, 3, -3], [-0.6, 3, 0.7], [0.2, 3, 1.1], [1.8, 3, 1.9], [3, 3, 2.5], [4, 3, 3], [6, 3, 4], [12, 3, 7], ] @staticmethod @pytest.mark.parametrize( "ts, time, magnitude_exp, unit_exp", [ (ts_constant, time_single, 1, "m"), (ts_constant, time_single_q, 1, "m"), (ts_constant, time_mul, [1, 1, 1, 1, 1, 1, 1, 1], "m"), (ts_constant, time_mul_q, [1, 1, 1, 1, 1, 1, 1, 1], "m"), (ts_disc_step, time_single, 12, "mm"), (ts_disc_step, time_single_q, 12, "mm"), (ts_disc_step, time_mul, [10, 10, 11, 11, 12, 14, 16, 16], "mm"), (ts_disc_step, time_mul_q, [10, 10, 11, 11, 12, 14, 16, 16], "mm"), (ts_disc_linear, time_single, 12.2, "mm"), (ts_disc_linear, time_single_q, 12.2, "mm"), (ts_disc_linear, time_mul, [10, 10.7, 11.1, 11.9, 13, 14, 16, 16 ], "mm"), (ts_disc_linear, time_mul_q, [10, 10.7, 11.1, 11.9, 13, 14, 16, 16], "mm"), (ts_expr, time_single, 2.2, "m"), (ts_expr, time_single_q, 2.2, "m"), (ts_expr, time_mul, [-8, -0.6, 0.2, 1.8, 3, 4, 6, 12], "m"), (ts_expr, time_mul_q, [-8, -0.6, 0.2, 1.8, 3, 4, 6, 12], "m"), (ts_expr_vec, time_single, [[2.2, 3, 2.1]], "m"), (ts_expr_vec, time_single_q, [[2.2, 3, 2.1]], "m"), (ts_expr_vec, time_mul, results_exp_vec, "m"), ], ) def test_interp_time(ts, time, magnitude_exp, unit_exp): """Test the interp_time function.""" result = ts.interp_time(time) assert np.all(np.isclose(result.data.magnitude, magnitude_exp)) assert Q_(1, str(result.data.units)) == Q_(1, unit_exp) if isinstance(time, pint.Quantity): assert np.all(result.time == ut.to_pandas_time_index(time)) else: assert np.all(result.time == time) # test_interp_time_exceptions ------------------------------------------------------ @staticmethod @pytest.mark.parametrize( "ts", [ts_constant, ts_disc_step, ts_disc_linear, ts_expr]) @pytest.mark.parametrize( "time, exception_type, test_name", [ (DTI(["2010-10-10"]), ValueError, "# wrong type #1"), ("a string", ValueError, "# wrong type #2"), ([1, 2, 3], ValueError, "# wrong type #3"), (1, ValueError, "# wrong type #4"), (Q_(2, "s/m"), Exception, "# wrong type #5"), ], ids=get_test_name, ) def test_interp_time_exceptions(ts, time, exception_type, test_name): """Test the exceptions of the 'set_parameter' method.""" with pytest.raises(exception_type): ts.interp_time(time)
}) csm_file = data["cs_hierarchy"] assert csm_root == csm_file # -------------------------------------------------------------------------------------- # TimeSeries # -------------------------------------------------------------------------------------- @pytest.mark.parametrize("copy_arrays", [True, False]) @pytest.mark.parametrize("lazy_load", [True, False]) @pytest.mark.parametrize( "ts", [ TimeSeries(Q_(42, "m")), TimeSeries(Q_(42.0, "m")), TimeSeries(Q_([42, 23, 12], "m"), time=pd.TimedeltaIndex([0, 2, 4])), TimeSeries(Q_([42, 23, 12], "m"), time=pd.TimedeltaIndex([0, 2, 5])), TimeSeries(ME("a*t+b", parameters={ "a": Q_(2, "1/s"), "b": Q_(5, "") })), ], ) def test_time_series_discrete(ts, copy_arrays, lazy_load): ts_file = _write_read_buffer({"ts": ts}, open_kwargs={ "copy_arrays": copy_arrays, "lazy_load": lazy_load })["ts"]