Skip to content

Commit 7296617

Browse files
authored
Merge pull request #345 from andlaus/implement_remaining_compumethods
Implement remaining compumethods
2 parents 2261174 + 55e0200 commit 7296617

13 files changed

+809
-8
lines changed

.gitignore

+13
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,16 @@ __pycache__/
2525
env
2626
venv
2727
/odxtools/version.py
28+
29+
# editor and git backup files
30+
*~
31+
*.orig
32+
*.rej
33+
34+
# files usually stemming from deflated PDX archives
35+
*.odx-d
36+
*.odx-c
37+
*.odx-cs
38+
*.jar
39+
index.xml
40+
!examples/data/*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# SPDX-License-Identifier: MIT
2+
from dataclasses import dataclass
3+
from typing import List, Optional, cast
4+
from xml.etree import ElementTree
5+
6+
from ..exceptions import DecodeError, EncodeError, odxassert, odxraise
7+
from ..odxlink import OdxDocFragment
8+
from ..odxtypes import AtomicOdxType, DataType
9+
from ..progcode import ProgCode
10+
from ..utils import dataclass_fields_asdict
11+
from .compumethod import CompuCategory, CompuMethod
12+
13+
14+
@dataclass
15+
class CompuCodeCompuMethod(CompuMethod):
16+
"""A compu method specifies the tranfer functions using Java bytecode
17+
18+
For details, refer to ASAM specification MCD-2 D (ODX), section 7.3.6.6.9.
19+
"""
20+
21+
@property
22+
def internal_to_phys_code(self) -> Optional[ProgCode]:
23+
if self.compu_internal_to_phys is None:
24+
return None
25+
26+
return self.compu_internal_to_phys.prog_code
27+
28+
@property
29+
def phys_to_internal_code(self) -> Optional[ProgCode]:
30+
if self.compu_phys_to_internal is None:
31+
return None
32+
33+
return self.compu_phys_to_internal.prog_code
34+
35+
@staticmethod
36+
def compu_method_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment], *,
37+
internal_type: DataType,
38+
physical_type: DataType) -> "CompuCodeCompuMethod":
39+
cm = CompuMethod.compu_method_from_et(
40+
et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
41+
kwargs = dataclass_fields_asdict(cm)
42+
43+
return CompuCodeCompuMethod(**kwargs)
44+
45+
def __post_init__(self) -> None:
46+
odxassert(self.category == CompuCategory.COMPUCODE,
47+
"CompuCodeCompuMethod must exhibit COMPUCODE category")
48+
49+
def convert_internal_to_physical(self, internal_value: AtomicOdxType) -> AtomicOdxType:
50+
odxraise(r"CompuCodeCompuMethod cannot be executed", DecodeError)
51+
return cast(AtomicOdxType, None)
52+
53+
def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
54+
odxraise(r"CompuCodeCompuMethod cannot be executed", EncodeError)
55+
return cast(AtomicOdxType, None)
56+
57+
def is_valid_physical_value(self, physical_value: AtomicOdxType) -> bool:
58+
odxraise(r"CompuCodeCompuMethod cannot be executed", NotImplementedError)
59+
return False
60+
61+
def is_valid_internal_value(self, internal_value: AtomicOdxType) -> bool:
62+
odxraise(r"CompuCodeCompuMethod cannot be executed", NotImplementedError)
63+
return False

odxtools/compumethods/compuinternaltophys.py

+19-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
# SPDX-License-Identifier: MIT
22
from dataclasses import dataclass
3-
from typing import List, Optional
3+
from typing import Any, Dict, List, Optional
44
from xml.etree import ElementTree
55

6-
from ..odxlink import OdxDocFragment
6+
from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
77
from ..odxtypes import DataType
88
from ..progcode import ProgCode
9+
from ..snrefcontext import SnRefContext
910
from .compudefaultvalue import CompuDefaultValue
1011
from .compuscale import CompuScale
1112

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

3839
return CompuInternalToPhys(
3940
compu_scales=compu_scales, prog_code=prog_code, compu_default_value=compu_default_value)
41+
42+
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
43+
result = {}
44+
45+
if self.prog_code is not None:
46+
result.update(self.prog_code._build_odxlinks())
47+
48+
return result
49+
50+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
51+
if self.prog_code is not None:
52+
self.prog_code._resolve_odxlinks(odxlinks)
53+
54+
def _resolve_snrefs(self, context: SnRefContext) -> None:
55+
if self.prog_code is not None:
56+
self.prog_code._resolve_snrefs(context)

odxtools/compumethods/compumethod.py

+28-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
# SPDX-License-Identifier: MIT
22
from dataclasses import dataclass
33
from enum import Enum
4-
from typing import List, Optional
4+
from typing import Any, Dict, List, Optional
55
from xml.etree import ElementTree
66

77
from ..exceptions import odxraise
8-
from ..odxlink import OdxDocFragment
8+
from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
99
from ..odxtypes import AtomicOdxType, DataType
10+
from ..snrefcontext import SnRefContext
1011
from .compuinternaltophys import CompuInternalToPhys
1112
from .compuphystointernal import CompuPhysToInternal
1213

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

81+
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
82+
result = {}
83+
84+
if self.compu_internal_to_phys is not None:
85+
result.update(self.compu_internal_to_phys._build_odxlinks())
86+
87+
if self.compu_phys_to_internal is not None:
88+
result.update(self.compu_phys_to_internal._build_odxlinks())
89+
90+
return result
91+
92+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
93+
if self.compu_internal_to_phys is not None:
94+
self.compu_internal_to_phys._resolve_odxlinks(odxlinks)
95+
96+
if self.compu_phys_to_internal is not None:
97+
self.compu_phys_to_internal._resolve_odxlinks(odxlinks)
98+
99+
def _resolve_snrefs(self, context: SnRefContext) -> None:
100+
if self.compu_internal_to_phys is not None:
101+
self.compu_internal_to_phys._resolve_snrefs(context)
102+
103+
if self.compu_phys_to_internal is not None:
104+
self.compu_phys_to_internal._resolve_snrefs(context)
105+
80106
def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
81107
raise NotImplementedError()
82108

odxtools/compumethods/compuphystointernal.py

+19-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
# SPDX-License-Identifier: MIT
22
from dataclasses import dataclass
3-
from typing import List, Optional
3+
from typing import Any, Dict, List, Optional
44
from xml.etree import ElementTree
55

6-
from ..odxlink import OdxDocFragment
6+
from ..odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
77
from ..odxtypes import DataType
88
from ..progcode import ProgCode
9+
from ..snrefcontext import SnRefContext
910
from .compudefaultvalue import CompuDefaultValue
1011
from .compuscale import CompuScale
1112

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

3839
return CompuPhysToInternal(
3940
compu_scales=compu_scales, prog_code=prog_code, compu_default_value=compu_default_value)
41+
42+
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
43+
result = {}
44+
45+
if self.prog_code is not None:
46+
result.update(self.prog_code._build_odxlinks())
47+
48+
return result
49+
50+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
51+
if self.prog_code is not None:
52+
self.prog_code._resolve_odxlinks(odxlinks)
53+
54+
def _resolve_snrefs(self, context: SnRefContext) -> None:
55+
if self.prog_code is not None:
56+
self.prog_code._resolve_snrefs(context)

odxtools/compumethods/createanycompumethod.py

+12
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@
55
from ..exceptions import odxraise, odxrequire
66
from ..odxlink import OdxDocFragment
77
from ..odxtypes import DataType
8+
from .compucodecompumethod import CompuCodeCompuMethod
89
from .compumethod import CompuMethod
910
from .identicalcompumethod import IdenticalCompuMethod
1011
from .linearcompumethod import LinearCompuMethod
12+
from .ratfunccompumethod import RatFuncCompuMethod
1113
from .scalelinearcompumethod import ScaleLinearCompuMethod
14+
from .scaleratfunccompumethod import ScaleRatFuncCompuMethod
1215
from .tabintpcompumethod import TabIntpCompuMethod
1316
from .texttablecompumethod import TexttableCompuMethod
1417

@@ -27,9 +30,18 @@ def create_any_compu_method_from_et(et_element: ElementTree.Element,
2730
elif compu_category == "SCALE-LINEAR":
2831
return ScaleLinearCompuMethod.compu_method_from_et(
2932
et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
33+
elif compu_category == "RAT-FUNC":
34+
return RatFuncCompuMethod.compu_method_from_et(
35+
et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
36+
elif compu_category == "SCALE-RAT-FUNC":
37+
return ScaleRatFuncCompuMethod.compu_method_from_et(
38+
et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
3039
elif compu_category == "TEXTTABLE":
3140
return TexttableCompuMethod.compu_method_from_et(
3241
et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
42+
elif compu_category == "COMPUCODE":
43+
return CompuCodeCompuMethod.compu_method_from_et(
44+
et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
3345
elif compu_category == "TAB-INTP":
3446
return TabIntpCompuMethod.compu_method_from_et(
3547
et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)

odxtools/compumethods/linearcompumethod.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,13 @@ def __post_init__(self) -> None:
7575

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

8080
return self._segment.convert_internal_to_physical(internal_value)
8181

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

8686
return self._segment.convert_physical_to_internal(physical_value)
8787

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# SPDX-License-Identifier: MIT
2+
from dataclasses import dataclass
3+
from typing import List, Optional, cast
4+
from xml.etree import ElementTree
5+
6+
from ..exceptions import DecodeError, EncodeError, odxassert, odxraise
7+
from ..odxlink import OdxDocFragment
8+
from ..odxtypes import AtomicOdxType, DataType
9+
from ..utils import dataclass_fields_asdict
10+
from .compumethod import CompuCategory, CompuMethod
11+
from .ratfuncsegment import RatFuncSegment
12+
13+
14+
@dataclass
15+
class RatFuncCompuMethod(CompuMethod):
16+
"""A compu method using a rational function
17+
18+
i.e. internal values are converted to physical ones using the
19+
function `f(x) = (a0 + a1*x + a2*x^2 ...)/(b0 + b0*x^2 ...)` where `f(x)`
20+
is the physical value and `x` is the internal value. In contrast
21+
to `ScaleRatFuncCompuMethod`, this compu method only exhibits a
22+
single segment)
23+
24+
For details, refer to ASAM specification MCD-2 D (ODX), section 7.3.6.6.5.
25+
"""
26+
27+
@property
28+
def int_to_phys_segment(self) -> RatFuncSegment:
29+
return self._int_to_phys_segment
30+
31+
@property
32+
def phys_to_int_segment(self) -> Optional[RatFuncSegment]:
33+
return self._phys_to_int_segment
34+
35+
@staticmethod
36+
def compu_method_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment], *,
37+
internal_type: DataType,
38+
physical_type: DataType) -> "RatFuncCompuMethod":
39+
cm = CompuMethod.compu_method_from_et(
40+
et_element, doc_frags, internal_type=internal_type, physical_type=physical_type)
41+
kwargs = dataclass_fields_asdict(cm)
42+
43+
return RatFuncCompuMethod(**kwargs)
44+
45+
def __post_init__(self) -> None:
46+
odxassert(self.category == CompuCategory.RAT_FUNC,
47+
"RatFuncCompuMethod must exhibit RAT-FUNC category")
48+
49+
odxassert(self.physical_type in [
50+
DataType.A_FLOAT32,
51+
DataType.A_FLOAT64,
52+
DataType.A_INT32,
53+
DataType.A_UINT32,
54+
])
55+
odxassert(self.internal_type in [
56+
DataType.A_FLOAT32,
57+
DataType.A_FLOAT64,
58+
DataType.A_INT32,
59+
DataType.A_UINT32,
60+
])
61+
62+
if self.compu_internal_to_phys is None:
63+
odxraise("RAT-FUNC compu methods require COMPU-INTERNAL-TO-PHYS")
64+
return
65+
66+
int_to_phys_scales = self.compu_internal_to_phys.compu_scales
67+
if len(int_to_phys_scales) != 1:
68+
odxraise("RAT-FUNC compu methods expect exactly one compu scale within "
69+
"COMPU-INTERNAL-TO-PHYS")
70+
return cast(None, RatFuncCompuMethod)
71+
72+
self._int_to_phys_segment = RatFuncSegment.from_compu_scale(
73+
int_to_phys_scales[0], value_type=self.physical_type)
74+
75+
self._phys_to_int_segment = None
76+
if self.compu_phys_to_internal is not None:
77+
phys_to_int_scales = self.compu_phys_to_internal.compu_scales
78+
if len(phys_to_int_scales) != 1:
79+
odxraise("RAT-FUNC compu methods expect exactly one compu scale within "
80+
"COMPU-PHYS-TO-INTERNAL")
81+
return cast(None, RatFuncCompuMethod)
82+
83+
self._phys_to_int_segment = RatFuncSegment.from_compu_scale(
84+
phys_to_int_scales[0], value_type=self.internal_type)
85+
86+
def convert_internal_to_physical(self, internal_value: AtomicOdxType) -> AtomicOdxType:
87+
if not self._int_to_phys_segment.applies(internal_value):
88+
odxraise(f"Cannot decode internal value {internal_value!r}", DecodeError)
89+
return cast(AtomicOdxType, None)
90+
91+
return self._int_to_phys_segment.convert(internal_value)
92+
93+
def convert_physical_to_internal(self, physical_value: AtomicOdxType) -> AtomicOdxType:
94+
if self._phys_to_int_segment is None or not self._phys_to_int_segment.applies(
95+
physical_value):
96+
odxraise(f"Cannot encode physical value {physical_value!r}", EncodeError)
97+
return cast(AtomicOdxType, None)
98+
99+
return self._phys_to_int_segment.convert(physical_value)
100+
101+
def is_valid_physical_value(self, physical_value: AtomicOdxType) -> bool:
102+
return self._phys_to_int_segment is not None and self._phys_to_int_segment.applies(
103+
physical_value)
104+
105+
def is_valid_internal_value(self, internal_value: AtomicOdxType) -> bool:
106+
return self._int_to_phys_segment.applies(internal_value)

0 commit comments

Comments
 (0)