This commit is contained in:
Ken McMillan 2017-06-27 18:40:38 -07:00
Родитель fe9c6fb64f
Коммит 00f13ae469
12 изменённых файлов: 382 добавлений и 88 удалений

Просмотреть файл

@ -515,6 +515,12 @@ class ProofDecl(Decl):
def name(self):
return 'proof'
class NamedDecl(Decl):
def name(self):
return 'named'
def defines(self):
return [(c.rep,lineno(c)) for c in self.args]
class SchemaDecl(Decl):
def name(self):
return 'schema'

Просмотреть файл

@ -95,6 +95,8 @@ def check_module():
missing = []
for x,y in im.module.isolates.iteritems():
print 'isolate= {}:{} '.format(x,y)
isolate = ivy_compiler.isolate.get()
if isolate != None:
isolates = [isolate]

Просмотреть файл

@ -549,9 +549,10 @@ def compile_schema_prem(self,sig):
def compile_schema_conc(self,sig):
iu.dbg('type(self)')
iu.dbg('self')
if isinstance(self,ivy_ast.Definition):
with ivy_logic.WithSymbols(sig.all_symbols()):
with ivy_logic.WithSymbols(sig.all_symbols()):
if isinstance(self,ivy_ast.Definition):
return compile_defn(self)
return sortify_with_inference(self)
def compile_schema_body(self):
sig = ivy_logic.Sig()
@ -594,7 +595,42 @@ class IvyDomainSetup(IvyDeclInterp):
def axiom(self,ax):
self.domain.labeled_axioms.append(ax.compile())
def property(self,ax):
self.domain.labeled_props.append(ax.compile())
lf = ax.compile()
self.domain.labeled_props.append(lf)
self.last_fact = lf
def named(self,lhs):
cond = ivy_logic.drop_universals(self.last_fact.formula)
if not ivy_logic.is_exists(cond) or len(cond.variables) != 1:
raise IvyError(lhs,'property is not existential')
rng = list(cond.variables)[0].sort
vmap = dict((x.name,x) for x in lu.variables_ast(cond))
used = set()
print 'foo!'
iu.dbg('lhs.args[0].sort')
with ivy_logic.UnsortedContext():
args = [arg.compile() for arg in lhs.args]
print 'bar!'
targs = []
for a in args:
if a.name in used:
raise IvyError(lhs,'repeat parameter: {}'.format(a.name))
used.add(a.name)
if a.name in vmap:
v = vmap[a.name]
targs.append(v)
if not (ivy_logic.is_topsort(a.sort) or a.sort != v.sort):
raise IvyError(lhs,'bad sort for {}'.format(a.name))
else:
if ivy_logic.is_topsort(a.sort):
raise IvyError(lhs,'cannot infer sort for {}'.format(a.name))
targs.append(a)
for x in vmap:
if x not in used:
raise IvyError(lhs,'{} must be a parameter of {}'.format(x,lhs.rep))
dom = [x.sort for x in targs]
sym = self.domain.sig.add_symbol(lhs.rep,ivy_logic.FuncConstSort(*(dom+[rng])))
iu.dbg('repr(sym)')
self.domain.named.append((self.last_fact,sym(*targs) if targs else sym))
def schema(self,sch):
if isinstance(sch.defn.args[1],ivy_ast.SchemaBody):
self.domain.schemata[sch.defn.defines()] = sch.defn.args[1].compile()
@ -1024,7 +1060,30 @@ def check_definitions(mod):
raise iu.IvyError(d,'definition of {} requires an recursion schema'.format(d.formula.defines()))
prover.admit_definition(d,pmap[d.id])
def check_properties(mod):
props = mod.labeled_props
mod.labeled_props = []
pmap = dict((lf.id,p) for lf,p in mod.proofs)
nmap = dict((lf.id,n) for lf,n in mod.named)
import ivy_proof
prover = ivy_proof.ProofChecker([],[],mod.schemata)
for prop in props:
if prop.id in pmap:
print 'checking {}...'.format(prop.label)
prover.admit_proposition(prop,pmap[prop.id])
if prop.id in nmap:
name = nmap[prop.id]
fmla = ivy_logic.drop_universals(prop.formula)
v = list(fmla.variables)[0]
fmla = fmla.body
iu.dbg('v.sort')
iu.dbg('name.sort')
fmla = lu.substitute_ast(fmla,{v.name:name})
iu.dbg('fmla')
prop = prop.clone([prop.label,fmla])
mod.labeled_axioms.append(prop)
else:
mod.labeled_props.append(prop)
def ivy_compile(decls,mod=None,create_isolate=True,**kwargs):
@ -1057,6 +1116,7 @@ def ivy_compile(decls,mod=None,create_isolate=True,**kwargs):
create_sort_order(mod)
check_definitions(mod)
check_properties(mod)
if create_isolate:
iso.create_isolate(isolate.get(),mod,**kwargs)
im.module.labeled_axioms.extend(im.module.labeled_props)

Просмотреть файл

@ -119,6 +119,7 @@ reserved = all_reserved = {
'of' : 'OF',
'scenario' : 'SCENARIO',
'proof' : 'PROOF',
'named' : 'NAMED',
}
tokens += tuple(all_reserved.values())

Просмотреть файл

@ -154,6 +154,7 @@ Symbol.is_relation = lambda self: isinstance(self.sort.rng,lg.BooleanSort)
Symbol.args = property(lambda self : [])
Symbol.is_numeral = lambda self : is_numeral_name(self.name)
Symbol.clone = lambda self,args : self
Symbol.resort = lambda self,sort : Symbol(self.name,sort)
BooleanSort = lg.BooleanSort
@ -668,6 +669,9 @@ def is_boolean(term):
def is_first_order_sort(s):
return isinstance(s,UninterpretedSort)
def FuncConstSort(*sorts):
return FunctionSort(*sorts) if len(sorts) > 1 else sorts[0]
def RelationSort(dom):
return FunctionSort(*(list(dom) + [lg.Boolean])) if len(dom) else lg.Boolean
@ -682,6 +686,9 @@ TopS = lg.TopS
def apply(symbol,args):
return App(symbol,*args)
def is_topsort(sort):
return isinstance(sort,lg.TopSort)
def sortify(ast):
args = [sortify(arg) for arg in ast.args]
if (isinstance(ast,App)) and isinstance(ast.rep.sort,lg.TopSort):

Просмотреть файл

@ -264,7 +264,7 @@ def variables_ast(ast):
# extend to clauses, etc...
variables_clause = variables_cube = apply_gen_to_list(variables_ast)
variables_clause = variables_cube = used_variables_asts = apply_gen_to_list(variables_ast)
variables_clauses = variables_cubes = apply_gen_to_clauses(variables_ast)
# get set of variables occurring
@ -950,10 +950,10 @@ def variables_distinct_ast(ast1,ast2):
map1 = distinct_variable_renaming(used_variables_ast(ast1),used_variables_ast(ast2))
return substitute_ast(ast1,map1)
def rename_variables_distinct_ast(vars,ast2):
""" rename variables in vars so they don't occur in ast2.
def rename_variables_distinct_asts(vars,asts):
""" rename variables in vars so they don't occur in asts.
"""
map1 = distinct_variable_renaming(vars,used_variables_ast(ast2))
map1 = distinct_variable_renaming(vars,used_variables_asts(asts))
return [map1[v.name] for v in vars]
def variables_distinct_list_ast(ast_list,ast2):

Просмотреть файл

@ -70,6 +70,7 @@ class Module(object):
self.variants = defaultdict(list) # map from sort name to list of sort
self.ext_preconds = {} # map from action name to formula
self.proofs = [] # list of pair (labeled formula, proof)
self.named = [] # list of pair (labeled formula, atom)
self.sig = il.sig.copy() # capture the current signature

Просмотреть файл

@ -234,12 +234,25 @@ def p_top_axiom_labeledfmla(p):
d.lineno = get_lineno(p,2)
p[0].declare(d)
def p_optskolem(p):
'optskolem : '
p[0] = None
def p_optskolem_symbol(p):
'optskolem : NAMED defnlhs'
p[0] = p[2]
p[0].lineno = get_lineno(p,1)
def p_top_property_labeledfmla(p):
'top : top PROPERTY labeledfmla'
'top : top PROPERTY labeledfmla optskolem optproof'
p[0] = p[1]
d = PropertyDecl(p[3])
d.lineno = get_lineno(p,2)
p[0].declare(d)
if p[4] is not None:
p[0].declare(NamedDecl(p[4]))
if p[5] is not None:
p[0].declare(ProofDecl(p[5]))
def p_top_conjecture_labeledfmla(p):
'top : top CONJECTURE labeledfmla'
@ -334,6 +347,10 @@ def p_schconc_defdecl(p):
'schconc : DEFINITION defn'
p[0] = p[2]
def p_schconc_propdecl(p):
'schconc : PROPERTY fmla'
p[0] = p[2]
def p_schdecls(p):
'schdecls :'
p[0] = []
@ -1394,7 +1411,7 @@ def p_callatom_atom(p):
if not (iu.get_numeric_version() <= [1,5]):
def p_callatom_this(p):
'callatom : THIS'
p[0] = This()
p[0] = Atom(This())
p[0].lineno = get_lineno(p,1)

Просмотреть файл

@ -16,6 +16,12 @@ class NoMatch(iu.IvyError):
class ProofError(iu.IvyError):
pass
class MatchProblem(object):
def __init__(self,pat,inst,freesyms,constants):
self.pat,self.inst,self.freesyms,self.constants = pat,inst,freesyms,constants
def __str__(self):
return '{{pat:{},inst:{},freesyms:{}}}'.format(self.pat,self.inst,map(str,self.freesyms))
class ProofChecker(object):
""" This is IVY's built-in proof checker """
@ -55,6 +61,20 @@ class ProofChecker(object):
raise NoMatch(defn,"recursive definition does not match the given schema")
self.definitions[sym] = defn
def admit_proposition(self,prop,proof=None):
""" Admits a proposition with proof. If a proof is given it
is used to match the definition to a schema, else default
heuristic matching is used.
- prop is an ivy_ast.LabeledFormula
"""
if proof is None:
raise NoMatch(prop,"no proof given for property")
if self.match_schema(prop.formula,proof) is None:
raise NoMatch(proof,"goal does not match the given schema")
self.axioms.append(prop)
def match_schema(self,decl,proof):
""" attempt to match a definition or property decl to a schema
@ -68,60 +88,119 @@ class ProofChecker(object):
if schemaname not in self.schemata:
raise ProofError(proof,"No schema {} exists".format(schemaname))
schema = self.schemata[schemaname]
conc = schema.conc()
if type(conc) is not type(decl):
iu.dbg('type(conc)')
iu.dbg('type(decl)')
return None
freesyms = set(x.args[0] for x in schema.prems() if isinstance(x,ia.ConstantDecl))
freesyms.update(x for x in schema.prems() if isinstance(x,il.UninterpretedSort))
if isinstance(decl,il.Definition):
declsym = decl.defines()
concsym = conc.defines()
declargs = decl.lhs().args
concargs = conc.lhs().args
if len(declargs) != len(concargs):
return None
declrhs = decl.rhs()
concrhs = conc.rhs()
vmap = dict((x.name,il.Variable(y.name,x.sort)) for x,y in zip(concargs,declargs))
concrhs = lu.substitute_ast(concrhs,vmap)
dmatch = {concsym:declsym}
for x,y in zip(func_sorts(concsym),func_sorts(declsym)):
if x in freesyms:
if x in dmatch and dmatch[x] != y:
print "lhs sorts didn't match"
return None
dmatch[x] = y
else:
if x != y:
print "lhs sorts didn't match"
return None
concrhs = apply_match(dmatch,concrhs)
iu.dbg('concrhs')
inst = declrhs
pat = concrhs
else:
raise ProofError(proof,"Property schemata not supported yet")
pmatch = compile_match(proof,freesyms,schemaname)
iu.dbg('pmatch')
pat = apply_match(pmatch,pat)
iu.dbg('pat')
iu.dbg('freesyms')
res = match(pat,inst,freesyms)
iu.dbg('res')
schema = transform_defn_schema(schema,decl)
prob = match_problem(schema,decl)
prob = transform_defn_match(prob)
pmatch = compile_match(proof,prob,schemaname)
prob.pat = apply_match(pmatch,prob.pat)
iu.dbg('prob')
res = match(prob.pat,prob.inst,prob.freesyms,prob.constants)
res = merge_matches(res,pmatch)
print res
show_match(res)
return res
def show_match(m):
if m is None:
print 'no match'
return
print 'match {'
for x,y in m.iteritems():
print '{} : {}'.format(x,y)
print '}'
def match_problem(schema,decl):
""" Creating a matching problem from a schema and a declaration """
freesyms = set(x.args[0] for x in schema.prems() if isinstance(x,ia.ConstantDecl))
freesyms.update(x for x in schema.prems() if isinstance(x,il.UninterpretedSort))
freesyms.update(lu.variables_ast(schema.conc()))
return MatchProblem(schema.conc(),decl,freesyms,set(lu.variables_ast(decl)))
def transform_defn_schema(schema,decl):
""" Transform definition schema to match a definition. """
conc = schema.conc()
if not(isinstance(decl,il.Definition) and isinstance(conc,il.Definition)):
return schema
declargs = decl.lhs().args
concargs = conc.lhs().args
if len(declargs) > len(concargs):
schema = parameterize_schema([x.sort for x in declargs[:len(declargs)-len(concargs)]],schema)
iu.dbg('schema')
return schema
def transform_defn_match(prob):
""" Transform a problem of matching definitions to a problem of
matching the right-hand sides. Requires prob.inst is a definition. """
conc,decl,freesyms = prob.pat,prob.inst,prob.freesyms
if not(isinstance(decl,il.Definition) and isinstance(conc,il.Definition)):
return prob
declsym = decl.defines()
concsym = conc.defines()
# dmatch = match(conc.lhs(),decl.lhs(),freesyms)
# if dmatch is None:
# print "left-hand sides didn't match: {}, {}".format(conc.lhs(),decl.lhs())
# return None
declargs = decl.lhs().args
concargs = conc.lhs().args
if len(declargs) < len(concargs):
return None
declrhs = decl.rhs()
concrhs = conc.rhs()
vmap = dict((x.name,il.Variable(y.name,x.sort)) for x,y in zip(concargs,declargs))
concrhs = lu.substitute_ast(concrhs,vmap)
dmatch = {concsym:declsym}
print 'func_sorts(concsym) = {}'.format(func_sorts(concsym))
print 'func_sorts(declsym) = {}'.format(func_sorts(declsym))
for x,y in zip(func_sorts(concsym),func_sorts(declsym)):
if x in freesyms:
if x in dmatch and dmatch[x] != y:
print "lhs sorts didn't match: {}, {}".format(x,y)
return None
dmatch[x] = y
else:
if x != y:
print "lhs sorts didn't match: {}, {}".format(x,y)
return None
iu.dbg('dmatch')
iu.dbg('concrhs')
concrhs = apply_match(dmatch,concrhs)
freesyms = apply_match_freesyms(dmatch,freesyms)
freesyms = [x for x in freesyms if x not in concargs]
constants = set(x for x in prob.constants if x not in declargs)
iu.dbg('freesyms')
iu.dbg('concrhs')
return MatchProblem(concrhs,declrhs,freesyms,constants)
def parameterize_schema(sorts,schema):
""" Add initial parameters to all the free symbols in a schema.
Takes a list of sorts and an ivy_ast.SchemaBody. """
vars = make_distinct_vars(sorts,schema.conc())
match = {}
prems = []
for prem in schema.prems():
if isinstance(prem,ia.ConstantDecl):
sym = prem.args[0]
vs2 = [il.Variable('X'+str(i),y) for i,y in enumerate(sym.sort.dom)]
sym2 = sym.resort(il.FuncConstSort(*(sorts + list(sym.sort.dom) + [sym.sort.rng])))
print repr(sym2)
match[sym] = il.Lambda(vs2,sym2(*(vars+vs2)))
prems.append(ia.ConstantDecl(sym2))
else:
prems.append(prem)
conc = apply_match(match,schema.conc())
return schema.clone(prems + [conc])
# A "match" is a map from symbols to lambda terms
def compile_match(proof,freesyms,schemaname):
def compile_match(proof,prob,schemaname):
""" Compiles match in a proof. Only the symbols in
freesyms may be used in the match."""
match = proof.match
freesyms = prob.freesyms
res = dict()
for m in proof.match():
sym = m.defines()
@ -140,13 +219,22 @@ def apply_match(match,fmla):
args = [apply_match(match,f) for f in fmla.args]
if il.is_app(fmla):
if fmla.rep in match:
return match[fmla.rep](*args)
sorts = func_sorts(fmla.rep)
sorts = [match.get(s,s) for s in sorts]
func = il.Symbol(fmla.rep.name,sorts[0] if len(sorts) == 1 else il.FunctionSort(*sorts))
return func(*args)
func = match[fmla.rep]
return func(*args)
return apply_match_func(match,fmla.rep)(*args)
return fmla.clone(args)
def apply_match_func(match,func):
sorts = func_sorts(func)
sorts = [match.get(s,s) for s in sorts]
return il.Symbol(func.name,sorts[0] if len(sorts) == 1 else il.FunctionSort(*sorts))
def apply_match_sym(match,sym):
return match.get(sym,sym) if isinstance(sym,il.UninterpretedSort) else apply_match_func(match,sym)
def apply_match_freesyms(match,freesyms):
return [apply_match_sym(match,sym) for sym in freesyms if sym not in match]
def func_sorts(func):
return list(func.sort.dom) + [func.sort.rng]
@ -170,13 +258,17 @@ def heads_match(pat,inst,freesyms):
or not il.is_app(pat) and not il.is_quantifier(pat)
and type(pat) is type(inst) and len(pat.args) == len(inst.args))
def make_distinct_vars(sorts,*asts):
vars = [il.Variable('V'+str(i),sort) for i,sort in enumerate(sorts)]
return lu.rename_variables_distinct_asts(vars,asts)
def extract_terms(inst,terms):
""" Returns a lambda term t such that t(terms) = inst and
terms do not occur in t. vars is a list of distinct variables
of same types as terms that are not free in inst. """
vars = [il.Variable('V'+str(i),t.sort) for i,t in enumerate(terms)]
vars = lu.rename_variables_distinct_ast(vars,inst)
vars = make_distinct_vars([t.sort for t in terms], inst)
def rec(inst):
for term,var in zip(terms,vars):
if term == inst:
@ -184,7 +276,7 @@ def extract_terms(inst,terms):
return inst.clone(map(rec,inst.args))
return il.Lambda(vars,rec(inst))
def match(pat,inst,freesyms):
def match(pat,inst,freesyms,constants):
""" Match an instance to a pattern.
A match is an assignment sigma to freesyms such
@ -194,15 +286,37 @@ def match(pat,inst,freesyms):
iu.dbg('pat')
iu.dbg('inst')
if il.is_quantifier(pat):
return match_quants(pat,inst,freesyms,constants)
if heads_match(pat,inst,freesyms):
matches = [match(x,y,freesyms) for x,y in zip(pat.args,inst.args)]
matches = [match(x,y,freesyms,constants) for x,y in zip(pat.args,inst.args)]
matches.extend([match_sort(x,y,freesyms) for x,y in zip(term_sorts(pat),term_sorts(inst))])
return merge_matches(*matches)
if il.is_app(pat) and pat.rep in freesyms:
B = extract_terms(inst,pat.args)
iu.dbg('B')
if lu.is_ground_ast(B):
if all(v in constants for v in lu.variables_ast(B)):
return {pat.rep:B}
else:
iu.dbg('constants')
def match_quants(pat,inst,freesyms,constants):
""" Match an instance to a pattern that is a quantifier.
"""
if type(pat) is not type(inst) or len(pat.variables) != len(inst.variables):
return None
with AddSymbols(freesyms,pat.variables):
matches = [match(x,y,freesyms,constants) for x,y in zip(pat.variables,inst.variables)]
matches.append(match(pat.body,inst.body,freesyms,constants))
mat = merge_matches(*matches)
if mat is not None:
for x in pat.variables:
if x in mat:
del mat[x]
return mat
def match_sort(pat,inst,freesyms):
if pat in freesyms:
@ -214,7 +328,6 @@ def merge_matches(*matches):
return dict()
if any(match is None for match in matches):
return None
iu.dbg('matches[0]')
res = dict(matches[0].iteritems())
for match2 in matches[1:]:
for sym,lmda in match2.iteritems():
@ -235,3 +348,24 @@ def equiv_alpha(x,y):
return x.body == il.substitute(y.body,zip(x.variables,y.variables))
return false
pass
class AddSymbols(object):
""" temporarily add some symbols to a set of symbols """
def __init__(self,symset,symlist):
self.symset,self.symlist = symset,list(symlist)
def __enter__(self):
global sig
self.saved = []
for sym in self.symlist:
if sym in self.symset:
self.saved.append(sym)
self.remove(sym)
self.symset.add(sym)
return self
def __exit__(self,exc_type, exc_val, exc_tb):
global sig
for sym in self.symlist:
self.symset.remove(sym)
for sym in self.saved:
self.symset.add(sym)
return False # don't block any exceptions

Просмотреть файл

@ -527,7 +527,8 @@ def collect_numerals(z3term):
def from_z3_numeral(z3term,sort):
name = str(z3term)
assert name[0].isdigit() or name[0] == '"'
if not(name[0].isdigit() or name[0] == '"'):
print "unexpected numeral from Z3 model: {}".format(name)
return ivy_logic.Symbol(name,sort)
def collect_model_values(sort,model,sym):

Просмотреть файл

@ -234,7 +234,8 @@ class And(recstruct('And', [], ['*terms'])):
', '.join(str(t) for t in terms),
bad_sorts,
))
return sorted(set(terms))
return tuple(terms)
# return sorted(set(terms))
def __str__(self):
return 'And({})'.format(
', '.join(str(t) for t in self)
@ -330,7 +331,7 @@ class Lambda(recstruct('Lambda', ['variables'], ['body'])):
def _preprocess_(cls, variables, body):
if not all(type(v) is Var for v in variables):
raise IvyError("Can only abstract over variables")
return frozenset(variables), body
return tuple(variables), body
def __str__(self):
return '(Lambda {}. {})'.format(
', '.join('{}:{}'.format(v.name, v.sort) for v in sorted(self.variables)),

Просмотреть файл

@ -2,31 +2,95 @@
type t
type r
schema foo = {
type q
function base(X:t) : q
function step(X:q) : q
function fun(X:t) : q
#---------------------------------------------------------
definition fun(X:t) = base(X) if X <= 0 else step(fun(X-1))
}
function bif(X:t):r
definition bif(X) = 0 if X <= 0 else bif(X-1) + 1
#proof foo with base(X) = 0, step(X) = X + 1
proof foo
function succ(X:t,Y:t) = (Y-1 = X)
type s
axiom (T:t < U & U < V) -> (T < V)
axiom ~(T:t < U & U < T)
axiom T:t < U | T = U | U < T
property [bif_base] bif(0) = 0
property [bif_step] F > 0 & succ(E,F) -> bif(F) = bif(E) + 1
relation succ(X:t,Y:t)
schema rec[t] = {
type q
function base(X:t) : q
function step(X:q,Y:t) : q
function fun(X:t) : q
#---------------------------------------------------------
definition fun(X:t) = base(X) if X <= 0 else step(fun(X-1),X)
}
schema lep[t] = {
function p(X:t) : bool
#---------------------------------------------------------
property exists L. (L >= 0 & forall B. (B >= 0 & p(B)-> p(L) & L <= B))
}
relation member(X:t,S:s)
function cnt(X:t):r
function card(S:s) : r
function cardUpTo(S:s,B:t) : r
individual n : t
axiom n > 0
object counting = {
object spec = {
property [cnt_base] cnt(0) = 0
property [cnt_step] F > 0 & succ(E,F) -> cnt(F) = cnt(E) + 1
property [cardUpTo_base] cardUpTo(S,0) = 0
property [cardUpTo_step] succ(B,BS) & BS > 0 ->
cardUpTo(S,BS) = cardUpTo(S,B)+1 if member(B,S) else cardUpTo(S,B)
}
object impl = {
definition cnt(X) = 0 if X <= 0 else cnt(X-1) + 1
proof rec[t]
definition cardUpTo(S,B) =
0 if B <= 0 else (cardUpTo(S,B-1)+1 if member(B-1,S) else cardUpTo(S,B-1))
proof rec[t]
definition succ(X:t,Y:t) = (Y-1 = X)
}
isolate iso = spec with impl
}
object disjointness = {
object spec = {
axiom succ(X,Y) -> X < Y
axiom ~(X < Y & Y < Z & succ(X,Z))
relation disjoint(X:s,Y:s)
definition disjoint(X,Y) = forall E. ~(member(E,X) & member(E,Y))
derived inv(X,Y,B) =
disjoint(X,Y) -> cardUpTo(X,B) + cardUpTo(Y,B) <= cnt(B)
property exists L. (L >= 0 & forall B. (B >= 0 & ~inv(X,Y,B) -> ~inv(X,Y,L) & L <= B))
named lerr(X,Y)
proof lep[t]
axiom [induc] exists E. succ(E,lerr(X,Y))
definition card(S) = cardUpTo(S,n)
property disjoint(X,Y) -> card(X) + card(Y) <= cnt(n)
interpret r->int
}
isolate iso = spec with counting
}
# fun(X) = bif(X)