Skip to content

Commit b49ae0a

Browse files
authored
Merge pull request #56 from lukovdm/master
Add synthesis for exact POMDPs
2 parents 703a4ab + 4f81b4d commit b49ae0a

22 files changed

+290
-102
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ prerequisites/
88

99
# OSX
1010
.DS_Store
11+
12+
build/

paynt/cli.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def setup_logger(log_path = None):
3232
# root.setLevel(logging.INFO)
3333

3434
# formatter = logging.Formatter('%(asctime)s %(threadName)s - %(name)s - %(levelname)s - %(message)s')
35-
formatter = logging.Formatter('%(asctime)s - %(filename)s - %(message)s')
35+
formatter = logging.Formatter('%(asctime)s - %(filename)s:%(lineno)d - %(message)s')
3636

3737
handlers = []
3838
if log_path is not None:
@@ -61,6 +61,8 @@ def setup_logger(log_path = None):
6161
help="known optimum bound")
6262
@click.option("--precision", type=click.FLOAT, default=1e-4,
6363
help="model checking precision")
64+
@click.option("--exact", is_flag=True, default=False,
65+
help="use exact synthesis (very limited at the moment)")
6466
@click.option("--timeout", type=int,
6567
help="timeout (s)")
6668

@@ -138,7 +140,7 @@ def setup_logger(log_path = None):
138140
help="run profiling")
139141

140142
def paynt_run(
141-
project, sketch, props, relative_error, optimum_threshold, precision, timeout,
143+
project, sketch, props, relative_error, optimum_threshold, precision, exact, timeout,
142144
export,
143145
method,
144146
disable_expected_visits,
@@ -187,7 +189,7 @@ def paynt_run(
187189

188190
sketch_path = os.path.join(project, sketch)
189191
properties_path = os.path.join(project, props)
190-
quotient = paynt.parser.sketch.Sketch.load_sketch(sketch_path, properties_path, export, relative_error, precision, constraint_bound)
192+
quotient = paynt.parser.sketch.Sketch.load_sketch(sketch_path, properties_path, export, relative_error, precision, constraint_bound, exact)
191193
synthesizer = paynt.synthesizer.synthesizer.Synthesizer.choose_synthesizer(quotient, method, fsc_synthesis, storm_control)
192194
synthesizer.run(optimum_threshold)
193195

paynt/family/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
__version__ = "unknown"
2+
3+
try:
4+
from .._version import __version__
5+
except ImportError:
6+
# We're running in a tree that doesn't have a _version.py, so we don't know what our version is.
7+
pass
8+
9+
def version():
10+
return __version__

paynt/models/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
__version__ = "unknown"
2+
3+
try:
4+
from .._version import __version__
5+
except ImportError:
6+
# We're running in a tree that doesn't have a _version.py, so we don't know what our version is.
7+
pass
8+
9+
def version():
10+
return __version__

paynt/models/model_builder.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,15 @@ def from_jani(cls, program, specification = None):
2929
return model
3030

3131
@classmethod
32-
def from_prism(cls, program, specification = None):
32+
def from_prism(cls, program, specification = None, use_exact=False):
3333
assert program.model_type in [stormpy.storage.PrismModelType.MDP, stormpy.storage.PrismModelType.POMDP]
3434
builder_options = cls.default_builder_options(specification)
35-
model = stormpy.build_sparse_model_with_options(program, builder_options)
35+
if use_exact:
36+
model = stormpy.build_sparse_exact_model_with_options(program, builder_options)
37+
else:
38+
model = stormpy.build_sparse_model_with_options(program, builder_options)
3639
return model
3740

3841
@classmethod
39-
def from_drn(cls, drn_path):
40-
return DrnParser.parse_drn(drn_path)
42+
def from_drn(cls, drn_path, use_exact=False):
43+
return DrnParser.parse_drn(drn_path, use_exact)

paynt/parser/drn_parser.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,22 @@ class DrnParser:
1717
WHITESPACES = ' \t\n\v\f\r'
1818

1919
@classmethod
20-
def parse_drn(cls, sketch_path):
20+
def parse_drn(cls, sketch_path, use_exact=False):
2121
# try to read a drn file and return POSMG or POMDP based on the type
2222
# ValueError if file is not dnr or the model is of unsupported type
2323
explicit_model = None
2424
try:
2525
type = DrnParser.decide_type_of_drn(sketch_path)
2626
if type == 'POSMG':
27+
if use_exact:
28+
raise ValueError('Exact synthesis is not supported for POSMG models')
2729
pomdp_path = sketch_path + '.tmp'
2830
state_player_indications = DrnParser.pomdp_from_posmg(sketch_path, pomdp_path)
2931
pomdp = DrnParser.read_drn(pomdp_path)
3032
explicit_model = payntbind.synthesis.posmg_from_pomdp(pomdp, state_player_indications)
3133
os.remove(pomdp_path)
3234
else:
33-
explicit_model = DrnParser.read_drn(sketch_path)
35+
explicit_model = DrnParser.read_drn(sketch_path, use_exact)
3436
except Exception as e:
3537
print(e)
3638
raise ValueError('Failed to read sketch file in a .drn format')
@@ -91,10 +93,13 @@ def str_remove_range(string: str, start_idx: int, end_idx: int) -> str:
9193
return string[:start_idx] + string[end_idx+1:]
9294

9395
@classmethod
94-
def read_drn(cls, sketch_path):
96+
def read_drn(cls, sketch_path, use_exact=False):
9597
builder_options = stormpy.core.DirectEncodingParserOptions()
9698
builder_options.build_choice_labels = True
97-
return stormpy.build_model_from_drn(sketch_path, builder_options)
99+
if use_exact:
100+
return stormpy.core._build_sparse_exact_model_from_drn(sketch_path, builder_options)
101+
else:
102+
return stormpy.build_model_from_drn(sketch_path, builder_options)
98103

99104
@staticmethod
100105
def parse_posmg_specification(properties_path):

paynt/parser/prism_parser.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
class PrismParser:
1818

1919
@classmethod
20-
def read_prism(cls, sketch_path, properties_path, relative_error):
20+
def read_prism(cls, sketch_path, properties_path, relative_error, use_exact=False):
2121

2222
# parse the program
2323
prism, hole_definitions = PrismParser.load_sketch_prism(sketch_path)
@@ -41,7 +41,7 @@ def read_prism(cls, sketch_path, properties_path, relative_error):
4141
prism, hole_expressions, family = PrismParser.parse_holes(prism, expression_parser, hole_definitions)
4242
prism = prism.label_unlabelled_commands({})
4343

44-
specification = PrismParser.parse_specification(properties_path, relative_error, prism)
44+
specification = PrismParser.parse_specification(properties_path, relative_error, prism, use_exact=use_exact)
4545

4646
# construct the quotient
4747
coloring = None
@@ -58,7 +58,7 @@ def read_prism(cls, sketch_path, properties_path, relative_error):
5858
obs_evaluator = payntbind.synthesis.ObservationEvaluator(prism, quotient_mdp)
5959
quotient_mdp = payntbind.synthesis.addChoiceLabelsFromJani(quotient_mdp)
6060
else:
61-
quotient_mdp = paynt.models.model_builder.ModelBuilder.from_prism(prism, specification)
61+
quotient_mdp = paynt.models.model_builder.ModelBuilder.from_prism(prism, specification, use_exact)
6262

6363
return prism, quotient_mdp, specification, family, coloring, jani_unfolder, obs_evaluator
6464

@@ -192,7 +192,7 @@ def parse_property(cls, line, prism=None):
192192
return props[0]
193193

194194
@classmethod
195-
def parse_specification(cls, properties_path, relative_error=0, prism=None):
195+
def parse_specification(cls, properties_path, relative_error=0, prism=None, use_exact=False):
196196
'''
197197
Expecting one property per line. The line may be terminated with a semicolon.
198198
Empty lines or comments are allowed.
@@ -211,7 +211,7 @@ def parse_specification(cls, properties_path, relative_error=0, prism=None):
211211
formula = PrismParser.parse_property(line,prism)
212212
if formula is None:
213213
continue
214-
prop = paynt.verification.property.construct_property(formula, relative_error)
214+
prop = paynt.verification.property.construct_property(formula, relative_error, use_exact)
215215
properties.append(prop)
216216

217217
specification = paynt.verification.property.Specification(properties)

paynt/parser/sketch.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,20 @@ def make_rewards_action_based(model):
4848
for action in range(tm.get_row_group_start(state),tm.get_row_group_end(state)):
4949
action_reward[action] += state_reward
5050

51-
payntbind.synthesis.remove_reward_model(model,name)
52-
new_reward_model = stormpy.storage.SparseRewardModel(optional_state_action_reward_vector=action_reward)
53-
model.add_reward_model(name, new_reward_model)
51+
if model.is_exact:
52+
payntbind.synthesis.remove_reward_model_exact(model,name)
53+
new_reward_model = stormpy.storage.SparseExactRewardModel(optional_state_action_reward_vector=action_reward)
54+
model.add_reward_model(name, new_reward_model)
55+
else:
56+
payntbind.synthesis.remove_reward_model(model,name)
57+
new_reward_model = stormpy.storage.SparseRewardModel(optional_state_action_reward_vector=action_reward)
58+
model.add_reward_model(name, new_reward_model)
5459

5560
class Sketch:
5661

5762
@classmethod
5863
def load_sketch(cls, sketch_path, properties_path,
59-
export=None, relative_error=0, precision=1e-4, constraint_bound=None):
64+
export=None, relative_error=0, precision=1e-4, constraint_bound=None, use_exact=False):
6065

6166
prism = None
6267
explicit_quotient = None
@@ -78,15 +83,15 @@ def load_sketch(cls, sketch_path, properties_path,
7883
try:
7984
logger.info(f"assuming sketch in PRISM format...")
8085
prism, explicit_quotient, specification, family, coloring, jani_unfolder, obs_evaluator = PrismParser.read_prism(
81-
sketch_path, properties_path, relative_error)
86+
sketch_path, properties_path, relative_error, use_exact)
8287
filetype = "prism"
8388
except SyntaxError:
8489
pass
8590
if filetype is None:
8691
try:
8792
logger.info(f"assuming sketch in DRN format...")
88-
explicit_quotient = paynt.models.model_builder.ModelBuilder.from_drn(sketch_path)
89-
specification = PrismParser.parse_specification(properties_path, relative_error)
93+
explicit_quotient = paynt.models.model_builder.ModelBuilder.from_drn(sketch_path, use_exact)
94+
specification = PrismParser.parse_specification(properties_path, relative_error, use_exact=use_exact)
9095
filetype = "drn"
9196
project_path = os.path.dirname(sketch_path)
9297
valuations_filename = "state-valuations.json"
@@ -96,6 +101,8 @@ def load_sketch(cls, sketch_path, properties_path,
96101
with open(valuations_path) as file:
97102
state_valuations = json.load(file)
98103
if state_valuations is not None:
104+
if use_exact:
105+
raise Exception("exact synthesis is not supported with state valuations")
99106
logger.info(f"found state valuations in {valuations_path}, adding to the model...")
100107
explicit_quotient = payntbind.synthesis.addStateValuations(explicit_quotient,state_valuations)
101108
except Exception as e:
@@ -128,7 +135,10 @@ def load_sketch(cls, sketch_path, properties_path,
128135
logger.info("sketch parsing OK")
129136

130137
paynt.verification.property.Property.initialize()
131-
updated = payntbind.synthesis.addMissingChoiceLabels(explicit_quotient)
138+
if explicit_quotient.is_exact:
139+
updated = payntbind.synthesis.addMissingChoiceLabelsExact(explicit_quotient)
140+
else:
141+
updated = payntbind.synthesis.addMissingChoiceLabels(explicit_quotient)
132142
if updated is not None: explicit_quotient = updated
133143
if not payntbind.synthesis.assertChoiceLabelingIsCanonic(explicit_quotient.nondeterministic_choice_indices,explicit_quotient.choice_labeling,False):
134144
logger.warning("WARNING: choice labeling for the quotient is not canonic")

paynt/quotient/pomdp.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def __init__(self, pomdp, specification, decpomdp_manager=None):
6363
# ^ this also asserts that states with the same observation have the
6464
# same number and the same order of available actions
6565

66-
logger.info(f"constructed POMDP having {self.observations} observations.")
66+
logger.info(f"constructed {'exact' if self.pomdp.is_exact else ''} POMDP having {self.observations} observations.")
6767

6868
state_obs = self.pomdp.observations.copy()
6969

@@ -116,10 +116,16 @@ def __init__(self, pomdp, specification, decpomdp_manager=None):
116116
self.observation_states[state_obs[state]] += 1
117117

118118
# initialize POMDP manager
119-
if not PomdpQuotient.posterior_aware:
120-
self.pomdp_manager = payntbind.synthesis.PomdpManager(self.pomdp)
119+
if self.pomdp.is_exact:
120+
if not PomdpQuotient.posterior_aware:
121+
self.pomdp_manager = payntbind.synthesis.ExactPomdpManager(self.pomdp)
122+
else:
123+
self.pomdp_manager = payntbind.synthesis.ExactPomdpManagerAposteriori(self.pomdp)
121124
else:
122-
self.pomdp_manager = payntbind.synthesis.PomdpManagerAposteriori(self.pomdp)
125+
if not PomdpQuotient.posterior_aware:
126+
self.pomdp_manager = payntbind.synthesis.PomdpManager(self.pomdp)
127+
else:
128+
self.pomdp_manager = payntbind.synthesis.PomdpManagerAposteriori(self.pomdp)
123129

124130
# do initial unfolding
125131
self.set_imperfect_memory_size(PomdpQuotient.initial_memory_size)
@@ -339,7 +345,10 @@ def unfold_memory(self):
339345

340346
logger.debug("unfolding {}-FSC template into POMDP...".format(max(self.observation_memory_size)))
341347
self.quotient_mdp = self.pomdp_manager.construct_mdp()
342-
self.choice_destinations = payntbind.synthesis.computeChoiceDestinations(self.quotient_mdp)
348+
if self.quotient_mdp.is_exact:
349+
self.choice_destinations = payntbind.synthesis.computeChoiceDestinationsExact(self.quotient_mdp)
350+
else:
351+
self.choice_destinations = payntbind.synthesis.computeChoiceDestinations(self.quotient_mdp)
343352
logger.debug(f"constructed quotient MDP having {self.quotient_mdp.nr_states} states and {self.quotient_mdp.nr_choices} actions.")
344353

345354
self.family, choice_to_hole_options = self.create_coloring()

paynt/quotient/quotient.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,14 @@ def mdp_to_dtmc(mdp):
8989
tm = mdp.transition_matrix
9090
tm.make_row_grouping_trivial()
9191
assert tm.nr_columns == tm.nr_rows, "expected transition matrix without non-trivial row groups"
92-
components = stormpy.storage.SparseModelComponents(tm, mdp.labeling, mdp.reward_models)
93-
dtmc = stormpy.storage.SparseDtmc(components)
94-
return dtmc
92+
if mdp.is_exact:
93+
components = stormpy.storage.SparseExactModelComponents(tm, mdp.labeling, mdp.reward_models)
94+
dtmc = stormpy.storage.SparseExactDtmc(components)
95+
return dtmc
96+
else:
97+
components = stormpy.storage.SparseModelComponents(tm, mdp.labeling, mdp.reward_models)
98+
dtmc = stormpy.storage.SparseDtmc(components)
99+
return dtmc
95100

96101
def build_assignment(self, family):
97102
assert family.size == 1, "expecting family of size 1"
@@ -121,7 +126,10 @@ def discard_unreachable_choices(self, state_to_choice):
121126
return state_to_choice_reachable
122127

123128
def scheduler_to_state_to_choice(self, submdp, scheduler, discard_unreachable_choices=True):
124-
state_to_quotient_choice = payntbind.synthesis.schedulerToStateToGlobalChoice(scheduler, submdp.model, submdp.quotient_choice_map)
129+
if submdp.model.is_exact:
130+
state_to_quotient_choice = payntbind.synthesis.schedulerToStateToGlobalChoiceExact(scheduler, submdp.model, submdp.quotient_choice_map)
131+
else:
132+
state_to_quotient_choice = payntbind.synthesis.schedulerToStateToGlobalChoice(scheduler, submdp.model, submdp.quotient_choice_map)
125133
state_to_choice = self.empty_scheduler()
126134
for state in range(submdp.model.nr_states):
127135
quotient_choice = state_to_quotient_choice[state]
@@ -159,7 +167,10 @@ def choice_values(self, mdp, prop, state_values):
159167
'''
160168

161169
# multiply probability with model checking results
162-
choice_values = payntbind.synthesis.multiply_with_vector(mdp.transition_matrix, state_values)
170+
if mdp.is_exact:
171+
choice_values = payntbind.synthesis.multiply_with_vector_exact(mdp.transition_matrix, state_values)
172+
else:
173+
choice_values = payntbind.synthesis.multiply_with_vector(mdp.transition_matrix, state_values)
163174
choice_values = Quotient.make_vector_defined(choice_values)
164175

165176
# if the associated reward model has state-action rewards, then these must be added to choice values

0 commit comments

Comments
 (0)