def test_and(self): ast = negation_normal_form(parse_ctlq("AX AG (? & False)")) ast2 = negation_normal_form(parse_ctlq("AX AG ?")) self.assertTrue(check_ctlqx(ast)) self.assertTrue(check_ctlqx(ast2)) fsm = self.init_model() self.assertEqual(solve_ctlqx(fsm, ast), solve_ctlqx(fsm, ast2))
def test_ko_not_nnf(self): kos = [ "~AX ?", "~EX ?", "~EF ?", "? -> 'a'", "'a' -> ?", "~(? -> AF 'a')" ] for ko in kos: self.assertFalse(check_ctlqx(parse_ctlq(ko)))
def test_false(self): ast = negation_normal_form( parse_ctlq("A['admin = bob' oW A['admin = alice' oU AG ?]]")) self.assertTrue(check_ctlqx(ast)) fsm = self.init_model() self.assertEqual(solve_ctlqx(fsm, ast), BDD.false(fsm.bddEnc.DDmanager))
def test_or(self): ast = negation_normal_form(parse_ctlq("AF ('admin = bob' | AG ?)")) self.assertTrue(check_ctlqx(ast)) fsm = self.init_model() solution = {HashableDict({'admin': 'alice', 'state': 'waiting'}), HashableDict({'admin': 'alice', 'state': 'processing'})} self.assertCountEqual(bdd_to_set(fsm, solve_ctlqx(fsm, ast)), solution)
def test_false(self): ast = negation_normal_form( parse_ctlq("A['admin = bob' oW A['admin = alice' oU AG ?]]") ) self.assertTrue(check_ctlqx(ast)) fsm = self.init_model() self.assertEqual(solve_ctlqx(fsm, ast), BDD.false(fsm.bddEnc.DDmanager))
def test_au(self): ast = negation_normal_form(parse_ctlq("A[? U 'state = processing']")) self.assertTrue(check_ctlqx(ast)) fsm = self.init_model() solution = {HashableDict({'admin': 'none', 'state': 'starting'}), HashableDict({'admin': 'none', 'state': 'choosing'}), HashableDict({'admin': 'alice', 'state': 'waiting'}), HashableDict({'admin': 'bob', 'state': 'waiting'})} self.assertCountEqual(bdd_to_set(fsm, solve_ctlqx(fsm, ast)), solution)
def test_simplify_conjunction(self): ast = negation_normal_form(parse_ctlq("AG (? -> AF 'heat')")) self.assertTrue(check_ctlqx(ast)) fsm = self.init_model('examples/microwave.smv') simplification = simplify(fsm, solve_ctlqx(fsm, ast), 2) self.assertCountEqual('(error = FALSE)\n& (close = TRUE)\n&' ' ((heat = FALSE & start = TRUE) |' ' (heat = TRUE & start = TRUE) |' ' (heat = TRUE & start = FALSE))', simplification)
def test_simplify_fail(self): ast = negation_normal_form(parse_ctlq("?")) self.assertTrue(check_ctlqx(ast)) fsm = self.init_model('examples/microwave.smv') with self.assertRaises(VariableNotInModelError): simplify(fsm, solve_ctlqx(fsm, ast), 1, ['a']) with self.assertRaises(ValueOutOfBoundsError): simplify(fsm, solve_ctlqx(fsm, ast), 0) with self.assertRaises(ValueOutOfBoundsError): simplify(fsm, solve_ctlqx(fsm, ast), 10)
def test_or(self): ast = negation_normal_form(parse_ctlq("AF ('admin = bob' | AG ?)")) self.assertTrue(check_ctlqx(ast)) fsm = self.init_model() solution = { HashableDict({ 'admin': 'alice', 'state': 'waiting' }), HashableDict({ 'admin': 'alice', 'state': 'processing' }) } self.assertCountEqual(bdd_to_set(fsm, solve_ctlqx(fsm, ast)), solution)
def test_ko(self): kos = [ "? <-> 'a'", "'a' <-> ?", "AF ?", "A['a' U ?]", "A['a' W ?]", "A['a' oU ?]", "A['a' oW ?]", "A[? dU 'a']", "A[? dW 'a']", "EX ?", "EF ?", "EG ?", "~AX ?", "~AF ?", "~AG ?", "E['a' U ?]", "E[? U 'a']", "E['a' W ?]", "E[? W 'a']", "E['a' oU ?]", "E[? oU 'a']", "E['a' oW ?]", "E[? oW 'a']", "E['a' dU ?]", "E[? dU 'a']", "E['a' dW ?]", "E[? dW 'a']", "~('a' -> AF ?)", "AF ('a' & AF ('b' | AG ?))", "AX ((AF A[? dU 'b']) | 'a')", "AF A[True oU A[? U 'a']]", "A['a' U A[True oU A[? U 'b']]]", "A['a' W A[True oU A[? U 'b']]]", "A['a' oU A[True oU A[? U 'b']]]", "A['a' oW A[True oU A[? U 'b']]]", "AF AX (A['a' W A[? U 'b']] | False)", "AF A['a' dW A['b' W A[? U 'c']]]", "AF A['a' oW A['b' W A[? U 'c']]]", "AF A[A['a' W A[? U 'b']] oW 'c']", "AF A['a' dU A['b' W A[? U 'c']]]", "AF A['a' oU A['b' W A[? U 'c']]]", "AF A[A['a' W A[? U 'b']] oU 'c']", "AF AX A[AF AX (AG ? & True) U False]", "A['a' U A['b' oU A[A[A['c' dW AG ?] W 'e'] oW 'f']]]", "AF AX ('a' & (('b' & A['c' oW AG ?]) | 'd'))", "AF AX ('a' & (('b' & ('c' | A['d' oW AG ?])) | 'e'))", "AF ('a' & A['b' oW (A['c' oW AG ?] | 'd')])", "AF AX A['b' oW (A['c' oW AG ?] | 'd')]", "AF AX (('a' | A[AG ? U 'b']) & 'c')", "AF ('a' & (A['b' oW (A['c' oW AG ?] | 'd')] | 'e'))", "A['a' U A['b' dW A['c' oU A[AG (AG ? | 'd') U 'e']]]]", "A['a' U A['b' oU A['c' oU A[AG (AG ? | 'd') U 'e']]]]", "A['a' U A[A['b' oU A[AG (AG ? | 'c') U 'd']] oU 'e']]", "A['a' U A['b' dU A['c' oU A[AG (AG ? | 'd') U 'e']]]]", "A['a' U A[A['b' oU A[AG (AG ? | 'c') U 'd']] oW 'e']]", "A['a' U A['b' oW A['c' oU A[AG (AG ? | 'd') U 'e']]]]", "AF A['a' oU A['b' oW A[AG A[AG ? U 'c'] U 'd']]]", "A['a' U AX A['b' U A[AG ? U 'c']]]", "A['a' U AX A['b' W A[AG ? U 'c']]]", "A['a' U AX A['b' dU A[AG ? U 'c']]]", "A['a' U AX A['b' dW A[AG ? U 'c']]]", "A['a' U AX A[A[AG ? U 'b'] U 'c']]", "A['a' U AX A[A[AG ? U 'b'] W 'c']]", "A['a' U AX A[A[AG ? U 'b'] oU 'c']]", "A['a' U AX A[A[AG ? U 'b'] oW 'c']]" ] for ko in kos: self.assertFalse(check_ctlqx(negation_normal_form(parse_ctlq(ko))))
def test_ko(self): kos = ["? <-> 'a'", "'a' <-> ?", "AF ?", "A['a' U ?]", "A['a' W ?]", "A['a' oU ?]", "A['a' oW ?]", "A[? dU 'a']", "A[? dW 'a']", "EX ?", "EF ?", "EG ?", "~AX ?", "~AF ?", "~AG ?", "E['a' U ?]", "E[? U 'a']", "E['a' W ?]", "E[? W 'a']", "E['a' oU ?]", "E[? oU 'a']", "E['a' oW ?]", "E[? oW 'a']", "E['a' dU ?]", "E[? dU 'a']", "E['a' dW ?]", "E[? dW 'a']", "~('a' -> AF ?)", "AF ('a' & AF ('b' | AG ?))", "AX ((AF A[? dU 'b']) | 'a')", "AF A[True oU A[? U 'a']]", "A['a' U A[True oU A[? U 'b']]]", "A['a' W A[True oU A[? U 'b']]]", "A['a' oU A[True oU A[? U 'b']]]", "A['a' oW A[True oU A[? U 'b']]]", "AF AX (A['a' W A[? U 'b']] | False)", "AF A['a' dW A['b' W A[? U 'c']]]", "AF A['a' oW A['b' W A[? U 'c']]]", "AF A[A['a' W A[? U 'b']] oW 'c']", "AF A['a' dU A['b' W A[? U 'c']]]", "AF A['a' oU A['b' W A[? U 'c']]]", "AF A[A['a' W A[? U 'b']] oU 'c']", "AF AX A[AF AX (AG ? & True) U False]", "A['a' U A['b' oU A[A[A['c' dW AG ?] W 'e'] oW 'f']]]", "AF AX ('a' & (('b' & A['c' oW AG ?]) | 'd'))", "AF AX ('a' & (('b' & ('c' | A['d' oW AG ?])) | 'e'))", "AF ('a' & A['b' oW (A['c' oW AG ?] | 'd')])", "AF AX A['b' oW (A['c' oW AG ?] | 'd')]", "AF AX (('a' | A[AG ? U 'b']) & 'c')", "AF ('a' & (A['b' oW (A['c' oW AG ?] | 'd')] | 'e'))", "A['a' U A['b' dW A['c' oU A[AG (AG ? | 'd') U 'e']]]]", "A['a' U A['b' oU A['c' oU A[AG (AG ? | 'd') U 'e']]]]", "A['a' U A[A['b' oU A[AG (AG ? | 'c') U 'd']] oU 'e']]", "A['a' U A['b' dU A['c' oU A[AG (AG ? | 'd') U 'e']]]]", "A['a' U A[A['b' oU A[AG (AG ? | 'c') U 'd']] oW 'e']]", "A['a' U A['b' oW A['c' oU A[AG (AG ? | 'd') U 'e']]]]", "AF A['a' oU A['b' oW A[AG A[AG ? U 'c'] U 'd']]]", "A['a' U AX A['b' U A[AG ? U 'c']]]", "A['a' U AX A['b' W A[AG ? U 'c']]]", "A['a' U AX A['b' dU A[AG ? U 'c']]]", "A['a' U AX A['b' dW A[AG ? U 'c']]]", "A['a' U AX A[A[AG ? U 'b'] U 'c']]", "A['a' U AX A[A[AG ? U 'b'] W 'c']]", "A['a' U AX A[A[AG ? U 'b'] oU 'c']]", "A['a' U AX A[A[AG ? U 'b'] oW 'c']]"] for ko in kos: self.assertFalse(check_ctlqx(negation_normal_form(parse_ctlq(ko))))
def test_ok(self): oks = [ "?", "~?", "? & 'a'", "'a' & ?", "? | 'a'", "'a' | ?", "? -> 'a'", "'a' -> ?", "AX ?", "AG ?", "~EX ?", "~EF ?", "A[? U 'a']", "A[? W 'a']", "A[? oU 'a']", "A[? oW 'a']", "A['a' dW ?]", "A['a' dU ?]", "AX ((AG A['b' dU ?]) | EX 'a')", "~(? -> AF 'a')", "True & A[? U 'a']", "True | A[? U 'a']", "True & A['a' oU A[? U 'b']]", "True | A['a' oU A[? U 'b']]", "AX A[? U 'a']", "AX A['a' oU A[? U 'b']]", "AF A[? U 'a']", "AG A[? U 'a']", "AG A['a' oU A[? U 'b']]", "A[A[? U 'a'] U 'b']", "A[A['a' oU A[? U 'b']] U 'c']", "A['a' U A[? U 'b']]", "A[A[? U 'a'] W 'b']", "A[A['a' oU A[? U 'b']] W 'c']", "A['a' W A[? U 'b']]", "A[A[? U 'a'] oU 'b']", "A[A['a' oU A[? U 'b']] oU 'c']", "A['a' dU A[? U 'b']]", "A['a' dU A['b' oU A[? U 'c']]]", "A[A[? oW 'a'] U 'b']", "A[A[? U 'a'] oW 'b']", "A[A['a' oU A[? U 'b']] oW 'c']", "A['a' oW A[? U 'b']]", "A['a' dW A[? U 'b']]", "A['a' dW A['b' oU A[? U 'c']]]", "A['a' W A['b' W A[? U 'c']]]", "A[A[A[? U 'a'] W 'b'] W 'c']", "A['a' U A['b' W A[? U 'c']]]", "A[A[A[? U 'a'] W 'b'] U 'c']", "AG A['b' W A[? U 'c']]", "AF A['b' W A[? U 'c']]", "A['a' dW A['b' W A['c' U AG AG ?]]]", "AF A['a' oW AG ?]", "A['a' oU A[AG ? oU 'b']]", "A['a' oW A[AG ? oW 'b']]", "A['a' dU A['b' dU A['c' oW AG ?]]]", "AX A['a' oW AG ?]", "AF A['a' oW ('b' | A['c' oW AG ?])]", "AF A[AG ? U False]", "A['a' dW A['b' oW A[A[A['c' oW AG ?] oU 'd'] oW 'e']]]", "A['a' U A['b' oW AG ?]]", "A['a' W A['b' oW AG ?]]", "A['a' oU A['b' oW AG ?]]", "AG A['a' oU A[AG ? U 'b']]", "AG AX AF ('a' | ('b' & AG A['c' oW AG ?]))", "A['a' W A[A['b' U A[AG A['c' oW AG ?] U 'd']] W 'e']]", "A['a' oW A[A['b' oU A[AG A['c' oW AG ?] oU 'd']] oW 'e']]", "A['a' dU A['b' dW AG A['c' oW AG ?]]]", "A['a' dU (AG A['b' dW ('c' | AG ?)] | 'd')]", "A[('a' | AG A['b' oU (AG ? | 'c')]) oU 'd']", "A[('a' | AG A['b' U (AG ? | 'c')]) U 'd']", "A[('a' | AG A['b' W (AG ? | 'c')]) W 'd']", "A['a' W A['b' oU A[A['c' oW AG ?] U 'd']]]", "A[A['a' oW A[A['b' oW AG ?] W 'c']] U 'd']", "A[A['a' oU A[AG ? U 'b']] W 'c']", "A['a' U A['b' oU A[AG ? U 'c']]]" ] for ok in oks: self.assertTrue(check_ctlqx(negation_normal_form(parse_ctlq(ok))))
def test_ok(self): oks = ["?", "~?", "? & 'a'", "'a' & ?", "? | 'a'", "'a' | ?", "? -> 'a'", "'a' -> ?", "AX ?", "AG ?", "~EX ?", "~EF ?", "A[? U 'a']", "A[? W 'a']", "A[? oU 'a']", "A[? oW 'a']", "A['a' dW ?]", "A['a' dU ?]", "AX ((AG A['b' dU ?]) | EX 'a')", "~(? -> AF 'a')", "True & A[? U 'a']", "True | A[? U 'a']", "True & A['a' oU A[? U 'b']]", "True | A['a' oU A[? U 'b']]", "AX A[? U 'a']", "AX A['a' oU A[? U 'b']]", "AF A[? U 'a']", "AG A[? U 'a']", "AG A['a' oU A[? U 'b']]", "A[A[? U 'a'] U 'b']", "A[A['a' oU A[? U 'b']] U 'c']", "A['a' U A[? U 'b']]", "A[A[? U 'a'] W 'b']", "A[A['a' oU A[? U 'b']] W 'c']", "A['a' W A[? U 'b']]", "A[A[? U 'a'] oU 'b']", "A[A['a' oU A[? U 'b']] oU 'c']", "A['a' dU A[? U 'b']]", "A['a' dU A['b' oU A[? U 'c']]]", "A[A[? oW 'a'] U 'b']", "A[A[? U 'a'] oW 'b']", "A[A['a' oU A[? U 'b']] oW 'c']", "A['a' oW A[? U 'b']]", "A['a' dW A[? U 'b']]", "A['a' dW A['b' oU A[? U 'c']]]", "A['a' W A['b' W A[? U 'c']]]", "A[A[A[? U 'a'] W 'b'] W 'c']", "A['a' U A['b' W A[? U 'c']]]", "A[A[A[? U 'a'] W 'b'] U 'c']", "AG A['b' W A[? U 'c']]", "AF A['b' W A[? U 'c']]", "A['a' dW A['b' W A['c' U AG AG ?]]]", "AF A['a' oW AG ?]", "A['a' oU A[AG ? oU 'b']]", "A['a' oW A[AG ? oW 'b']]", "A['a' dU A['b' dU A['c' oW AG ?]]]", "AX A['a' oW AG ?]", "AF A['a' oW ('b' | A['c' oW AG ?])]", "AF A[AG ? U False]", "A['a' dW A['b' oW A[A[A['c' oW AG ?] oU 'd'] oW 'e']]]", "A['a' U A['b' oW AG ?]]", "A['a' W A['b' oW AG ?]]", "A['a' oU A['b' oW AG ?]]", "AG A['a' oU A[AG ? U 'b']]", "AG AX AF ('a' | ('b' & AG A['c' oW AG ?]))", "A['a' W A[A['b' U A[AG A['c' oW AG ?] U 'd']] W 'e']]", "A['a' oW A[A['b' oU A[AG A['c' oW AG ?] oU 'd']] oW 'e']]", "A['a' dU A['b' dW AG A['c' oW AG ?]]]", "A['a' dU (AG A['b' dW ('c' | AG ?)] | 'd')]", "A[('a' | AG A['b' oU (AG ? | 'c')]) oU 'd']", "A[('a' | AG A['b' U (AG ? | 'c')]) U 'd']", "A[('a' | AG A['b' W (AG ? | 'c')]) W 'd']", "A['a' W A['b' oU A[A['c' oW AG ?] U 'd']]]", "A[A['a' oW A[A['b' oW AG ?] W 'c']] U 'd']", "A[A['a' oU A[AG ? U 'b']] W 'c']", "A['a' U A['b' oU A[AG ? U 'c']]]"] for ok in oks: self.assertTrue(check_ctlqx(negation_normal_form(parse_ctlq(ok))))
def test_au(self): ast = negation_normal_form(parse_ctlq("A[? U 'state = processing']")) self.assertTrue(check_ctlqx(ast)) fsm = self.init_model() solution = { HashableDict({ 'admin': 'none', 'state': 'starting' }), HashableDict({ 'admin': 'none', 'state': 'choosing' }), HashableDict({ 'admin': 'alice', 'state': 'waiting' }), HashableDict({ 'admin': 'bob', 'state': 'waiting' }) } self.assertCountEqual(bdd_to_set(fsm, solve_ctlqx(fsm, ast)), solution)
def test_not_placeholder(self): ast = negation_normal_form(parse_ctlq("~?")) self.assertTrue(check_ctlqx(ast)) fsm = self.init_model() self.assertEqual(solve_ctlqx(fsm, ast), fsm.reachable_states - fsm.init)
def test_ax(self): ast = negation_normal_form(parse_ctlq("AX ?")) self.assertTrue(check_ctlqx(ast)) fsm = self.init_model() solution = {HashableDict({'admin': 'none', 'state': 'choosing'})} self.assertCountEqual(bdd_to_set(fsm, solve_ctlqx(fsm, ast)), solution)
def cli(model_path, query, order): """Solve QUERY that belongs to fragment CTLQx for model in MODEL_PATH.""" try: # Parse `query` and transform it in NNF. ast = negation_normal_form(parse_ctlq(query)) # Check that `query` belongs to fragment CTLQx. if not check_ctlqx(ast): click.echo("Error: {query} does not belong to CTLQx".format(query=query)) # Quit PyTLQ. sys.exit() # Initialize NuSMV. with init_nusmv(): # Load model from `model_path`. load(model_path) # Enable dynamic reordering of the variables. enable_dynamic_reordering() # Check if an order file is given. if order: # Build model with pre-calculated variable ordering. compute_model(variables_ordering=order) else: # Build model. compute_model() # Retrieve FSM of the model. fsm = prop_database().master.bddFsm # Solve `query` in `fsm`. solution = solve_ctlqx(fsm, ast) # Display solution. click.echo("Solution states:") if not solution: click.echo("No solution") # Quit PyTLQ. sys.exit() elif solution.is_false(): click.echo("False") # Quit PyTLQ. sys.exit() else: size = fsm.count_states(solution) if size > 100: if click.confirm( "The number of states is too large" " ({size}). Do you still want to print" " them?".format(size=size) ): pprint(bdd_to_set(fsm, solution)) else: pprint(bdd_to_set(fsm, solution)) # Ask for further manipulations. while True: command = click.prompt( "\nWhat do you want to do?" "\n 1. Project the solution on a" " subset of the variables" "\n 2. Simplify the solution according" " to Chan's approximate conjunctive" " decomposition" "\n 3. Quit PyTLQ" "\nYour choice", type=click.IntRange(1, 3), default=3, ) # Check if solution must be projected or simplified. if command == 1 or command == 2: # Gather more information. click.echo("") if command == 2: maximum = click.prompt( "Please enter the maximum" " number of variables that must" " appear in the conjuncts of" " the simplification", type=int, default=1, ) variables = click.prompt( "Please enter the list of" " variables of interest," " separated by commas", type=str, default="all the variables", ) # Format `variables`. if variables == "all the variables": variables = None else: variables = variables.replace(" ", "").split(",") if command == 1: # Project solution and display projection. click.echo("\nProjection:") click.echo(project(fsm, solution, variables)) else: # Simplify solution and display simplification. click.echo("\nApproximate conjunctive decomposition:") click.echo(simplify(fsm, solution, maximum, variables)) # No further manipulations are needed. else: break except Exception as error: click.echo("Error: {msg}".format(msg=error))
def test_ag(self): ast = negation_normal_form(parse_ctlq("AG ?")) self.assertTrue(check_ctlqx(ast)) fsm = self.init_model() self.assertEqual(solve_ctlqx(fsm, ast), fsm.reachable_states)
def test_simplify_all(self): ast = negation_normal_form(parse_ctlq("AG ?")) self.assertTrue(check_ctlqx(ast)) fsm = self.init_model('examples/microwave.smv') simplification = simplify(fsm, solve_ctlqx(fsm, ast)) self.assertEqual('No possible simplification', simplification)
def test_simplify_candidate(self): ast = negation_normal_form(parse_ctlq("?")) self.assertTrue(check_ctlqx(ast)) fsm = self.init_model('examples/short.smv') simplification = simplify(fsm, solve_ctlqx(fsm, ast)) self.assertEqual('(state = ready)', simplification)
def test_project(self): ast = negation_normal_form(parse_ctlq("AG (? -> AF 'heat')")) self.assertTrue(check_ctlqx(ast)) fsm = self.init_model('examples/microwave.smv') projection = project(fsm, solve_ctlqx(fsm, ast), ['error']) self.assertEqual('(error = FALSE)', projection)
def test_ko_not_nnf(self): kos = ["~AX ?", "~EX ?", "~EF ?", "? -> 'a'", "'a' -> ?", "~(? -> AF 'a')"] for ko in kos: self.assertFalse(check_ctlqx(parse_ctlq(ko)))
def test_aou(self): ast = negation_normal_form(parse_ctlq("A[? oU 'state = processing']")) self.assertTrue(check_ctlqx(ast)) fsm = self.init_model() self.assertEqual(solve_ctlqx(fsm, ast), fsm.reachable_states)
def cli(model_path, query, order): """Solve QUERY that belongs to fragment CTLQx for model in MODEL_PATH.""" try: # Parse `query` and transform it in NNF. ast = negation_normal_form(parse_ctlq(query)) # Check that `query` belongs to fragment CTLQx. if not check_ctlqx(ast): click.echo('Error: {query} does not belong to CTLQx' .format(query=query)) # Quit PyTLQ. sys.exit() # Initialize NuSMV. with init_nusmv(): # Load model from `model_path`. load(model_path) # Enable dynamic reordering of the variables. enable_dynamic_reordering() # Check if an order file is given. if order: # Build model with pre-calculated variable ordering. compute_model(variables_ordering=order) else: # Build model. compute_model() # Retrieve FSM of the model. fsm = prop_database().master.bddFsm # Solve `query` in `fsm`. solution = solve_ctlqx(fsm, ast) # Display solution. click.echo('Solution states:') if not solution: click.echo('No solution') # Quit PyTLQ. sys.exit() elif solution.is_false(): click.echo('False') # Quit PyTLQ. sys.exit() else: size = fsm.count_states(solution) if size > 100: if click.confirm('The number of states is too large' ' ({size}). Do you still want to print' ' them?'.format(size=size)): pprint(bdd_to_set(fsm, solution)) else: pprint(bdd_to_set(fsm, solution)) # Ask for further manipulations. while True: command = click.prompt('\nWhat do you want to do?' '\n 1. Project the solution on a' ' subset of the variables' '\n 2. Simplify the solution according' ' to Chan\'s approximate conjunctive' ' decomposition' '\n 3. Quit PyTLQ' '\nYour choice', type=click.IntRange(1, 3), default=3) # Check if solution must be projected or simplified. if command == 1 or command == 2: # Gather more information. click.echo('') if command == 2: maximum = click.prompt('Please enter the maximum' ' number of variables that must' ' appear in the conjuncts of' ' the simplification', type=int, default=1) variables = click.prompt('Please enter the list of' ' variables of interest,' ' separated by commas', type=str, default='all the variables') # Format `variables`. if variables == 'all the variables': variables = None else: variables = variables.replace(" ", "").split(',') if command == 1: # Project solution and display projection. click.echo('\nProjection:') click.echo(project(fsm, solution, variables)) else: # Simplify solution and display simplification. click.echo('\nApproximate conjunctive decomposition:') click.echo(simplify(fsm, solution, maximum, variables)) # No further manipulations are needed. else: break except Exception as error: click.echo('Error: {msg}'.format(msg=error))