def make_duplicate_rules(self): """make_duplicate_rules create the list of rules asserting that each row, column and block cannot containn duplicates.""" rules = [] # All elements in a row must be distinct for i in range(9): rule = Terms.distinct([self.var(i, j) for j in range(9)]) self.explanation[rule] = f'Row {i + 1} cannot contain duplicates' rules.append(rule) # All elements in a column must be distinct for i in range(9): rule = Terms.distinct([self.var(j, i) for j in range(9)]) # pylint: disable=W1114 self.explanation[ rule] = f'Column {i + 1} cannot contain duplicates' rules.append(rule) # All elements in each 3x3 square must be distinct def block(row, column): rname = {0: 'Top', 1: 'Middle', 2: 'Bottom'} cname = {0: 'left', 1: 'center', 2: 'right'} return f'{rname[row]}-{cname[column]}' for row in range(3): for column in range(3): rule = Terms.distinct([ self.var(i + 3 * row, j + 3 * column) for i in range(3) for j in range(3) ]) self.explanation[ rule] = f'{block(row,column)} block cannot contain duplicates' rules.append(rule) return rules
def test_error(self): Yices.reset() # First with no error errcode = Yices.error_code() self.assertEqual(errcode, 0) errep = Yices.error_report() self.assertEqual(errep.code, 0) Yices.clear_error() errstr = Yices.error_string() self.assertEqual(errstr, 'no error') Yices.print_error(1) # Illegal - only scalar or uninterpreted types allowed bool_t = Types.bool_type() self.assertTrue(Types.is_bool(bool_t)) with assertRaisesRegex( self, YicesException, 'The function yices_constant failed because: invalid type in constant creation' ): Terms.constant(bool_t, 0) Yices.clear_error() errpt = Yices.error_report() self.assertEqual(Yices.error_code(), 0) self.assertEqual(Yices.error_code(), errpt.code) errstr = Yices.error_string() self.assertEqual(errstr, 'no error') Yices.print_error(1) Yices.clear_error() self.assertEqual(Yices.error_code(), 0)
def unsat_core(self): retcode = 2 if not self.addModel(): sys.stderr.write('addModel failed\n') return retcode self.protocol.constrainVariablesAPI(self.context) smt_stat = self.context.check_context() #sys.stderr.write('context.check_context returned {0}\n'.format(Status.name(smt_stat))) if smt_stat == Status.UNSAT: sys.stderr.write('The model is UNSAT: something is rotten in the State\n') retcode = 1 return retcode assertions = self.protocol.toYicesTerms() alen = len(assertions) if alen: smt_stat = self.context.check_context_with_assumptions(None, assertions) sys.stderr.write('context.check_context_with_assumptions returned {0}\n'.format(Status.name(smt_stat))) if smt_stat == Status.SAT: sys.stderr.write('Calling unsat_core on a satisfiable theory is user error\n') retcode = 1 elif smt_stat == Status.UNSAT: unsat_core = self.context.get_unsat_core() if not unsat_core: sys.stderr.write('unsat_core is empty\n') else: for term in unsat_core: Terms.print_to_fd(1, term, 80, 100, 0) else: retcode = 1 retcode = 0 self._cleanUp() return retcode
def test_bv_models(self): bv_t = Types.bv_type(3) bv1 = define_const('bv1', bv_t) bv2 = define_const('bv2', bv_t) bv3 = define_const('bv3', bv_t) fmla1 = Terms.parse_term('(= bv1 (bv-add bv2 bv3))') fmla2 = Terms.parse_term('(bv-gt bv2 0b000)') fmla3 = Terms.parse_term('(bv-gt bv3 0b000)') self.ctx.assert_formula(fmla1) self.ctx.assert_formulas([fmla1, fmla2, fmla3]) self.assertEqual(self.ctx.check_context(self.param), Status.SAT) mdl1 = Model.from_context(self.ctx, 1) val1 = mdl1.get_value(bv1) self.assertEqual(val1[0], 0) self.assertEqual(val1[1], 0) self.assertEqual(val1[2], 0) val2 = mdl1.get_value(bv2) self.assertEqual(val2[0], 0) self.assertEqual(val2[1], 0) self.assertEqual(val2[2], 1) val3 = mdl1.get_value(bv3) self.assertEqual(val3[0], 0) self.assertEqual(val3[1], 0) self.assertEqual(val3[2], 1) mdl1.dispose()
def test_bool_models(self): b1 = define_const('b1', bool_t) b2 = define_const('b2', bool_t) b3 = define_const('b3', bool_t) b_fml1 = Terms.parse_term('(or b1 b2 b3)') self.ctx.assert_formula(b_fml1) self.assertEqual(self.ctx.check_context(self.param), Status.SAT) b_mdl1 = Model.from_context(self.ctx, 1) self.assertNotEqual(b_mdl1, None) bval1 = b_mdl1.get_bool_value(b1) bval2 = b_mdl1.get_bool_value(b2) bval3 = b_mdl1.get_bool_value(b3) self.assertEqual(bval1, False) self.assertEqual(bval2, False) self.assertEqual(bval3, True) b_fmla2 = Terms.parse_term('(not b3)') self.ctx.assert_formula(b_fmla2) self.assertEqual(self.ctx.check_context(self.param), Status.SAT) b_mdl1 = Model.from_context(self.ctx, 1) self.assertNotEqual(b_mdl1, None) bval1 = b_mdl1.get_bool_value(b1) bval2 = b_mdl1.get_bool_value(b2) bval3 = b_mdl1.get_bool_value(b3) val1 = b_mdl1.get_value(b1) val2 = b_mdl1.get_value(b2) val3 = b_mdl1.get_value(b3) self.assertEqual(bval1, False) self.assertEqual(bval2, True) self.assertEqual(bval3, False) self.assertEqual(bval1, val1) self.assertEqual(bval2, val2) self.assertEqual(bval3, val3)
def entire(self): retcode = 2 if not self.addModel(): return retcode self.protocol.constrainVariablesAPI(self.context) searchSpace = Frontier(self.protocol) tower = list(searchSpace.tower()) tower.sort() for point in tower: print(point) assertions = self.protocol.toYicesTerms(point) alen = len(assertions) if alen: smt_stat = self.context.check_context_with_assumptions(None, assertions) if smt_stat == Status.SAT: print('SAT') #get the model model = Model.from_context(self.context, 1) self.print_solution(model, header=None, frees=self.protocol.FV(point)) model.dispose() elif smt_stat == Status.UNSAT: print('UNSAT') unsat_core = self.context.get_unsat_core() print("\nUnsat core:\n") for term in unsat_core: Terms.print_to_fd(1, term, 80, 100, 0) print("\n") else: sys.stderr.write('context.check_context_with_assumptions returned {0}\n', smt_stat) self._cleanUp() return retcode
def toYicesTerms(self, ts): retval = [] for botname in self.bots: (bot_pt, bot_ts) = self.bot_state[botname] all_other_pts = self.pts if bot_ts == ts: all_other_pts = [ pt for pt in self.pts if pt != bot_pt] for ptname in all_other_pts: atloc_term = YicesSignature.mkatloc(botname, ptname, self.current_timestamp) assert atloc_term is not None not_atloc_term = Terms.ynot(atloc_term) retval.append(not_atloc_term) for ptname in self.pts: (pt_stage, pt_ts) = self.pt_state[ptname] all_other_stages = self.stages if pt_ts == ts: all_other_stages = [ stage for stage in self.stages if stage != pt_stage ] #Semantics.assert_not_treatstages(context, ptname, all_other_stages, self.current_timestamp) for stage in all_other_stages: treatstage_term = YicesSignature.mktreatstage(ptname, stage, self.current_timestamp) assert treatstage_term is not None not_treatstage_term = Terms.ynot(treatstage_term) retval.append(not_treatstage_term) return retval
def yassert_formula(solver, formula): if DEBUG: Terms.print_to_fd(1, formula, 120, 40, 0) errcode = solver.context.assert_formula(formula) if DEBUG: smt_stat = solver.context.check_context() if smt_stat != Status.SAT: print('Not SAT: {0}\n'.format(smt_stat)) return errcode
def yassert_formulas(solver, formulas): if DEBUG: for formula in formulas: Terms.print_to_fd(1, formula, 120, 40, 0) errcode = solver.context.assert_formulas(formulas) if DEBUG: smt_stat = solver.context.check_context() if smt_stat != Status.SAT: print(f'Not SAT: {smt_stat}\n') return errcode
def missing(self): retcode = 2 if not self.addModel(): sys.stderr.write('addModel failed\n') return retcode self.protocol.constrainVariablesAPI(self.context) timelines = self.protocol.timelines tlen = len(timelines) interpretation = Configuration.timeline_interpretation tint_text = Configuration.INTERPRETATIONS[interpretation] print('There are {0} timelines ({1})\n'.format(tlen, tint_text)) if not timelines: return retcode for timeline in timelines: formula = timeline.toYicesTerm(interpretation) yassert_formula(self, formula) smt_stat = self.context.check_context() if smt_stat == Status.UNSAT: sys.stderr.write('The model is UNSAT: something is rotten in the State\n') retcode = 1 return retcode for timeline in timelines: satisfiable = True yevents = [ ] for eindex, timevar in enumerate(timeline.varlist): events = self.protocol.event_map[timevar] for e in events: yevents.append(e.yices_term) smt_stat = self.context.check_context_with_assumptions(None, yevents) if smt_stat == Status.UNSAT: satisfiable = False print('UNSAT at level {0}, i.e. timestamp {1}'.format(eindex, timevar)) unsat_core = self.context.get_unsat_core() print("\nUnsat core :\n") for term in unsat_core: Terms.print_to_fd(1, term, 80, 100, 0) print("\n") break elif self.verbose and smt_stat == Status.SAT: print('SAT at level {0}, i.e. timestamp {1}'.format(eindex, timevar)) model = Model.from_context(self.context, 1) self.print_solution(model, header=None, frees=timeline.fv) model.dispose() if satisfiable: print('Timeline of {0} OK ({1}):\n'.format(timeline.term, tint_text)) #print a model model = Model.from_context(self.context, 1) self.print_solution(model, header=None, frees=timeline.fv) model.dispose() retcode = 0 self._cleanUp() return retcode
def test_model_from_map(self): bv_t = Types.bv_type(8) i1 = define_const('i1', int_t) r1 = define_const('r1', real_t) bv1 = define_const('bv1', bv_t) iconst1 = Terms.integer(42) rconst1 = Terms.rational(13, 131) bvconst1 = Terms.bvconst_integer(8, 134) mapping = {i1: iconst1, r1: rconst1, bv1: bvconst1} mdl = Model.from_map(mapping) mdlstr = mdl.to_string(80, 100, 0) self.assertEqual(mdlstr, '(= i1 42)\n(= r1 13/131)\n(= bv1 0b10000110)') mdl.dispose()
def make_variables(): """make_variables creates a 9x9 grid with each cell containing the Yices term representing that cell.""" variables = make_grid() for i in range(9): for j in range(9): variables[i][j] = Terms.new_uninterpreted_term(int_t) return variables
def test_implicant(self): i1 = define_const('i1', int_t) assert_formula('(and (> i1 2) (< i1 8) (/= i1 4))', self.ctx) self.assertEqual(self.ctx.check_context(self.param), Status.SAT) mdl = Model.from_context(self.ctx, 1) mdlstr = mdl.to_string(80, 100, 0) self.assertEqual(mdlstr, '(= i1 7)') fml = Terms.parse_term('(>= i1 3)') tarray = mdl.implicant_for_formula(fml) self.assertEqual(len(tarray), 1) implstr = Terms.to_string(tarray[0], 200, 10, 0) self.assertEqual(implstr, '(>= (+ -3 i1) 0)') fml2 = Terms.parse_term('(<= i1 9)') tarray2 = mdl.implicant_for_formulas([fml, fml2]) self.assertEqual(len(tarray2), 2) implstr2 = Terms.to_string(tarray2[0], 200, 10, 0) self.assertEqual(implstr2, '(>= (+ -3 i1) 0)') implstr3 = Terms.to_string(tarray2[1], 200, 10, 0) self.assertEqual(implstr3, '(>= (+ 9 (* -1 i1)) 0)')
def test_scalar_models(self): scalar_t = Types.new_scalar_type(10) sc1 = define_const('sc1', scalar_t) sc2 = define_const('sc2', scalar_t) sc3 = define_const('sc3', scalar_t) assert_formula('(/= sc1 sc2)', self.ctx) assert_formula('(/= sc1 sc3)', self.ctx) self.assertEqual(self.ctx.check_context(self.param), Status.SAT) mdl = Model.from_context(self.ctx, 1) val1 = mdl.get_scalar_value(sc1) val2 = mdl.get_scalar_value(sc2) val3 = mdl.get_scalar_value(sc3) self.assertEqual(val1, 9) self.assertEqual(val2, 8) self.assertEqual(val3, 8) self.assertEqual(Terms.is_scalar(sc1), True) sc1val = mdl.get_value_as_term(sc1) self.assertEqual(Terms.is_scalar(sc1val), True) self.assertEqual(mdl.get_value(sc1), sc1val)
def test_model_support(self): x = define_const('x', real_t) y = define_const('y', real_t) z = define_const('z', real_t) formula = Terms.parse_term('(> x 0)') t0 = Terms.parse_term('(ite (> x 0) (+ x z) y)') t1 = Terms.parse_term('(+ (* x z) y)') self.ctx.assert_formula(formula) self.assertEqual(self.ctx.check_context(), Status.SAT) mdl = Model.from_context(self.ctx, 1) support = mdl.support_for_term(t0) self.assertEqual(len(support), 2) self.assertEqual(support[0], x) self.assertEqual(support[1], z) support = mdl.support_for_terms([t0, t1]) self.assertEqual(len(support), 3) self.assertEqual(support[0], x) self.assertEqual(support[1], y) self.assertEqual(support[2], z)
def incrementally_solve(self): retcode = 2 if not self.addModel(): return retcode self.protocol.constrainVariablesAPI(self.context) if self.context.status() == Status.UNSAT: print('The model appears to be UNSAT: smt_stat = {}\n'.format(self.context.status())) print('For more information use the --consistency option.\n') return retcode # The meaning of the levels: # # 0. Just the events # 1. The events plus the distinctness of each timeline: distinct(varlist) for timeline(bot, varlist) in facts. # 2. The events plus the ascendingness of each timeline for level in range(0, 4): #print('context status: ', Status.name(self.context.status())) self.context.push() assertions = self.getProtocolIncrementally(level) alen = len(assertions) if alen: smt_stat = self.context.check_context_with_assumptions(None, assertions) sys.stderr.write('Level {0}: context.check_context_with_assumptions returned {1}\n'.format(level, Status.name(smt_stat))) if smt_stat == Status.SAT: #get the model model = Model.from_context(self.context, 1) print('Level {0} has a solution: smt_stat = {1}\n'.format(level, smt_stat)) self.print_solution(model) model.dispose() elif smt_stat == Status.UNSAT: unsat_core = self.context.get_unsat_core() if not unsat_core: sys.stderr.write('unsat_core is empty\n') else: print('Level {0} unsat core is:\n'.format(level)) for term in unsat_core: Terms.print_to_fd(1, term, 80, 100, 0) print('') return 0 else: sys.stderr.write('context.check_context_with_assumptions returned {0}\n'.format(Status.name(smt_stat))) self.context.pop() self._cleanUp() return 0
def test_timeout(self): cfg = Config() cfg.default_config_for_logic('QF_NIA') ctx = Context(cfg) int_t = Types.int_type() [x, y, z] = [ Terms.new_uninterpreted_term(int_t, id) for id in ['x', 'y', 'z'] ] # x, y, z > 0 for var in [x, y, z]: ctx.assert_formula(Terms.arith_gt0_atom(var)) # x^3 + y^3 = z3 [x3, y3, z3] = [Terms.product([var, var, var]) for var in [x, y, z]] lhs = Terms.sum([x3, y3]) eq = Terms.arith_eq_atom(lhs, z3) ctx.assert_formula(eq) status = ctx.check_context(timeout=1) self.assertEqual(status, Status.INTERRUPTED) ctx.dispose() cfg.dispose()
def frontier(self): retcode = 2 if not self.addModel(): return retcode self.protocol.constrainVariablesAPI(self.context) searchSpace = Frontier(self.protocol) interpretation = Configuration.timeline_interpretation tint_text = Configuration.INTERPRETATIONS[interpretation] print('The timeline interpretation is: {0}\n'.format(tint_text)) while not searchSpace.finished(): point = searchSpace.nextElement() if point is None: break if self.verbose: print(point) assertions = self.protocol.toYicesTerms(point) alen = len(assertions) if alen: smt_stat = self.context.check_context_with_assumptions(None, assertions) if smt_stat == Status.SAT: if self.verbose: print('SAT') #get the model model = Model.from_context(self.context, 1) self.print_solution(model, header=None, frees=self.protocol.FV(point)) model.dispose() elif smt_stat == Status.UNSAT: searchSpace.addUnsat(point) if self.verbose: print('UNSAT') unsat_core = self.context.get_unsat_core() print("\nUnsat core:\n") for term in unsat_core: Terms.print_to_fd(1, term, 80, 100, 0) print("\n") else: sys.stderr.write('context.check_context_with_assumptions returned {0}\n', smt_stat) print('Frontier: {0}'.format(searchSpace.frontier)) self._cleanUp() return retcode
def consistency_check(self): assertions = self.diagram.toYicesTerms(point=None, completeMe=True) alen = len(assertions) if alen: smt_stat = self.context.check_context_with_assumptions(None, assertions) sys.stderr.write('context.check_context_with_assumptions returned {0}\n'.format(Status.name(smt_stat))) if smt_stat == Status.SAT: #sys.stderr.write('Calling unsat_core on a satisfiable theory is user error\n') retcode = 0 elif smt_stat == Status.UNSAT: unsat_core = self.context.get_unsat_core() if not unsat_core: sys.stderr.write('unsat_core is empty\n') else: print("\nUnsat core:\n") for term in unsat_core: Terms.print_to_fd(1, term, 80, 100, 0) print("\n") else: retcode = 1 retcode = 0 self._cleanUp() return retcode
def define_const(name, ytype, defn=None): '''Tries to emulate yices define_term (see eval_define_term in yices2/src/parser_utils/term_stack2) ''' if defn is None: term = Terms.new_uninterpreted_term(ytype) Terms.set_name(term, name) return term # Have a defn if isstr(defn): term = Terms.parse_term(defn) else: term = defn term_type = Terms.type_of_term(term) if not Types.is_subtype(term_type, ytype): raise YicesException(msg='incompatible sort in definition') Terms.set_name(term, name) return term
def exhaust(self): result = 0 self.context.push() if not self.addFacts(): print('Bummer') return 2 # BD says: this is complete but crude # another way would be to use 'yices_assert_blocking_clause' while self.context.check_context() == Status.SAT: model = Model.from_context(self.context, 1) self.print_solution(model, 'Model #{0}:'.format(result)) diagram = YicesSignature.model2term(model) self.context.assert_formula(Terms.ynot(diagram)) model.dispose() result += 1 if result == Configuration.aleph_nought: break self.context.pop() print("I found {1} distinct model{2} (using an upper limit of {0})".format(Configuration.aleph_nought, result, "" if result == 1 else "s")) return result
def get_type_element_as_yices_term(i, yices_type): if yices_type == SymbolTable.TIME: return Terms.integer(i) if yices_type == SymbolTable.PT2: return Terms.get_by_name(SymbolTable.pt2_name(i)) if yices_type == SymbolTable.XAXIS: return Terms.integer(i) if yices_type == SymbolTable.YAXIS: return Terms.integer(i) if yices_type == SymbolTable.STAGE: return Terms.integer(i) if yices_type == SymbolTable.BINDEX: return Terms.get_by_name(SymbolTable.bot_name(i)) if yices_type == SymbolTable.OBINDEX: return Terms.get_by_name( SymbolTable.obs_name(i)) # could do all enums like this sys.stderr.write( f'SymbolTable.get_type_element_as_string: unrecognized type {yices_type}\n' ) return None
def test_context(self): cfg = Config() ctx = Context(cfg) stat = ctx.status() ret = ctx.push() ret = ctx.pop() ctx.reset_context() ret = ctx.enable_option("arith-elim") ret = ctx.disable_option("arith-elim") stat = ctx.status() self.assertEqual(stat, 0) ctx.reset_context() bool_t = Types.bool_type() bvar1 = Terms.new_variable(bool_t) with assertRaisesRegex(self, YicesException, 'assertion contains a free variable'): ctx.assert_formula(bvar1) bv_t = Types.bv_type(3) bvvar1 = Terms.new_uninterpreted_term(bv_t, 'x') bvvar2 = Terms.new_uninterpreted_term(bv_t, 'y') bvvar3 = Terms.new_uninterpreted_term(bv_t, 'z') fmla1 = Terms.parse_term('(= x (bv-add y z))') fmla2 = Terms.parse_term('(bv-gt y 0b000)') fmla3 = Terms.parse_term('(bv-gt z 0b000)') ctx.assert_formula(fmla1) ctx.assert_formulas([fmla1, fmla2, fmla3]) smt_stat = ctx.check_context(None) self.assertEqual(smt_stat, Status.SAT) ctx.assert_blocking_clause() ctx.stop_search() param = Parameters() param.default_params_for_context(ctx) param.set_param("dyn-ack", "true") with assertRaisesRegex(self, YicesException, 'invalid parameter'): param.set_param("foo", "bar") with assertRaisesRegex(self, YicesException, 'value not valid for parameter'): param.set_param("dyn-ack", "bar") param.dispose() ctx.dispose()
from yices.Context import Context from yices.Status import Status from yices.Model import Model from yices.Yices import Yices int_t = Types.int_type() #seems logical to make the terms in a grid. X = [None] * 9 for i in range(9): X[i] = [None] * 9 for j in range(9): X[i][j] = Terms.new_uninterpreted_term(int_t) #not real happy about the indexing going from 0 to 8, but #isolating access via V could make it easier to go from 1 to 9 def V(vi, vj): return X[vi][vj] #make the constants that we will need C = {} for i in range(1, 10): C[i] = Terms.integer(i) one = C[1] nine = C[9]
def test_dimacs(self): formulas = make_formulas() bound = len(formulas) + 1 simplified = [None] * bound # first round, don't simplify the CNF for i in range(1, bound): simplify = False filename = f'/tmp/basic{1}.cnf' terms = truncate(formulas, i) file_ok, status = Dimacs.export_formulas(terms, filename, simplify) notify( f'Round 1: {file_ok}, {Status.name(status)} = export@{i}({terms}, {filename}, {simplify})\n' ) if file_ok: self.assertEqual(os.path.exists(filename), True) else: self.assertEqual(status in [Status.SAT, Status.UNSAT], True) term = Terms.yand(terms) file_ok_c, status_c = Dimacs.export_formula( term, filename, simplify) notify( f'Round 1: {file_ok_c}, {Status.name(status_c)} = export@{i}({term}, {filename}, {simplify})\n' ) # second round, simplify the CNF for i in range(1, bound): simplify = True filename = f'/tmp/simplify{i}.cnf' terms = truncate(formulas, i) file_ok, status = Dimacs.export_formulas(terms, filename, simplify) # save the status for later simplified[i] = status notify( f'Round 2: {file_ok}, {Status.name(status)} = export@{i}({terms}, {filename}, {simplify})\n' ) if file_ok: self.assertEqual(os.path.exists(filename), True) else: self.assertEqual(status in [Status.SAT, Status.UNSAT], True) term = Terms.yand(terms) file_ok_c, status_c = Dimacs.export_formula( term, filename, simplify) notify( f'Round 2: {file_ok_c}, {Status.name(status_c)} = export@{i}({term}, {filename}, {simplify})\n' ) self.assertEqual(status_c, simplified[i]) # third round check the results for i in range(1, bound): config = Config() config.default_config_for_logic("QF_BV") context = Context(config) terms = truncate(formulas, i) context.assert_formulas(terms) status = context.check_context() notify(f'Round 3: status = {Status.name(status)} for i = {i}\n') self.assertEqual(status, simplified[i]) config.dispose() context.dispose()
def assert_formula(formula, ctx): if isstr(formula): formula = Terms.parse_term(formula) ctx.assert_formula(formula)
def make_formulas(): tau = Types.bv_type(20) x0 = Terms.new_uninterpreted_term(tau, "x") y0 = Terms.new_uninterpreted_term(tau, "y") z0 = Terms.new_uninterpreted_term(tau, "z") f0 = Terms.bveq_atom(Terms.bvmul(x0, y0), Terms.bvconst_integer(20, 12289)) f1 = Terms.bveq_atom(Terms.bvmul(y0, z0), Terms.bvconst_integer(20, 20031)) f2 = Terms.bveq_atom(Terms.bvmul(x0, z0), Terms.bvconst_integer(20, 10227)) return [f0, f1, f2]
def set_value(ctx, position, value): (row, column) = position assert 1 <= row <= 9 assert 1 <= column <= 9 assert 1 <= value <= 9 ctx.assert_formula(Terms.arith_eq_atom(V(row - 1, column - 1), C[value]))
def conjoin(formulas, n): return Terms.yand(truncate(formulas, n))
def between_1_and_9(x): return Terms.yand( [Terms.arith_leq_atom(one, x), Terms.arith_leq_atom(x, nine)])