Skip to content

Commit

Permalink
scale linear compu methods: improve detection of invertibility
Browse files Browse the repository at this point in the history
this makes the code conform to section 7.3.6.6.4 of the spec. Note
that DOPs using non-invertible SCALE-LINEAR compu methods currently
cannot be encoded.

Signed-off-by: Andreas Lauser <[email protected]>
Signed-off-by: Katja Köhler <[email protected]>
  • Loading branch information
andlaus committed May 23, 2024
1 parent eea043c commit 7f7c47a
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 1 deletion.
16 changes: 16 additions & 0 deletions odxtools/compumethods/linearsegment.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ class LinearSegment:
internal_lower_limit: Optional[Limit]
internal_upper_limit: Optional[Limit]

inverse_value: Union[int, float] # value used as inverse if factor is 0

internal_type: DataType
physical_type: DataType

Expand All @@ -42,6 +44,15 @@ def from_compu_scale(scale: CompuScale, *, internal_type: DataType,
if len(coeffs.denominators) > 0:
denominator = coeffs.denominators[0]

inverse_value: Union[int, float] = 0
if scale.compu_inverse_value is not None:
if abs(factor) < 1e-10:
odxraise(f"COMPU-INVERSE-VALUE for non-zero slope ({factor}) defined")
x = odxrequire(scale.compu_inverse_value).value
if not isinstance(x, (int, float)):
odxraise(f"Non-numeric COMPU-INVERSE-VALUE specified ({x!r})")
inverse_value = x

internal_lower_limit = scale.lower_limit
internal_upper_limit = scale.upper_limit

Expand All @@ -51,6 +62,7 @@ def from_compu_scale(scale: CompuScale, *, internal_type: DataType,
denominator=denominator,
internal_lower_limit=internal_lower_limit,
internal_upper_limit=internal_upper_limit,
inverse_value=inverse_value,
internal_type=internal_type,
physical_type=physical_type)

Expand Down Expand Up @@ -85,6 +97,10 @@ def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> Union[f
odxraise(f"Physical values of linear compumethods must "
f"either be int or float (is: {type(physical_value).__name__})")

if abs(self.factor) < 1e-10:
# "If factor = 0 then COMPU-INVERSE-VALUE shall be specified.
return self.inverse_value

result = (physical_value * self.denominator - self.offset) / self.factor

if self.internal_type in [
Expand Down
45 changes: 44 additions & 1 deletion odxtools/compumethods/scalelinearcompumethod.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from typing import List, Union, cast
from xml.etree import ElementTree

from ..exceptions import DecodeError, EncodeError, odxassert, odxraise
from ..exceptions import DecodeError, EncodeError, odxassert, odxraise, odxrequire
from ..odxlink import OdxDocFragment
from ..odxtypes import AtomicOdxType, DataType
from ..utils import dataclass_fields_asdict
Expand Down Expand Up @@ -62,7 +62,49 @@ def __post_init__(self) -> None:
LinearSegment.from_compu_scale(
scale, internal_type=self.internal_type, physical_type=self.physical_type))

# find out if the transfer function is invertible (i.e. if it
# can be encoded by normal means). section 7.3.6.6.4 of the
# ODX specification states that the condition for
# invertibility is that adjacent COMPU-SCALES exhibit the same
# values on their common boundaries and that the slope in all
# intervals exhibit the same sign (or are 0). For segments
# with a slope of zero, COMPU-INVERSE-VALUE shall be used.
self._is_invertible = True
ref_factor = self._segments[0].factor
for i in range(0, len(self._segments) - 1):
s0 = self.segments[i]
s1 = self.segments[i + 1]

if ref_factor * s1.factor < 0:
self._is_invertible = False
break
if s1.factor != 0:
ref_factor = s1.factor

if s0.internal_upper_limit is None or s1.internal_lower_limit is None:
self._is_invertible = False
break

if (x := odxrequire(s0.internal_upper_limit).value) != odxrequire(
s1.internal_lower_limit).value:
self._is_invertible = False
break

if not isinstance(x, (int, float)):
odxraise("Linear segments must use int or float for all quantities")

y0 = s0.convert_internal_to_physical(x)
y1 = s1.convert_internal_to_physical(x)
if abs(y0 - y1) < 1e-10:
self._is_invertible = False
break

def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> Union[float, int]:
if not self._is_invertible:
odxraise(
f"Trying to encode value {physical_value!r} using a non-invertible "
f"SCALE-LINEAR transfer function", EncodeError)

applicable_segments = [
seg for seg in self._segments if seg.physical_applies(physical_value)
]
Expand All @@ -73,6 +115,7 @@ def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> Union[f
odxraise(r"Multiple applicable segments for value {physical_value} found", EncodeError)

seg = applicable_segments[0]

return seg.convert_physical_to_internal(physical_value)

def convert_internal_to_physical(self, internal_value: AtomicOdxType) -> Union[float, int]:
Expand Down

0 comments on commit 7f7c47a

Please sign in to comment.