Exemplo n.º 1
0
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
Exemplo n.º 2
0
    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
Exemplo n.º 3
0
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)
Exemplo n.º 4
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
Exemplo n.º 5
0
    def from_yaml_tree(self, node: dict, tag: str, ctx):
        """Construct from tree."""

        parameters = {}
        for k, v in node["parameters"].items():
            if hasattr(v, META_ATTR):
                dims = getattr(v, META_ATTR)["dims"]
                delattr(v, META_ATTR)
                v = (v, dims)
            parameters[k] = v

        return MathematicalExpression(sympy.sympify(node["expression"]),
                                      parameters=parameters)
Exemplo n.º 6
0
    def _default_transformation(kwargs: dict = None) -> SignalTransformation:
        """Return a default `SignalTransformation`.

        Use the kwargs parameter to modify the default values.

        """
        default_kwargs = dict(
            name="transformation",
            error=Error(0.1),
            func=MathematicalExpression("a*x", parameters={"a": Q_(1, "1/V")}),
            type_transformation="AD",
        )
        if kwargs is not None:
            default_kwargs.update(kwargs)

        return SignalTransformation(**default_kwargs)
Exemplo n.º 7
0
    def test_set_parameter(self):
        """Test the set_parameter function of the mathematical expression."""
        expr = MathematicalExpression("a*b + c/d - e")

        # check initial configuration
        self._check_params_and_vars(expr, {}, ["a", "b", "c", "d", "e"])

        # set first parameters
        expr.set_parameter("d", 1)
        expr.set_parameter("e", 2)

        self._check_params_and_vars(expr, {"d": 1, "e": 2}, ["a", "b", "c"])

        # set another parameter and overwrite others
        expr.set_parameter("a", 5)
        expr.set_parameter("d", 7)
        expr.set_parameter("e", -1)

        self._check_params_and_vars(expr, {"a": 5, "d": 7, "e": -1}, ["b", "c"])
Exemplo n.º 8
0
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)
Exemplo n.º 9
0
def test_generic_measurement():
    """Test basic measurement creation and ASDF read/write."""
    data_01 = msm.Data(
        name="Welding current", data=xr.DataArray([1, 2, 3, 4], dims=["time"])
    )

    data_02 = msm.Data(
        name="Welding voltage", data=xr.DataArray([10, 20, 30, 40], dims=["time"])
    )

    src_01 = msm.Source(
        name="Current Sensor",
        output_signal=msm.Signal("analog", "V", data=None),
        error=msm.Error(1337.42),
    )

    src_02 = msm.Source(
        name="Voltage Sensor",
        output_signal=msm.Signal("analog", "V", data=None),
        error=msm.Error(1),
    )

    dp_01 = msm.DataTransformation(
        name="AD conversion current measurement",
        input_signal=src_01.output_signal,
        output_signal=msm.Signal("digital", "V", data=None),
        error=msm.Error(999.0),
    )

    dp_02 = msm.DataTransformation(
        name="Calibration current measurement",
        input_signal=dp_01.output_signal,
        output_signal=msm.Signal("digital", "A", data=data_01),
        error=msm.Error(43.0),
    )

    dp_03 = msm.DataTransformation(
        name="AD conversion voltage measurement",
        input_signal=dp_02.output_signal,
        output_signal=msm.Signal("digital", "V", data=None),
        error=msm.Error(2.0),
    )

    dp_04 = msm.DataTransformation(
        name="Calibration voltage measurement",
        input_signal=dp_03.output_signal,
        output_signal=msm.Signal("digital", "V", data=data_02),
        error=msm.Error(Q_(3.0, "percent")),
    )

    chn_01 = msm.MeasurementChain(
        name="Current measurement", data_source=src_01, data_processors=[dp_01, dp_02]
    )

    chn_02 = msm.MeasurementChain(
        name="Voltage measurement", data_source=src_02, data_processors=[dp_03, dp_04]
    )

    eqp_01 = msm.GenericEquipment(
        "Current Sensor", sources=[src_01], data_transformations=[dp_02]
    )
    eqp_02 = msm.GenericEquipment(
        "AD Converter", sources=None, data_transformations=[dp_01, dp_03]
    )
    eqp_03 = msm.GenericEquipment(
        "Voltage Sensor", sources=None, data_transformations=[dp_04]
    )

    measurement_01 = msm.Measurement(
        name="Current measurement", data=[data_01], measurement_chain=chn_01
    )
    measurement_02 = msm.Measurement(
        name="Voltage measurement", data=[data_02], measurement_chain=chn_02
    )

    equipment = [eqp_01, eqp_02, eqp_03]
    measurement_data = [data_01, data_02]
    measurement_chains = [chn_01]
    measurements = [measurement_01, measurement_02]
    sources = [src_01]
    processors = [dp_01, dp_02]

    [a, x, b] = sympy.symbols("a x b")
    expr_01 = MathematicalExpression(a * x + b)
    expr_01.set_parameter("a", 2)
    expr_01.set_parameter("b", 3)
    print(expr_01.parameters)
    print(expr_01.get_variable_names())
    print(expr_01.evaluate(x=3))

    tree = {
        "equipment": equipment,
        "data": measurement_data,
        "measurements": measurements,
        # "expression": expr_01,
        "measurement_chains": measurement_chains,
        "data_sources": sources,
        "data_processors": processors,
    }

    _write_read_buffer(tree)
Exemplo n.º 10
0
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,
        )
Exemplo n.º 11
0
class TestMeasurementChain:
    """Test the `MeasurementChain` class."""

    # helper functions -----------------------------------------------------------------

    @staticmethod
    def _default_source_kwargs(kwargs: dict = None) -> dict:
        """Update a dict with default keyword arguments to create a `SignalSource`."""
        default_kwargs = dict(
            name="source", output_signal=Signal("analog", "V"), error=Error(0.01)
        )

        if kwargs is not None:
            default_kwargs.update(kwargs)

        return default_kwargs

    @classmethod
    def _default_init_kwargs(
        cls, kwargs: dict = None, source_kwargs: dict = None
    ) -> dict:
        """Return a dictionary of keyword arguments required by the `__init__` method.

        Parameters
        ----------
        kwargs :
            A dictionary containing some key word arguments that should replace the
            default ones.
        source_kwargs :
            A dictionary of key word arguments that should replace the arguments used
            for the default source.

        Returns
        -------
        Dict :
            Dictionary with keyword arguments for the `__init__` method

        """
        source_kwargs = cls._default_source_kwargs(source_kwargs)

        default_kwargs = dict(
            name="name",
            source=SignalSource(**source_kwargs),
            signal_data=[1, 3, 5],
        )
        if kwargs is not None:
            default_kwargs.update(kwargs)

        return default_kwargs

    @staticmethod
    def _default_transformation(kwargs: dict = None) -> SignalTransformation:
        """Return a default `SignalTransformation`.

        Use the kwargs parameter to modify the default values.

        """
        default_kwargs = dict(
            name="transformation",
            error=Error(0.1),
            func=MathematicalExpression("a*x", parameters={"a": Q_(1, "1/V")}),
            type_transformation="AD",
        )
        if kwargs is not None:
            default_kwargs.update(kwargs)

        return SignalTransformation(**default_kwargs)

    @classmethod
    def _default_add_transformation_kwargs(cls, kwargs: dict = None) -> dict:
        """Update a dict with default keyword arguments to call `add_transformation`."""
        default_kwargs = dict(
            transformation=cls._default_transformation(),
            error=Error(0.02),
            output_signal_type="digital",
            output_signal_unit="",
        )
        if kwargs is not None:
            default_kwargs.update(kwargs)

        return default_kwargs

    # test_init ------------------------------------------------------------------------

    @staticmethod
    @pytest.mark.parametrize(
        "kwargs, source_kwargs",
        [
            ({}, {}),
            (dict(signal_data=None), dict(output_signal=Signal("analog", "V", [1]))),
        ],
    )
    def test_init(kwargs: dict, source_kwargs: dict):
        """Test the `__init__` method of the `MeasurementChain`.

        Parameters
        ----------
        kwargs:
            A dictionary with keyword arguments that are passed to the `__init__`
            method. Missing arguments are added.
        source_kwargs :
            A dictionary with keyword arguments that are used to construct the
            `SignalSource` that is passed to the `__init__` method. Missing arguments
            are added.

        """
        kwargs = TestMeasurementChain._default_init_kwargs(kwargs, source_kwargs)
        MeasurementChain(**kwargs)

    # test_init_exceptions -------------------------------------------------------------

    @staticmethod
    @pytest.mark.parametrize(
        "kwargs, source_kwargs,  exception_type, test_name",
        [({}, {"output_signal": Signal("analog", "V", [1])}, KeyError, "# 2x data")],
        ids=get_test_name,
    )
    def test_init_exceptions(
        kwargs: dict, source_kwargs: dict, exception_type, test_name: str
    ):
        """Test the exceptions of the `__init__` method.

        Parameters
        ----------
        kwargs :
            A dictionary with keyword arguments that are passed to the `__init__`
            method. Missing arguments are added.
        source_kwargs :
            A dictionary with keyword arguments that are used to construct the
            `SignalSource` that is passed to the `__init__` method. Missing arguments
            are added.
        exception_type :
            The expected exception type
        test_name :
            Name of the test

        """
        kwargs = TestMeasurementChain._default_init_kwargs(kwargs, source_kwargs)
        with pytest.raises(exception_type):
            MeasurementChain(**kwargs)

    # test_from_equipment --------------------------------------------------------------

    @pytest.mark.parametrize(
        "num_sources, source_name, exception",
        [
            (0, None, ValueError),
            (1, None, None),
            (2, "source_1", None),
            (2, "wrong name", KeyError),
            (2, None, ValueError),
        ],
    )
    def test_from_equipment(
        self, num_sources: int, source_name: str, exception: Exception
    ):
        """Test the `from_equipment` factory and its exceptions.

        Parameters
        ----------
        num_sources :
            Number of sources of the generated equipment
        source_name :
            Corresponding parameter of `from_equipment`
        exception :
            Expected exception

        """
        sources = [
            SignalSource(**self._default_source_kwargs({"name": f"source_{i}"}))
            for i in range(num_sources)
        ]
        equipment = MeasurementEquipment("Equipment", sources=sources)

        if exception is not None:
            with pytest.raises(exception):
                MeasurementChain.from_equipment(
                    name="name", equipment=equipment, source_name=source_name
                )
        else:
            MeasurementChain.from_equipment(
                name="name", equipment=equipment, source_name=source_name
            )

    # test_add_transformations ---------------------------------------------------------

    @pytest.mark.parametrize(
        "tf_kwargs, exp_signal_type, exp_signal_unit",
        [
            ({}, "digital", U_("")),
            (dict(type_transformation="AA"), "analog", U_("")),
            (dict(type_transformation=None), "analog", U_("")),
            (dict(func=None), "digital", U_("V")),
        ],
    )
    def test_add_transformation(self, tf_kwargs, exp_signal_type, exp_signal_unit):
        """Test the `add_transformation` method of the `MeasurementChain`.

        Parameters
        ----------
        tf_kwargs:
            A dictionary with keyword arguments that are used to construct the
            `SignalTransformation` that is passed to the `add_transformation` method.
            Missing arguments are added.
        exp_signal_type :
            The expected signal type after the transformation
        exp_signal_unit :
            The expected unit after the transformation

        """
        mc = MeasurementChain(**self._default_init_kwargs())

        mc.add_transformation(self._default_transformation(tf_kwargs))

        signal = mc.output_signal
        assert signal.signal_type == exp_signal_type
        assert U_(signal.units) == exp_signal_unit

    # test_add_transformation_exceptions -----------------------------------------------

    @pytest.mark.parametrize(
        "tf_kwargs, input_signal_source, exception_type, test_name",
        [
            (dict(type_transformation="DA"), None, ValueError, "# inv. signal type #1"),
            (dict(type_transformation="DD"), None, ValueError, "# inv. signal type #2"),
            ({}, "not found", KeyError, "# invalid input signal source"),
            (dict(name="source"), None, KeyError, "# Name does already exist"),
            (
                dict(func=MathematicalExpression("x+a", parameters={"a": Q_(1, "A")})),
                None,
                ValueError,
                "# incompatible function",
            ),
        ],
        ids=get_test_name,
    )
    def test_add_transformation_exceptions(
        self, tf_kwargs: dict, input_signal_source: str, exception_type, test_name: str
    ):
        """Test the exceptions of the `add_transformation` method.

        Parameters
        ----------
        tf_kwargs:
            A dictionary with keyword arguments that are used to construct the
            `SignalTransformation` that is passed to the `add_transformation` method.
            Missing arguments are added.
        input_signal_source :
            The value of the corresponding parameter of 'add_transformation'
        exception_type :
            The expected exception type
        test_name :
            Name of the test

        """
        mc = MeasurementChain(**self._default_init_kwargs())

        tf = self._default_transformation(tf_kwargs)

        with pytest.raises(exception_type):
            mc.add_transformation(tf, input_signal_source=input_signal_source)

    # test_add_transformation_from_equipment -------------------------------------------

    @pytest.mark.parametrize(
        "num_transformations, transformation_name, exception",
        [
            (0, None, ValueError),
            (1, None, None),
            (2, "transformation_1", None),
            (2, "wrong name", KeyError),
            (2, None, ValueError),
        ],
    )
    def test_add_transformation_from_equipment(
        self, num_transformations: int, transformation_name: str, exception
    ):
        """Test `add_transformation_from_equipment` and its exceptions.

        Parameters
        ----------
        num_transformations :
            Number of transformations of the generated equipment
        transformation_name :
            Corresponding parameter of `add_transformation_from_equipment`
        exception :
            Expected exception

        """
        mc = MeasurementChain(**self._default_init_kwargs())
        transformations = [
            self._default_transformation({"name": f"transformation_{i}"})
            for i in range(num_transformations)
        ]
        equipment = MeasurementEquipment(name="name", transformations=transformations)

        if exception is not None:
            with pytest.raises(exception):
                mc.add_transformation_from_equipment(
                    equipment=equipment, transformation_name=transformation_name
                )
        else:
            mc.add_transformation_from_equipment(
                equipment=equipment, transformation_name=transformation_name
            )

    # test_add_signal_data -------------------------------------------------------------

    @pytest.mark.parametrize(
        "kwargs",
        [
            dict(data=xr.DataArray([2, 3])),
            dict(signal_source="source"),
        ],
    )
    def test_add_signal_data(self, kwargs):
        """Test the `add_signal_data` method of the `MeasurementChain`.

        Parameters
        ----------
        kwargs:
            A dictionary with keyword arguments that are passed to the
            `add_signal_data` method. If no name is in the kwargs, a default one is
            added.

        """
        mc = MeasurementChain(**self._default_init_kwargs({"signal_data": None}))
        mc.add_transformation(self._default_transformation())

        full_kwargs = dict(data=xr.DataArray([1, 2]))
        full_kwargs.update(kwargs)

        mc.add_signal_data(**full_kwargs)

    # test_add_signal_data_exceptions --------------------------------------------------

    @pytest.mark.parametrize(
        "kwargs,  exception_type, test_name",
        [
            (dict(signal_source="what"), KeyError, "# invalid signal source"),
            (dict(signal_source="source"), KeyError, "# already has data #1"),
            (dict(signal_source="transformation"), KeyError, "# already has data #2"),
        ],
        ids=get_test_name,
    )
    def test_add_signal_data_exceptions(
        self, kwargs: dict, exception_type, test_name: str
    ):
        """Test the exceptions of the `add_signal_data` method.

        Parameters
        ----------
        kwargs :
            A dictionary with keyword arguments that are passed to the `add_signal_data`
            method. Missing arguments are added.
        exception_type :
            The expected exception type
        test_name :
            Name of the test

        """
        mc = MeasurementChain(**self._default_init_kwargs())
        mc.add_transformation(self._default_transformation(), data=[1, 2, 3])
        mc.add_transformation(
            self._default_transformation(
                dict(name="transformation 2", type_transformation="DA")
            )
        )

        full_kwargs = dict(data=xr.DataArray([1, 2]))
        full_kwargs.update(kwargs)

        with pytest.raises(exception_type):
            mc.add_signal_data(**full_kwargs)

    # test_get_equipment ---------------------------------------------------------------

    @pytest.mark.parametrize(
        "signal_source, exception",
        [
            ("source", None),
            ("transformation_1", None),
            ("transformation_2", None),
            ("transformation_3", KeyError),
        ],
    )
    def test_get_equipment(self, signal_source, exception):
        """Test the `get_equipment` function and their exceptions.

        Parameters
        ----------
        signal_source :
            Corresponding function parameter
        exception :
            Expected exception

        """
        src_eq = MeasurementEquipment(
            "Source Eq", sources=[SignalSource(**self._default_source_kwargs())]
        )
        tf_eq = MeasurementEquipment(
            "Transformation_eq",
            transformations=[
                self._default_transformation({"name": "transformation_1"})
            ],
        )

        mc = MeasurementChain.from_equipment("Chain", src_eq)
        mc.add_transformation_from_equipment(tf_eq)
        mc.create_transformation("transformation_2", None, output_signal_unit="A")

        if exception is not None:
            with pytest.raises(exception):
                mc.get_equipment(signal_source=signal_source)
        else:
            mc.get_equipment(signal_source=signal_source)

    # test_get_signal_data -------------------------------------------------------------

    def test_get_signal_data(self):
        """Test the `get_signal_data` method.

        This test assures that the returned data is identical to the one passed
        to the
        measurement chain and that a key error is raised if the requested data is
        not
        present.

        """
        data = xr.DataArray([1, 2, 3])

        mc = MeasurementChain(**self._default_init_kwargs())
        mc.add_transformation(self._default_transformation(), data=data)
        mc.create_transformation("transformation_2", None, output_signal_unit="A")

        assert np.all(mc.get_signal_data("transformation") == data)

        # no data
        with pytest.raises(KeyError):
            mc.get_signal_data("transformation_2")

        # source not present
        with pytest.raises(KeyError):
            mc.get_signal_data("not found")

    # test_get_transformation ----------------------------------------------------------

    def test_get_transformation(self):
        """Test the `get_transformation` method."""
        mc = MeasurementChain(**self._default_init_kwargs())
        mc.add_transformation(self._default_transformation())

        transformation = mc.get_transformation("transformation")

        assert transformation == self._default_transformation()

    # test_get_transformation_exception ------------------------------------------------

    def test_get_transformation_exception(self):
        """Test that a `KeyError` is raised if the transformation does not exist."""
        mc = MeasurementChain(**self._default_init_kwargs())
        mc.add_transformation(self._default_transformation())

        with pytest.raises(KeyError):
            mc.get_transformation("not found")
Exemplo n.º 12
0
    def create_transformation(
        self,
        name: str,
        error: Error,
        output_signal_type: str = None,
        output_signal_unit: Union[str, Unit] = None,
        func: MathematicalExpression = None,
        data: TimeSeries = None,
        input_signal_source: str = None,
    ):
        """Create and add a transformation to the measurement chain.

        Parameters
        ----------
        name :
            Name of the transformation
        error :
            The error of the transformation
        output_signal_type :
            Type of the output signal (analog or digital)
        output_signal_unit :
            Unit of the output signal. If a function is provided, it is not necessary to
            provide this parameter since it can be derived from the function. In case
            both, the function and the unit are provided, an exception is raised if a
            mismatch is dimensionality is detected. This functionality may be used as
            extra safety layer. If no function is provided, a simple unit conversion
            function is created.
        func :
            A function describing the transformation. The provided value interacts
            with the 'output_signal_unit' parameter as described in its documentation
        data :
            A set of measurement data that is associated with the output signal of the
            transformation
        input_signal_source :
            The source of the signal that should be used as input of the transformation.
            If `None` is provided, the name of the last added transformation (or the
            source, if no transformation was added to the chain) is used.

        Examples
        --------
        >>> from weldx import Q_
        >>> from weldx.core import MathematicalExpression
        >>> from weldx.measurement import Error, MeasurementChain, SignalTransformation

        >>> mc = MeasurementChain.from_parameters(
        ...          name="Current measurement chain",
        ...          source_error=Error(deviation=Q_(0.5, "percent")),
        ...          source_name="Current sensor",
        ...          output_signal_type="analog",
        ...          output_signal_unit="V"
        ...      )

        Create a mathematical expression that accepts a quantity with volts as unit and
        that returns a dimentsionless quantity.

        >>> func = MathematicalExpression(expression="a*x + b",
        ...                               parameters=dict(a=Q_(5, "1/V"), b=Q_(1, ""))
        ...                               )

        Use the mathematical expression to create a new transformation which also
        performs a analog-digital conversion.

        >>> mc.create_transformation(name="Current AD conversion",
        ...                          error=Error(deviation=Q_(1,"percent")),
        ...                          func=func,
        ...                          output_signal_type="digital"
        ...                          )

        """
        if output_signal_unit is not None:
            output_signal_unit = U_(output_signal_unit)

        if output_signal_type is None and output_signal_unit is None and func is None:
            warn("The created transformation does not perform any transformations.")

        input_signal_source = self._check_and_get_node_name(input_signal_source)
        input_signal: Signal = self._graph.nodes[input_signal_source]["signal"]
        if output_signal_type is None:
            output_signal_type = input_signal.signal_type
        type_tf = f"{input_signal.signal_type[0]}{output_signal_type[0]}".upper()
        if output_signal_unit is not None:
            if func is not None:
                if not output_signal_unit.is_compatible_with(
                    self._determine_output_signal_unit(func, input_signal.units),
                ):
                    raise ValueError(
                        "The unit of the provided functions output has not the same "
                        f"dimensionality as {output_signal_unit}"
                    )
            else:
                unit_conversion = output_signal_unit / input_signal.units
                func = MathematicalExpression(
                    "a*x",
                    parameters={"a": Q_(1, unit_conversion)},
                )

        transformation = SignalTransformation(name, error, func, type_tf)
        self.add_transformation(transformation, data, input_signal_source)
Exemplo n.º 13
0
    def test_evaluation(expression, parameters, variables, exp_result):
        """Test the evaluation of the mathematical function."""
        expr = MathematicalExpression(expression=expression, parameters=parameters)

        assert np.all(expr.evaluate(**variables) == exp_result)
Exemplo n.º 14
0
    twincat_scope = Software(name="Beckhoff TwinCAT ScopeView",
                             version="3.4.3143")

    src_current = msm.Source(
        name="Current Sensor",
        output_signal=msm.Signal(signal_type="analog", unit="V", data=None),
        error=msm.Error(Q_(0.1, "percent")),
    )

    HKS_sensor.sources = []
    HKS_sensor.sources.append(src_current)

    from weldx.core import MathematicalExpression

    [a, x, b] = sympy.symbols("a x b")
    current_AD_func = MathematicalExpression(a * x + b)
    current_AD_func.set_parameter("a", Q_(32768.0 / 10.0, "1/V"))
    current_AD_func.set_parameter("b", Q_(0.0, ""))

    current_AD_transform = msm.DataTransformation(
        name="AD conversion current measurement",
        input_signal=src_current.output_signal,
        output_signal=msm.Signal("digital", "", data=None),
        error=msm.Error(Q_(0.01, "percent")),
        func=current_AD_func,
    )

    BH_ELM.data_transformations = []
    BH_ELM.data_transformations.append(current_AD_transform)

    # define current output calibration expression and transformation
Exemplo n.º 15
0
 def ma_def() -> MathematicalExpression:
     """Get a default instance for tests."""
     return MathematicalExpression(
         TestMathematicalExpression.expr_def,
         TestMathematicalExpression.params_def,
     )
Exemplo n.º 16
0
 def from_tree(cls, tree, ctx):
     obj = MathematicalExpression(sympy.sympify(tree["expression"]),
                                  parameters=tree["parameters"])
     return obj
Exemplo n.º 17
0
 def test_construction_exceptions(expression, parameters, exception_type, name):
     """Test the exceptions of the '__init__' method."""
     with pytest.raises(exception_type):
         MathematicalExpression(expression=expression, parameters=parameters)
Exemplo n.º 18
0
    def test_construction(self, expression, parameters, exp_vars):
        """Test the construction."""
        expr = MathematicalExpression(expression=expression, parameters=parameters)

        self._check_params_and_vars(expr, parameters, exp_vars)