Skip to content

Commit 1010972

Browse files
authored
Merge pull request #59 from ourstudio-se/52-plog-create-proposition-with-from_dict-does-not-work-for-variables-with-bounds
Issue solved by fixing from_dict function
2 parents 77f0e85 + ea3328e commit 1010972

2 files changed

Lines changed: 167 additions & 6 deletions

File tree

puan/logic/plog/__init__.py

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -854,7 +854,22 @@ def from_json(data: dict, class_map) -> "All":
854854
)
855855

856856
@staticmethod
857-
def from_list(propositions: list, variable: typing.Union[str, puan.variable] = None):
857+
def from_list(propositions: list, variable: typing.Union[str, puan.variable] = None) -> "All":
858+
859+
"""
860+
Convert from list of propositions to an object of this proposition class.
861+
862+
Examples
863+
--------
864+
>>> model = All.from_list([puan.variable("x"), puan.variable("y")], variable="a")
865+
>>> type(model) == All
866+
True
867+
868+
Returns
869+
-------
870+
out : All
871+
"""
872+
858873
return All(*propositions, variable=variable)
859874

860875
def to_json(self) -> dict:
@@ -913,7 +928,22 @@ def from_json(data: dict, class_map) -> "Any":
913928
)
914929

915930
@staticmethod
916-
def from_list(propositions: list, variable: typing.Union[str, puan.variable] = None):
931+
def from_list(propositions: list, variable: typing.Union[str, puan.variable] = None) -> "Any":
932+
933+
"""
934+
Convert from list of propositions to an object of this proposition class.
935+
936+
Examples
937+
--------
938+
>>> model = Any.from_list([puan.variable("x"), puan.variable("y")], variable="a")
939+
>>> type(model) == Any
940+
True
941+
942+
Returns
943+
-------
944+
out : Any
945+
"""
946+
917947
return Any(*propositions, variable=variable)
918948

919949
def to_json(self) -> dict:
@@ -1114,7 +1144,22 @@ def from_json(data: dict, class_map) -> "Xor":
11141144
)
11151145

11161146
@staticmethod
1117-
def from_list(propositions: list, variable: typing.Union[str, puan.variable] = None):
1147+
def from_list(propositions: list, variable: typing.Union[str, puan.variable] = None) -> "Xor":
1148+
1149+
"""
1150+
Convert from list of propositions to an object of this proposition class.
1151+
1152+
Examples
1153+
--------
1154+
>>> model = Xor.from_list([puan.variable("x"), puan.variable("y")], variable="a")
1155+
>>> type(model) == Xor
1156+
True
1157+
1158+
Returns
1159+
-------
1160+
out : Xor
1161+
"""
1162+
11181163
return Xor(*propositions, variable=variable)
11191164

11201165
def to_json(self) -> dict:
@@ -1207,7 +1252,26 @@ def from_json(data: dict, class_map) -> "XNor":
12071252
)
12081253

12091254
@staticmethod
1210-
def from_list(propositions: list, variable: typing.Union[str, puan.variable] = None):
1255+
def from_list(propositions: list, variable: typing.Union[str, puan.variable] = None) -> AtLeast:
1256+
1257+
"""
1258+
Convert from list of propositions to an object of this proposition class.
1259+
1260+
Notes
1261+
-----
1262+
XNor is not its own type but instead returns an AtLeast proposition which preserves the same logic.
1263+
1264+
Examples
1265+
--------
1266+
>>> model = XNor.from_list([puan.variable("x"), puan.variable("y")], variable="a")
1267+
>>> type(model) == AtLeast
1268+
True
1269+
1270+
Returns
1271+
-------
1272+
out : AtLeast
1273+
"""
1274+
12111275
return XNor(*propositions, variable=variable)
12121276

12131277
def to_json(self) -> dict:
@@ -1349,7 +1413,7 @@ def from_dict(d: dict, id: str = None) -> AtLeast:
13491413
raise Exception(f"dict has multiple top nodes ({sort_order[-1]}) but exactly one is required")
13501414

13511415
for level in sort_order:
1352-
for i in filter(lambda x: x in d_conv, level):
1416+
for i in filter(lambda x: x in d_conv and hasattr(d_conv[x], "propositions"), level):
13531417
d_conv[i].propositions = list(
13541418
map(
13551419
lambda j: d_conv[j] if j in d_conv else j,

tests/test_puan.py

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,21 @@
1616

1717
from hypothesis import example, given, strategies as st, settings, assume
1818

19+
def short_proposition_strategy():
20+
mn,mx = -99,99
21+
return st.tuples(
22+
st.text(),
23+
st.sampled_from([-1,1]),
24+
st.lists(
25+
st.text(),
26+
),
27+
st.integers(mn,mx),
28+
st.tuples(
29+
st.integers(mn,mx),
30+
st.integers(mn,mx),
31+
)
32+
)
33+
1934
def atom_proposition_strategy():
2035
return st.builds(
2136
puan.variable,
@@ -203,6 +218,17 @@ def test_json_conversion_id_should_be_returned_if_explicitly_defined(proposition
203218
# generated id (i.e. no ID was explicitly defined) implies that there shouldn't be an ID in the json
204219
assert not (proposition.generated_id and 'id' in json_model)
205220

221+
@given(short_proposition_strategy())
222+
@settings(deadline=None)
223+
def test_from_short_wont_crash(short_proposition):
224+
225+
# Should raise if has sub propositions and bounds are other than (0,1)
226+
if len(short_proposition[2]) > 0 and short_proposition[4] != (0,1):
227+
with pytest.raises(Exception):
228+
pg.AtLeast.from_short(short_proposition)
229+
else:
230+
pg.AtLeast.from_short(short_proposition)
231+
206232
def test_json_conversion_special_cases():
207233

208234
model = cc.StingyConfigurator(
@@ -2449,4 +2475,75 @@ def test_json_dump_puan_variables():
24492475
assert json.dumps(puan.SolutionVariable("x", (-10,10), value=1)) == '{"id": "x", "bounds": {"lower": -10, "upper": 10}, "value": 1}'
24502476
assert json.dumps(puan.SolutionVariable("x", dtype="int", value=1)) == '{"id": "x", "bounds": {"lower": -32768, "upper": 32767}, "value": 1}'
24512477
assert json.dumps(puan.SolutionVariable("x", dtype="int", value=-9999)) == '{"id": "x", "bounds": {"lower": -32768, "upper": 32767}, "value": -9999}'
2452-
assert json.dumps(puan.SolutionVariable("x", dtype="int", value=None)) == '{"id": "x", "bounds": {"lower": -32768, "upper": 32767}, "value": null}'
2478+
assert json.dumps(puan.SolutionVariable("x", dtype="int", value=None)) == '{"id": "x", "bounds": {"lower": -32768, "upper": 32767}, "value": null}'
2479+
2480+
def test_from_dict():
2481+
2482+
data = {
2483+
'x': [1, [], 0, [-10,10]],
2484+
}
2485+
model = pg.from_dict(data)
2486+
assert model.id == "x"
2487+
assert type(model) == puan.variable
2488+
2489+
# should raise because of multiple top nodes (require exactly one)
2490+
data = {
2491+
'x': [1, [], 0, [-10,10]],
2492+
'y': [1, [], 0, [-10,10]],
2493+
}
2494+
with pytest.raises(Exception):
2495+
pg.from_dict(data)
2496+
2497+
# should raise because of multiple top nodes (require exactly one)
2498+
data = {
2499+
'a': [1, ['x','y'], 0, [0,1]],
2500+
'b': [1, ['x','y'], 0, [0,1]],
2501+
'x': [1, [], 0, [-10,10]],
2502+
'y': [1, [], 0, [-10,10]],
2503+
}
2504+
with pytest.raises(Exception):
2505+
pg.from_dict(data)
2506+
2507+
# should raise exception because of circular reference
2508+
data = {
2509+
'a': [1, ['b'], 0, [0,1]],
2510+
'b': [1, ['c'], 0, [0,1]],
2511+
'c': [1, ['a'], 0, [0,1]],
2512+
}
2513+
with pytest.raises(Exception):
2514+
pg.from_dict(data)
2515+
2516+
data = {
2517+
'a': [1, ['x','y'], 0, [10,11]], # <- should raise because of this
2518+
'x': [1, [], 0, [0,10]],
2519+
'y': [1, [], 0, [-2,5]],
2520+
}
2521+
with pytest.raises(Exception):
2522+
pg.from_dict(data)
2523+
2524+
# Common model structure with specific variable bounds
2525+
data = {
2526+
'a': [1, ['x','y'], -2, [0,1]],
2527+
'x': [1, [], 0, [-10,10]],
2528+
'y': [1, [], 0, [-10,10]],
2529+
}
2530+
model = pg.from_dict(data)
2531+
assert model.id == 'a'
2532+
assert len(model.propositions) == 2
2533+
assert all(map(lambda prop: prop.bounds == puan.Bounds(-10,10), model.propositions))
2534+
2535+
# Model with variable sharing propositions
2536+
data = {
2537+
'a': [1, ['b','c'], -2, [0,1]],
2538+
'b': [1, ['x','y'], -2, [0,1]],
2539+
'c': [1, ['x','y'], -1, [0,1]],
2540+
'x': [1, [], 0, [-10,10]],
2541+
'y': [1, [], 0, [-10,10]],
2542+
}
2543+
model = pg.from_dict(data)
2544+
assert model.id == 'a'
2545+
assert len(model.propositions) == 2
2546+
assert len(model.propositions[0].propositions) == 2
2547+
assert len(model.propositions[1].propositions) == 2
2548+
assert all(map(lambda prop: prop.bounds == puan.Bounds(-10,10), model.propositions[0].propositions))
2549+
assert all(map(lambda prop: prop.bounds == puan.Bounds(-10,10), model.propositions[1].propositions))

0 commit comments

Comments
 (0)