def test_cross_section(groove): # noqa @contextmanager def temp_attr(obj, attr, new_value): old_value = getattr(obj, attr) setattr(obj, attr, new_value) yield setattr(obj, attr, old_value) groove_obj, groove_cls = groove # make rasterization for U-based grooves rather rough. with temp_attr( # skipcq: PYL-E1129 groove_obj, "_AREA_RASTER_WIDTH", Q_(0.75, _DEFAULT_LEN_UNIT)): try: A = groove_obj.cross_sect_area except NotImplementedError: return except Exception as ex: raise ex # check docstring got inherited. assert groove_cls.cross_sect_area.__doc__ is not None assert hasattr(A, "units") assert A.units == Q_("mm²") assert A > 0
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_welding_speed(): # noqa groove = IGroove(b=Q_(10, "mm"), t=Q_(5, "cm")) wire_diameter = Q_(1, "mm") wire_feed = Q_(1, "mm/s") result = compute_welding_speed(groove, wire_feed, wire_diameter) assert result.units == wire_feed.units assert result > 0
def measurement_chain_without_equipment() -> MeasurementChain: """Get a default measurement chain without attached equipment.""" mc = MeasurementChain( "Current measurement chain", SignalSource( "Current measurement", Signal("analog", "V"), Error(Q_(0.1, "percent")), ), ) mc.add_transformation( SignalTransformation( name="AD conversion", error=Error(Q_(0.5, "percent")), type_transformation="AD", )) mc.add_transformation( SignalTransformation( name="Calibration", error=Error(Q_(1.5, "percent")), func=MathematicalExpression("a*x+b", parameters=dict(a=Q_(3, "A/V"), b=Q_(2, "A"))), )) return mc
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 test_call_operator_expression(u, v, w): # setup expression = "a*u + b*v + w" units = dict(u="m", v="K", w="m*m") parameters = dict(a="2m", b="5m*m/K") params = dict(u=u, v=v, w=w) gs = GenericSeries(expression, parameters=parameters, units=units) print(gs.ndims) # perform interpolation gs_interp = gs(**params) print(gs_interp) # calculate expected result params = {k: Q_(val) for k, val in params.items()} for k, val in params.items(): if len(val.shape) == 0: params[k] = np.expand_dims(val, 0) exp_shape = tuple(len(val) for val in params.values()) exp_data = np.zeros(exp_shape) a = Q_(parameters["a"]) b = Q_(parameters["b"]) for i, u_v in enumerate(params["u"]): for j, v_v in enumerate(params["v"]): for k, w_v in enumerate(params["w"]): exp_data[i, j, k] = (a * u_v + b * v_v + w_v).m assert np.allclose(gs_interp.data, Q_(exp_data, "m*m"))
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 compute_welding_speed( groove: IsoBaseGroove, wire_feed: QuantityLike, wire_diameter: QuantityLike, ) -> pint.Quantity: """Compute how fast the torch has to be moved to fill the given groove. Parameters ---------- groove groove definition to compute welding speed for. wire_feed: pint.Quantity feed of the wire, given in dimensionality "length/time". wire_diameter: pint.Quantity diameter of welding wire, given in dimensionality "length". Returns ------- speed: pint.Quantity The computed welding speed, given in dimensionality "length/time". """ groove_area = groove.cross_sect_area wire_area = np.pi / 4 * Q_(wire_diameter)**2 weld_speed = wire_area * Q_(wire_feed) / groove_area weld_speed.ito_reduced_units() return weld_speed
def test_rotation_euler_prefix(inputs): """Test unit prefix handling.""" degrees = "degree" in str(inputs.u) rot = WXRotation.from_euler(seq="x", angles=inputs) data = write_read_buffer({"rot": rot}) r = data["rot"].as_euler("xyz", degrees=degrees)[0] r = Q_(r, "degree") if degrees else Q_(r, "rad") assert np.allclose(inputs, r)
def to_tree(cls, node: Rotation, ctx): """ Convert an instance of the 'Dimension' type into YAML representations. Parameters ---------- node : Instance of the 'Dimension' type to be serialized. ctx : An instance of the 'AsdfFile' object that is being written out. Returns ------- A basic YAML type ('dict', 'list', 'str', 'int', 'float', or 'complex') representing the properties of the 'Dimension' type to be serialized. """ tree = {} if not hasattr(node, "wx_meta"): # default to quaternion representation tree["quaternions"] = node.as_quat() elif node.wx_meta["constructor"] == "from_quat": tree["quaternions"] = node.as_quat() elif node.wx_meta["constructor"] == "from_matrix": tree["matrix"] = node.as_matrix() elif node.wx_meta["constructor"] == "from_rotvec": tree["rotvec"] = node.as_rotvec() elif node.wx_meta["constructor"] == "from_euler": seq_str = node.wx_meta["seq"] if not len(seq_str) == 3: if all([c in "xyz" for c in seq_str]): seq_str = seq_str + "".join( [c for c in "xyz" if c not in seq_str]) elif all([c in "XYZ" for c in seq_str]): seq_str = seq_str + "".join( [c for c in "XYZ" if c not in seq_str]) else: # pragma: no cover raise ValueError( "Mix of intrinsic and extrinsic euler angles.") angles = node.as_euler(seq_str, degrees=node.wx_meta["degrees"]) angles = np.squeeze(angles[..., :len(node.wx_meta["seq"])]) if node.wx_meta["degrees"]: angles = Q_(angles, "degree") else: angles = Q_(angles, "rad") tree["sequence"] = node.wx_meta["seq"] tree["angles"] = angles else: # pragma: no cover raise NotImplementedError("unknown or missing constructor") return tree
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_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)
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_pandas_time_delta_to_quantity(): """Test the 'pandas_time_delta_to_quantity' utility function.""" is_close = np.vectorize(math.isclose) def _check_close(t1, t2): assert np.all(is_close(t1.magnitude, t2.magnitude)) assert t1.units == t2.units time_single = pd.TimedeltaIndex([1], unit="s") _check_close(ut.pandas_time_delta_to_quantity(time_single), Q_(1, "s")) _check_close(ut.pandas_time_delta_to_quantity(time_single, "ms"), Q_(1000, "ms")) _check_close(ut.pandas_time_delta_to_quantity(time_single, "us"), Q_(1000000, "us")) _check_close( ut.pandas_time_delta_to_quantity(time_single, "ns"), Q_(1000000000, "ns") ) time_multi = pd.TimedeltaIndex([1, 2, 3], unit="s") _check_close(ut.pandas_time_delta_to_quantity(time_multi), Q_([1, 2, 3], "s")) _check_close( ut.pandas_time_delta_to_quantity(time_multi, "ms"), Q_([1000, 2000, 3000], "ms") ) _check_close( ut.pandas_time_delta_to_quantity(time_multi, "us"), Q_([1000000, 2000000, 3000000], "us"), ) _check_close( ut.pandas_time_delta_to_quantity(time_multi, "ns"), Q_([1000000000, 2000000000, 3000000000], "ns"), )
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)
def _unit_validator(instance: Mapping, expected_dimensionality: str, position: List[str]) -> Iterator[ValidationError]: """Validate the 'unit' key of the instance against the given string. Parameters ---------- instance: Tree serialization with 'unit' key to validate. expected_dimensionality: String representation of the unit dimensionality to test against. position: Current position in nested structure for debugging Yields ------ asdf.ValidationError """ if not position: position = instance unit = instance["unit"] valid = Q_(unit).check(UREG.get_dimensionality(expected_dimensionality)) if not valid: yield ValidationError( f"Error validating unit dimension for property '{position}'. " f"Expected unit of dimension '{expected_dimensionality}' " f"but got unit '{unit}'")
def _determine_output_signal_unit( func: MathematicalExpression, input_unit: Union[str, Union] ) -> pint.Unit: """Determine the unit of a transformations' output signal. Parameters ---------- func : The function describing the transformation input_unit : The unit of the input signal Returns ------- pint.Unit: Unit of the transformations' output signal """ input_unit = U_(input_unit) if func is not None: variables = func.get_variable_names() if len(variables) != 1: raise ValueError("The provided function must have exactly 1 parameter") try: test_output = func.evaluate(**{variables[0]: Q_(1, input_unit)}) except Exception as e: raise ValueError( "The provided function is incompatible with the input signals unit." f" \nThe test raised the following exception:\n{e}" ) return test_output.data.units return input_unit
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 _get_coordinate_quantities(da) -> dict[str, pint.Quantity]: """Convert coordinates of an xarray object to a quantity dictionary.""" return { k: (Q_(v.data, v.attrs.get(UNITS_KEY)) if v.attrs.get(UNITS_KEY, None) else v.data) for k, v in da.coords.items() }
def from_tree(cls, tree, ctx): """ Converts basic types representing YAML trees into custom types. 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 ------- Variable : An instance of the 'Variable' type. """ dtype = np.dtype(tree["dtype"]) if "unit" in tree: # convert to pint.Quantity data = Q_(tree["data"].astype(dtype), tree["unit"]) else: data = tree["data"].astype(dtype) return Variable(tree["name"], tree["dimensions"], data)
def indexes_as_quantities(self) -> dict[str, pint.Quantity]: """Convert indexes of an xarray object to a quantity dictionary.""" da = self._obj return { k: (Q_(v.data, unit) if (unit := v.attrs.get(UNITS_KEY, None)) else v.data) for k, v in da.indexes.items() }
def measurement_chain_with_equipment() -> MeasurementChain: """Get a default measurement chain with attached equipment.""" source = SignalSource( "Current measurement", output_signal=Signal(signal_type="analog", units="V"), error=Error(Q_(1, "percent")), ) ad_conversion = SignalTransformation( "AD conversion current measurement", error=Error(Q_(0, "percent")), func=MathematicalExpression(expression="a*x+b", parameters=dict(a=Q_(1, "1/V"), b=Q_(1, ""))), ) calibration = SignalTransformation( "Current measurement calibration", error=Error(Q_(1.2, "percent")), func=MathematicalExpression(expression="a*x+b", parameters=dict(a=Q_(1, "A"), b=Q_(1, "A"))), ) eq_source = MeasurementEquipment( name="Source Equipment", sources=[source], ) eq_ad_conversion = MeasurementEquipment(name="AD Equipment", transformations=[ad_conversion]) eq_calibration = MeasurementEquipment(name="Calibration Equipment", transformations=[calibration]) mc = MeasurementChain.from_equipment("Measurement chain", eq_source) mc.add_transformation_from_equipment(eq_ad_conversion) mc.add_transformation_from_equipment(eq_calibration) return mc
def test_evaluation_preprocessor_expression(use_expr): """Test the evaluation preprocessor.""" class _DerivedSeries(GenericSeries): _evaluation_preprocessor = dict(a=lambda x: 2 * x, b=lambda x: 5 * x) if use_expr: ds = _DerivedSeries("a+b") else: ds = _DerivedSeries( Q_([[2, 21], [11, 30]]), dims=["a", "b"], coords=dict(a=Q_([1, 10]), b=Q_([1, 20])), ) result = ds(a=Q_([1, 3, 4]), b=Q_([3, 2, 1])) exp_result = [[17, 12, 7], [21, 16, 11], [23, 18, 13]] assert np.allclose(exp_result, result.data.m)
def test_pint_default_ureg(): """Test if the weldx unit registry is set as the default unit registry.""" da = xr.DataArray( Q_([1, 2, 3, 4], "mm"), dims=["a"], coords={"a": ("a", [1, 2, 3, 4], { "units": U_("s") })}, ) da.pint.dequantify().pint.quantify().pint.dequantify().pint.quantify()
def test_asdf_groove_exceptions(): """Test special cases and exceptions of groove classes.""" # test parameter string generation v_groove = get_groove( groove_type="VGroove", workpiece_thickness=Q_(9, "mm"), groove_angle=Q_(50, "deg"), root_face=Q_(4, "mm"), root_gap=Q_(2, "mm"), ) assert set(v_groove.param_strings()) == { "alpha=50 deg", "b=2 mm", "c=4 mm", "t=9 mm", } # test custom groove axis labels fig, _ = plt.subplots() v_groove.plot(axis_label=["x", "y"]) plt.close(fig) # test exceptions with pytest.raises(KeyError): get_groove( groove_type="WrongGrooveString", workpiece_thickness=Q_(9, "mm"), groove_angle=Q_(50, "deg"), ) with pytest.raises(NotImplementedError): IsoBaseGroove().to_profile() with pytest.raises(ValueError): get_groove( groove_type="FFGroove", workpiece_thickness=Q_(2, "mm"), workpiece_thickness2=Q_(5, "mm"), groove_angle=Q_(80, "deg"), root_gap=Q_(1, "mm"), code_number="6.1.1", ).to_profile()
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)
def xr_3d_vector( data: wxt.ArrayLike, time: types_time_like = None, add_dims: list[str] = None, add_coords: dict[str, Any] = None, ) -> xr.DataArray: """Create an xarray 3d vector with correctly named dimensions and coordinates. Parameters ---------- data Full data array. time Optional values that will fill the 'time' dimension. add_dims Addition dimensions to add between ["time", "c"]. If either "c" or "time" are present in add_dims they are used to locate the dimension position in the passed array. add_coords Additional coordinates to assign to the xarray. ("c" and "time" coordinates will be assigned automatically) Returns ------- xarray.DataArray """ if add_dims is None: add_dims = [] if add_coords is None: add_coords = {} dims = ["c"] coords = dict(c=["x", "y", "z"]) # if data is static but time passed we discard time information if time is not None and Q_(data).ndim == 1: time = None # remove duplicates and keep order dims = list(dict.fromkeys(add_dims + dims)) if time is not None: if "time" not in dims: # prepend to beginning if not already set dims = ["time"] + dims coords["time"] = time # type: ignore[assignment] if "time" in coords: coords["time"] = Time(coords["time"]).index coords = dict(add_coords, **coords) da = xr.DataArray(data=data, dims=dims, coords=coords).transpose(..., "c") return da.astype(float).weldx.time_ref_restore()
def get_local_coordinate_system(time_dep_orientation: bool, time_dep_coordinates: bool): """ Get a local coordinate system. Parameters ---------- time_dep_orientation : If True, the coordinate system has a time dependent orientation. time_dep_coordinates : If True, the coordinate system has a time dependent coordinates. Returns ------- weldx.transformations.LocalCoordinateSystem: A local coordinate system """ if not time_dep_coordinates: coords = Q_([2.0, 5.0, 1.0], "mm") else: coords = Q_( [[2.0, 5.0, 1.0], [1.0, -4.0, 1.2], [0.3, 4.4, 4.2], [1.1, 2.3, 0.2]], "mm", ) if not time_dep_orientation: orientation = WXRotation.from_euler("z", np.pi / 3).as_matrix() else: orientation = WXRotation.from_euler( "z", np.pi / 2 * np.array([1, 2, 3, 4])).as_matrix() if not time_dep_orientation and not time_dep_coordinates: return tf.LocalCoordinateSystem(orientation=orientation, coordinates=coords) time = pd.DatetimeIndex( ["2000-01-01", "2000-01-02", "2000-01-03", "2000-01-04"]) return tf.LocalCoordinateSystem(orientation=orientation, coordinates=coords, time=time)
def to_yaml_tree(self, obj: Rotation, tag: str, ctx) -> dict: """Convert to python dict.""" tree = {} if not hasattr(obj, ROT_META): # default to quaternion representation tree["quaternions"] = obj.as_quat() elif getattr(obj, ROT_META)["constructor"] == "from_quat": tree["quaternions"] = obj.as_quat() elif getattr(obj, ROT_META)["constructor"] == "from_matrix": tree["matrix"] = obj.as_matrix() elif getattr(obj, ROT_META)["constructor"] == "from_rotvec": tree["rotvec"] = obj.as_rotvec() elif getattr(obj, ROT_META)["constructor"] == "from_euler": seq_str = getattr(obj, ROT_META)["seq"] if not len(seq_str) == 3: if all(c in "xyz" for c in seq_str): seq_str = seq_str + "".join( [c for c in "xyz" if c not in seq_str]) elif all(c in "XYZ" for c in seq_str): seq_str = seq_str + "".join( [c for c in "XYZ" if c not in seq_str]) else: # pragma: no cover raise ValueError( "Mix of intrinsic and extrinsic euler angles.") angles = obj.as_euler(seq_str, degrees=getattr(obj, ROT_META)["degrees"]) angles = np.squeeze( angles[..., :len(getattr(obj, ROT_META)["seq"])]) if getattr(obj, ROT_META)["degrees"]: angles = Q_(angles, "degree") else: angles = Q_(angles, "rad") tree["sequence"] = getattr(obj, ROT_META)["seq"] tree["angles"] = angles else: # pragma: no cover raise NotImplementedError("unknown or missing constructor") return tree
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)