Skip to content

Commit e9eeed7

Browse files
authored
PLA-13739 Update Cake demo to not have dimensionless absolute quantities (#203)
1 parent 4ea751d commit e9eeed7

File tree

4 files changed

+103
-38
lines changed

4 files changed

+103
-38
lines changed

gemd/demo/cake.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -647,7 +647,7 @@ def _make_material(*, material_name, template, process_tmpl_name, process_kwargs
647647
material=eggs,
648648
labels=['wet'],
649649
process=wetmix.process,
650-
absolute_quantity=NominalReal(nominal=4, units='')
650+
absolute_quantity=NominalReal(nominal=4, units='count')
651651
)
652652

653653
vanilla = _make_material(

gemd/entity/object/has_quantities.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
"""For entities that hve quantities."""
2+
from sys import float_info
3+
24
from gemd.entity.bounds.real_bounds import RealBounds
35
from gemd.entity.value.continuous_value import ContinuousValue
46
from gemd.entity.value.base_value import BaseValue
@@ -50,7 +52,7 @@ def _check(value: BaseValue):
5052
level = get_validation_level()
5153
accept = level == WarningLevel.IGNORE or fraction_bounds.contains(value)
5254
if not accept:
53-
message = f"Value {value} is not between 0 and 1."
55+
message = f"Value {value} is not a dimensionless value between 0 and 1."
5456
if level == WarningLevel.WARNING:
5557
logger.warning(message)
5658
else:
@@ -110,7 +112,29 @@ def absolute_quantity(self) -> ContinuousValue:
110112
def absolute_quantity(self, absolute_quantity: ContinuousValue):
111113
if absolute_quantity is None:
112114
self._absolute_quantity = None
113-
elif isinstance(absolute_quantity, ContinuousValue):
114-
self._absolute_quantity = absolute_quantity
115-
else:
115+
elif not isinstance(absolute_quantity, ContinuousValue):
116116
raise TypeError("absolute_quantity was not given as a continuous value")
117+
else:
118+
max_bounds = RealBounds(
119+
lower_bound=0.0,
120+
upper_bound=float_info.max,
121+
default_units=absolute_quantity.units
122+
)
123+
dimensionless = RealBounds(
124+
lower_bound=0.0,
125+
upper_bound=float_info.max,
126+
default_units=''
127+
)
128+
level = get_validation_level()
129+
if level != WarningLevel.IGNORE:
130+
messages = []
131+
if not max_bounds.contains(absolute_quantity):
132+
messages.append(f"Value {absolute_quantity} is less than 0.0.")
133+
if dimensionless.contains(absolute_quantity):
134+
messages.append(f"Value {absolute_quantity} is dimensionless.")
135+
if level == WarningLevel.WARNING:
136+
for message in messages:
137+
logger.warning(message)
138+
else:
139+
raise ValueError("; ".join(messages))
140+
self._absolute_quantity = absolute_quantity

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
packages.append("")
55

66
setup(name='gemd',
7-
version='1.16.9',
7+
version='1.17.0',
88
python_requires='>=3.8',
99
url='http://github.com/CitrineInformatics/gemd-python',
1010
description="Python binding for Citrine's GEMD data model",

tests/entity/object/test_ingredient_spec.py

Lines changed: 73 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,30 @@ def test_ingredient_reassignment():
3333
assert set(frying.ingredients) == {oil, potatoes}
3434

3535

36+
VALID_FRACTIONS = [
37+
NominalReal(1.0, ''),
38+
UniformReal(0.5, 0.6, ''),
39+
NormalReal(0.2, 0.3, '')
40+
]
41+
42+
INVALID_FRACTIONS = [
43+
NominalReal(1.0, 'm'),
44+
UniformReal(0.7, 1.1, ''),
45+
NormalReal(-0.2, 0.3, '')
46+
]
47+
3648
VALID_QUANTITIES = [
37-
NominalReal(14.0, ''),
38-
UniformReal(0.5, 0.6, 'm'),
39-
NormalReal(-0.3, 0.6, "kg")
49+
NominalReal(14.0, 'g'),
50+
UniformReal(0.5, 0.6, 'mol'),
51+
NormalReal(0.3, 0.6, 'cc')
4052
]
4153

4254
INVALID_QUANTITIES = [
55+
NominalReal(14.0, ''),
56+
UniformReal(-0.1, 0.3, 'mol'),
57+
]
58+
59+
INVALID_TYPES = [
4360
NominalCategorical("blue"),
4461
NominalInteger(5),
4562
EmpiricalFormula("CH4"),
@@ -48,55 +65,79 @@ def test_ingredient_reassignment():
4865
]
4966

5067

68+
@pytest.mark.parametrize("valid_fraction", VALID_FRACTIONS)
69+
def test_valid_fractions(valid_fraction, caplog):
70+
"""
71+
Check that all fractional quantities must be continuous values.
72+
"""
73+
with validation_level(WarningLevel.WARNING):
74+
ingred = IngredientSpec(name="name", mass_fraction=valid_fraction)
75+
assert ingred.mass_fraction == valid_fraction
76+
ingred = IngredientSpec(name="name", volume_fraction=valid_fraction)
77+
assert ingred.volume_fraction == valid_fraction
78+
ingred = IngredientSpec(name="name", number_fraction=valid_fraction)
79+
assert ingred.number_fraction == valid_fraction
80+
assert ingred.absolute_quantity is None
81+
assert len(caplog.records) == 0, "Warned on valid values with WARNING."
82+
83+
5184
@pytest.mark.parametrize("valid_quantity", VALID_QUANTITIES)
5285
def test_valid_quantities(valid_quantity, caplog):
5386
"""
5487
Check that all quantities must be continuous values.
55-
56-
There are no restrictions on the value or the units. Although a volume fraction of -5 kg
57-
does not make physical sense, it will not throw an error.
5888
"""
59-
with validation_level(WarningLevel.IGNORE):
60-
ingred = IngredientSpec(name="name", mass_fraction=valid_quantity)
61-
assert ingred.mass_fraction == valid_quantity
62-
ingred = IngredientSpec(name="name", volume_fraction=valid_quantity)
63-
assert ingred.volume_fraction == valid_quantity
64-
ingred = IngredientSpec(name="name", number_fraction=valid_quantity)
65-
assert ingred.number_fraction == valid_quantity
89+
with validation_level(WarningLevel.WARNING):
6690
ingred = IngredientSpec(name="name", absolute_quantity=valid_quantity)
6791
assert ingred.absolute_quantity == valid_quantity
68-
assert len(caplog.records) == 0, "Warned when validation set to IGNORE."
92+
assert ingred.mass_fraction is None
93+
assert ingred.number_fraction is None
94+
assert ingred.volume_fraction is None
95+
assert len(caplog.records) == 0, "Warned on valid values with WARNING."
6996

7097

71-
def test_validation_control(caplog):
72-
"""Verify that when validation is requested, limits are enforced."""
98+
@pytest.mark.parametrize("invalid_fraction", INVALID_FRACTIONS)
99+
def test_invalid_fractions(invalid_fraction, caplog):
100+
"""
101+
Verify that when validation is requested, limits are enforced for fractions.
102+
"""
103+
with validation_level(WarningLevel.IGNORE):
104+
IngredientSpec(name="name", mass_fraction=invalid_fraction)
105+
assert len(caplog.records) == 0, f"Warned on invalid values with IGNORE: {invalid_fraction}"
73106
with validation_level(WarningLevel.WARNING):
74-
IngredientSpec(name="name", mass_fraction=NominalReal(0.5, ''))
75-
assert len(caplog.records) == 0, "Warned on valid values with WARNING."
76-
IngredientSpec(name="name", mass_fraction=NominalReal(5, ''))
77-
assert len(caplog.records) == 1, "Didn't warn on invalid values with WARNING."
78-
IngredientSpec(name="name", mass_fraction=NominalReal(0.5, 'm'))
79-
assert len(caplog.records) == 2, "Didn't warn on invalid units with WARNING."
107+
IngredientSpec(name="name", mass_fraction=invalid_fraction)
108+
assert len(caplog.records) == 1, f"Didn't warn on invalid values with IGNORE: {invalid_fraction}"
80109
with validation_level(WarningLevel.FATAL):
81-
# The following should not raise an exception
82-
IngredientSpec(name="name", mass_fraction=NominalReal(0.5, ''))
83-
with pytest.raises(ValueError):
84-
IngredientSpec(name="name", mass_fraction=NominalReal(5, ''))
85110
with pytest.raises(ValueError):
86-
IngredientSpec(name="name", mass_fraction=NominalReal(0.5, 'm'))
111+
IngredientSpec(name="name", mass_fraction=invalid_fraction)
87112

88113

89114
@pytest.mark.parametrize("invalid_quantity", INVALID_QUANTITIES)
90-
def test_invalid_quantities(invalid_quantity):
115+
def test_invalid_quantities(invalid_quantity, caplog):
116+
"""
117+
Verify that when validation is requested, limits are enforced for fractions.
118+
"""
119+
with validation_level(WarningLevel.IGNORE):
120+
IngredientSpec(name="name", absolute_quantity=invalid_quantity)
121+
assert len(caplog.records) == 0, f"Warned on invalid values with IGNORE: {invalid_quantity}"
122+
with validation_level(WarningLevel.WARNING):
123+
IngredientSpec(name="name", absolute_quantity=invalid_quantity)
124+
assert len(caplog.records) == 1, f"Didn't warn on invalid values with IGNORE: {invalid_quantity}"
125+
with validation_level(WarningLevel.FATAL):
126+
with pytest.raises(ValueError):
127+
IngredientSpec(name="name", absolute_quantity=invalid_quantity)
128+
129+
130+
@pytest.mark.parametrize("invalid_type", INVALID_TYPES)
131+
def test_invalid_types(invalid_type):
91132
"""Check that any non-continuous value for a quantity throws a TypeError."""
92133
with pytest.raises(TypeError):
93-
IngredientSpec(name="name", mass_fraction=invalid_quantity)
134+
IngredientSpec(name="name", mass_fraction=invalid_type)
94135
with pytest.raises(TypeError):
95-
IngredientSpec(name="name", volume_fraction=invalid_quantity)
136+
IngredientSpec(name="name", volume_fraction=invalid_type)
96137
with pytest.raises(TypeError):
97-
IngredientSpec(name="name", number_fraction=invalid_quantity)
138+
IngredientSpec(name="name", number_fraction=invalid_type)
98139
with pytest.raises(TypeError):
99-
IngredientSpec(name="name", absolute_quantity=invalid_quantity)
140+
IngredientSpec(name="name", absolute_quantity=invalid_type)
100141

101142

102143
def test_invalid_assignment():

0 commit comments

Comments
 (0)