Skip to content

Commit

Permalink
Merge pull request #345 from andlaus/implement_remaining_compumethods
Browse files Browse the repository at this point in the history
Implement remaining compumethods
  • Loading branch information
andlaus authored Sep 23, 2024
2 parents 2261174 + 55e0200 commit 7296617
Show file tree
Hide file tree
Showing 13 changed files with 809 additions and 8 deletions.
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,16 @@ __pycache__/
env
venv
/odxtools/version.py

# editor and git backup files
*~
*.orig
*.rej

# files usually stemming from deflated PDX archives
*.odx-d
*.odx-c
*.odx-cs
*.jar
index.xml
!examples/data/*
63 changes: 63 additions & 0 deletions odxtools/compumethods/compucodecompumethod.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# SPDX-License-Identifier: MIT
from dataclasses import dataclass
from typing import List, Optional, cast
from xml.etree import ElementTree

from ..exceptions import DecodeError, EncodeError, odxassert, odxraise
from ..odxlink import OdxDocFragment
from ..odxtypes import AtomicOdxType, DataType
from ..progcode import ProgCode
from ..utils import dataclass_fields_asdict
from .compumethod import CompuCategory, CompuMethod


@dataclass
class CompuCodeCompuMethod(CompuMethod):
"""A compu method specifies the tranfer functions using Java bytecode
For details, refer to ASAM specification MCD-2 D (ODX), section 7.3.6.6.9.
"""

@property
def internal_to_phys_code(self) -> Optional[ProgCode]:
if self.compu_internal_to_phys is None:
return None

return self.compu_internal_to_phys.prog_code

@property
def phys_to_internal_code(self) -> Optional[ProgCode]:
if self.compu_phys_to_internal is None:
return None

return self.compu_phys_to_internal.prog_code

@staticmethod
def compu_method_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment], *,
internal_type: DataType,
physical_type: DataType) -> "CompuCodeCompuMethod":
cm = CompuMethod.compu_method_from_et(
et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
kwargs = dataclass_fields_asdict(cm)

return CompuCodeCompuMethod(**kwargs)

def __post_init__(self) -> None:
odxassert(self.category == CompuCategory.COMPUCODE,
"CompuCodeCompuMethod must exhibit COMPUCODE category")

def convert_internal_to_physical(self, internal_value: AtomicOdxType) -> AtomicOdxType:
odxraise(r"CompuCodeCompuMethod cannot be executed", DecodeError)
return cast(AtomicOdxType, None)

def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
odxraise(r"CompuCodeCompuMethod cannot be executed", EncodeError)
return cast(AtomicOdxType, None)

def is_valid_physical_value(self, physical_value: AtomicOdxType) -> bool:
odxraise(r"CompuCodeCompuMethod cannot be executed", NotImplementedError)
return False

def is_valid_internal_value(self, internal_value: AtomicOdxType) -> bool:
odxraise(r"CompuCodeCompuMethod cannot be executed", NotImplementedError)
return False
21 changes: 19 additions & 2 deletions odxtools/compumethods/compuinternaltophys.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# SPDX-License-Identifier: MIT
from dataclasses import dataclass
from typing import List, Optional
from typing import Any, Dict, List, Optional
from xml.etree import ElementTree

from ..odxlink import OdxDocFragment
from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
from ..odxtypes import DataType
from ..progcode import ProgCode
from ..snrefcontext import SnRefContext
from .compudefaultvalue import CompuDefaultValue
from .compuscale import CompuScale

Expand Down Expand Up @@ -37,3 +38,19 @@ def compu_internal_to_phys_from_et(et_element: ElementTree.Element,

return CompuInternalToPhys(
compu_scales=compu_scales, prog_code=prog_code, compu_default_value=compu_default_value)

def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
result = {}

if self.prog_code is not None:
result.update(self.prog_code._build_odxlinks())

return result

def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
if self.prog_code is not None:
self.prog_code._resolve_odxlinks(odxlinks)

def _resolve_snrefs(self, context: SnRefContext) -> None:
if self.prog_code is not None:
self.prog_code._resolve_snrefs(context)
30 changes: 28 additions & 2 deletions odxtools/compumethods/compumethod.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# SPDX-License-Identifier: MIT
from dataclasses import dataclass
from enum import Enum
from typing import List, Optional
from typing import Any, Dict, List, Optional
from xml.etree import ElementTree

from ..exceptions import odxraise
from ..odxlink import OdxDocFragment
from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
from ..odxtypes import AtomicOdxType, DataType
from ..snrefcontext import SnRefContext
from .compuinternaltophys import CompuInternalToPhys
from .compuphystointernal import CompuPhysToInternal

Expand Down Expand Up @@ -77,6 +78,31 @@ def compu_method_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDoc
physical_type=physical_type,
internal_type=internal_type)

def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
result = {}

if self.compu_internal_to_phys is not None:
result.update(self.compu_internal_to_phys._build_odxlinks())

if self.compu_phys_to_internal is not None:
result.update(self.compu_phys_to_internal._build_odxlinks())

return result

def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
if self.compu_internal_to_phys is not None:
self.compu_internal_to_phys._resolve_odxlinks(odxlinks)

if self.compu_phys_to_internal is not None:
self.compu_phys_to_internal._resolve_odxlinks(odxlinks)

def _resolve_snrefs(self, context: SnRefContext) -> None:
if self.compu_internal_to_phys is not None:
self.compu_internal_to_phys._resolve_snrefs(context)

if self.compu_phys_to_internal is not None:
self.compu_phys_to_internal._resolve_snrefs(context)

def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
raise NotImplementedError()

Expand Down
21 changes: 19 additions & 2 deletions odxtools/compumethods/compuphystointernal.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# SPDX-License-Identifier: MIT
from dataclasses import dataclass
from typing import List, Optional
from typing import Any, Dict, List, Optional
from xml.etree import ElementTree

from ..odxlink import OdxDocFragment
from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
from ..odxtypes import DataType
from ..progcode import ProgCode
from ..snrefcontext import SnRefContext
from .compudefaultvalue import CompuDefaultValue
from .compuscale import CompuScale

Expand Down Expand Up @@ -37,3 +38,19 @@ def compu_phys_to_internal_from_et(et_element: ElementTree.Element,

return CompuPhysToInternal(
compu_scales=compu_scales, prog_code=prog_code, compu_default_value=compu_default_value)

def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
result = {}

if self.prog_code is not None:
result.update(self.prog_code._build_odxlinks())

return result

def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
if self.prog_code is not None:
self.prog_code._resolve_odxlinks(odxlinks)

def _resolve_snrefs(self, context: SnRefContext) -> None:
if self.prog_code is not None:
self.prog_code._resolve_snrefs(context)
12 changes: 12 additions & 0 deletions odxtools/compumethods/createanycompumethod.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@
from ..exceptions import odxraise, odxrequire
from ..odxlink import OdxDocFragment
from ..odxtypes import DataType
from .compucodecompumethod import CompuCodeCompuMethod
from .compumethod import CompuMethod
from .identicalcompumethod import IdenticalCompuMethod
from .linearcompumethod import LinearCompuMethod
from .ratfunccompumethod import RatFuncCompuMethod
from .scalelinearcompumethod import ScaleLinearCompuMethod
from .scaleratfunccompumethod import ScaleRatFuncCompuMethod
from .tabintpcompumethod import TabIntpCompuMethod
from .texttablecompumethod import TexttableCompuMethod

Expand All @@ -27,9 +30,18 @@ def create_any_compu_method_from_et(et_element: ElementTree.Element,
elif compu_category == "SCALE-LINEAR":
return ScaleLinearCompuMethod.compu_method_from_et(
et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
elif compu_category == "RAT-FUNC":
return RatFuncCompuMethod.compu_method_from_et(
et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
elif compu_category == "SCALE-RAT-FUNC":
return ScaleRatFuncCompuMethod.compu_method_from_et(
et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
elif compu_category == "TEXTTABLE":
return TexttableCompuMethod.compu_method_from_et(
et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
elif compu_category == "COMPUCODE":
return CompuCodeCompuMethod.compu_method_from_et(
et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
elif compu_category == "TAB-INTP":
return TabIntpCompuMethod.compu_method_from_et(
et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
Expand Down
4 changes: 2 additions & 2 deletions odxtools/compumethods/linearcompumethod.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,13 @@ def __post_init__(self) -> None:

def convert_internal_to_physical(self, internal_value: AtomicOdxType) -> AtomicOdxType:
if not self._segment.internal_applies(internal_value):
odxraise(r"Cannot decode internal value {internal_value}", DecodeError)
odxraise(f"Cannot decode internal value {internal_value!r}", DecodeError)

return self._segment.convert_internal_to_physical(internal_value)

def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
if not self._segment.physical_applies(physical_value):
odxraise(r"Cannot decode physical value {physical_value}", EncodeError)
odxraise(f"Cannot decode physical value {physical_value!r}", EncodeError)

return self._segment.convert_physical_to_internal(physical_value)

Expand Down
106 changes: 106 additions & 0 deletions odxtools/compumethods/ratfunccompumethod.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# SPDX-License-Identifier: MIT
from dataclasses import dataclass
from typing import List, Optional, cast
from xml.etree import ElementTree

from ..exceptions import DecodeError, EncodeError, odxassert, odxraise
from ..odxlink import OdxDocFragment
from ..odxtypes import AtomicOdxType, DataType
from ..utils import dataclass_fields_asdict
from .compumethod import CompuCategory, CompuMethod
from .ratfuncsegment import RatFuncSegment


@dataclass
class RatFuncCompuMethod(CompuMethod):
"""A compu method using a rational function
i.e. internal values are converted to physical ones using the
function `f(x) = (a0 + a1*x + a2*x^2 ...)/(b0 + b0*x^2 ...)` where `f(x)`
is the physical value and `x` is the internal value. In contrast
to `ScaleRatFuncCompuMethod`, this compu method only exhibits a
single segment)
For details, refer to ASAM specification MCD-2 D (ODX), section 7.3.6.6.5.
"""

@property
def int_to_phys_segment(self) -> RatFuncSegment:
return self._int_to_phys_segment

@property
def phys_to_int_segment(self) -> Optional[RatFuncSegment]:
return self._phys_to_int_segment

@staticmethod
def compu_method_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment], *,
internal_type: DataType,
physical_type: DataType) -> "RatFuncCompuMethod":
cm = CompuMethod.compu_method_from_et(
et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
kwargs = dataclass_fields_asdict(cm)

return RatFuncCompuMethod(**kwargs)

def __post_init__(self) -> None:
odxassert(self.category == CompuCategory.RAT_FUNC,
"RatFuncCompuMethod must exhibit RAT-FUNC category")

odxassert(self.physical_type in [
DataType.A_FLOAT32,
DataType.A_FLOAT64,
DataType.A_INT32,
DataType.A_UINT32,
])
odxassert(self.internal_type in [
DataType.A_FLOAT32,
DataType.A_FLOAT64,
DataType.A_INT32,
DataType.A_UINT32,
])

if self.compu_internal_to_phys is None:
odxraise("RAT-FUNC compu methods require COMPU-INTERNAL-TO-PHYS")
return

int_to_phys_scales = self.compu_internal_to_phys.compu_scales
if len(int_to_phys_scales) != 1:
odxraise("RAT-FUNC compu methods expect exactly one compu scale within "
"COMPU-INTERNAL-TO-PHYS")
return cast(None, RatFuncCompuMethod)

self._int_to_phys_segment = RatFuncSegment.from_compu_scale(
int_to_phys_scales[0], value_type=self.physical_type)

self._phys_to_int_segment = None
if self.compu_phys_to_internal is not None:
phys_to_int_scales = self.compu_phys_to_internal.compu_scales
if len(phys_to_int_scales) != 1:
odxraise("RAT-FUNC compu methods expect exactly one compu scale within "
"COMPU-PHYS-TO-INTERNAL")
return cast(None, RatFuncCompuMethod)

self._phys_to_int_segment = RatFuncSegment.from_compu_scale(
phys_to_int_scales[0], value_type=self.internal_type)

def convert_internal_to_physical(self, internal_value: AtomicOdxType) -> AtomicOdxType:
if not self._int_to_phys_segment.applies(internal_value):
odxraise(f"Cannot decode internal value {internal_value!r}", DecodeError)
return cast(AtomicOdxType, None)

return self._int_to_phys_segment.convert(internal_value)

def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
if self._phys_to_int_segment is None or not self._phys_to_int_segment.applies(
physical_value):
odxraise(f"Cannot encode physical value {physical_value!r}", EncodeError)
return cast(AtomicOdxType, None)

return self._phys_to_int_segment.convert(physical_value)

def is_valid_physical_value(self, physical_value: AtomicOdxType) -> bool:
return self._phys_to_int_segment is not None and self._phys_to_int_segment.applies(
physical_value)

def is_valid_internal_value(self, internal_value: AtomicOdxType) -> bool:
return self._int_to_phys_segment.applies(internal_value)
Loading

0 comments on commit 7296617

Please sign in to comment.