def _verify(P, A): # eg. for dReal import z3 from src.sympy_converter import sympy_converter from src.linear import f0, f1 from src.common import OrNotZero eigvals = sp.Matrix(P).eigenvals() #log("eig %s" % eigvals) try: if all(sp.re(l) > 0 for (l, m) in eigvals.items()): # eig P > 0 return True else: return False except: pass x = sp.Matrix([sp.Symbol('x%s' % i) for i in range(P.shape[0])]) x3 = [z3.Real('x%s' % i) for i in range(P.shape[0])] _, _, V = sympy_converter(f0(x, x.T, P)) _, _, Vd = sympy_converter(f1(x, x.T, P, A)) s = z3.Solver() s.add(z3.Not(z3.Implies(OrNotZero(x3), z3.And(V > 0, Vd < 0)))) r = s.check() #if r == z3.sat: #log("_verify has model %s\n\t against V = %s ; Vd = %s" % (s.model(), V, Vd)) return r == z3.unsat
def _subs(self, e, e_sym, model): try: return model.evaluate(e) except: return convert.sympy_converter(e_sym.subs({ self._xs_sym_map[str(k)]: v for k, v in izip(self.xs, model) }))[-1]
def check(self, P): """ :param P: matrix :return: unsat if there have been found no counterexamples, sat if there have been found and unknown otherwise """ self._solver = Solver() self._set_timeout() log("Verifier got P %s" % P) V, Vd = self._V, self._Vd for i in range(len(self._P_sym)): V = V.subs({ self._P_sym[i][r, c]: P[i][r, c] for r in range(self.n) for c in range(self.n) }) Vd = Vd.subs({ self._P_sym[i][r, c]: P[i][r, c] for r in range(self.n) for c in range(self.n) }) _, _, V = sympy_converter(V.doit()[0]) _, _, Vd = sympy_converter(Vd.doit()[0]) if V == 0 or Vd == 0: log("error: P=0") return sat fml = Implies(OrNotZero(self._xs_z3), And(V >= 0, Vd <= 0)) res, c0 = self._prove(fml, exp=0) if res == sat: self._add_counterexample(c0) self._solver.push() # keep fml self._generate_counterexamples(c0 is not None) self._solver.pop() return res
def _add_constraint(self, counterexample): for V_sym, Vd_sym in izip(self.V_list_sym, self.Vd_list_sym): V = V_sym.subs( {x: c for x, c in izip(self._xs_sym, counterexample)}) Vd = Vd_sym.subs( {x: c for x, c in izip(self._xs_sym, counterexample)}) _, __, V = convert.sympy_converter(V, var_map=self.P_map, target=convert.TARGET_SOLVER) _, __, Vd = convert.sympy_converter(Vd, var_map=self.P_map, target=convert.TARGET_SOLVER) try: self.model.addConstr(V >= consts.ZERO) self.model.addConstr(Vd <= -consts.ZERO) self.model.update() except Exception as e: log('Could not add counterexample: %s. (V:%s, Vd:%s). Exception: %s' % (counterexample, V, Vd, e))
def __init__(self, params): assert is_valid_matrix_A(params.A) self.A = params.A self.n = params.A.shape[0] self.iter = -1 self.solution = None self.xs = params.xs self.P_sym = params.P self.P_z3 = [] # flat array of P Z3's Real self._solver = Solver() self.vars_map = {} self._V_sym = params.V.doit()[0] self._Vd_sym = params.Vd.doit()[0] self._Vd_diag_sym = params.Vd_diag.doit()[0] # self._Vd_list = params.Vd_list self._Vd_diag_list = params.Vd_diag_list # the following lines impose a diagonal P if diag = True # or a full P if diag = False diag = True # is_diagonal(params.A) for P in params.P: for r in range(self.n): for c in range(self.n): name = str(P[r, c]) p = Real(name) if diag and r != c: # self.P_sym[r, c] = 0 self._solver.add(p == 0) self._V_sym = self._V_sym.subs(P[r, c], 0) self._Vd_sym = self._Vd_sym.subs(P[r, c], 0) self._Vd_diag_sym = self._Vd_diag_sym.subs(P[r, c], 0) if name not in self.vars_map and (not diag or r == c): self.vars_map[name] = p self.P_z3.append(p) # this prevents a whole P_i matrix to be null if len(self.P_sym) > 1: self._P_not_zero = True n = len(self.A[0,:]) for l in range(len(self.P_sym)): c = Or( *( self.P_z3[l*n+i] != 0 for i in range(n) ) ) self._P_not_zero = And(c, self._P_not_zero) else: self._P_not_zero = Or(*(p != 0 for p in self.P_z3)) self._xs_sym_map = {str(_x): _x for _x in self.xs} _, __, self.V_z3expr = convert.sympy_converter(self._V_sym, var_map=self.vars_map) _, __, self.Vd_z3expr = convert.sympy_converter(self._Vd_sym, var_map=self.vars_map) _, __, self.Vd_diag_z3expr = convert.sympy_converter(self._Vd_diag_sym, var_map=self.vars_map) #self.Vd_list_z3expr = [] self.Vd_diag_list_z3expr = [] for l in range(len(self._Vd_diag_list)): # _, __, temp = convert.sympy_converter(self._Vd_list[l][0], var_map=self.vars_map) # self.Vd_list_z3expr += [temp] _, __, temp2 = convert.sympy_converter(self._Vd_diag_list[l][0], var_map=self.vars_map) self.Vd_diag_list_z3expr += [temp2] self._multiply_by_inv_min = False self._close_diagonal_by = 0 self._lb, self._ub = None, None self._min_sum, self._max_sum = None, None self._timeout = float('inf') self._has_timedout = False self._error = False self._set_solver()
def solve(self): S = [] for idx in range(self.batch_size): s = torch.normal(0, self.outer / 3, size=torch.Size([self.n])).double() # if inner_radius < torch.norm(s) < outer_radius: S.append(s) Sdot = list(map(torch.tensor, map(self.f, S))) S, Sdot = torch.stack(S), torch.stack(Sdot) if self.learner_type == LearnerType.NN: self.optimizer = torch.optim.AdamW(self.learner.parameters(), lr=self.learning_rate) stats = {} # the CEGIS loop iters = 0 stop, found = False, False start = timeit.default_timer() # while not stop: print_section('Learning', iters) if self.learner_type == LearnerType.NN: learned = self.learner.learn(self.optimizer, S, Sdot, self.lf) # to disable rounded numbers, set rounding=-1 x_sp = [sp.Symbol('x%d' % i) for i in range(len(self.x))] V_s, Vdot_s = get_symbolic_formula(self.learner, self.x, self.f(x_sp), self.eq, rounding=3, lf=self.lf) V_s, Vdot_s = sp.simplify(V_s), sp.simplify(Vdot_s) V = sympy_converter(V_s, var_map=self.x_map, target=type(self.verifier)) Vdot = sympy_converter(Vdot_s, var_map=self.x_map, target=type(self.verifier)) if self.verifier == Z3Verifier: V, Vdot = z3.simplify(V), z3.simplify(Vdot) else: P = self.learner.learn(S.numpy().T, Sdot.numpy().T) # might modify get_symbolic_formula to work with x*P*x Lyapunov candidate... V, Vdot = self.learner.get_poly_formula(self.x, self.xdot, P) print_section('Candidate', iters) print(f'V: {V_s}') print(f'Vdot: {Vdot_s}') print_section('Verification', iters) found, ces = self.verifier.verify(V, Vdot) if self.max_cegis_iter == iters: print('Out of Cegis loops') stop = True if found: print('Found a Lyapunov function, baby!') stop = True else: iters += 1 if len(ces) > 0: S, Sdot = self.add_ces_to_data(S, Sdot, ces) # the original ctx is in the last row of ces trajectory = self.trajectoriser(ces[-1]) S, Sdot = self.add_ces_to_data(S, Sdot, trajectory) print('Learner times: {}'.format(self.learner.get_timer())) print('Verifier times: {}'.format(self.verifier.get_timer())) return self.learner, found, iters