|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
"""Implements objects to represent problems, theorems, proofs, traceback."""
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
from collections import defaultdict
|
|
|
from typing import Any
|
|
|
|
|
|
import geometry as gm
|
|
|
import pretty as pt
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def reshape(l: list[Any], n: int = 1) -> list[list[Any]]:
|
|
|
assert len(l) % n == 0
|
|
|
columns = [[] for i in range(n)]
|
|
|
for i, x in enumerate(l):
|
|
|
columns[i % n].append(x)
|
|
|
return zip(*columns)
|
|
|
|
|
|
|
|
|
def isint(x: str) -> bool:
|
|
|
try:
|
|
|
int(x)
|
|
|
return True
|
|
|
except:
|
|
|
return False
|
|
|
|
|
|
|
|
|
class Construction:
|
|
|
"""One predicate."""
|
|
|
|
|
|
@classmethod
|
|
|
def from_txt(cls, data: str) -> Construction:
|
|
|
data = data.split(' ')
|
|
|
return Construction(data[0], data[1:])
|
|
|
|
|
|
def __init__(self, name: str, args: list[str]):
|
|
|
self.name = name
|
|
|
self.args = args
|
|
|
|
|
|
def translate(self, mapping: dict[str, str]) -> Construction:
|
|
|
args = [a if isint(a) else mapping[a] for a in self.args]
|
|
|
return Construction(self.name, args)
|
|
|
|
|
|
def txt(self) -> str:
|
|
|
return ' '.join([self.name] + list(self.args))
|
|
|
|
|
|
|
|
|
class Clause:
|
|
|
"""One construction (>= 1 predicate)."""
|
|
|
|
|
|
@classmethod
|
|
|
def from_txt(cls, data: str) -> Clause:
|
|
|
if data == ' =':
|
|
|
return Clause([], [])
|
|
|
points, constructions = data.split(' = ')
|
|
|
return Clause(
|
|
|
points.split(' '),
|
|
|
[Construction.from_txt(c) for c in constructions.split(', ')],
|
|
|
)
|
|
|
|
|
|
def __init__(self, points: list[str], constructions: list[Construction]):
|
|
|
self.points = []
|
|
|
self.nums = []
|
|
|
|
|
|
for p in points:
|
|
|
num = None
|
|
|
if isinstance(p, str) and '@' in p:
|
|
|
p, num = p.split('@')
|
|
|
x, y = num.split('_')
|
|
|
num = float(x), float(y)
|
|
|
self.points.append(p)
|
|
|
self.nums.append(num)
|
|
|
|
|
|
self.constructions = constructions
|
|
|
|
|
|
def translate(self, mapping: dict[str, str]) -> Clause:
|
|
|
points0 = []
|
|
|
for p in self.points:
|
|
|
pcount = len(mapping) + 1
|
|
|
name = chr(96 + pcount)
|
|
|
if name > 'z':
|
|
|
name = chr(97 + (pcount - 1) % 26) + str((pcount - 1) // 26)
|
|
|
|
|
|
p0 = mapping.get(p, name)
|
|
|
mapping[p] = p0
|
|
|
points0.append(p0)
|
|
|
return Clause(points0, [c.translate(mapping) for c in self.constructions])
|
|
|
|
|
|
def add(self, name: str, args: list[str]) -> None:
|
|
|
self.constructions.append(Construction(name, args))
|
|
|
|
|
|
def txt(self) -> str:
|
|
|
return (
|
|
|
' '.join(self.points)
|
|
|
+ ' = '
|
|
|
+ ', '.join(c.txt() for c in self.constructions)
|
|
|
)
|
|
|
|
|
|
|
|
|
def _gcd(x: int, y: int) -> int:
|
|
|
while y:
|
|
|
x, y = y, x % y
|
|
|
return x
|
|
|
|
|
|
|
|
|
def simplify(n: int, d: int) -> tuple[int, int]:
|
|
|
g = _gcd(n, d)
|
|
|
return (n // g, d // g)
|
|
|
|
|
|
|
|
|
def compare_fn(dep: Dependency) -> tuple[Dependency, str]:
|
|
|
return (dep, pt.pretty(dep))
|
|
|
|
|
|
|
|
|
def sort_deps(deps: list[Dependency]) -> list[Dependency]:
|
|
|
return sorted(deps, key=compare_fn)
|
|
|
|
|
|
|
|
|
class Problem:
|
|
|
"""Describe one problem to solve."""
|
|
|
|
|
|
@classmethod
|
|
|
def from_txt_file(
|
|
|
cls, fname: str, to_dict: bool = False, translate: bool = True
|
|
|
):
|
|
|
"""Load a problem from a text file."""
|
|
|
with open(fname, 'r') as f:
|
|
|
lines = f.read().split('\n')
|
|
|
|
|
|
lines = [l for l in lines if l]
|
|
|
data = [
|
|
|
cls.from_txt(url + '\n' + problem, translate)
|
|
|
for (url, problem) in reshape(lines, 2)
|
|
|
]
|
|
|
if to_dict:
|
|
|
return cls.to_dict(data)
|
|
|
return data
|
|
|
|
|
|
@classmethod
|
|
|
def from_txt(cls, data: str, translate: bool = True) -> Problem:
|
|
|
"""Load a problem from a str object."""
|
|
|
url = ''
|
|
|
if '\n' in data:
|
|
|
url, data = data.split('\n')
|
|
|
|
|
|
if ' ? ' in data:
|
|
|
clauses, goal = data.split(' ? ')
|
|
|
goal = Construction.from_txt(goal)
|
|
|
else:
|
|
|
clauses, goal = data, None
|
|
|
|
|
|
clauses = clauses.split('; ')
|
|
|
problem = Problem(
|
|
|
url=url, clauses=[Clause.from_txt(c) for c in clauses], goal=goal
|
|
|
)
|
|
|
if translate:
|
|
|
return problem.translate()
|
|
|
return problem
|
|
|
|
|
|
@classmethod
|
|
|
def to_dict(cls, data: list[Problem]) -> dict[str, Problem]:
|
|
|
return {p.url: p for p in data}
|
|
|
|
|
|
def __init__(self, url: str, clauses: list[Clause], goal: Construction):
|
|
|
self.url = url
|
|
|
self.clauses = clauses
|
|
|
self.goal = goal
|
|
|
|
|
|
def copy(self) -> Problem:
|
|
|
return Problem(self.url, list(self.clauses), self.goal)
|
|
|
|
|
|
def translate(self) -> Problem:
|
|
|
"""Translate point names into alphabetical."""
|
|
|
mapping = {}
|
|
|
clauses = []
|
|
|
|
|
|
for clause in self.clauses:
|
|
|
clauses.append(clause.translate(mapping))
|
|
|
|
|
|
if self.goal:
|
|
|
goal = self.goal.translate(mapping)
|
|
|
else:
|
|
|
goal = self.goal
|
|
|
|
|
|
p = Problem(self.url, clauses, goal)
|
|
|
p.mapping = mapping
|
|
|
return p
|
|
|
|
|
|
def txt(self) -> str:
|
|
|
return (
|
|
|
'; '.join([c.txt() for c in self.clauses]) + ' ? ' + self.goal.txt()
|
|
|
if self.goal
|
|
|
else ''
|
|
|
)
|
|
|
|
|
|
def setup_str_from_problem(self, definitions: list[Definition]) -> str:
|
|
|
"""Construct the <theorem_premises> string from Problem object."""
|
|
|
ref = 0
|
|
|
|
|
|
string = []
|
|
|
for clause in self.clauses:
|
|
|
group = {}
|
|
|
p2deps = defaultdict(list)
|
|
|
for c in clause.constructions:
|
|
|
cdef = definitions[c.name]
|
|
|
|
|
|
if len(c.args) != len(cdef.construction.args):
|
|
|
assert len(c.args) + len(clause.points) == len(cdef.construction.args)
|
|
|
c.args = clause.points + c.args
|
|
|
|
|
|
mapping = dict(zip(cdef.construction.args, c.args))
|
|
|
for points, bs in cdef.basics:
|
|
|
points = tuple([mapping[x] for x in points])
|
|
|
for p in points:
|
|
|
group[p] = points
|
|
|
|
|
|
for b in bs:
|
|
|
args = [mapping[a] for a in b.args]
|
|
|
name = b.name
|
|
|
if b.name in ['s_angle', 'aconst']:
|
|
|
x, y, z, v = args
|
|
|
name = 'aconst'
|
|
|
v = int(v)
|
|
|
|
|
|
if v < 0:
|
|
|
v = -v
|
|
|
x, z = z, x
|
|
|
|
|
|
m, n = simplify(int(v), 180)
|
|
|
args = [y, z, y, x, f'{m}pi/{n}']
|
|
|
|
|
|
p2deps[points].append(hashed_txt(name, args))
|
|
|
|
|
|
for k, v in p2deps.items():
|
|
|
p2deps[k] = sort_deps(v)
|
|
|
|
|
|
points = clause.points
|
|
|
while points:
|
|
|
p = points[0]
|
|
|
gr = group[p]
|
|
|
points = [x for x in points if x not in gr]
|
|
|
|
|
|
deps_str = []
|
|
|
for dep in p2deps[gr]:
|
|
|
ref_str = '{:02}'.format(ref)
|
|
|
dep_str = pt.pretty(dep)
|
|
|
|
|
|
if dep[0] == 'aconst':
|
|
|
m, n = map(int, dep[-1].split('pi/'))
|
|
|
mn = f'{m}. pi / {n}.'
|
|
|
dep_str = ' '.join(dep_str.split()[:-1] + [mn])
|
|
|
|
|
|
deps_str.append(dep_str + ' ' + ref_str)
|
|
|
ref += 1
|
|
|
|
|
|
string.append(' '.join(gr) + ' : ' + ' '.join(deps_str))
|
|
|
|
|
|
string = '{S} ' + ' ; '.join([s.strip() for s in string])
|
|
|
goal = self.goal
|
|
|
string += ' ? ' + pt.pretty([goal.name] + goal.args)
|
|
|
return string
|
|
|
|
|
|
|
|
|
def parse_rely(s: str) -> dict[str, str]:
|
|
|
result = {}
|
|
|
if not s:
|
|
|
return result
|
|
|
s = [x.strip() for x in s.split(',')]
|
|
|
for x in s:
|
|
|
a, b = x.split(':')
|
|
|
a, b = a.strip().split(), b.strip().split()
|
|
|
result.update({m: b for m in a})
|
|
|
return result
|
|
|
|
|
|
|
|
|
class Definition:
|
|
|
"""Definitions of construction statements."""
|
|
|
|
|
|
@classmethod
|
|
|
def from_txt_file(cls, fname: str, to_dict: bool = False) -> Definition:
|
|
|
with open(fname, 'r') as f:
|
|
|
lines = f.read()
|
|
|
return cls.from_string(lines, to_dict)
|
|
|
|
|
|
@classmethod
|
|
|
def from_string(cls, string: str, to_dict: bool = False) -> Definition:
|
|
|
lines = string.split('\n')
|
|
|
data = [cls.from_txt('\n'.join(group)) for group in reshape(lines, 6)]
|
|
|
if to_dict:
|
|
|
return cls.to_dict(data)
|
|
|
return data
|
|
|
|
|
|
@classmethod
|
|
|
def to_dict(cls, data: list[Definition]) -> dict[str, Definition]:
|
|
|
return {d.construction.name: d for d in data}
|
|
|
|
|
|
@classmethod
|
|
|
def from_txt(cls, data: str) -> Definition:
|
|
|
"""Load definitions from a str object."""
|
|
|
construction, rely, deps, basics, numerics, _ = data.split('\n')
|
|
|
basics = [] if not basics else [b.strip() for b in basics.split(';')]
|
|
|
|
|
|
levels = []
|
|
|
for bs in basics:
|
|
|
if ':' in bs:
|
|
|
points, bs = bs.split(':')
|
|
|
points = points.strip().split()
|
|
|
else:
|
|
|
points = []
|
|
|
if bs.strip():
|
|
|
bs = [Construction.from_txt(b.strip()) for b in bs.strip().split(',')]
|
|
|
else:
|
|
|
bs = []
|
|
|
levels.append((points, bs))
|
|
|
|
|
|
numerics = [] if not numerics else numerics.split(', ')
|
|
|
|
|
|
return Definition(
|
|
|
construction=Construction.from_txt(construction),
|
|
|
rely=parse_rely(rely),
|
|
|
deps=Clause.from_txt(deps),
|
|
|
basics=levels,
|
|
|
numerics=[Construction.from_txt(c) for c in numerics],
|
|
|
)
|
|
|
|
|
|
def __init__(
|
|
|
self,
|
|
|
construction: Construction,
|
|
|
rely: dict[str, str],
|
|
|
deps: Clause,
|
|
|
basics: list[tuple[list[str], list[Construction]]],
|
|
|
numerics: list[Construction],
|
|
|
):
|
|
|
self.construction = construction
|
|
|
self.rely = rely
|
|
|
self.deps = deps
|
|
|
self.basics = basics
|
|
|
self.numerics = numerics
|
|
|
|
|
|
args = set()
|
|
|
for num in numerics:
|
|
|
args.update(num.args)
|
|
|
|
|
|
self.points = []
|
|
|
self.args = []
|
|
|
for p in self.construction.args:
|
|
|
if p in args:
|
|
|
self.args.append(p)
|
|
|
else:
|
|
|
self.points.append(p)
|
|
|
|
|
|
|
|
|
class Theorem:
|
|
|
"""Deduction rule."""
|
|
|
|
|
|
@classmethod
|
|
|
def from_txt_file(cls, fname: str, to_dict: bool = False) -> Theorem:
|
|
|
with open(fname, 'r') as f:
|
|
|
theorems = f.read()
|
|
|
return cls.from_string(theorems, to_dict)
|
|
|
|
|
|
@classmethod
|
|
|
def from_string(cls, string: str, to_dict: bool = False) -> Theorem:
|
|
|
"""Load deduction rule from a str object."""
|
|
|
theorems = string.split('\n')
|
|
|
theorems = [l for l in theorems if l and not l.startswith('#')]
|
|
|
theorems = [cls.from_txt(l) for l in theorems]
|
|
|
|
|
|
for i, th in enumerate(theorems):
|
|
|
th.rule_name = 'r{:02}'.format(i)
|
|
|
|
|
|
if to_dict:
|
|
|
result = {}
|
|
|
for t in theorems:
|
|
|
if t.name in result:
|
|
|
t.name += '_'
|
|
|
result[t.rule_name] = t
|
|
|
|
|
|
return result
|
|
|
|
|
|
return theorems
|
|
|
|
|
|
@classmethod
|
|
|
def from_txt(cls, data: str) -> Theorem:
|
|
|
premises, conclusion = data.split(' => ')
|
|
|
premises = premises.split(', ')
|
|
|
conclusion = conclusion.split(', ')
|
|
|
return Theorem(
|
|
|
premise=[Construction.from_txt(p) for p in premises],
|
|
|
conclusion=[Construction.from_txt(c) for c in conclusion],
|
|
|
)
|
|
|
|
|
|
def __init__(
|
|
|
self, premise: list[Construction], conclusion: list[Construction]
|
|
|
):
|
|
|
if len(conclusion) != 1:
|
|
|
raise ValueError('Cannot have more than one conclusion')
|
|
|
self.name = '_'.join([p.name for p in premise + conclusion])
|
|
|
self.premise = premise
|
|
|
self.conclusion = conclusion
|
|
|
self.is_arg_reduce = False
|
|
|
|
|
|
assert len(self.conclusion) == 1
|
|
|
con = self.conclusion[0]
|
|
|
|
|
|
if con.name in [
|
|
|
'eqratio3',
|
|
|
'midp',
|
|
|
'contri',
|
|
|
'simtri',
|
|
|
'contri2',
|
|
|
'simtri2',
|
|
|
'simtri*',
|
|
|
'contri*',
|
|
|
]:
|
|
|
return
|
|
|
|
|
|
prem_args = set(sum([p.args for p in self.premise], []))
|
|
|
con_args = set(con.args)
|
|
|
if len(prem_args) <= len(con_args):
|
|
|
self.is_arg_reduce = True
|
|
|
|
|
|
def txt(self) -> str:
|
|
|
premise_txt = ', '.join([clause.txt() for clause in self.premise])
|
|
|
conclusion_txt = ', '.join([clause.txt() for clause in self.conclusion])
|
|
|
return f'{premise_txt} => {conclusion_txt}'
|
|
|
|
|
|
def conclusion_name_args(
|
|
|
self, mapping: dict[str, gm.Point]
|
|
|
) -> tuple[str, list[gm.Point]]:
|
|
|
mapping = {arg: p for arg, p in mapping.items() if isinstance(arg, str)}
|
|
|
c = self.conclusion[0]
|
|
|
args = [mapping[a] for a in c.args]
|
|
|
return c.name, args
|
|
|
|
|
|
|
|
|
def why_eqratio(
|
|
|
d1: gm.Direction,
|
|
|
d2: gm.Direction,
|
|
|
d3: gm.Direction,
|
|
|
d4: gm.Direction,
|
|
|
level: int,
|
|
|
) -> list[Dependency]:
|
|
|
"""Why two ratios are equal, returns a Dependency objects."""
|
|
|
all12 = list(gm.all_ratios(d1, d2, level))
|
|
|
all34 = list(gm.all_ratios(d3, d4, level))
|
|
|
|
|
|
min_why = None
|
|
|
for ang12, d1s, d2s in all12:
|
|
|
for ang34, d3s, d4s in all34:
|
|
|
why0 = gm.why_equal(ang12, ang34, level)
|
|
|
if why0 is None:
|
|
|
continue
|
|
|
d1_, d2_ = ang12._l
|
|
|
d3_, d4_ = ang34._l
|
|
|
why1 = gm.bfs_backtrack(d1, [d1_], d1s)
|
|
|
why2 = gm.bfs_backtrack(d2, [d2_], d2s)
|
|
|
why3 = gm.bfs_backtrack(d3, [d3_], d3s)
|
|
|
why4 = gm.bfs_backtrack(d4, [d4_], d4s)
|
|
|
why = why0 + why1 + why2 + why3 + why4
|
|
|
if min_why is None or len(why) < len(min_why[0]):
|
|
|
min_why = why, ang12, ang34, why0, why1, why2, why3, why4
|
|
|
|
|
|
if min_why is None:
|
|
|
return None
|
|
|
|
|
|
_, ang12, ang34, why0, why1, why2, why3, why4 = min_why
|
|
|
d1_, d2_ = ang12._l
|
|
|
d3_, d4_ = ang34._l
|
|
|
|
|
|
if d1 == d1_ and d2 == d2_ and d3 == d3_ and d4 == d4_:
|
|
|
return why0
|
|
|
|
|
|
(a_, b_), (c_, d_) = d1_._obj.points, d2_._obj.points
|
|
|
(e_, f_), (g_, h_) = d3_._obj.points, d4_._obj.points
|
|
|
deps = []
|
|
|
if why0:
|
|
|
dep = Dependency('eqratio', [a_, b_, c_, d_, e_, f_, g_, h_], '', level)
|
|
|
dep.why = why0
|
|
|
deps.append(dep)
|
|
|
|
|
|
(a, b), (c, d) = d1._obj.points, d2._obj.points
|
|
|
(e, f), (g, h) = d3._obj.points, d4._obj.points
|
|
|
for why, (x, y), (x_, y_) in zip(
|
|
|
[why1, why2, why3, why4],
|
|
|
[(a, b), (c, d), (e, f), (g, h)],
|
|
|
[(a_, b_), (c_, d_), (e_, f_), (g_, h_)],
|
|
|
):
|
|
|
if why:
|
|
|
dep = Dependency('cong', [x, y, x_, y_], '', level)
|
|
|
dep.why = why
|
|
|
deps.append(dep)
|
|
|
|
|
|
return deps
|
|
|
|
|
|
|
|
|
def why_eqangle(
|
|
|
d1: gm.Direction,
|
|
|
d2: gm.Direction,
|
|
|
d3: gm.Direction,
|
|
|
d4: gm.Direction,
|
|
|
level: int,
|
|
|
verbose: bool = False,
|
|
|
) -> list[Dependency]:
|
|
|
"""Why two angles are equal, returns a Dependency objects."""
|
|
|
all12 = list(gm.all_angles(d1, d2, level))
|
|
|
all34 = list(gm.all_angles(d3, d4, level))
|
|
|
|
|
|
min_why = None
|
|
|
for ang12, d1s, d2s in all12:
|
|
|
for ang34, d3s, d4s in all34:
|
|
|
why0 = gm.why_equal(ang12, ang34, level)
|
|
|
if why0 is None:
|
|
|
continue
|
|
|
d1_, d2_ = ang12._d
|
|
|
d3_, d4_ = ang34._d
|
|
|
why1 = gm.bfs_backtrack(d1, [d1_], d1s)
|
|
|
why2 = gm.bfs_backtrack(d2, [d2_], d2s)
|
|
|
why3 = gm.bfs_backtrack(d3, [d3_], d3s)
|
|
|
why4 = gm.bfs_backtrack(d4, [d4_], d4s)
|
|
|
why = why0 + why1 + why2 + why3 + why4
|
|
|
if min_why is None or len(why) < len(min_why[0]):
|
|
|
min_why = why, ang12, ang34, why0, why1, why2, why3, why4
|
|
|
|
|
|
if min_why is None:
|
|
|
return None
|
|
|
|
|
|
_, ang12, ang34, why0, why1, why2, why3, why4 = min_why
|
|
|
why0 = gm.why_equal(ang12, ang34, level)
|
|
|
d1_, d2_ = ang12._d
|
|
|
d3_, d4_ = ang34._d
|
|
|
|
|
|
if d1 == d1_ and d2 == d2_ and d3 == d3_ and d4 == d4_:
|
|
|
return (d1_, d2_, d3_, d4_), why0
|
|
|
|
|
|
(a_, b_), (c_, d_) = d1_._obj.points, d2_._obj.points
|
|
|
(e_, f_), (g_, h_) = d3_._obj.points, d4_._obj.points
|
|
|
deps = []
|
|
|
if why0:
|
|
|
dep = Dependency('eqangle', [a_, b_, c_, d_, e_, f_, g_, h_], '', None)
|
|
|
dep.why = why0
|
|
|
deps.append(dep)
|
|
|
|
|
|
(a, b), (c, d) = d1._obj.points, d2._obj.points
|
|
|
(e, f), (g, h) = d3._obj.points, d4._obj.points
|
|
|
for why, d_xy, (x, y), d_xy_, (x_, y_) in zip(
|
|
|
[why1, why2, why3, why4],
|
|
|
[d1, d2, d3, d4],
|
|
|
[(a, b), (c, d), (e, f), (g, h)],
|
|
|
[d1_, d2_, d3_, d4_],
|
|
|
[(a_, b_), (c_, d_), (e_, f_), (g_, h_)],
|
|
|
):
|
|
|
xy, xy_ = d_xy._obj, d_xy_._obj
|
|
|
if why:
|
|
|
if xy == xy_:
|
|
|
name = 'collx'
|
|
|
else:
|
|
|
name = 'para'
|
|
|
dep = Dependency(name, [x_, y_, x, y], '', None)
|
|
|
dep.why = why
|
|
|
deps.append(dep)
|
|
|
|
|
|
return (d1_, d2_, d3_, d4_), deps
|
|
|
|
|
|
|
|
|
CONSTRUCTION_RULE = 'c0'
|
|
|
|
|
|
|
|
|
class EmptyDependency:
|
|
|
"""Empty dependency predicate ready to get filled up."""
|
|
|
|
|
|
def __init__(self, level: int, rule_name: str):
|
|
|
self.level = level
|
|
|
self.rule_name = rule_name or ''
|
|
|
self.empty = True
|
|
|
self.why = []
|
|
|
self.trace = None
|
|
|
|
|
|
def populate(self, name: str, args: list[gm.Point]) -> Dependency:
|
|
|
dep = Dependency(name, args, self.rule_name, self.level)
|
|
|
dep.trace2 = self.trace
|
|
|
dep.why = list(self.why)
|
|
|
return dep
|
|
|
|
|
|
def copy(self) -> EmptyDependency:
|
|
|
other = EmptyDependency(self.level, self.rule_name)
|
|
|
other.why = list(self.why)
|
|
|
return other
|
|
|
|
|
|
def extend(
|
|
|
self,
|
|
|
g: Any,
|
|
|
name0: str,
|
|
|
args0: list[gm.Point],
|
|
|
name: str,
|
|
|
args: list[gm.Point],
|
|
|
) -> EmptyDependency:
|
|
|
"""Extend the dependency list by (name, args)."""
|
|
|
dep0 = self.populate(name0, args0)
|
|
|
deps = EmptyDependency(level=self.level, rule_name=None)
|
|
|
dep = Dependency(name, args, None, deps.level)
|
|
|
deps.why = [dep0, dep.why_me_or_cache(g, None)]
|
|
|
return deps
|
|
|
|
|
|
def extend_many(
|
|
|
self,
|
|
|
g: Any,
|
|
|
name0: str,
|
|
|
args0: list[gm.Point],
|
|
|
name_args: list[tuple[str, list[gm.Point]]],
|
|
|
) -> EmptyDependency:
|
|
|
"""Extend the dependency list by many name_args."""
|
|
|
if not name_args:
|
|
|
return self
|
|
|
dep0 = self.populate(name0, args0)
|
|
|
deps = EmptyDependency(level=self.level, rule_name=None)
|
|
|
deps.why = [dep0]
|
|
|
for name, args in name_args:
|
|
|
dep = Dependency(name, args, None, deps.level)
|
|
|
deps.why += [dep.why_me_or_cache(g, None)]
|
|
|
return deps
|
|
|
|
|
|
|
|
|
def maybe_make_equal_pairs(
|
|
|
a: gm.Point,
|
|
|
b: gm.Point,
|
|
|
c: gm.Point,
|
|
|
d: gm.Point,
|
|
|
m: gm.Point,
|
|
|
n: gm.Point,
|
|
|
p: gm.Point,
|
|
|
q: gm.Point,
|
|
|
ab: gm.Line,
|
|
|
mn: gm.Line,
|
|
|
g: Any,
|
|
|
level: int,
|
|
|
) -> list[Dependency]:
|
|
|
"""Make a-b:c-d==m-n:p-q in case a-b==m-n or c-d==p-q."""
|
|
|
if ab != mn:
|
|
|
return
|
|
|
why = []
|
|
|
eqname = 'para' if isinstance(ab, gm.Line) else 'cong'
|
|
|
colls = [a, b, m, n]
|
|
|
if len(set(colls)) > 2 and eqname == 'para':
|
|
|
dep = Dependency('collx', colls, None, level)
|
|
|
dep.why_me(g, level)
|
|
|
why += [dep]
|
|
|
|
|
|
dep = Dependency(eqname, [c, d, p, q], None, level)
|
|
|
dep.why_me(g, level)
|
|
|
why += [dep]
|
|
|
return why
|
|
|
|
|
|
|
|
|
class Dependency(Construction):
|
|
|
"""Dependency is a predicate that other predicates depend on."""
|
|
|
|
|
|
def __init__(
|
|
|
self, name: str, args: list[gm.Point], rule_name: str, level: int
|
|
|
):
|
|
|
super().__init__(name, args)
|
|
|
self.rule_name = rule_name or ''
|
|
|
self.level = level
|
|
|
self.why = []
|
|
|
|
|
|
self._stat = None
|
|
|
self.trace = None
|
|
|
|
|
|
def _find(self, dep_hashed: tuple[str, ...]) -> Dependency:
|
|
|
for w in self.why:
|
|
|
f = w._find(dep_hashed)
|
|
|
if f:
|
|
|
return f
|
|
|
if w.hashed() == dep_hashed:
|
|
|
return w
|
|
|
|
|
|
def remove_loop(self) -> Dependency:
|
|
|
f = self._find(self.hashed())
|
|
|
if f:
|
|
|
return f
|
|
|
return self
|
|
|
|
|
|
def copy(self) -> Dependency:
|
|
|
dep = Dependency(self.name, self.args, self.rule_name, self.level)
|
|
|
dep.trace = self.trace
|
|
|
dep.why = list(self.why)
|
|
|
return dep
|
|
|
|
|
|
def why_me_or_cache(self, g: Any, level: int) -> Dependency:
|
|
|
if self.hashed() in g.cache:
|
|
|
return g.cache[self.hashed()]
|
|
|
self.why_me(g, level)
|
|
|
return self
|
|
|
|
|
|
def populate(self, name: str, args: list[gm.Point]) -> Dependency:
|
|
|
assert self.rule_name == CONSTRUCTION_RULE, self.rule_name
|
|
|
dep = Dependency(self.name, self.args, self.rule_name, self.level)
|
|
|
dep.why = list(self.why)
|
|
|
return dep
|
|
|
|
|
|
def why_me(self, g: Any, level: int) -> None:
|
|
|
"""Figure out the dependencies predicates of self."""
|
|
|
name, args = self.name, self.args
|
|
|
|
|
|
hashed_me = hashed(name, args)
|
|
|
if hashed_me in g.cache:
|
|
|
dep = g.cache[hashed_me]
|
|
|
self.why = dep.why
|
|
|
self.rule_name = dep.rule_name
|
|
|
return
|
|
|
|
|
|
if self.name == 'para':
|
|
|
a, b, c, d = self.args
|
|
|
if {a, b} == {c, d}:
|
|
|
self.why = []
|
|
|
return
|
|
|
|
|
|
ab = g._get_line(a, b)
|
|
|
cd = g._get_line(c, d)
|
|
|
if ab == cd:
|
|
|
if {a, b} == {c, d}:
|
|
|
self.why = []
|
|
|
self.rule_name = ''
|
|
|
return
|
|
|
dep = Dependency('coll', list({a, b, c, d}), 't??', None)
|
|
|
self.why = [dep.why_me_or_cache(g, level)]
|
|
|
return
|
|
|
|
|
|
for (x, y), xy in zip([(a, b), (c, d)], [ab, cd]):
|
|
|
x_, y_ = xy.points
|
|
|
if {x, y} == {x_, y_}:
|
|
|
continue
|
|
|
d = Dependency('collx', [x, y, x_, y_], None, level)
|
|
|
self.why += [d.why_me_or_cache(g, level)]
|
|
|
|
|
|
whypara = g.why_equal(ab, cd, None)
|
|
|
self.why += whypara
|
|
|
|
|
|
elif self.name == 'midp':
|
|
|
m, a, b = self.args
|
|
|
ma = g._get_segment(m, a)
|
|
|
mb = g._get_segment(m, b)
|
|
|
dep = Dependency('coll', [m, a, b], None, None).why_me_or_cache(g, None)
|
|
|
self.why = [dep] + g.why_equal(ma, mb, level)
|
|
|
|
|
|
elif self.name == 'perp':
|
|
|
a, b, c, d = self.args
|
|
|
ab = g._get_line(a, b)
|
|
|
cd = g._get_line(c, d)
|
|
|
for (x, y), xy in zip([(a, b), (c, d)], [ab, cd]):
|
|
|
x_, y_ = xy.points
|
|
|
if {x, y} == {x_, y_}:
|
|
|
continue
|
|
|
d = Dependency('collx', [x, y, x_, y_], None, level)
|
|
|
self.why += [d.why_me_or_cache(g, level)]
|
|
|
|
|
|
_, why = why_eqangle(ab._val, cd._val, cd._val, ab._val, level)
|
|
|
a, b = ab.points
|
|
|
c, d = cd.points
|
|
|
|
|
|
if hashed(self.name, [a, b, c, d]) != self.hashed():
|
|
|
d = Dependency(self.name, [a, b, c, d], None, level)
|
|
|
d.why = why
|
|
|
why = [d]
|
|
|
|
|
|
self.why += why
|
|
|
|
|
|
elif self.name == 'cong':
|
|
|
a, b, c, d = self.args
|
|
|
ab = g._get_segment(a, b)
|
|
|
cd = g._get_segment(c, d)
|
|
|
|
|
|
self.why = g.why_equal(ab, cd, level)
|
|
|
|
|
|
elif self.name == 'coll':
|
|
|
_, why = gm.line_of_and_why(self.args, level)
|
|
|
self.why = why
|
|
|
|
|
|
elif self.name == 'collx':
|
|
|
if g.check_coll(self.args):
|
|
|
args = list(set(self.args))
|
|
|
hashed_me = hashed('coll', args)
|
|
|
if hashed_me in g.cache:
|
|
|
dep = g.cache[hashed_me]
|
|
|
self.why = [dep]
|
|
|
self.rule_name = ''
|
|
|
return
|
|
|
_, self.why = gm.line_of_and_why(args, level)
|
|
|
else:
|
|
|
self.name = 'para'
|
|
|
self.why_me(g, level)
|
|
|
|
|
|
elif self.name == 'cyclic':
|
|
|
_, why = gm.circle_of_and_why(self.args, level)
|
|
|
self.why = why
|
|
|
|
|
|
elif self.name == 'circle':
|
|
|
o, a, b, c = self.args
|
|
|
oa = g._get_segment(o, a)
|
|
|
ob = g._get_segment(o, b)
|
|
|
oc = g._get_segment(o, c)
|
|
|
self.why = g.why_equal(oa, ob, level) + g.why_equal(oa, oc, level)
|
|
|
|
|
|
elif self.name in ['eqangle', 'eqangle6']:
|
|
|
a, b, c, d, m, n, p, q = self.args
|
|
|
|
|
|
ab, why1 = g.get_line_thru_pair_why(a, b)
|
|
|
cd, why2 = g.get_line_thru_pair_why(c, d)
|
|
|
mn, why3 = g.get_line_thru_pair_why(m, n)
|
|
|
pq, why4 = g.get_line_thru_pair_why(p, q)
|
|
|
|
|
|
if ab is None or cd is None or mn is None or pq is None:
|
|
|
if {a, b} == {m, n}:
|
|
|
d = Dependency('para', [c, d, p, q], None, level)
|
|
|
self.why = [d.why_me_or_cache(g, level)]
|
|
|
if {a, b} == {c, d}:
|
|
|
d = Dependency('para', [p, q, m, n], None, level)
|
|
|
self.why = [d.why_me_or_cache(g, level)]
|
|
|
if {c, d} == {p, q}:
|
|
|
d = Dependency('para', [a, b, m, n], None, level)
|
|
|
self.why = [d.why_me_or_cache(g, level)]
|
|
|
if {p, q} == {m, n}:
|
|
|
d = Dependency('para', [a, b, c, d], None, level)
|
|
|
self.why = [d.why_me_or_cache(g, level)]
|
|
|
return
|
|
|
|
|
|
for (x, y), xy, whyxy in zip(
|
|
|
[(a, b), (c, d), (m, n), (p, q)],
|
|
|
[ab, cd, mn, pq],
|
|
|
[why1, why2, why3, why4],
|
|
|
):
|
|
|
x_, y_ = xy.points
|
|
|
if {x, y} == {x_, y_}:
|
|
|
continue
|
|
|
d = Dependency('collx', [x, y, x_, y_], None, level)
|
|
|
d.why = whyxy
|
|
|
self.why += [d]
|
|
|
|
|
|
a, b = ab.points
|
|
|
c, d = cd.points
|
|
|
m, n = mn.points
|
|
|
p, q = pq.points
|
|
|
diff = hashed(self.name, [a, b, c, d, m, n, p, q]) != self.hashed()
|
|
|
|
|
|
whyeqangle = None
|
|
|
if ab._val and cd._val and mn._val and pq._val:
|
|
|
whyeqangle = why_eqangle(ab._val, cd._val, mn._val, pq._val, level)
|
|
|
|
|
|
if whyeqangle:
|
|
|
(dab, dcd, dmn, dpq), whyeqangle = whyeqangle
|
|
|
if diff:
|
|
|
d = Dependency('eqangle', [a, b, c, d, m, n, p, q], None, level)
|
|
|
d.why = whyeqangle
|
|
|
whyeqangle = [d]
|
|
|
self.why += whyeqangle
|
|
|
|
|
|
else:
|
|
|
if (ab == cd and mn == pq) or (ab == mn and cd == pq):
|
|
|
self.why += []
|
|
|
elif ab == mn:
|
|
|
self.why += maybe_make_equal_pairs(
|
|
|
a, b, c, d, m, n, p, q, ab, mn, g, level
|
|
|
)
|
|
|
elif cd == pq:
|
|
|
self.why += maybe_make_equal_pairs(
|
|
|
c, d, a, b, p, q, m, n, cd, pq, g, level
|
|
|
)
|
|
|
elif ab == cd:
|
|
|
self.why += maybe_make_equal_pairs(
|
|
|
a, b, m, n, c, d, p, q, ab, cd, g, level
|
|
|
)
|
|
|
elif mn == pq:
|
|
|
self.why += maybe_make_equal_pairs(
|
|
|
m, n, a, b, p, q, c, d, mn, pq, g, level
|
|
|
)
|
|
|
elif g.is_equal(ab, mn) or g.is_equal(cd, pq):
|
|
|
dep1 = Dependency('para', [a, b, m, n], None, level)
|
|
|
dep1.why_me(g, level)
|
|
|
dep2 = Dependency('para', [c, d, p, q], None, level)
|
|
|
dep2.why_me(g, level)
|
|
|
self.why += [dep1, dep2]
|
|
|
elif g.is_equal(ab, cd) or g.is_equal(mn, pq):
|
|
|
dep1 = Dependency('para', [a, b, c, d], None, level)
|
|
|
dep1.why_me(g, level)
|
|
|
dep2 = Dependency('para', [m, n, p, q], None, level)
|
|
|
dep2.why_me(g, level)
|
|
|
self.why += [dep1, dep2]
|
|
|
elif ab._val and cd._val and mn._val and pq._val:
|
|
|
self.why = why_eqangle(ab._val, cd._val, mn._val, pq._val, level)
|
|
|
|
|
|
elif self.name in ['eqratio', 'eqratio6']:
|
|
|
a, b, c, d, m, n, p, q = self.args
|
|
|
ab = g._get_segment(a, b)
|
|
|
cd = g._get_segment(c, d)
|
|
|
mn = g._get_segment(m, n)
|
|
|
pq = g._get_segment(p, q)
|
|
|
|
|
|
if ab is None or cd is None or mn is None or pq is None:
|
|
|
if {a, b} == {m, n}:
|
|
|
d = Dependency('cong', [c, d, p, q], None, level)
|
|
|
self.why = [d.why_me_or_cache(g, level)]
|
|
|
if {a, b} == {c, d}:
|
|
|
d = Dependency('cong', [p, q, m, n], None, level)
|
|
|
self.why = [d.why_me_or_cache(g, level)]
|
|
|
if {c, d} == {p, q}:
|
|
|
d = Dependency('cong', [a, b, m, n], None, level)
|
|
|
self.why = [d.why_me_or_cache(g, level)]
|
|
|
if {p, q} == {m, n}:
|
|
|
d = Dependency('cong', [a, b, c, d], None, level)
|
|
|
self.why = [d.why_me_or_cache(g, level)]
|
|
|
return
|
|
|
|
|
|
if ab._val and cd._val and mn._val and pq._val:
|
|
|
self.why = why_eqratio(ab._val, cd._val, mn._val, pq._val, level)
|
|
|
|
|
|
if self.why is None:
|
|
|
self.why = []
|
|
|
if (ab == cd and mn == pq) or (ab == mn and cd == pq):
|
|
|
self.why = []
|
|
|
elif ab == mn:
|
|
|
self.why += maybe_make_equal_pairs(
|
|
|
a, b, c, d, m, n, p, q, ab, mn, g, level
|
|
|
)
|
|
|
elif cd == pq:
|
|
|
self.why += maybe_make_equal_pairs(
|
|
|
c, d, a, b, p, q, m, n, cd, pq, g, level
|
|
|
)
|
|
|
elif ab == cd:
|
|
|
self.why += maybe_make_equal_pairs(
|
|
|
a, b, m, n, c, d, p, q, ab, cd, g, level
|
|
|
)
|
|
|
elif mn == pq:
|
|
|
self.why += maybe_make_equal_pairs(
|
|
|
m, n, a, b, p, q, c, d, mn, pq, g, level
|
|
|
)
|
|
|
elif g.is_equal(ab, mn) or g.is_equal(cd, pq):
|
|
|
dep1 = Dependency('cong', [a, b, m, n], None, level)
|
|
|
dep1.why_me(g, level)
|
|
|
dep2 = Dependency('cong', [c, d, p, q], None, level)
|
|
|
dep2.why_me(g, level)
|
|
|
self.why += [dep1, dep2]
|
|
|
elif g.is_equal(ab, cd) or g.is_equal(mn, pq):
|
|
|
dep1 = Dependency('cong', [a, b, c, d], None, level)
|
|
|
dep1.why_me(g, level)
|
|
|
dep2 = Dependency('cong', [m, n, p, q], None, level)
|
|
|
dep2.why_me(g, level)
|
|
|
self.why += [dep1, dep2]
|
|
|
elif ab._val and cd._val and mn._val and pq._val:
|
|
|
self.why = why_eqangle(ab._val, cd._val, mn._val, pq._val, level)
|
|
|
|
|
|
elif self.name in ['diff', 'npara', 'nperp', 'ncoll', 'sameside']:
|
|
|
self.why = []
|
|
|
|
|
|
elif self.name == 'simtri':
|
|
|
a, b, c, x, y, z = self.args
|
|
|
dep1 = Dependency('eqangle', [a, b, a, c, x, y, x, z], '', level)
|
|
|
dep1.why_me(g, level)
|
|
|
dep2 = Dependency('eqangle', [b, a, b, c, y, x, y, z], '', level)
|
|
|
dep2.why_me(g, level)
|
|
|
self.rule_name = 'r34'
|
|
|
self.why = [dep1, dep2]
|
|
|
|
|
|
elif self.name == 'contri':
|
|
|
a, b, c, x, y, z = self.args
|
|
|
dep1 = Dependency('cong', [a, b, x, y], '', level)
|
|
|
dep1.why_me(g, level)
|
|
|
dep2 = Dependency('cong', [b, c, y, z], '', level)
|
|
|
dep2.why_me(g, level)
|
|
|
dep3 = Dependency('cong', [c, a, z, x], '', level)
|
|
|
dep3.why_me(g, level)
|
|
|
self.rule_name = 'r32'
|
|
|
self.why = [dep1, dep2, dep3]
|
|
|
|
|
|
elif self.name == 'ind':
|
|
|
pass
|
|
|
|
|
|
elif self.name == 'aconst':
|
|
|
a, b, c, d, ang0 = self.args
|
|
|
|
|
|
measure = ang0._val
|
|
|
|
|
|
for ang in measure.neighbors(gm.Angle):
|
|
|
if ang == ang0:
|
|
|
continue
|
|
|
d1, d2 = ang._d
|
|
|
l1, l2 = d1._obj, d2._obj
|
|
|
(a1, b1), (c1, d1) = l1.points, l2.points
|
|
|
|
|
|
if not g.check_para_or_coll([a, b, a1, b1]) or not g.check_para_or_coll(
|
|
|
[c, d, c1, d1]
|
|
|
):
|
|
|
continue
|
|
|
|
|
|
self.why = []
|
|
|
for args in [(a, b, a1, b1), (c, d, c1, d1)]:
|
|
|
if g.check_coll(args):
|
|
|
if len(set(args)) > 2:
|
|
|
dep = Dependency('coll', args, None, None)
|
|
|
self.why.append(dep.why_me_or_cache(g, level))
|
|
|
else:
|
|
|
dep = Dependency('para', args, None, None)
|
|
|
self.why.append(dep.why_me_or_cache(g, level))
|
|
|
|
|
|
self.why += gm.why_equal(ang, ang0)
|
|
|
break
|
|
|
|
|
|
elif self.name == 'rconst':
|
|
|
a, b, c, d, rat0 = self.args
|
|
|
|
|
|
val = rat0._val
|
|
|
|
|
|
for rat in val.neighbors(gm.Ratio):
|
|
|
if rat == rat0:
|
|
|
continue
|
|
|
l1, l2 = rat._l
|
|
|
s1, s2 = l1._obj, l2._obj
|
|
|
(a1, b1), (c1, d1) = list(s1.points), list(s2.points)
|
|
|
|
|
|
if not g.check_cong([a, b, a1, b1]) or not g.check_cong([c, d, c1, d1]):
|
|
|
continue
|
|
|
|
|
|
self.why = []
|
|
|
for args in [(a, b, a1, b1), (c, d, c1, d1)]:
|
|
|
if len(set(args)) > 2:
|
|
|
dep = Dependency('cong', args, None, None)
|
|
|
self.why.append(dep.why_me_or_cache(g, level))
|
|
|
|
|
|
self.why += gm.why_equal(rat, rat0)
|
|
|
break
|
|
|
|
|
|
else:
|
|
|
raise ValueError('Not recognize', self.name)
|
|
|
|
|
|
def hashed(self, rename: bool = False) -> tuple[str, ...]:
|
|
|
return hashed(self.name, self.args, rename=rename)
|
|
|
|
|
|
|
|
|
def hashed(
|
|
|
name: str, args: list[gm.Point], rename: bool = False
|
|
|
) -> tuple[str, ...]:
|
|
|
if name == 's_angle':
|
|
|
args = [p.name if not rename else p.new_name for p in args[:-1]] + [
|
|
|
str(args[-1])
|
|
|
]
|
|
|
else:
|
|
|
args = [p.name if not rename else p.new_name for p in args]
|
|
|
return hashed_txt(name, args)
|
|
|
|
|
|
|
|
|
def hashed_txt(name: str, args: list[str]) -> tuple[str, ...]:
|
|
|
"""Return a tuple unique to name and args upto arg permutation equivariant."""
|
|
|
|
|
|
if name in ['const', 'aconst', 'rconst']:
|
|
|
a, b, c, d, y = args
|
|
|
a, b = sorted([a, b])
|
|
|
c, d = sorted([c, d])
|
|
|
return name, a, b, c, d, y
|
|
|
|
|
|
if name in ['npara', 'nperp', 'para', 'cong', 'perp', 'collx']:
|
|
|
a, b, c, d = args
|
|
|
|
|
|
a, b = sorted([a, b])
|
|
|
c, d = sorted([c, d])
|
|
|
(a, b), (c, d) = sorted([(a, b), (c, d)])
|
|
|
|
|
|
return (name, a, b, c, d)
|
|
|
|
|
|
if name in ['midp', 'midpoint']:
|
|
|
a, b, c = args
|
|
|
b, c = sorted([b, c])
|
|
|
return (name, a, b, c)
|
|
|
|
|
|
if name in ['coll', 'cyclic', 'ncoll', 'diff', 'triangle']:
|
|
|
return (name,) + tuple(sorted(list(set(args))))
|
|
|
|
|
|
if name == 'circle':
|
|
|
x, a, b, c = args
|
|
|
return (name, x) + tuple(sorted([a, b, c]))
|
|
|
|
|
|
if name in ['eqangle', 'eqratio', 'eqangle6', 'eqratio6']:
|
|
|
a, b, c, d, e, f, g, h = args
|
|
|
a, b = sorted([a, b])
|
|
|
c, d = sorted([c, d])
|
|
|
e, f = sorted([e, f])
|
|
|
g, h = sorted([g, h])
|
|
|
if tuple(sorted([a, b, e, f])) > tuple(sorted([c, d, g, h])):
|
|
|
a, b, e, f, c, d, g, h = c, d, g, h, a, b, e, f
|
|
|
if (a, b, c, d) > (e, f, g, h):
|
|
|
a, b, c, d, e, f, g, h = e, f, g, h, a, b, c, d
|
|
|
|
|
|
if name == 'eqangle6':
|
|
|
name = 'eqangle'
|
|
|
if name == 'eqratio6':
|
|
|
name = 'eqratio'
|
|
|
return (name,) + (a, b, c, d, e, f, g, h)
|
|
|
|
|
|
if name in ['contri', 'simtri', 'simtri2', 'contri2', 'contri*', 'simtri*']:
|
|
|
a, b, c, x, y, z = args
|
|
|
(a, x), (b, y), (c, z) = sorted([(a, x), (b, y), (c, z)], key=sorted)
|
|
|
(a, b, c), (x, y, z) = sorted([(a, b, c), (x, y, z)], key=sorted)
|
|
|
return (name, a, b, c, x, y, z)
|
|
|
|
|
|
if name in ['eqratio3']:
|
|
|
a, b, c, d, o, o = args
|
|
|
(a, c), (b, d) = sorted([(a, c), (b, d)], key=sorted)
|
|
|
(a, b), (c, d) = sorted([(a, b), (c, d)], key=sorted)
|
|
|
return (name, a, b, c, d, o, o)
|
|
|
|
|
|
if name in ['sameside', 's_angle']:
|
|
|
return (name,) + tuple(args)
|
|
|
|
|
|
raise ValueError(f'Not recognize {name} to hash.')
|
|
|
|