Spaces:
Sleeping
Sleeping
# Copyright 2023 DeepMind Technologies Limited | |
# | |
# Licensed under the Apache License, Version 2.0 (the "License"); | |
# you may not use this file except in compliance with the License. | |
# You may obtain a copy of the License at | |
# | |
# http://www.apache.org/licenses/LICENSE-2.0 | |
# | |
# Unless required by applicable law or agreed to in writing, software | |
# distributed under the License is distributed on an "AS IS" BASIS, | |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
# See the License for the specific language governing permissions and | |
# limitations under the License. | |
# ============================================================================== | |
"""Implements objects to represent problems, theorems, proofs, traceback.""" | |
from __future__ import annotations | |
from collections import defaultdict # pylint: disable=g-importing-member | |
from typing import Any | |
import geometry as gm | |
import pretty as pt | |
# pylint: disable=protected-access | |
# pylint: disable=unused-variable | |
# pylint: disable=unused-argument | |
# pylint: disable=unused-assignment | |
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: # pylint: disable=bare-except | |
return False | |
class Construction: | |
"""One predicate.""" | |
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).""" | |
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': # pcount = 26 -> 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.""" | |
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 | |
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 | |
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: # to single-char point names | |
"""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.""" | |
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) | |
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 | |
def to_dict(cls, data: list[Definition]) -> dict[str, Definition]: | |
return {d.construction.name: d for d in data} | |
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.""" | |
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) | |
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 | |
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 # pylint: disable=redeclared-assigned-name | |
(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.') | |