Skip to content

Commit 9ff9497

Browse files
committed
more validations for UmbIndex
1 parent 39d0a93 commit 9ff9497

14 files changed

Lines changed: 291 additions & 41 deletions

tests/binary/test_sized_type.py

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,10 @@ class TestSizedTypeValidation:
6767

6868
def test_positive_size_required(self):
6969
"""Test that size must be positive."""
70-
with pytest.raises(AssertionError):
70+
with pytest.raises(ValueError):
7171
SizedType(NumericPrimitiveType.UINT, 0)
7272

73-
with pytest.raises(AssertionError):
73+
with pytest.raises(ValueError):
7474
SizedType(NumericPrimitiveType.UINT, -1)
7575

7676
def test_string_must_be_64_bits(self):
@@ -147,3 +147,50 @@ def test_double_default_size(self):
147147
"""Test default size for DOUBLE."""
148148
sized_type = SizedType.for_type(NumericPrimitiveType.DOUBLE)
149149
assert sized_type.size_bits == 64
150+
151+
152+
class TestSizedTypeNumericProperties:
153+
"""Test SizedType numeric classification properties."""
154+
155+
def test_is_numeric_for_numeric_types(self):
156+
"""Test is_numeric returns True for numeric types."""
157+
assert SizedType(NumericPrimitiveType.INT, 32).is_numeric
158+
assert SizedType(NumericPrimitiveType.UINT, 32).is_numeric
159+
assert SizedType(NumericPrimitiveType.DOUBLE, 64).is_numeric
160+
assert SizedType(NumericPrimitiveType.RATIONAL, 128).is_numeric
161+
162+
def test_is_numeric_for_primitive_types(self):
163+
"""Test is_numeric returns False for primitive types."""
164+
assert not SizedType(PrimitiveType.BOOL, 1).is_numeric
165+
assert not SizedType(PrimitiveType.STRING, 64).is_numeric
166+
167+
def test_is_discrete_for_discrete_types(self):
168+
"""Test is_discrete returns True for discrete numeric types."""
169+
assert SizedType(NumericPrimitiveType.INT, 32).is_discrete
170+
assert SizedType(NumericPrimitiveType.UINT, 32).is_discrete
171+
assert SizedType(NumericPrimitiveType.INT, 64).is_discrete
172+
173+
def test_is_discrete_for_continuous_types(self):
174+
"""Test is_discrete returns False for continuous numeric types."""
175+
assert not SizedType(NumericPrimitiveType.DOUBLE, 64).is_discrete
176+
assert not SizedType(NumericPrimitiveType.RATIONAL, 128).is_discrete
177+
178+
def test_is_discrete_for_primitive_types(self):
179+
"""Test is_discrete returns False for primitive types."""
180+
assert not SizedType(PrimitiveType.BOOL, 1).is_discrete
181+
assert not SizedType(PrimitiveType.STRING, 64).is_discrete
182+
183+
def test_is_continuous_for_continuous_types(self):
184+
"""Test is_continuous returns True for continuous numeric types."""
185+
assert SizedType(NumericPrimitiveType.DOUBLE, 64).is_continuous
186+
assert SizedType(NumericPrimitiveType.RATIONAL, 128).is_continuous
187+
188+
def test_is_continuous_for_discrete_types(self):
189+
"""Test is_continuous returns False for discrete numeric types."""
190+
assert not SizedType(NumericPrimitiveType.INT, 32).is_continuous
191+
assert not SizedType(NumericPrimitiveType.UINT, 32).is_continuous
192+
193+
def test_is_continuous_for_primitive_types(self):
194+
"""Test is_continuous returns False for primitive types."""
195+
assert not SizedType(PrimitiveType.BOOL, 1).is_continuous
196+
assert not SizedType(PrimitiveType.STRING, 64).is_continuous

tests/datatypes/test_interval.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,44 @@ def test_interval_equal_bounds(self):
7878
assert interval.left == 5
7979
assert interval.right == 5
8080

81+
82+
class TestIntervalTypeClassification:
83+
"""Test IntervalType classification properties."""
84+
85+
def test_is_discrete_for_int_interval(self):
86+
"""Test that int-interval is discrete."""
87+
it = IntervalType(NumericPrimitiveType.INT)
88+
assert it.is_discrete
89+
90+
def test_is_discrete_for_uint_interval(self):
91+
"""Test that uint-interval is discrete."""
92+
it = IntervalType(NumericPrimitiveType.UINT)
93+
assert it.is_discrete
94+
95+
def test_is_discrete_for_continuous_intervals(self):
96+
"""Test that double-interval and rational-interval are not discrete."""
97+
double_it = IntervalType(NumericPrimitiveType.DOUBLE)
98+
rational_it = IntervalType(NumericPrimitiveType.RATIONAL)
99+
assert not double_it.is_discrete
100+
assert not rational_it.is_discrete
101+
102+
def test_is_continuous_for_double_interval(self):
103+
"""Test that double-interval is continuous."""
104+
it = IntervalType(NumericPrimitiveType.DOUBLE)
105+
assert it.is_continuous
106+
107+
def test_is_continuous_for_rational_interval(self):
108+
"""Test that rational-interval is continuous."""
109+
it = IntervalType(NumericPrimitiveType.RATIONAL)
110+
assert it.is_continuous
111+
112+
def test_is_continuous_for_discrete_intervals(self):
113+
"""Test that int-interval and uint-interval are not continuous."""
114+
int_it = IntervalType(NumericPrimitiveType.INT)
115+
uint_it = IntervalType(NumericPrimitiveType.UINT)
116+
assert not int_it.is_continuous
117+
assert not uint_it.is_continuous
118+
81119
def test_interval_str_repr(self):
82120
"""Test Interval string representation."""
83121
interval = Interval(1, 5)

tests/datatypes/test_numeric_primitive.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,36 @@ def test_bool_not_numeric_primitive(self):
5252
assert numeric_primitive_type_of(True) == NumericPrimitiveType.INT
5353

5454

55+
class TestNumericPrimitiveTypeClassification:
56+
"""Test numeric type classification properties."""
57+
58+
def test_is_discrete_for_int(self):
59+
"""Test that INT is discrete."""
60+
assert NumericPrimitiveType.INT.is_discrete
61+
62+
def test_is_discrete_for_uint(self):
63+
"""Test that UINT is discrete."""
64+
assert NumericPrimitiveType.UINT.is_discrete
65+
66+
def test_is_discrete_for_continuous_types(self):
67+
"""Test that DOUBLE and RATIONAL are not discrete."""
68+
assert not NumericPrimitiveType.DOUBLE.is_discrete
69+
assert not NumericPrimitiveType.RATIONAL.is_discrete
70+
71+
def test_is_continuous_for_double(self):
72+
"""Test that DOUBLE is continuous."""
73+
assert NumericPrimitiveType.DOUBLE.is_continuous
74+
75+
def test_is_continuous_for_rational(self):
76+
"""Test that RATIONAL is continuous."""
77+
assert NumericPrimitiveType.RATIONAL.is_continuous
78+
79+
def test_is_continuous_for_discrete_types(self):
80+
"""Test that INT and UINT are not continuous."""
81+
assert not NumericPrimitiveType.INT.is_continuous
82+
assert not NumericPrimitiveType.UINT.is_continuous
83+
84+
5585
class TestNumericPrimitivePromotionType:
5686
"""Test numeric_primitive_promotion_type function."""
5787

umbi/__main__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import click
44

55
import umbi
6+
import umbi.umb
67

78
logger = logging.getLogger(__name__)
89

@@ -36,9 +37,9 @@ def main(log_level, import_umb, import_ats, export):
3637
raise ValueError("--export specified, but nothing to export")
3738
if umb is not None and ats is not None:
3839
raise ValueError("cannot specify both --import-umb and --import-ats when using --export")
39-
if import_umb is not None:
40+
if umb is not None:
4041
umbi.umb.write(umb, export)
41-
if import_ats is not None:
42+
if ats is not None:
4243
umbi.ats.write(ats, export)
4344

4445

umbi/binary/sized_type.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
IntervalType,
1010
NumericPrimitive,
1111
NumericPrimitiveType,
12+
NumericType,
1213
Primitive,
1314
PrimitiveType,
1415
Scalar,
@@ -38,7 +39,8 @@ def __post_init__(self):
3839
self.validate()
3940

4041
def validate(self):
41-
assert self.size_bits > 0, "size must be positive"
42+
if self.size_bits <= 0:
43+
raise ValueError("size must be positive")
4244
validate_scalar_type_size(self.type, self.size_bits)
4345

4446
@property
@@ -49,6 +51,18 @@ def is_byte_aligned(self) -> bool:
4951
def size_bytes(self) -> int:
5052
return (self.size_bits + 7) // 8 # ceiling division
5153

54+
@property
55+
def is_numeric(self) -> bool:
56+
return isinstance(self.type, NumericType)
57+
58+
@property
59+
def is_discrete(self) -> bool:
60+
return self.is_numeric and self.type.is_discrete # type: ignore[union-attr]
61+
62+
@property
63+
def is_continuous(self) -> bool:
64+
return self.is_numeric and self.type.is_continuous # type: ignore[union-attr]
65+
5266

5367
# Default bitsizes for types
5468

umbi/datatypes/interval.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ def from_string(cls, s: str) -> "IntervalType":
3232
base_type = NumericPrimitiveType(base_type_string)
3333
return cls(base_type)
3434

35+
@property
36+
def is_discrete(self) -> bool:
37+
"""Check if the interval type is discrete."""
38+
return self.base_type.is_discrete
39+
40+
@property
41+
def is_continuous(self) -> bool:
42+
"""Check if the interval type is continuous."""
43+
return self.base_type.is_continuous
44+
3545

3646
@dataclass(frozen=False)
3747
class Interval:

umbi/datatypes/numeric_primitive.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@ class NumericPrimitiveType(str, enum.Enum):
1414
DOUBLE = "double"
1515
RATIONAL = "rational"
1616

17+
@property
18+
def is_discrete(self) -> bool:
19+
"""Check if the type is discrete."""
20+
return self in {NumericPrimitiveType.INT, NumericPrimitiveType.UINT}
21+
22+
@property
23+
def is_continuous(self) -> bool:
24+
"""Check if the type is continuous."""
25+
return self in {NumericPrimitiveType.DOUBLE, NumericPrimitiveType.RATIONAL}
26+
1727

1828
#: Primitive numeric values (integers, floats, or rational numbers).
1929
NumericPrimitive: TypeAlias = int | float | Fraction

umbi/umb/explicit_umb.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,12 @@
1111
class ExplicitUmb:
1212
"""Explicit representation of a umbfile using Python collections."""
1313

14+
#: UMB index file
1415
index: UmbIndex = field(default_factory=UmbIndex)
1516

17+
#: for each state, whether it is an initial state
1618
state_is_initial: list[bool] = field(default_factory=list)
19+
#: for each state, the choices available
1720
state_to_choices: list[int] | None = None
1821
state_to_player: list[int] | None = None
1922

umbi/umb/index/__init__.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
"""
2-
umbi.umb.index: handling the .umb index json file.
3-
"""
1+
"""umbi.umb.index: in-memory representation of the .umb index json file."""
42

53
from .annotations import AnnotationDescription
6-
from .file_data import FileData, umbi_file_data
4+
from .file_data import FileData, umbi_file_data, unix_timestamp
75
from .model_data import ModelData
86
from .transition_system import TransitionSystem
97
from .umb_index import UmbIndex
@@ -12,6 +10,7 @@
1210
__all__ = [
1311
"ModelData",
1412
"FileData",
13+
"unix_timestamp",
1514
"umbi_file_data",
1615
"AnnotationDescription",
1716
"TransitionSystem",

umbi/umb/index/file_data.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
"""
2-
File data schemas and classes.
3-
"""
1+
"""File data schemas and classes."""
42

53
import time
64
from dataclasses import dataclass
@@ -32,23 +30,36 @@ def schema_class(cls) -> type:
3230

3331
@dataclass
3432
class FileData(JsonSchemaResult):
35-
"""File data class."""
33+
"""File data class.
3634
35+
Information about how this file was created (for reproducibility purposes).
36+
"""
37+
38+
#: the tool used to create this model (strongly encouraged)
3739
tool: str | None = None
40+
#: the tool's version
3841
tool_version: str | None = None
42+
#: export date (Unix timestamp)
3943
creation_date: int | None = None
44+
#: the tool parameters (e.g. string or list of command-line arguments) used
4045
parameters: umbi.datatypes.JsonLike | None = None
4146

4247
@classmethod
4348
def class_schema(cls) -> type:
4449
return FileDataSchema
4550

4651

52+
def unix_timestamp() -> int:
53+
"""Get the current Unix timestamp."""
54+
return int(time.time())
55+
56+
4757
def umbi_file_data() -> FileData:
4858
"""Generate file data for use in umbfiles created by umbi."""
4959
return FileData(
5060
tool=umbi.version.__toolname__,
5161
tool_version=umbi.version.__version__,
52-
creation_date=int(time.time()),
62+
creation_date=unix_timestamp(),
63+
# TODO include parameters
5364
# parameters=parameters,
5465
)

0 commit comments

Comments
 (0)