|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
"""Implements the graph representation of the proof state."""
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
from collections import defaultdict
|
|
|
from typing import Callable, Generator, Optional, Type, Union
|
|
|
|
|
|
from absl import logging
|
|
|
import ar
|
|
|
import geometry as gm
|
|
|
from geometry import Angle, Direction, Length, Ratio
|
|
|
from geometry import Circle, Line, Point, Segment
|
|
|
from geometry import Measure, Value
|
|
|
import graph_utils as utils
|
|
|
import numericals as nm
|
|
|
import problem
|
|
|
from problem import Dependency, EmptyDependency
|
|
|
|
|
|
|
|
|
np = nm.np
|
|
|
|
|
|
|
|
|
FREE = [
|
|
|
'free',
|
|
|
'segment',
|
|
|
'r_triangle',
|
|
|
'risos',
|
|
|
'triangle',
|
|
|
'triangle12',
|
|
|
'ieq_triangle',
|
|
|
'eq_quadrangle',
|
|
|
'eq_trapezoid',
|
|
|
'eqdia_quadrangle',
|
|
|
'quadrangle',
|
|
|
'r_trapezoid',
|
|
|
'rectangle',
|
|
|
'isquare',
|
|
|
'trapezoid',
|
|
|
'pentagon',
|
|
|
'iso_triangle',
|
|
|
]
|
|
|
|
|
|
INTERSECT = [
|
|
|
'angle_bisector',
|
|
|
'angle_mirror',
|
|
|
'eqdistance',
|
|
|
'lc_tangent',
|
|
|
'on_aline',
|
|
|
'on_bline',
|
|
|
'on_circle',
|
|
|
'on_line',
|
|
|
'on_pline',
|
|
|
'on_tline',
|
|
|
'on_dia',
|
|
|
's_angle',
|
|
|
'on_opline',
|
|
|
'eqangle3',
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DepCheckFailError(Exception):
|
|
|
pass
|
|
|
|
|
|
|
|
|
class PointTooCloseError(Exception):
|
|
|
pass
|
|
|
|
|
|
|
|
|
class PointTooFarError(Exception):
|
|
|
pass
|
|
|
|
|
|
|
|
|
class Graph:
|
|
|
"""Graph data structure representing proof state."""
|
|
|
|
|
|
def __init__(self):
|
|
|
self.type2nodes = {
|
|
|
Point: [],
|
|
|
Line: [],
|
|
|
Segment: [],
|
|
|
Circle: [],
|
|
|
Direction: [],
|
|
|
Length: [],
|
|
|
Angle: [],
|
|
|
Ratio: [],
|
|
|
Measure: [],
|
|
|
Value: [],
|
|
|
}
|
|
|
self._name2point = {}
|
|
|
self._name2node = {}
|
|
|
|
|
|
self.rconst = {}
|
|
|
self.aconst = {}
|
|
|
|
|
|
self.halfpi, _ = self.get_or_create_const_ang(1, 2)
|
|
|
self.vhalfpi = self.halfpi.val
|
|
|
|
|
|
self.atable = ar.AngleTable()
|
|
|
self.dtable = ar.DistanceTable()
|
|
|
self.rtable = ar.RatioTable()
|
|
|
|
|
|
|
|
|
self.cache = {}
|
|
|
|
|
|
self._pair2line = {}
|
|
|
self._triplet2circle = {}
|
|
|
|
|
|
def copy(self) -> Graph:
|
|
|
"""Make a copy of self."""
|
|
|
p, definitions = self.build_def
|
|
|
|
|
|
p = p.copy()
|
|
|
for clause in p.clauses:
|
|
|
clause.nums = []
|
|
|
for pname in clause.points:
|
|
|
clause.nums.append(self._name2node[pname].num)
|
|
|
|
|
|
g, _ = Graph.build_problem(p, definitions, verbose=False, init_copy=False)
|
|
|
|
|
|
g.build_clauses = list(getattr(self, 'build_clauses', []))
|
|
|
return g
|
|
|
|
|
|
def _create_const_ang(self, n: int, d: int) -> None:
|
|
|
n, d = ar.simplify(n, d)
|
|
|
ang = self.aconst[(n, d)] = self.new_node(Angle, f'{n}pi/{d}')
|
|
|
ang.set_directions(None, None)
|
|
|
self.connect_val(ang, deps=None)
|
|
|
|
|
|
def _create_const_rat(self, n: int, d: int) -> None:
|
|
|
n, d = ar.simplify(n, d)
|
|
|
rat = self.rconst[(n, d)] = self.new_node(Ratio, f'{n}/{d}')
|
|
|
rat.set_lengths(None, None)
|
|
|
self.connect_val(rat, deps=None)
|
|
|
|
|
|
def get_or_create_const_ang(self, n: int, d: int) -> None:
|
|
|
n, d = ar.simplify(n, d)
|
|
|
if (n, d) not in self.aconst:
|
|
|
self._create_const_ang(n, d)
|
|
|
ang1 = self.aconst[(n, d)]
|
|
|
|
|
|
n, d = ar.simplify(d - n, d)
|
|
|
if (n, d) not in self.aconst:
|
|
|
self._create_const_ang(n, d)
|
|
|
ang2 = self.aconst[(n, d)]
|
|
|
return ang1, ang2
|
|
|
|
|
|
def get_or_create_const_rat(self, n: int, d: int) -> None:
|
|
|
n, d = ar.simplify(n, d)
|
|
|
if (n, d) not in self.rconst:
|
|
|
self._create_const_rat(n, d)
|
|
|
rat1 = self.rconst[(n, d)]
|
|
|
|
|
|
if (d, n) not in self.rconst:
|
|
|
self._create_const_rat(d, n)
|
|
|
rat2 = self.rconst[(d, n)]
|
|
|
return rat1, rat2
|
|
|
|
|
|
def add_algebra(self, dep: Dependency, level: int) -> None:
|
|
|
"""Add new algebraic predicates."""
|
|
|
_ = level
|
|
|
if dep.name not in [
|
|
|
'para',
|
|
|
'perp',
|
|
|
'eqangle',
|
|
|
'eqratio',
|
|
|
'aconst',
|
|
|
'rconst',
|
|
|
'cong',
|
|
|
]:
|
|
|
return
|
|
|
|
|
|
name, args = dep.name, dep.args
|
|
|
|
|
|
if name == 'para':
|
|
|
ab, cd = dep.algebra
|
|
|
self.atable.add_para(ab, cd, dep)
|
|
|
|
|
|
if name == 'perp':
|
|
|
ab, cd = dep.algebra
|
|
|
self.atable.add_const_angle(ab, cd, 90, dep)
|
|
|
|
|
|
if name == 'eqangle':
|
|
|
ab, cd, mn, pq = dep.algebra
|
|
|
if (ab, cd) == (pq, mn):
|
|
|
self.atable.add_const_angle(ab, cd, 90, dep)
|
|
|
else:
|
|
|
self.atable.add_eqangle(ab, cd, mn, pq, dep)
|
|
|
|
|
|
if name == 'eqratio':
|
|
|
ab, cd, mn, pq = dep.algebra
|
|
|
if (ab, cd) == (pq, mn):
|
|
|
self.rtable.add_eq(ab, cd, dep)
|
|
|
else:
|
|
|
self.rtable.add_eqratio(ab, cd, mn, pq, dep)
|
|
|
|
|
|
if name == 'aconst':
|
|
|
bx, ab, y = dep.algebra
|
|
|
self.atable.add_const_angle(bx, ab, y, dep)
|
|
|
|
|
|
if name == 'rconst':
|
|
|
l1, l2, m, n = dep.algebra
|
|
|
self.rtable.add_const_ratio(l1, l2, m, n, dep)
|
|
|
|
|
|
if name == 'cong':
|
|
|
a, b, c, d = args
|
|
|
ab, _ = self.get_line_thru_pair_why(a, b)
|
|
|
cd, _ = self.get_line_thru_pair_why(c, d)
|
|
|
self.dtable.add_cong(ab, cd, a, b, c, d, dep)
|
|
|
|
|
|
ab, cd = dep.algebra
|
|
|
self.rtable.add_eq(ab, cd, dep)
|
|
|
|
|
|
def add_eqrat_const(
|
|
|
self, args: list[Point], deps: EmptyDependency
|
|
|
) -> list[Dependency]:
|
|
|
"""Add new algebraic predicates of type eqratio-constant."""
|
|
|
a, b, c, d, num, den = args
|
|
|
nd, dn = self.get_or_create_const_rat(num, den)
|
|
|
|
|
|
if num == den:
|
|
|
return self.add_cong([a, b, c, d], deps)
|
|
|
|
|
|
ab = self._get_or_create_segment(a, b, deps=None)
|
|
|
cd = self._get_or_create_segment(c, d, deps=None)
|
|
|
|
|
|
self.connect_val(ab, deps=None)
|
|
|
self.connect_val(cd, deps=None)
|
|
|
|
|
|
if ab.val == cd.val:
|
|
|
raise ValueError(f'{ab.name} and {cd.name} cannot be equal')
|
|
|
|
|
|
args = [a, b, c, d, nd]
|
|
|
i = 0
|
|
|
for x, y, xy in [(a, b, ab), (c, d, cd)]:
|
|
|
i += 1
|
|
|
x_, y_ = list(xy._val._obj.points)
|
|
|
if {x, y} == {x_, y_}:
|
|
|
continue
|
|
|
if deps:
|
|
|
deps = deps.extend(self, 'rconst', list(args), 'cong', [x, y, x_, y_])
|
|
|
args[2 * i - 2] = x_
|
|
|
args[2 * i - 1] = y_
|
|
|
|
|
|
ab_cd, cd_ab, why = self._get_or_create_ratio(ab, cd, deps=None)
|
|
|
if why:
|
|
|
dep0 = deps.populate('rconst', [a, b, c, d, nd])
|
|
|
deps = EmptyDependency(level=deps.level, rule_name=None)
|
|
|
deps.why = [dep0] + why
|
|
|
|
|
|
lab, lcd = ab_cd._l
|
|
|
a, b = list(lab._obj.points)
|
|
|
c, d = list(lcd._obj.points)
|
|
|
|
|
|
add = []
|
|
|
if not self.is_equal(ab_cd, nd):
|
|
|
args = [a, b, c, d, nd]
|
|
|
dep1 = deps.populate('rconst', args)
|
|
|
dep1.algebra = ab._val, cd._val, num, den
|
|
|
self.make_equal(nd, ab_cd, deps=dep1)
|
|
|
self.cache_dep('rconst', [a, b, c, d, nd], dep1)
|
|
|
add += [dep1]
|
|
|
|
|
|
if not self.is_equal(cd_ab, dn):
|
|
|
args = [c, d, a, b, dn]
|
|
|
dep2 = deps.populate('rconst', args)
|
|
|
dep2.algebra = cd._val, ab._val, num, den
|
|
|
self.make_equal(dn, cd_ab, deps=dep2)
|
|
|
self.cache_dep('rconst', [c, d, a, b, dn], dep2)
|
|
|
add += [dep2]
|
|
|
|
|
|
return add
|
|
|
|
|
|
def do_algebra(self, name: str, args: list[Point]) -> list[Dependency]:
|
|
|
"""Derive (but not add) new algebraic predicates."""
|
|
|
if name == 'para':
|
|
|
a, b, dep = args
|
|
|
if gm.is_equiv(a, b):
|
|
|
return []
|
|
|
(x, y), (m, n) = a._obj.points, b._obj.points
|
|
|
return self.add_para([x, y, m, n], dep)
|
|
|
|
|
|
if name == 'aconst':
|
|
|
a, b, n, d, dep = args
|
|
|
ab, ba, why = self.get_or_create_angle_d(a, b, deps=None)
|
|
|
nd, dn = self.get_or_create_const_ang(n, d)
|
|
|
|
|
|
(x, y), (m, n) = a._obj.points, b._obj.points
|
|
|
|
|
|
if why:
|
|
|
dep0 = dep.populate('aconst', [x, y, m, n, nd])
|
|
|
dep = EmptyDependency(level=dep.level, rule_name=None)
|
|
|
dep.why = [dep0] + why
|
|
|
|
|
|
a, b = ab._d
|
|
|
(x, y), (m, n) = a._obj.points, b._obj.points
|
|
|
|
|
|
added = []
|
|
|
if not self.is_equal(ab, nd):
|
|
|
if nd == self.halfpi:
|
|
|
added += self.add_perp([x, y, m, n], dep)
|
|
|
|
|
|
name = 'aconst'
|
|
|
args = [x, y, m, n, nd]
|
|
|
dep1 = dep.populate(name, args)
|
|
|
self.cache_dep(name, args, dep1)
|
|
|
self.make_equal(nd, ab, deps=dep1)
|
|
|
added += [dep1]
|
|
|
|
|
|
if not self.is_equal(ba, dn):
|
|
|
if dn == self.halfpi:
|
|
|
added += self.add_perp([m, n, x, y], dep)
|
|
|
name = 'aconst'
|
|
|
args = [m, n, x, y, dn]
|
|
|
dep2 = dep.populate(name, args)
|
|
|
self.cache_dep(name, args, dep2)
|
|
|
self.make_equal(dn, ba, deps=dep2)
|
|
|
added += [dep2]
|
|
|
return added
|
|
|
|
|
|
if name == 'rconst':
|
|
|
a, b, c, d, num, den, dep = args
|
|
|
return self.add_eqrat_const([a, b, c, d, num, den], dep)
|
|
|
|
|
|
if name == 'eqangle':
|
|
|
d1, d2, d3, d4, dep = args
|
|
|
a, b = d1._obj.points
|
|
|
c, d = d2._obj.points
|
|
|
e, f = d3._obj.points
|
|
|
g, h = d4._obj.points
|
|
|
|
|
|
return self.add_eqangle([a, b, c, d, e, f, g, h], dep)
|
|
|
|
|
|
if name == 'eqratio':
|
|
|
d1, d2, d3, d4, dep = args
|
|
|
a, b = d1._obj.points
|
|
|
c, d = d2._obj.points
|
|
|
e, f = d3._obj.points
|
|
|
g, h = d4._obj.points
|
|
|
|
|
|
return self.add_eqratio([a, b, c, d, e, f, g, h], dep)
|
|
|
|
|
|
if name in ['cong', 'cong2']:
|
|
|
a, b, c, d, dep = args
|
|
|
if not (a != b and c != d and (a != c or b != d)):
|
|
|
return []
|
|
|
return self.add_cong([a, b, c, d], dep)
|
|
|
|
|
|
return []
|
|
|
|
|
|
def derive_algebra(
|
|
|
self, level: int, verbose: bool = False
|
|
|
) -> tuple[
|
|
|
dict[str, list[tuple[Point, ...]]], dict[str, [tuple[Point, ...]]]
|
|
|
]:
|
|
|
"""Derive new algebraic predicates."""
|
|
|
derives = {}
|
|
|
ang_derives = self.derive_angle_algebra(level, verbose=verbose)
|
|
|
dist_derives = self.derive_distance_algebra(level, verbose=verbose)
|
|
|
rat_derives = self.derive_ratio_algebra(level, verbose=verbose)
|
|
|
|
|
|
derives.update(ang_derives)
|
|
|
derives.update(dist_derives)
|
|
|
derives.update(rat_derives)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
eqs = {'eqangle': derives.pop('eqangle'), 'eqratio': derives.pop('eqratio')}
|
|
|
return derives, eqs
|
|
|
|
|
|
def derive_ratio_algebra(
|
|
|
self, level: int, verbose: bool = False
|
|
|
) -> dict[str, list[tuple[Point, ...]]]:
|
|
|
"""Derive new eqratio predicates."""
|
|
|
added = {'cong2': [], 'eqratio': []}
|
|
|
|
|
|
for x in self.rtable.get_all_eqs_and_why():
|
|
|
x, why = x[:-1], x[-1]
|
|
|
dep = EmptyDependency(level=level, rule_name='a01')
|
|
|
dep.why = why
|
|
|
|
|
|
if len(x) == 2:
|
|
|
a, b = x
|
|
|
if gm.is_equiv(a, b):
|
|
|
continue
|
|
|
|
|
|
(m, n), (p, q) = a._obj.points, b._obj.points
|
|
|
added['cong2'].append((m, n, p, q, dep))
|
|
|
|
|
|
if len(x) == 4:
|
|
|
a, b, c, d = x
|
|
|
added['eqratio'].append((a, b, c, d, dep))
|
|
|
|
|
|
return added
|
|
|
|
|
|
def derive_angle_algebra(
|
|
|
self, level: int, verbose: bool = False
|
|
|
) -> dict[str, list[tuple[Point, ...]]]:
|
|
|
"""Derive new eqangles predicates."""
|
|
|
added = {'eqangle': [], 'aconst': [], 'para': []}
|
|
|
|
|
|
for x in self.atable.get_all_eqs_and_why():
|
|
|
x, why = x[:-1], x[-1]
|
|
|
dep = EmptyDependency(level=level, rule_name='a02')
|
|
|
dep.why = why
|
|
|
|
|
|
if len(x) == 2:
|
|
|
a, b = x
|
|
|
if gm.is_equiv(a, b):
|
|
|
continue
|
|
|
|
|
|
(e, f), (p, q) = a._obj.points, b._obj.points
|
|
|
if not nm.check('para', [e, f, p, q]):
|
|
|
continue
|
|
|
|
|
|
added['para'].append((a, b, dep))
|
|
|
|
|
|
if len(x) == 3:
|
|
|
a, b, (n, d) = x
|
|
|
|
|
|
(e, f), (p, q) = a._obj.points, b._obj.points
|
|
|
if not nm.check('aconst', [e, f, p, q, n, d]):
|
|
|
continue
|
|
|
|
|
|
added['aconst'].append((a, b, n, d, dep))
|
|
|
|
|
|
if len(x) == 4:
|
|
|
a, b, c, d = x
|
|
|
added['eqangle'].append((a, b, c, d, dep))
|
|
|
|
|
|
return added
|
|
|
|
|
|
def derive_distance_algebra(
|
|
|
self, level: int, verbose: bool = False
|
|
|
) -> dict[str, list[tuple[Point, ...]]]:
|
|
|
"""Derive new cong predicates."""
|
|
|
added = {'inci': [], 'cong': [], 'rconst': []}
|
|
|
for x in self.dtable.get_all_eqs_and_why():
|
|
|
x, why = x[:-1], x[-1]
|
|
|
dep = EmptyDependency(level=level, rule_name='a00')
|
|
|
dep.why = why
|
|
|
|
|
|
if len(x) == 2:
|
|
|
a, b = x
|
|
|
if a == b:
|
|
|
continue
|
|
|
|
|
|
dep.name = f'inci {a.name} {b.name}'
|
|
|
added['inci'].append((x, dep))
|
|
|
|
|
|
if len(x) == 4:
|
|
|
a, b, c, d = x
|
|
|
if not (a != b and c != d and (a != c or b != d)):
|
|
|
continue
|
|
|
added['cong'].append((a, b, c, d, dep))
|
|
|
|
|
|
if len(x) == 6:
|
|
|
a, b, c, d, num, den = x
|
|
|
if not (a != b and c != d and (a != c or b != d)):
|
|
|
continue
|
|
|
added['rconst'].append((a, b, c, d, num, den, dep))
|
|
|
|
|
|
return added
|
|
|
|
|
|
@classmethod
|
|
|
def build_problem(
|
|
|
cls,
|
|
|
pr: problem.Problem,
|
|
|
definitions: dict[str, problem.Definition],
|
|
|
verbose: bool = True,
|
|
|
init_copy: bool = True,
|
|
|
) -> tuple[Graph, list[Dependency]]:
|
|
|
"""Build a problem into a gr.Graph object."""
|
|
|
check = False
|
|
|
g = None
|
|
|
added = None
|
|
|
if verbose:
|
|
|
logging.info(pr.url)
|
|
|
logging.info(pr.txt())
|
|
|
while not check:
|
|
|
try:
|
|
|
g = Graph()
|
|
|
added = []
|
|
|
plevel = 0
|
|
|
for clause in pr.clauses:
|
|
|
adds, plevel = g.add_clause(
|
|
|
clause, plevel, definitions, verbose=verbose
|
|
|
)
|
|
|
added += adds
|
|
|
g.plevel = plevel
|
|
|
|
|
|
except (nm.InvalidLineIntersectError, nm.InvalidQuadSolveError):
|
|
|
continue
|
|
|
except DepCheckFailError:
|
|
|
continue
|
|
|
except (PointTooCloseError, PointTooFarError):
|
|
|
continue
|
|
|
|
|
|
if not pr.goal:
|
|
|
break
|
|
|
|
|
|
args = list(map(lambda x: g.get(x, lambda: int(x)), pr.goal.args))
|
|
|
check = nm.check(pr.goal.name, args)
|
|
|
|
|
|
g.url = pr.url
|
|
|
g.build_def = (pr, definitions)
|
|
|
for add in added:
|
|
|
g.add_algebra(add, level=0)
|
|
|
|
|
|
return g, added
|
|
|
|
|
|
def all_points(self) -> list[Point]:
|
|
|
"""Return all nodes of type Point."""
|
|
|
return list(self.type2nodes[Point])
|
|
|
|
|
|
def all_nodes(self) -> list[gm.Node]:
|
|
|
"""Return all nodes."""
|
|
|
return list(self._name2node.values())
|
|
|
|
|
|
def add_points(self, pnames: list[str]) -> list[Point]:
|
|
|
"""Add new points with given names in list pnames."""
|
|
|
result = [self.new_node(Point, name) for name in pnames]
|
|
|
self._name2point.update(zip(pnames, result))
|
|
|
return result
|
|
|
|
|
|
def names2nodes(self, pnames: list[str]) -> list[gm.Node]:
|
|
|
return [self._name2node[name] for name in pnames]
|
|
|
|
|
|
def names2points(
|
|
|
self, pnames: list[str], create_new_point: bool = False
|
|
|
) -> list[Point]:
|
|
|
"""Return Point objects given names."""
|
|
|
result = []
|
|
|
for name in pnames:
|
|
|
if name not in self._name2node and not create_new_point:
|
|
|
raise ValueError(f'Cannot find point {name} in graph')
|
|
|
elif name in self._name2node:
|
|
|
obj = self._name2node[name]
|
|
|
else:
|
|
|
obj = self.new_node(Point, name)
|
|
|
result.append(obj)
|
|
|
|
|
|
return result
|
|
|
|
|
|
def names2points_or_int(self, pnames: list[str]) -> list[Point]:
|
|
|
"""Return Point objects given names."""
|
|
|
result = []
|
|
|
for name in pnames:
|
|
|
if name.isdigit():
|
|
|
result += [int(name)]
|
|
|
elif 'pi/' in name:
|
|
|
n, d = name.split('pi/')
|
|
|
ang, _ = self.get_or_create_const_ang(int(n), int(d))
|
|
|
result += [ang]
|
|
|
elif '/' in name:
|
|
|
n, d = name.split('/')
|
|
|
rat, _ = self.get_or_create_const_rat(int(n), int(d))
|
|
|
result += [rat]
|
|
|
else:
|
|
|
result += [self._name2point[name]]
|
|
|
|
|
|
return result
|
|
|
|
|
|
def get(self, pointname: str, default_fn: Callable[str, Point]) -> Point:
|
|
|
if pointname in self._name2point:
|
|
|
return self._name2point[pointname]
|
|
|
if pointname in self._name2node:
|
|
|
return self._name2node[pointname]
|
|
|
return default_fn()
|
|
|
|
|
|
def new_node(self, oftype: Type[gm.Node], name: str = '') -> gm.Node:
|
|
|
node = oftype(name, self)
|
|
|
|
|
|
self.type2nodes[oftype].append(node)
|
|
|
self._name2node[name] = node
|
|
|
|
|
|
if isinstance(node, Point):
|
|
|
self._name2point[name] = node
|
|
|
|
|
|
return node
|
|
|
|
|
|
def merge(self, nodes: list[gm.Node], deps: Dependency) -> gm.Node:
|
|
|
"""Merge all nodes."""
|
|
|
if len(nodes) < 2:
|
|
|
return
|
|
|
|
|
|
node0, *nodes1 = nodes
|
|
|
all_nodes = self.type2nodes[type(node0)]
|
|
|
|
|
|
|
|
|
|
|
|
for node in nodes:
|
|
|
if node in all_nodes:
|
|
|
node0 = node
|
|
|
nodes1 = [n for n in nodes if n != node0]
|
|
|
break
|
|
|
return self.merge_into(node0, nodes1, deps)
|
|
|
|
|
|
def merge_into(
|
|
|
self, node0: gm.Node, nodes1: list[gm.Node], deps: Dependency
|
|
|
) -> gm.Node:
|
|
|
"""Merge nodes1 into a single node0."""
|
|
|
node0.merge(nodes1, deps)
|
|
|
for n in nodes1:
|
|
|
if n.rep() != n:
|
|
|
self.remove([n])
|
|
|
|
|
|
nodes = [node0] + nodes1
|
|
|
if any([node._val for node in nodes]):
|
|
|
for node in nodes:
|
|
|
self.connect_val(node, deps=None)
|
|
|
|
|
|
vals1 = [n._val for n in nodes1]
|
|
|
node0._val.merge(vals1, deps)
|
|
|
|
|
|
for v in vals1:
|
|
|
if v.rep() != v:
|
|
|
self.remove([v])
|
|
|
|
|
|
return node0
|
|
|
|
|
|
def remove(self, nodes: list[gm.Node]) -> None:
|
|
|
"""Remove nodes out of self because they are merged."""
|
|
|
if not nodes:
|
|
|
return
|
|
|
|
|
|
for node in nodes:
|
|
|
all_nodes = self.type2nodes[type(nodes[0])]
|
|
|
|
|
|
if node in all_nodes:
|
|
|
all_nodes.remove(node)
|
|
|
|
|
|
if node.name in self._name2node.values():
|
|
|
self._name2node.pop(node.name)
|
|
|
|
|
|
def connect(self, a: gm.Node, b: gm.Node, deps: Dependency) -> None:
|
|
|
a.connect_to(b, deps)
|
|
|
b.connect_to(a, deps)
|
|
|
|
|
|
def connect_val(self, node: gm.Node, deps: Dependency) -> gm.Node:
|
|
|
"""Connect a node into its value (equality) node."""
|
|
|
if node._val:
|
|
|
return node._val
|
|
|
name = None
|
|
|
if isinstance(node, Line):
|
|
|
name = 'd(' + node.name + ')'
|
|
|
if isinstance(node, Angle):
|
|
|
name = 'm(' + node.name + ')'
|
|
|
if isinstance(node, Segment):
|
|
|
name = 'l(' + node.name + ')'
|
|
|
if isinstance(node, Ratio):
|
|
|
name = 'r(' + node.name + ')'
|
|
|
v = self.new_node(gm.val_type(node), name)
|
|
|
self.connect(node, v, deps=deps)
|
|
|
return v
|
|
|
|
|
|
def is_equal(self, x: gm.Node, y: gm.Node, level: int = None) -> bool:
|
|
|
return gm.is_equal(x, y, level)
|
|
|
|
|
|
def add_piece(
|
|
|
self, name: str, args: list[Point], deps: EmptyDependency
|
|
|
) -> list[Dependency]:
|
|
|
"""Add a new predicate."""
|
|
|
if name in ['coll', 'collx']:
|
|
|
return self.add_coll(args, deps)
|
|
|
elif name == 'para':
|
|
|
return self.add_para(args, deps)
|
|
|
elif name == 'perp':
|
|
|
return self.add_perp(args, deps)
|
|
|
elif name == 'midp':
|
|
|
return self.add_midp(args, deps)
|
|
|
elif name == 'cong':
|
|
|
return self.add_cong(args, deps)
|
|
|
elif name == 'circle':
|
|
|
return self.add_circle(args, deps)
|
|
|
elif name == 'cyclic':
|
|
|
return self.add_cyclic(args, deps)
|
|
|
elif name in ['eqangle', 'eqangle6']:
|
|
|
return self.add_eqangle(args, deps)
|
|
|
elif name in ['eqratio', 'eqratio6']:
|
|
|
return self.add_eqratio(args, deps)
|
|
|
|
|
|
elif name == 's_angle':
|
|
|
return self.add_s_angle(args, deps)
|
|
|
elif name == 'aconst':
|
|
|
a, b, c, d, ang = args
|
|
|
|
|
|
if isinstance(ang, str):
|
|
|
name = ang
|
|
|
else:
|
|
|
name = ang.name
|
|
|
|
|
|
num, den = name.split('pi/')
|
|
|
num, den = int(num), int(den)
|
|
|
return self.add_aconst([a, b, c, d, num, den], deps)
|
|
|
elif name == 's_angle':
|
|
|
b, x, a, b, ang = (
|
|
|
args
|
|
|
)
|
|
|
|
|
|
if isinstance(ang, str):
|
|
|
name = ang
|
|
|
else:
|
|
|
name = ang.name
|
|
|
|
|
|
n, d = name.split('pi/')
|
|
|
ang = int(n) * 180 / int(d)
|
|
|
return self.add_s_angle([a, b, x, ang], deps)
|
|
|
elif name == 'rconst':
|
|
|
a, b, c, d, rat = args
|
|
|
|
|
|
if isinstance(rat, str):
|
|
|
name = rat
|
|
|
else:
|
|
|
name = rat.name
|
|
|
|
|
|
num, den = name.split('/')
|
|
|
num, den = int(num), int(den)
|
|
|
return self.add_eqrat_const([a, b, c, d, num, den], deps)
|
|
|
|
|
|
|
|
|
elif name == 'cong2':
|
|
|
return self.add_cong2(args, deps)
|
|
|
elif name == 'eqratio3':
|
|
|
return self.add_eqratio3(args, deps)
|
|
|
elif name == 'eqratio4':
|
|
|
return self.add_eqratio4(args, deps)
|
|
|
elif name == 'simtri':
|
|
|
return self.add_simtri(args, deps)
|
|
|
elif name == 'contri':
|
|
|
return self.add_contri(args, deps)
|
|
|
elif name == 'simtri2':
|
|
|
return self.add_simtri2(args, deps)
|
|
|
elif name == 'contri2':
|
|
|
return self.add_contri2(args, deps)
|
|
|
elif name == 'simtri*':
|
|
|
return self.add_simtri_check(args, deps)
|
|
|
elif name == 'contri*':
|
|
|
return self.add_contri_check(args, deps)
|
|
|
elif name in ['acompute', 'rcompute']:
|
|
|
dep = deps.populate(name, args)
|
|
|
self.cache_dep(name, args, dep)
|
|
|
return [dep]
|
|
|
elif name in ['fixl', 'fixc', 'fixb', 'fixt', 'fixp']:
|
|
|
dep = deps.populate(name, args)
|
|
|
self.cache_dep(name, args, dep)
|
|
|
return [dep]
|
|
|
elif name in ['ind']:
|
|
|
return []
|
|
|
raise ValueError(f'Not recognize {name}')
|
|
|
|
|
|
def check(self, name: str, args: list[Point]) -> bool:
|
|
|
"""Symbolically check if a predicate is True."""
|
|
|
if name == 'ncoll':
|
|
|
return self.check_ncoll(args)
|
|
|
if name == 'npara':
|
|
|
return self.check_npara(args)
|
|
|
if name == 'nperp':
|
|
|
return self.check_nperp(args)
|
|
|
if name == 'midp':
|
|
|
return self.check_midp(args)
|
|
|
if name == 'cong':
|
|
|
return self.check_cong(args)
|
|
|
if name == 'perp':
|
|
|
return self.check_perp(args)
|
|
|
if name == 'para':
|
|
|
return self.check_para(args)
|
|
|
if name == 'coll':
|
|
|
return self.check_coll(args)
|
|
|
if name == 'cyclic':
|
|
|
return self.check_cyclic(args)
|
|
|
if name == 'circle':
|
|
|
return self.check_circle(args)
|
|
|
if name == 'aconst':
|
|
|
return self.check_aconst(args)
|
|
|
if name == 'rconst':
|
|
|
return self.check_rconst(args)
|
|
|
if name == 'acompute':
|
|
|
return self.check_acompute(args)
|
|
|
if name == 'rcompute':
|
|
|
return self.check_rcompute(args)
|
|
|
if name in ['eqangle', 'eqangle6']:
|
|
|
if len(args) == 5:
|
|
|
return self.check_aconst(args)
|
|
|
return self.check_eqangle(args)
|
|
|
if name in ['eqratio', 'eqratio6']:
|
|
|
if len(args) == 5:
|
|
|
return self.check_rconst(args)
|
|
|
return self.check_eqratio(args)
|
|
|
if name in ['simtri', 'simtri2', 'simtri*']:
|
|
|
return self.check_simtri(args)
|
|
|
if name in ['contri', 'contri2', 'contri*']:
|
|
|
return self.check_contri(args)
|
|
|
if name == 'sameside':
|
|
|
return self.check_sameside(args)
|
|
|
if name in 'diff':
|
|
|
a, b = args
|
|
|
return not a.num.close(b.num)
|
|
|
if name in ['fixl', 'fixc', 'fixb', 'fixt', 'fixp']:
|
|
|
return self.in_cache(name, args)
|
|
|
if name in ['ind']:
|
|
|
return True
|
|
|
raise ValueError(f'Not recognize {name}')
|
|
|
|
|
|
def get_lines_thru_all(self, *points: list[gm.Point]) -> list[Line]:
|
|
|
line2count = defaultdict(lambda: 0)
|
|
|
points = set(points)
|
|
|
for p in points:
|
|
|
for l in p.neighbors(Line):
|
|
|
line2count[l] += 1
|
|
|
return [l for l, count in line2count.items() if count == len(points)]
|
|
|
|
|
|
def _get_line(self, a: Point, b: Point) -> Optional[Line]:
|
|
|
linesa = a.neighbors(Line)
|
|
|
for l in b.neighbors(Line):
|
|
|
if l in linesa:
|
|
|
return l
|
|
|
return None
|
|
|
|
|
|
def _get_line_all(self, a: Point, b: Point) -> Generator[Line, None, None]:
|
|
|
linesa = a.neighbors(Line, do_rep=False)
|
|
|
linesb = b.neighbors(Line, do_rep=False)
|
|
|
for l in linesb:
|
|
|
if l in linesa:
|
|
|
yield l
|
|
|
|
|
|
def _get_lines(self, *points: list[Point]) -> list[Line]:
|
|
|
"""Return all lines that connect to >= 2 points."""
|
|
|
line2count = defaultdict(lambda: 0)
|
|
|
for p in points:
|
|
|
for l in p.neighbors(Line):
|
|
|
line2count[l] += 1
|
|
|
return [l for l, count in line2count.items() if count >= 2]
|
|
|
|
|
|
def get_circle_thru_triplet(self, p1: Point, p2: Point, p3: Point) -> Circle:
|
|
|
p1, p2, p3 = sorted([p1, p2, p3], key=lambda x: x.name)
|
|
|
if (p1, p2, p3) in self._triplet2circle:
|
|
|
return self._triplet2circle[(p1, p2, p3)]
|
|
|
return self.get_new_circle_thru_triplet(p1, p2, p3)
|
|
|
|
|
|
def get_new_circle_thru_triplet(
|
|
|
self, p1: Point, p2: Point, p3: Point
|
|
|
) -> Circle:
|
|
|
"""Get a new Circle that goes thru three given Points."""
|
|
|
p1, p2, p3 = sorted([p1, p2, p3], key=lambda x: x.name)
|
|
|
name = p1.name.lower() + p2.name.lower() + p3.name.lower()
|
|
|
circle = self.new_node(Circle, f'({name})')
|
|
|
circle.num = nm.Circle(p1=p1.num, p2=p2.num, p3=p3.num)
|
|
|
circle.points = p1, p2, p3
|
|
|
|
|
|
self.connect(p1, circle, deps=None)
|
|
|
self.connect(p2, circle, deps=None)
|
|
|
self.connect(p3, circle, deps=None)
|
|
|
self._triplet2circle[(p1, p2, p3)] = circle
|
|
|
return circle
|
|
|
|
|
|
def get_line_thru_pair(self, p1: Point, p2: Point) -> Line:
|
|
|
if (p1, p2) in self._pair2line:
|
|
|
return self._pair2line[(p1, p2)]
|
|
|
if (p2, p1) in self._pair2line:
|
|
|
return self._pair2line[(p2, p1)]
|
|
|
return self.get_new_line_thru_pair(p1, p2)
|
|
|
|
|
|
def get_new_line_thru_pair(self, p1: Point, p2: Point) -> Line:
|
|
|
if p1.name.lower() > p2.name.lower():
|
|
|
p1, p2 = p2, p1
|
|
|
name = p1.name.lower() + p2.name.lower()
|
|
|
line = self.new_node(Line, name)
|
|
|
line.num = nm.Line(p1.num, p2.num)
|
|
|
line.points = p1, p2
|
|
|
|
|
|
self.connect(p1, line, deps=None)
|
|
|
self.connect(p2, line, deps=None)
|
|
|
self._pair2line[(p1, p2)] = line
|
|
|
return line
|
|
|
|
|
|
def get_line_thru_pair_why(
|
|
|
self, p1: Point, p2: Point
|
|
|
) -> tuple[Line, list[Dependency]]:
|
|
|
"""Get one line thru two given points and the corresponding dependency list."""
|
|
|
if p1.name.lower() > p2.name.lower():
|
|
|
p1, p2 = p2, p1
|
|
|
if (p1, p2) in self._pair2line:
|
|
|
return self._pair2line[(p1, p2)].rep_and_why()
|
|
|
|
|
|
l, why = gm.line_of_and_why([p1, p2])
|
|
|
if l is None:
|
|
|
l = self.get_new_line_thru_pair(p1, p2)
|
|
|
why = []
|
|
|
return l, why
|
|
|
|
|
|
def coll_dep(self, points: list[Point], p: Point) -> list[Dependency]:
|
|
|
"""Return the dep(.why) explaining why p is coll with points."""
|
|
|
for p1, p2 in utils.comb2(points):
|
|
|
if self.check_coll([p1, p2, p]):
|
|
|
dep = Dependency('coll', [p1, p2, p], None, None)
|
|
|
return dep.why_me_or_cache(self, None)
|
|
|
|
|
|
def add_coll(
|
|
|
self, points: list[Point], deps: EmptyDependency
|
|
|
) -> list[Dependency]:
|
|
|
"""Add a predicate that `points` are collinear."""
|
|
|
points = list(set(points))
|
|
|
og_points = list(points)
|
|
|
|
|
|
all_lines = []
|
|
|
for p1, p2 in utils.comb2(points):
|
|
|
all_lines.append(self.get_line_thru_pair(p1, p2))
|
|
|
points = sum([l.neighbors(Point) for l in all_lines], [])
|
|
|
points = list(set(points))
|
|
|
|
|
|
existed = set()
|
|
|
new = set()
|
|
|
for p1, p2 in utils.comb2(points):
|
|
|
if p1.name > p2.name:
|
|
|
p1, p2 = p2, p1
|
|
|
if (p1, p2) in self._pair2line:
|
|
|
line = self._pair2line[(p1, p2)]
|
|
|
existed.add(line)
|
|
|
else:
|
|
|
line = self.get_new_line_thru_pair(p1, p2)
|
|
|
new.add(line)
|
|
|
|
|
|
existed = sorted(existed, key=lambda l: l.name)
|
|
|
new = sorted(new, key=lambda l: l.name)
|
|
|
|
|
|
existed, new = list(existed), list(new)
|
|
|
if not existed:
|
|
|
line0, *lines = new
|
|
|
else:
|
|
|
line0, lines = existed[0], existed[1:] + new
|
|
|
|
|
|
add = []
|
|
|
line0, why0 = line0.rep_and_why()
|
|
|
a, b = line0.points
|
|
|
for line in lines:
|
|
|
c, d = line.points
|
|
|
args = list({a, b, c, d})
|
|
|
if len(args) < 3:
|
|
|
continue
|
|
|
|
|
|
whys = []
|
|
|
for x in args:
|
|
|
if x not in og_points:
|
|
|
whys.append(self.coll_dep(og_points, x))
|
|
|
|
|
|
abcd_deps = deps
|
|
|
if whys + why0:
|
|
|
dep0 = deps.populate('coll', og_points)
|
|
|
abcd_deps = EmptyDependency(level=deps.level, rule_name=None)
|
|
|
abcd_deps.why = [dep0] + whys
|
|
|
|
|
|
is_coll = self.check_coll(args)
|
|
|
dep = abcd_deps.populate('coll', args)
|
|
|
self.cache_dep('coll', args, dep)
|
|
|
self.merge_into(line0, [line], dep)
|
|
|
|
|
|
if not is_coll:
|
|
|
add += [dep]
|
|
|
|
|
|
return add
|
|
|
|
|
|
def check_coll(self, points: list[Point]) -> bool:
|
|
|
points = list(set(points))
|
|
|
if len(points) < 3:
|
|
|
return True
|
|
|
line2count = defaultdict(lambda: 0)
|
|
|
for p in points:
|
|
|
for l in p.neighbors(Line):
|
|
|
line2count[l] += 1
|
|
|
return any([count == len(points) for _, count in line2count.items()])
|
|
|
|
|
|
def why_coll(self, args: tuple[Line, list[Point]]) -> list[Dependency]:
|
|
|
line, points = args
|
|
|
return line.why_coll(points)
|
|
|
|
|
|
def check_ncoll(self, points: list[Point]) -> bool:
|
|
|
if self.check_coll(points):
|
|
|
return False
|
|
|
return not nm.check_coll([p.num for p in points])
|
|
|
|
|
|
def check_sameside(self, points: list[Point]) -> bool:
|
|
|
return nm.check_sameside([p.num for p in points])
|
|
|
|
|
|
def make_equal(self, x: gm.Node, y: gm.Node, deps: Dependency) -> None:
|
|
|
"""Make that two nodes x and y are equal, i.e. merge their value node."""
|
|
|
if x.val is None:
|
|
|
x, y = y, x
|
|
|
|
|
|
self.connect_val(x, deps=None)
|
|
|
self.connect_val(y, deps=None)
|
|
|
vx = x._val
|
|
|
vy = y._val
|
|
|
|
|
|
if vx == vy:
|
|
|
return
|
|
|
|
|
|
merges = [vx, vy]
|
|
|
|
|
|
if (
|
|
|
isinstance(x, Angle)
|
|
|
and x not in self.aconst.values()
|
|
|
and y not in self.aconst.values()
|
|
|
and x.directions == y.directions[::-1]
|
|
|
and x.directions[0] != x.directions[1]
|
|
|
):
|
|
|
merges = [self.vhalfpi, vx, vy]
|
|
|
|
|
|
self.merge(merges, deps)
|
|
|
|
|
|
def merge_vals(self, vx: gm.Node, vy: gm.Node, deps: Dependency) -> None:
|
|
|
if vx == vy:
|
|
|
return
|
|
|
merges = [vx, vy]
|
|
|
self.merge(merges, deps)
|
|
|
|
|
|
def why_equal(self, x: gm.Node, y: gm.Node, level: int) -> list[Dependency]:
|
|
|
return gm.why_equal(x, y, level)
|
|
|
|
|
|
def _why_coll4(
|
|
|
self,
|
|
|
a: Point,
|
|
|
b: Point,
|
|
|
ab: Line,
|
|
|
c: Point,
|
|
|
d: Point,
|
|
|
cd: Line,
|
|
|
level: int,
|
|
|
) -> list[Dependency]:
|
|
|
return self._why_coll2(a, b, ab, level) + self._why_coll2(c, d, cd, level)
|
|
|
|
|
|
def _why_coll8(
|
|
|
self,
|
|
|
a: Point,
|
|
|
b: Point,
|
|
|
ab: Line,
|
|
|
c: Point,
|
|
|
d: Point,
|
|
|
cd: Line,
|
|
|
m: Point,
|
|
|
n: Point,
|
|
|
mn: Line,
|
|
|
p: Point,
|
|
|
q: Point,
|
|
|
pq: Line,
|
|
|
level: int,
|
|
|
) -> list[Dependency]:
|
|
|
"""Dependency list of why 8 points are collinear."""
|
|
|
why8 = self._why_coll4(a, b, ab, c, d, cd, level)
|
|
|
why8 += self._why_coll4(m, n, mn, p, q, pq, level)
|
|
|
return why8
|
|
|
|
|
|
def add_para(
|
|
|
self, points: list[Point], deps: EmptyDependency
|
|
|
) -> list[Dependency]:
|
|
|
"""Add a new predicate that 4 points (2 lines) are parallel."""
|
|
|
a, b, c, d = points
|
|
|
ab, why1 = self.get_line_thru_pair_why(a, b)
|
|
|
cd, why2 = self.get_line_thru_pair_why(c, d)
|
|
|
|
|
|
is_equal = self.is_equal(ab, cd)
|
|
|
|
|
|
(a, b), (c, d) = ab.points, cd.points
|
|
|
|
|
|
dep0 = deps.populate('para', points)
|
|
|
deps = EmptyDependency(level=deps.level, rule_name=None)
|
|
|
|
|
|
deps = deps.populate('para', [a, b, c, d])
|
|
|
deps.why = [dep0] + why1 + why2
|
|
|
|
|
|
self.make_equal(ab, cd, deps)
|
|
|
deps.algebra = ab._val, cd._val
|
|
|
|
|
|
self.cache_dep('para', [a, b, c, d], deps)
|
|
|
if not is_equal:
|
|
|
return [deps]
|
|
|
return []
|
|
|
|
|
|
def why_para(self, args: list[Point]) -> list[Dependency]:
|
|
|
ab, cd, lvl = args
|
|
|
return self.why_equal(ab, cd, lvl)
|
|
|
|
|
|
def check_para_or_coll(self, points: list[Point]) -> bool:
|
|
|
return self.check_para(points) or self.check_coll(points)
|
|
|
|
|
|
def check_para(self, points: list[Point]) -> bool:
|
|
|
a, b, c, d = points
|
|
|
if (a == b) or (c == d):
|
|
|
return False
|
|
|
ab = self._get_line(a, b)
|
|
|
cd = self._get_line(c, d)
|
|
|
if not ab or not cd:
|
|
|
return False
|
|
|
|
|
|
return self.is_equal(ab, cd)
|
|
|
|
|
|
def check_npara(self, points: list[Point]) -> bool:
|
|
|
if self.check_para(points):
|
|
|
return False
|
|
|
return not nm.check_para([p.num for p in points])
|
|
|
|
|
|
def _get_angle(
|
|
|
self, d1: Direction, d2: Direction
|
|
|
) -> tuple[Angle, Optional[Angle]]:
|
|
|
for a in self.type2nodes[Angle]:
|
|
|
if a.directions == (d1, d2):
|
|
|
return a, a.opposite
|
|
|
return None, None
|
|
|
|
|
|
def get_first_angle(
|
|
|
self, l1: Line, l2: Line
|
|
|
) -> tuple[Angle, list[Dependency]]:
|
|
|
"""Get a first angle between line l1 and line l2."""
|
|
|
d1, d2 = l1._val, l2._val
|
|
|
|
|
|
d1s = d1.all_reps()
|
|
|
d2s = d2.all_reps()
|
|
|
|
|
|
found = d1.first_angle(d2s)
|
|
|
if found is None:
|
|
|
found = d2.first_angle(d1s)
|
|
|
if found is None:
|
|
|
return None, []
|
|
|
ang, x2, x1 = found
|
|
|
found = ang.opposite, x1, x2
|
|
|
|
|
|
ang, x1, x2 = found
|
|
|
return ang, d1.deps_upto(x1) + d2.deps_upto(x2)
|
|
|
|
|
|
def _get_or_create_angle(
|
|
|
self, l1: Line, l2: Line, deps: Dependency
|
|
|
) -> tuple[Angle, Angle, list[Dependency]]:
|
|
|
return self.get_or_create_angle_d(l1._val, l2._val, deps)
|
|
|
|
|
|
def get_or_create_angle_d(
|
|
|
self, d1: Direction, d2: Direction, deps: Dependency
|
|
|
) -> tuple[Angle, Angle, list[Dependency]]:
|
|
|
"""Get or create an angle between two Direction d1 and d2."""
|
|
|
for a in self.type2nodes[Angle]:
|
|
|
if a.directions == (d1.rep(), d2.rep()):
|
|
|
d1_, d2_ = a._d
|
|
|
why1 = d1.why_equal([d1_], None) + d1_.why_rep()
|
|
|
why2 = d2.why_equal([d2_], None) + d2_.why_rep()
|
|
|
return a, a.opposite, why1 + why2
|
|
|
|
|
|
d1, why1 = d1.rep_and_why()
|
|
|
d2, why2 = d2.rep_and_why()
|
|
|
a12 = self.new_node(Angle, f'{d1.name}-{d2.name}')
|
|
|
a21 = self.new_node(Angle, f'{d2.name}-{d1.name}')
|
|
|
self.connect(d1, a12, deps)
|
|
|
self.connect(d2, a21, deps)
|
|
|
self.connect(a12, a21, deps)
|
|
|
a12.set_directions(d1, d2)
|
|
|
a21.set_directions(d2, d1)
|
|
|
a12.opposite = a21
|
|
|
a21.opposite = a12
|
|
|
return a12, a21, why1 + why2
|
|
|
|
|
|
def _add_para_or_coll(
|
|
|
self,
|
|
|
a: Point,
|
|
|
b: Point,
|
|
|
c: Point,
|
|
|
d: Point,
|
|
|
x: Point,
|
|
|
y: Point,
|
|
|
m: Point,
|
|
|
n: Point,
|
|
|
deps: EmptyDependency,
|
|
|
) -> list[Dependency]:
|
|
|
"""Add a new parallel or collinear predicate."""
|
|
|
extends = [('perp', [x, y, m, n])]
|
|
|
if {a, b} == {x, y}:
|
|
|
pass
|
|
|
elif self.check_para([a, b, x, y]):
|
|
|
extends.append(('para', [a, b, x, y]))
|
|
|
elif self.check_coll([a, b, x, y]):
|
|
|
extends.append(('coll', set(list([a, b, x, y]))))
|
|
|
else:
|
|
|
return None
|
|
|
|
|
|
if m in [c, d] or n in [c, d] or c in [m, n] or d in [m, n]:
|
|
|
pass
|
|
|
elif self.check_coll([c, d, m]):
|
|
|
extends.append(('coll', [c, d, m]))
|
|
|
elif self.check_coll([c, d, n]):
|
|
|
extends.append(('coll', [c, d, n]))
|
|
|
elif self.check_coll([c, m, n]):
|
|
|
extends.append(('coll', [c, m, n]))
|
|
|
elif self.check_coll([d, m, n]):
|
|
|
extends.append(('coll', [d, m, n]))
|
|
|
else:
|
|
|
deps = deps.extend_many(self, 'perp', [a, b, c, d], extends)
|
|
|
return self.add_para([c, d, m, n], deps)
|
|
|
|
|
|
deps = deps.extend_many(self, 'perp', [a, b, c, d], extends)
|
|
|
return self.add_coll(list(set([c, d, m, n])), deps)
|
|
|
|
|
|
def maybe_make_para_from_perp(
|
|
|
self, points: list[Point], deps: EmptyDependency
|
|
|
) -> Optional[list[Dependency]]:
|
|
|
"""Maybe add a new parallel predicate from perp predicate."""
|
|
|
a, b, c, d = points
|
|
|
halfpi = self.aconst[(1, 2)]
|
|
|
for ang in halfpi.val.neighbors(Angle):
|
|
|
if ang == halfpi:
|
|
|
continue
|
|
|
d1, d2 = ang.directions
|
|
|
x, y = d1._obj.points
|
|
|
m, n = d2._obj.points
|
|
|
|
|
|
for args in [
|
|
|
(a, b, c, d, x, y, m, n),
|
|
|
(a, b, c, d, m, n, x, y),
|
|
|
(c, d, a, b, x, y, m, n),
|
|
|
(c, d, a, b, m, n, x, y),
|
|
|
]:
|
|
|
args = args + (deps,)
|
|
|
add = self._add_para_or_coll(*args)
|
|
|
if add:
|
|
|
return add
|
|
|
|
|
|
return None
|
|
|
|
|
|
def add_perp(
|
|
|
self, points: list[Point], deps: EmptyDependency
|
|
|
) -> list[Dependency]:
|
|
|
"""Add a new perpendicular predicate from 4 points (2 lines)."""
|
|
|
add = self.maybe_make_para_from_perp(points, deps)
|
|
|
if add is not None:
|
|
|
return add
|
|
|
|
|
|
a, b, c, d = points
|
|
|
ab, why1 = self.get_line_thru_pair_why(a, b)
|
|
|
cd, why2 = self.get_line_thru_pair_why(c, d)
|
|
|
|
|
|
(a, b), (c, d) = ab.points, cd.points
|
|
|
|
|
|
if why1 + why2:
|
|
|
dep0 = deps.populate('perp', points)
|
|
|
deps = EmptyDependency(level=deps.level, rule_name=None)
|
|
|
deps.why = [dep0] + why1 + why2
|
|
|
|
|
|
self.connect_val(ab, deps=None)
|
|
|
self.connect_val(cd, deps=None)
|
|
|
|
|
|
if ab.val == cd.val:
|
|
|
raise ValueError(f'{ab.name} and {cd.name} Cannot be perp.')
|
|
|
|
|
|
args = [a, b, c, d]
|
|
|
i = 0
|
|
|
for x, y, xy in [(a, b, ab), (c, d, cd)]:
|
|
|
i += 1
|
|
|
x_, y_ = xy._val._obj.points
|
|
|
if {x, y} == {x_, y_}:
|
|
|
continue
|
|
|
if deps:
|
|
|
deps = deps.extend(self, 'perp', list(args), 'para', [x, y, x_, y_])
|
|
|
args[2 * i - 2] = x_
|
|
|
args[2 * i - 1] = y_
|
|
|
|
|
|
a12, a21, why = self._get_or_create_angle(ab, cd, deps=None)
|
|
|
|
|
|
if why:
|
|
|
dep0 = deps.populate('perp', [a, b, c, d])
|
|
|
deps = EmptyDependency(level=deps.level, rule_name=None)
|
|
|
deps.why = [dep0] + why
|
|
|
|
|
|
dab, dcd = a12._d
|
|
|
a, b = dab._obj.points
|
|
|
c, d = dcd._obj.points
|
|
|
|
|
|
is_equal = self.is_equal(a12, a21)
|
|
|
deps = deps.populate('perp', [a, b, c, d])
|
|
|
deps.algebra = [dab, dcd]
|
|
|
self.make_equal(a12, a21, deps=deps)
|
|
|
|
|
|
self.cache_dep('perp', [a, b, c, d], deps)
|
|
|
self.cache_dep('eqangle', [a, b, c, d, c, d, a, b], deps)
|
|
|
|
|
|
if not is_equal:
|
|
|
return [deps]
|
|
|
return []
|
|
|
|
|
|
def why_perp(
|
|
|
self, args: list[Union[Point, list[Dependency]]]
|
|
|
) -> list[Dependency]:
|
|
|
a, b, deps = args
|
|
|
return deps + self.why_equal(a, b, None)
|
|
|
|
|
|
def check_perpl(self, ab: Line, cd: Line) -> bool:
|
|
|
if ab.val is None or cd.val is None:
|
|
|
return False
|
|
|
if ab.val == cd.val:
|
|
|
return False
|
|
|
a12, a21 = self._get_angle(ab.val, cd.val)
|
|
|
if a12 is None or a21 is None:
|
|
|
return False
|
|
|
return self.is_equal(a12, a21)
|
|
|
|
|
|
def check_perp(self, points: list[Point]) -> bool:
|
|
|
a, b, c, d = points
|
|
|
ab = self._get_line(a, b)
|
|
|
cd = self._get_line(c, d)
|
|
|
if not ab or not cd:
|
|
|
return False
|
|
|
return self.check_perpl(ab, cd)
|
|
|
|
|
|
def check_nperp(self, points: list[Point]) -> bool:
|
|
|
if self.check_perp(points):
|
|
|
return False
|
|
|
return not nm.check_perp([p.num for p in points])
|
|
|
|
|
|
def _get_segment(self, p1: Point, p2: Point) -> Optional[Segment]:
|
|
|
for s in self.type2nodes[Segment]:
|
|
|
if s.points == {p1, p2}:
|
|
|
return s
|
|
|
return None
|
|
|
|
|
|
def _get_or_create_segment(
|
|
|
self, p1: Point, p2: Point, deps: Dependency
|
|
|
) -> Segment:
|
|
|
"""Get or create a Segment object between two Points p1 and p2."""
|
|
|
if p1 == p2:
|
|
|
raise ValueError(f'Creating same 0-length segment {p1.name}')
|
|
|
|
|
|
for s in self.type2nodes[Segment]:
|
|
|
if s.points == {p1, p2}:
|
|
|
return s
|
|
|
|
|
|
if p1.name > p2.name:
|
|
|
p1, p2 = p2, p1
|
|
|
s = self.new_node(Segment, name=f'{p1.name.upper()}{p2.name.upper()}')
|
|
|
self.connect(p1, s, deps=deps)
|
|
|
self.connect(p2, s, deps=deps)
|
|
|
s.points = {p1, p2}
|
|
|
return s
|
|
|
|
|
|
def add_cong(
|
|
|
self, points: list[Point], deps: EmptyDependency
|
|
|
) -> list[Dependency]:
|
|
|
"""Add that two segments (4 points) are congruent."""
|
|
|
a, b, c, d = points
|
|
|
ab = self._get_or_create_segment(a, b, deps=None)
|
|
|
cd = self._get_or_create_segment(c, d, deps=None)
|
|
|
|
|
|
is_equal = self.is_equal(ab, cd)
|
|
|
|
|
|
dep = deps.populate('cong', [a, b, c, d])
|
|
|
self.make_equal(ab, cd, deps=dep)
|
|
|
dep.algebra = ab._val, cd._val
|
|
|
|
|
|
self.cache_dep('cong', [a, b, c, d], dep)
|
|
|
|
|
|
result = []
|
|
|
|
|
|
if not is_equal:
|
|
|
result += [dep]
|
|
|
|
|
|
if a not in [c, d] and b not in [c, d]:
|
|
|
return result
|
|
|
|
|
|
if b in [c, d]:
|
|
|
a, b = b, a
|
|
|
if a == d:
|
|
|
c, d = d, c
|
|
|
|
|
|
result += self._maybe_add_cyclic_from_cong(a, b, d, dep)
|
|
|
return result
|
|
|
|
|
|
def _maybe_add_cyclic_from_cong(
|
|
|
self, a: Point, b: Point, c: Point, cong_ab_ac: Dependency
|
|
|
) -> list[Dependency]:
|
|
|
"""Maybe add a new cyclic predicate from given congruent segments."""
|
|
|
ab = self._get_or_create_segment(a, b, deps=None)
|
|
|
|
|
|
|
|
|
segs = [s for s in ab.val.neighbors(Segment) if a in s.points]
|
|
|
|
|
|
|
|
|
points = []
|
|
|
for s in segs:
|
|
|
x, y = list(s.points)
|
|
|
points.append(x if y == a else y)
|
|
|
|
|
|
|
|
|
points = [p for p in points if p not in [b, c]]
|
|
|
|
|
|
if len(points) < 2:
|
|
|
return []
|
|
|
|
|
|
x, y = points[:2]
|
|
|
|
|
|
if self.check_cyclic([b, c, x, y]):
|
|
|
return []
|
|
|
|
|
|
ax = self._get_or_create_segment(a, x, deps=None)
|
|
|
ay = self._get_or_create_segment(a, y, deps=None)
|
|
|
why = ab._val.why_equal([ax._val, ay._val], level=None)
|
|
|
why += [cong_ab_ac]
|
|
|
|
|
|
deps = EmptyDependency(cong_ab_ac.level, '')
|
|
|
deps.why = why
|
|
|
|
|
|
return self.add_cyclic([b, c, x, y], deps)
|
|
|
|
|
|
def check_cong(self, points: list[Point]) -> bool:
|
|
|
a, b, c, d = points
|
|
|
if {a, b} == {c, d}:
|
|
|
return True
|
|
|
|
|
|
ab = self._get_segment(a, b)
|
|
|
cd = self._get_segment(c, d)
|
|
|
if ab is None or cd is None:
|
|
|
return False
|
|
|
return self.is_equal(ab, cd)
|
|
|
|
|
|
def why_cong(self, args: tuple[Segment, Segment]) -> list[Dependency]:
|
|
|
ab, cd = args
|
|
|
return self.why_equal(ab, cd, None)
|
|
|
|
|
|
def add_midp(
|
|
|
self, points: list[Point], deps: EmptyDependency
|
|
|
) -> list[Dependency]:
|
|
|
m, a, b = points
|
|
|
add = self.add_coll(points, deps=deps)
|
|
|
add += self.add_cong([m, a, m, b], deps)
|
|
|
return add
|
|
|
|
|
|
def why_midp(
|
|
|
self, args: tuple[Line, list[Point], Segment, Segment]
|
|
|
) -> list[Dependency]:
|
|
|
line, points, ma, mb = args
|
|
|
return self.why_coll([line, points]) + self.why_cong([ma, mb])
|
|
|
|
|
|
def check_midp(self, points: list[Point]) -> bool:
|
|
|
if not self.check_coll(points):
|
|
|
return False
|
|
|
m, a, b = points
|
|
|
return self.check_cong([m, a, m, b])
|
|
|
|
|
|
def add_circle(
|
|
|
self, points: list[Point], deps: EmptyDependency
|
|
|
) -> list[Dependency]:
|
|
|
o, a, b, c = points
|
|
|
add = self.add_cong([o, a, o, b], deps=deps)
|
|
|
add += self.add_cong([o, a, o, c], deps=deps)
|
|
|
return add
|
|
|
|
|
|
def why_circle(
|
|
|
self, args: tuple[Segment, Segment, Segment]
|
|
|
) -> list[Dependency]:
|
|
|
oa, ob, oc = args
|
|
|
return self.why_equal(oa, ob, None) and self.why_equal(oa, oc, None)
|
|
|
|
|
|
def check_circle(self, points: list[Point]) -> bool:
|
|
|
o, a, b, c = points
|
|
|
return self.check_cong([o, a, o, b]) and self.check_cong([o, a, o, c])
|
|
|
|
|
|
def get_circles_thru_all(self, *points: list[Point]) -> list[Circle]:
|
|
|
circle2count = defaultdict(lambda: 0)
|
|
|
points = set(points)
|
|
|
for p in points:
|
|
|
for c in p.neighbors(Circle):
|
|
|
circle2count[c] += 1
|
|
|
return [c for c, count in circle2count.items() if count == len(points)]
|
|
|
|
|
|
def _get_circles(self, *points: list[Point]) -> list[Circle]:
|
|
|
circle2count = defaultdict(lambda: 0)
|
|
|
for p in points:
|
|
|
for c in p.neighbors(Circle):
|
|
|
circle2count[c] += 1
|
|
|
return [c for c, count in circle2count.items() if count >= 3]
|
|
|
|
|
|
def cyclic_dep(self, points: list[Point], p: Point) -> list[Dependency]:
|
|
|
for p1, p2, p3 in utils.comb3(points):
|
|
|
if self.check_cyclic([p1, p2, p3, p]):
|
|
|
dep = Dependency('cyclic', [p1, p2, p3, p], None, None)
|
|
|
return dep.why_me_or_cache(self, None)
|
|
|
|
|
|
def add_cyclic(
|
|
|
self, points: list[Point], deps: EmptyDependency
|
|
|
) -> list[Dependency]:
|
|
|
"""Add a new cyclic predicate that 4 points are concyclic."""
|
|
|
points = list(set(points))
|
|
|
og_points = list(points)
|
|
|
|
|
|
all_circles = []
|
|
|
for p1, p2, p3 in utils.comb3(points):
|
|
|
all_circles.append(self.get_circle_thru_triplet(p1, p2, p3))
|
|
|
points = sum([c.neighbors(Point) for c in all_circles], [])
|
|
|
points = list(set(points))
|
|
|
|
|
|
existed = set()
|
|
|
new = set()
|
|
|
for p1, p2, p3 in utils.comb3(points):
|
|
|
p1, p2, p3 = sorted([p1, p2, p3], key=lambda x: x.name)
|
|
|
|
|
|
if (p1, p2, p3) in self._triplet2circle:
|
|
|
circle = self._triplet2circle[(p1, p2, p3)]
|
|
|
existed.add(circle)
|
|
|
else:
|
|
|
circle = self.get_new_circle_thru_triplet(p1, p2, p3)
|
|
|
new.add(circle)
|
|
|
|
|
|
existed = sorted(existed, key=lambda l: l.name)
|
|
|
new = sorted(new, key=lambda l: l.name)
|
|
|
|
|
|
existed, new = list(existed), list(new)
|
|
|
if not existed:
|
|
|
circle0, *circles = new
|
|
|
else:
|
|
|
circle0, circles = existed[0], existed[1:] + new
|
|
|
|
|
|
add = []
|
|
|
circle0, why0 = circle0.rep_and_why()
|
|
|
a, b, c = circle0.points
|
|
|
for circle in circles:
|
|
|
d, e, f = circle.points
|
|
|
args = list({a, b, c, d, e, f})
|
|
|
if len(args) < 4:
|
|
|
continue
|
|
|
whys = []
|
|
|
for x in [a, b, c, d, e, f]:
|
|
|
if x not in og_points:
|
|
|
whys.append(self.cyclic_dep(og_points, x))
|
|
|
abcdef_deps = deps
|
|
|
if whys + why0:
|
|
|
dep0 = deps.populate('cyclic', og_points)
|
|
|
abcdef_deps = EmptyDependency(level=deps.level, rule_name=None)
|
|
|
abcdef_deps.why = [dep0] + whys
|
|
|
|
|
|
is_cyclic = self.check_cyclic(args)
|
|
|
|
|
|
dep = abcdef_deps.populate('cyclic', args)
|
|
|
self.cache_dep('cyclic', args, dep)
|
|
|
self.merge_into(circle0, [circle], dep)
|
|
|
if not is_cyclic:
|
|
|
add += [dep]
|
|
|
|
|
|
return add
|
|
|
|
|
|
def check_cyclic(self, points: list[Point]) -> bool:
|
|
|
points = list(set(points))
|
|
|
if len(points) < 4:
|
|
|
return True
|
|
|
circle2count = defaultdict(lambda: 0)
|
|
|
for p in points:
|
|
|
for c in p.neighbors(Circle):
|
|
|
circle2count[c] += 1
|
|
|
return any([count == len(points) for _, count in circle2count.items()])
|
|
|
|
|
|
def make_equal_pairs(
|
|
|
self,
|
|
|
a: Point,
|
|
|
b: Point,
|
|
|
c: Point,
|
|
|
d: Point,
|
|
|
m: Point,
|
|
|
n: Point,
|
|
|
p: Point,
|
|
|
q: Point,
|
|
|
ab: Line,
|
|
|
cd: Line,
|
|
|
mn: Line,
|
|
|
pq: Line,
|
|
|
deps: EmptyDependency,
|
|
|
) -> list[Dependency]:
|
|
|
"""Add ab/cd = mn/pq in case either two of (ab,cd,mn,pq) are equal."""
|
|
|
depname = 'eqratio' if isinstance(ab, Segment) else 'eqangle'
|
|
|
eqname = 'cong' if isinstance(ab, Segment) else 'para'
|
|
|
|
|
|
is_equal = self.is_equal(mn, pq)
|
|
|
|
|
|
if ab != cd:
|
|
|
dep0 = deps.populate(depname, [a, b, c, d, m, n, p, q])
|
|
|
deps = EmptyDependency(level=deps.level, rule_name=None)
|
|
|
|
|
|
dep = Dependency(eqname, [a, b, c, d], None, deps.level)
|
|
|
deps.why = [dep0, dep.why_me_or_cache(self, None)]
|
|
|
|
|
|
elif eqname == 'para':
|
|
|
colls = [a, b, c, d]
|
|
|
if len(set(colls)) > 2:
|
|
|
dep0 = deps.populate(depname, [a, b, c, d, m, n, p, q])
|
|
|
deps = EmptyDependency(level=deps.level, rule_name=None)
|
|
|
|
|
|
dep = Dependency('collx', colls, None, deps.level)
|
|
|
deps.why = [dep0, dep.why_me_or_cache(self, None)]
|
|
|
|
|
|
deps = deps.populate(eqname, [m, n, p, q])
|
|
|
self.make_equal(mn, pq, deps=deps)
|
|
|
|
|
|
deps.algebra = mn._val, pq._val
|
|
|
self.cache_dep(eqname, [m, n, p, q], deps)
|
|
|
|
|
|
if is_equal:
|
|
|
return []
|
|
|
return [deps]
|
|
|
|
|
|
def maybe_make_equal_pairs(
|
|
|
self,
|
|
|
a: Point,
|
|
|
b: Point,
|
|
|
c: Point,
|
|
|
d: Point,
|
|
|
m: Point,
|
|
|
n: Point,
|
|
|
p: Point,
|
|
|
q: Point,
|
|
|
ab: Line,
|
|
|
cd: Line,
|
|
|
mn: Line,
|
|
|
pq: Line,
|
|
|
deps: EmptyDependency,
|
|
|
) -> Optional[list[Dependency]]:
|
|
|
"""Add ab/cd = mn/pq in case maybe either two of (ab,cd,mn,pq) are equal."""
|
|
|
level = deps.level
|
|
|
if self.is_equal(ab, cd, level):
|
|
|
return self.make_equal_pairs(a, b, c, d, m, n, p, q, ab, cd, mn, pq, deps)
|
|
|
elif self.is_equal(mn, pq, level):
|
|
|
return self.make_equal_pairs(
|
|
|
m,
|
|
|
n,
|
|
|
p,
|
|
|
q,
|
|
|
a,
|
|
|
b,
|
|
|
c,
|
|
|
d,
|
|
|
mn,
|
|
|
pq,
|
|
|
ab,
|
|
|
cd,
|
|
|
deps,
|
|
|
)
|
|
|
elif self.is_equal(ab, mn, level):
|
|
|
return self.make_equal_pairs(
|
|
|
a,
|
|
|
b,
|
|
|
m,
|
|
|
n,
|
|
|
c,
|
|
|
d,
|
|
|
p,
|
|
|
q,
|
|
|
ab,
|
|
|
mn,
|
|
|
cd,
|
|
|
pq,
|
|
|
deps,
|
|
|
)
|
|
|
elif self.is_equal(cd, pq, level):
|
|
|
return self.make_equal_pairs(
|
|
|
c,
|
|
|
d,
|
|
|
p,
|
|
|
q,
|
|
|
a,
|
|
|
b,
|
|
|
m,
|
|
|
n,
|
|
|
cd,
|
|
|
pq,
|
|
|
ab,
|
|
|
mn,
|
|
|
deps,
|
|
|
)
|
|
|
else:
|
|
|
return None
|
|
|
|
|
|
def _add_eqangle(
|
|
|
self,
|
|
|
a: Point,
|
|
|
b: Point,
|
|
|
c: Point,
|
|
|
d: Point,
|
|
|
m: Point,
|
|
|
n: Point,
|
|
|
p: Point,
|
|
|
q: Point,
|
|
|
ab: Line,
|
|
|
cd: Line,
|
|
|
mn: Line,
|
|
|
pq: Line,
|
|
|
deps: EmptyDependency,
|
|
|
) -> list[Dependency]:
|
|
|
"""Add eqangle core."""
|
|
|
if deps:
|
|
|
deps = deps.copy()
|
|
|
|
|
|
args = [a, b, c, d, m, n, p, q]
|
|
|
i = 0
|
|
|
for x, y, xy in [(a, b, ab), (c, d, cd), (m, n, mn), (p, q, pq)]:
|
|
|
i += 1
|
|
|
x_, y_ = xy._val._obj.points
|
|
|
if {x, y} == {x_, y_}:
|
|
|
continue
|
|
|
if deps:
|
|
|
deps = deps.extend(self, 'eqangle', list(args), 'para', [x, y, x_, y_])
|
|
|
|
|
|
args[2 * i - 2] = x_
|
|
|
args[2 * i - 1] = y_
|
|
|
|
|
|
add = []
|
|
|
ab_cd, cd_ab, why1 = self._get_or_create_angle(ab, cd, deps=None)
|
|
|
mn_pq, pq_mn, why2 = self._get_or_create_angle(mn, pq, deps=None)
|
|
|
|
|
|
why = why1 + why2
|
|
|
if why:
|
|
|
dep0 = deps.populate('eqangle', args)
|
|
|
deps = EmptyDependency(level=deps.level, rule_name=None)
|
|
|
deps.why = [dep0] + why
|
|
|
|
|
|
dab, dcd = ab_cd._d
|
|
|
dmn, dpq = mn_pq._d
|
|
|
|
|
|
a, b = dab._obj.points
|
|
|
c, d = dcd._obj.points
|
|
|
m, n = dmn._obj.points
|
|
|
p, q = dpq._obj.points
|
|
|
|
|
|
is_eq1 = self.is_equal(ab_cd, mn_pq)
|
|
|
deps1 = None
|
|
|
if deps:
|
|
|
deps1 = deps.populate('eqangle', [a, b, c, d, m, n, p, q])
|
|
|
deps1.algebra = [dab, dcd, dmn, dpq]
|
|
|
if not is_eq1:
|
|
|
add += [deps1]
|
|
|
self.cache_dep('eqangle', [a, b, c, d, m, n, p, q], deps1)
|
|
|
self.make_equal(ab_cd, mn_pq, deps=deps1)
|
|
|
|
|
|
is_eq2 = self.is_equal(cd_ab, pq_mn)
|
|
|
deps2 = None
|
|
|
if deps:
|
|
|
deps2 = deps.populate('eqangle', [c, d, a, b, p, q, m, n])
|
|
|
deps2.algebra = [dcd, dab, dpq, dmn]
|
|
|
if not is_eq2:
|
|
|
add += [deps2]
|
|
|
self.cache_dep('eqangle', [c, d, a, b, p, q, m, n], deps2)
|
|
|
self.make_equal(cd_ab, pq_mn, deps=deps2)
|
|
|
|
|
|
return add
|
|
|
|
|
|
def add_eqangle(
|
|
|
self, points: list[Point], deps: EmptyDependency
|
|
|
) -> list[Dependency]:
|
|
|
"""Add eqangle made by 8 points in `points`."""
|
|
|
if deps:
|
|
|
deps = deps.copy()
|
|
|
a, b, c, d, m, n, p, q = points
|
|
|
ab, why1 = self.get_line_thru_pair_why(a, b)
|
|
|
cd, why2 = self.get_line_thru_pair_why(c, d)
|
|
|
mn, why3 = self.get_line_thru_pair_why(m, n)
|
|
|
pq, why4 = self.get_line_thru_pair_why(p, q)
|
|
|
|
|
|
a, b = ab.points
|
|
|
c, d = cd.points
|
|
|
m, n = mn.points
|
|
|
p, q = pq.points
|
|
|
|
|
|
if deps and why1 + why2 + why3 + why4:
|
|
|
dep0 = deps.populate('eqangle', points)
|
|
|
deps = EmptyDependency(level=deps.level, rule_name=None)
|
|
|
deps.why = [dep0] + why1 + why2 + why3 + why4
|
|
|
|
|
|
add = self.maybe_make_equal_pairs(
|
|
|
a, b, c, d, m, n, p, q, ab, cd, mn, pq, deps
|
|
|
)
|
|
|
|
|
|
if add is not None:
|
|
|
return add
|
|
|
|
|
|
self.connect_val(ab, deps=None)
|
|
|
self.connect_val(cd, deps=None)
|
|
|
self.connect_val(mn, deps=None)
|
|
|
self.connect_val(pq, deps=None)
|
|
|
|
|
|
add = []
|
|
|
if (
|
|
|
ab.val != cd.val
|
|
|
and mn.val != pq.val
|
|
|
and (ab.val != mn.val or cd.val != pq.val)
|
|
|
):
|
|
|
add += self._add_eqangle(a, b, c, d, m, n, p, q, ab, cd, mn, pq, deps)
|
|
|
|
|
|
if (
|
|
|
ab.val != mn.val
|
|
|
and cd.val != pq.val
|
|
|
and (ab.val != cd.val or mn.val != pq.val)
|
|
|
):
|
|
|
add += self._add_eqangle(
|
|
|
a,
|
|
|
b,
|
|
|
m,
|
|
|
n,
|
|
|
c,
|
|
|
d,
|
|
|
p,
|
|
|
q,
|
|
|
ab,
|
|
|
mn,
|
|
|
cd,
|
|
|
pq,
|
|
|
deps,
|
|
|
)
|
|
|
|
|
|
return add
|
|
|
|
|
|
def add_aconst(
|
|
|
self, points: list[Point], deps: EmptyDependency
|
|
|
) -> list[Dependency]:
|
|
|
"""Add that an angle is equal to some constant."""
|
|
|
a, b, c, d, num, den = points
|
|
|
nd, dn = self.get_or_create_const_ang(num, den)
|
|
|
|
|
|
if nd == self.halfpi:
|
|
|
return self.add_perp([a, b, c, d], deps)
|
|
|
|
|
|
ab, why1 = self.get_line_thru_pair_why(a, b)
|
|
|
cd, why2 = self.get_line_thru_pair_why(c, d)
|
|
|
|
|
|
(a, b), (c, d) = ab.points, cd.points
|
|
|
if why1 + why2:
|
|
|
args = points[:-2] + [nd]
|
|
|
dep0 = deps.populate('aconst', args)
|
|
|
deps = EmptyDependency(level=deps.level, rule_name=None)
|
|
|
deps.why = [dep0] + why1 + why2
|
|
|
|
|
|
self.connect_val(ab, deps=None)
|
|
|
self.connect_val(cd, deps=None)
|
|
|
|
|
|
if ab.val == cd.val:
|
|
|
raise ValueError(f'{ab.name} - {cd.name} cannot be {nd.name}')
|
|
|
|
|
|
args = [a, b, c, d, nd]
|
|
|
i = 0
|
|
|
for x, y, xy in [(a, b, ab), (c, d, cd)]:
|
|
|
i += 1
|
|
|
x_, y_ = xy._val._obj.points
|
|
|
if {x, y} == {x_, y_}:
|
|
|
continue
|
|
|
if deps:
|
|
|
deps = deps.extend(self, 'aconst', list(args), 'para', [x, y, x_, y_])
|
|
|
args[2 * i - 2] = x_
|
|
|
args[2 * i - 1] = y_
|
|
|
|
|
|
ab_cd, cd_ab, why = self._get_or_create_angle(ab, cd, deps=None)
|
|
|
if why:
|
|
|
dep0 = deps.populate('aconst', [a, b, c, d, nd])
|
|
|
deps = EmptyDependency(level=deps.level, rule_name=None)
|
|
|
deps.why = [dep0] + why
|
|
|
|
|
|
dab, dcd = ab_cd._d
|
|
|
a, b = dab._obj.points
|
|
|
c, d = dcd._obj.points
|
|
|
|
|
|
ang = int(num) * 180 / int(den)
|
|
|
add = []
|
|
|
if not self.is_equal(ab_cd, nd):
|
|
|
deps1 = deps.populate('aconst', [a, b, c, d, nd])
|
|
|
deps1.algebra = dab, dcd, ang % 180
|
|
|
self.make_equal(ab_cd, nd, deps=deps1)
|
|
|
self.cache_dep('aconst', [a, b, c, d, nd], deps1)
|
|
|
add += [deps1]
|
|
|
|
|
|
if not self.is_equal(cd_ab, dn):
|
|
|
deps2 = deps.populate('aconst', [c, d, a, b, dn])
|
|
|
deps2.algebra = dcd, dab, 180 - ang % 180
|
|
|
self.make_equal(cd_ab, dn, deps=deps2)
|
|
|
self.cache_dep('aconst', [c, d, a, b, dn], deps2)
|
|
|
add += [deps2]
|
|
|
return add
|
|
|
|
|
|
def add_s_angle(
|
|
|
self, points: list[Point], deps: EmptyDependency
|
|
|
) -> list[Dependency]:
|
|
|
"""Add that an angle abx is equal to constant y."""
|
|
|
a, b, x, y = points
|
|
|
|
|
|
n, d = ar.simplify(y % 180, 180)
|
|
|
nd, dn = self.get_or_create_const_ang(n, d)
|
|
|
|
|
|
if nd == self.halfpi:
|
|
|
return self.add_perp([a, b, b, x], deps)
|
|
|
|
|
|
ab, why1 = self.get_line_thru_pair_why(a, b)
|
|
|
bx, why2 = self.get_line_thru_pair_why(b, x)
|
|
|
|
|
|
self.connect_val(ab, deps=None)
|
|
|
self.connect_val(bx, deps=None)
|
|
|
add = []
|
|
|
|
|
|
if ab.val == bx.val:
|
|
|
return add
|
|
|
|
|
|
deps.why += why1 + why2
|
|
|
|
|
|
for p, q, pq in [(a, b, ab), (b, x, bx)]:
|
|
|
p_, q_ = pq.val._obj.points
|
|
|
if {p, q} == {p_, q_}:
|
|
|
continue
|
|
|
dep = Dependency('para', [p, q, p_, q_], None, deps.level)
|
|
|
deps.why += [dep.why_me_or_cache(self, None)]
|
|
|
|
|
|
xba, abx, why = self._get_or_create_angle(bx, ab, deps=None)
|
|
|
if why:
|
|
|
dep0 = deps.populate('aconst', [b, x, a, b, nd])
|
|
|
deps = EmptyDependency(level=deps.level, rule_name=None)
|
|
|
deps.why = [dep0] + why
|
|
|
|
|
|
dab, dbx = abx._d
|
|
|
a, b = dab._obj.points
|
|
|
c, x = dbx._obj.points
|
|
|
|
|
|
if not self.is_equal(xba, nd):
|
|
|
deps1 = deps.populate('aconst', [c, x, a, b, nd])
|
|
|
deps1.algebra = dbx, dab, y % 180
|
|
|
|
|
|
self.make_equal(xba, nd, deps=deps1)
|
|
|
self.cache_dep('aconst', [c, x, a, b, nd], deps1)
|
|
|
add += [deps1]
|
|
|
|
|
|
if not self.is_equal(abx, dn):
|
|
|
deps2 = deps.populate('aconst', [a, b, c, x, dn])
|
|
|
deps2.algebra = dab, dbx, 180 - (y % 180)
|
|
|
|
|
|
self.make_equal(abx, dn, deps=deps2)
|
|
|
self.cache_dep('s_angle', [a, b, c, x, dn], deps2)
|
|
|
add += [deps2]
|
|
|
return add
|
|
|
|
|
|
def check_aconst(self, points: list[Point], verbose: bool = False) -> bool:
|
|
|
"""Check if the angle is equal to a certain constant."""
|
|
|
a, b, c, d, nd = points
|
|
|
_ = verbose
|
|
|
if isinstance(nd, str):
|
|
|
name = nd
|
|
|
else:
|
|
|
name = nd.name
|
|
|
num, den = name.split('pi/')
|
|
|
ang, _ = self.get_or_create_const_ang(int(num), int(den))
|
|
|
|
|
|
ab = self._get_line(a, b)
|
|
|
cd = self._get_line(c, d)
|
|
|
if not ab or not cd:
|
|
|
return False
|
|
|
|
|
|
if not (ab.val and cd.val):
|
|
|
return False
|
|
|
|
|
|
for ang1, _, _ in gm.all_angles(ab._val, cd._val):
|
|
|
if self.is_equal(ang1, ang):
|
|
|
return True
|
|
|
return False
|
|
|
|
|
|
def check_acompute(self, points: list[Point]) -> bool:
|
|
|
"""Check if an angle has a constant value."""
|
|
|
a, b, c, d = points
|
|
|
ab = self._get_line(a, b)
|
|
|
cd = self._get_line(c, d)
|
|
|
if not ab or not cd:
|
|
|
return False
|
|
|
|
|
|
if not (ab.val and cd.val):
|
|
|
return False
|
|
|
|
|
|
for ang0 in self.aconst.values():
|
|
|
for ang in ang0.val.neighbors(Angle):
|
|
|
d1, d2 = ang.directions
|
|
|
if ab.val == d1 and cd.val == d2:
|
|
|
return True
|
|
|
return False
|
|
|
|
|
|
def check_eqangle(self, points: list[Point]) -> bool:
|
|
|
"""Check if two angles are equal."""
|
|
|
a, b, c, d, m, n, p, q = points
|
|
|
|
|
|
if {a, b} == {c, d} and {m, n} == {p, q}:
|
|
|
return True
|
|
|
if {a, b} == {m, n} and {c, d} == {p, q}:
|
|
|
return True
|
|
|
|
|
|
if (a == b) or (c == d) or (m == n) or (p == q):
|
|
|
return False
|
|
|
ab = self._get_line(a, b)
|
|
|
cd = self._get_line(c, d)
|
|
|
mn = self._get_line(m, n)
|
|
|
pq = self._get_line(p, q)
|
|
|
|
|
|
if {a, b} == {c, d} and mn and pq and self.is_equal(mn, pq):
|
|
|
return True
|
|
|
if {a, b} == {m, n} and cd and pq and self.is_equal(cd, pq):
|
|
|
return True
|
|
|
if {p, q} == {m, n} and ab and cd and self.is_equal(ab, cd):
|
|
|
return True
|
|
|
if {p, q} == {c, d} and ab and mn and self.is_equal(ab, mn):
|
|
|
return True
|
|
|
|
|
|
if not ab or not cd or not mn or not pq:
|
|
|
return False
|
|
|
|
|
|
if self.is_equal(ab, cd) and self.is_equal(mn, pq):
|
|
|
return True
|
|
|
if self.is_equal(ab, mn) and self.is_equal(cd, pq):
|
|
|
return True
|
|
|
|
|
|
if not (ab.val and cd.val and mn.val and pq.val):
|
|
|
return False
|
|
|
|
|
|
if (ab.val, cd.val) == (mn.val, pq.val) or (ab.val, mn.val) == (
|
|
|
cd.val,
|
|
|
pq.val,
|
|
|
):
|
|
|
return True
|
|
|
|
|
|
for ang1, _, _ in gm.all_angles(ab._val, cd._val):
|
|
|
for ang2, _, _ in gm.all_angles(mn._val, pq._val):
|
|
|
if self.is_equal(ang1, ang2):
|
|
|
return True
|
|
|
|
|
|
if self.check_perp([a, b, m, n]) and self.check_perp([c, d, p, q]):
|
|
|
return True
|
|
|
if self.check_perp([a, b, p, q]) and self.check_perp([c, d, m, n]):
|
|
|
return True
|
|
|
|
|
|
return False
|
|
|
|
|
|
def _get_ratio(self, l1: Length, l2: Length) -> tuple[Ratio, Ratio]:
|
|
|
for r in self.type2nodes[Ratio]:
|
|
|
if r.lengths == (l1, l2):
|
|
|
return r, r.opposite
|
|
|
return None, None
|
|
|
|
|
|
def _get_or_create_ratio(
|
|
|
self, s1: Segment, s2: Segment, deps: Dependency
|
|
|
) -> tuple[Ratio, Ratio, list[Dependency]]:
|
|
|
return self._get_or_create_ratio_l(s1._val, s2._val, deps)
|
|
|
|
|
|
def _get_or_create_ratio_l(
|
|
|
self, l1: Length, l2: Length, deps: Dependency
|
|
|
) -> tuple[Ratio, Ratio, list[Dependency]]:
|
|
|
"""Get or create a new Ratio from two Lenghts l1 and l2."""
|
|
|
for r in self.type2nodes[Ratio]:
|
|
|
if r.lengths == (l1.rep(), l2.rep()):
|
|
|
l1_, l2_ = r._l
|
|
|
why1 = l1.why_equal([l1_], None) + l1_.why_rep()
|
|
|
why2 = l2.why_equal([l2_], None) + l2_.why_rep()
|
|
|
return r, r.opposite, why1 + why2
|
|
|
|
|
|
l1, why1 = l1.rep_and_why()
|
|
|
l2, why2 = l2.rep_and_why()
|
|
|
r12 = self.new_node(Ratio, f'{l1.name}/{l2.name}')
|
|
|
r21 = self.new_node(Ratio, f'{l2.name}/{l1.name}')
|
|
|
self.connect(l1, r12, deps)
|
|
|
self.connect(l2, r21, deps)
|
|
|
self.connect(r12, r21, deps)
|
|
|
r12.set_lengths(l1, l2)
|
|
|
r21.set_lengths(l2, l1)
|
|
|
r12.opposite = r21
|
|
|
r21.opposite = r12
|
|
|
return r12, r21, why1 + why2
|
|
|
|
|
|
def add_cong2(
|
|
|
self, points: list[Point], deps: EmptyDependency
|
|
|
) -> list[Dependency]:
|
|
|
m, n, a, b = points
|
|
|
add = []
|
|
|
add += self.add_cong([m, a, n, a], deps)
|
|
|
add += self.add_cong([m, b, n, b], deps)
|
|
|
return add
|
|
|
|
|
|
def add_eqratio3(
|
|
|
self, points: list[Point], deps: EmptyDependency
|
|
|
) -> list[Dependency]:
|
|
|
"""Add three eqratios through a list of 6 points (due to parallel lines)."""
|
|
|
a, b, c, d, m, n = points
|
|
|
|
|
|
|
|
|
|
|
|
add = []
|
|
|
add += self.add_eqratio([m, a, m, c, n, b, n, d], deps)
|
|
|
add += self.add_eqratio([a, m, a, c, b, n, b, d], deps)
|
|
|
add += self.add_eqratio([c, m, c, a, d, n, d, b], deps)
|
|
|
if m == n:
|
|
|
add += self.add_eqratio([m, a, m, c, a, b, c, d], deps)
|
|
|
return add
|
|
|
|
|
|
def add_eqratio4(
|
|
|
self, points: list[Point], deps: EmptyDependency
|
|
|
) -> list[Dependency]:
|
|
|
o, a, b, c, d = points
|
|
|
|
|
|
|
|
|
|
|
|
add = self.add_eqratio3([a, b, c, d, o, o], deps)
|
|
|
add += self.add_eqratio([o, a, o, c, a, b, c, d], deps)
|
|
|
return add
|
|
|
|
|
|
def _add_eqratio(
|
|
|
self,
|
|
|
a: Point,
|
|
|
b: Point,
|
|
|
c: Point,
|
|
|
d: Point,
|
|
|
m: Point,
|
|
|
n: Point,
|
|
|
p: Point,
|
|
|
q: Point,
|
|
|
ab: Segment,
|
|
|
cd: Segment,
|
|
|
mn: Segment,
|
|
|
pq: Segment,
|
|
|
deps: EmptyDependency,
|
|
|
) -> list[Dependency]:
|
|
|
"""Add a new eqratio from 8 points (core)."""
|
|
|
if deps:
|
|
|
deps = deps.copy()
|
|
|
|
|
|
args = [a, b, c, d, m, n, p, q]
|
|
|
i = 0
|
|
|
for x, y, xy in [(a, b, ab), (c, d, cd), (m, n, mn), (p, q, pq)]:
|
|
|
if {x, y} == set(xy.points):
|
|
|
continue
|
|
|
x_, y_ = list(xy.points)
|
|
|
if deps:
|
|
|
deps = deps.extend(self, 'eqratio', list(args), 'cong', [x, y, x_, y_])
|
|
|
args[2 * i - 2] = x_
|
|
|
args[2 * i - 1] = y_
|
|
|
|
|
|
add = []
|
|
|
ab_cd, cd_ab, why1 = self._get_or_create_ratio(ab, cd, deps=None)
|
|
|
mn_pq, pq_mn, why2 = self._get_or_create_ratio(mn, pq, deps=None)
|
|
|
|
|
|
why = why1 + why2
|
|
|
if why:
|
|
|
dep0 = deps.populate('eqratio', args)
|
|
|
deps = EmptyDependency(level=deps.level, rule_name=None)
|
|
|
deps.why = [dep0] + why
|
|
|
|
|
|
lab, lcd = ab_cd._l
|
|
|
lmn, lpq = mn_pq._l
|
|
|
|
|
|
a, b = lab._obj.points
|
|
|
c, d = lcd._obj.points
|
|
|
m, n = lmn._obj.points
|
|
|
p, q = lpq._obj.points
|
|
|
|
|
|
is_eq1 = self.is_equal(ab_cd, mn_pq)
|
|
|
deps1 = None
|
|
|
if deps:
|
|
|
deps1 = deps.populate('eqratio', [a, b, c, d, m, n, p, q])
|
|
|
deps1.algebra = [ab._val, cd._val, mn._val, pq._val]
|
|
|
if not is_eq1:
|
|
|
add += [deps1]
|
|
|
self.cache_dep('eqratio', [a, b, c, d, m, n, p, q], deps1)
|
|
|
self.make_equal(ab_cd, mn_pq, deps=deps1)
|
|
|
|
|
|
is_eq2 = self.is_equal(cd_ab, pq_mn)
|
|
|
deps2 = None
|
|
|
if deps:
|
|
|
deps2 = deps.populate('eqratio', [c, d, a, b, p, q, m, n])
|
|
|
deps2.algebra = [cd._val, ab._val, pq._val, mn._val]
|
|
|
if not is_eq2:
|
|
|
add += [deps2]
|
|
|
self.cache_dep('eqratio', [c, d, a, b, p, q, m, n], deps2)
|
|
|
self.make_equal(cd_ab, pq_mn, deps=deps2)
|
|
|
return add
|
|
|
|
|
|
def add_eqratio(
|
|
|
self, points: list[Point], deps: EmptyDependency
|
|
|
) -> list[Dependency]:
|
|
|
"""Add a new eqratio from 8 points."""
|
|
|
if deps:
|
|
|
deps = deps.copy()
|
|
|
a, b, c, d, m, n, p, q = points
|
|
|
ab = self._get_or_create_segment(a, b, deps=None)
|
|
|
cd = self._get_or_create_segment(c, d, deps=None)
|
|
|
mn = self._get_or_create_segment(m, n, deps=None)
|
|
|
pq = self._get_or_create_segment(p, q, deps=None)
|
|
|
|
|
|
add = self.maybe_make_equal_pairs(
|
|
|
a, b, c, d, m, n, p, q, ab, cd, mn, pq, deps
|
|
|
)
|
|
|
|
|
|
if add is not None:
|
|
|
return add
|
|
|
|
|
|
self.connect_val(ab, deps=None)
|
|
|
self.connect_val(cd, deps=None)
|
|
|
self.connect_val(mn, deps=None)
|
|
|
self.connect_val(pq, deps=None)
|
|
|
|
|
|
add = []
|
|
|
if (
|
|
|
ab.val != cd.val
|
|
|
and mn.val != pq.val
|
|
|
and (ab.val != mn.val or cd.val != pq.val)
|
|
|
):
|
|
|
add += self._add_eqratio(a, b, c, d, m, n, p, q, ab, cd, mn, pq, deps)
|
|
|
|
|
|
if (
|
|
|
ab.val != mn.val
|
|
|
and cd.val != pq.val
|
|
|
and (ab.val != cd.val or mn.val != pq.val)
|
|
|
):
|
|
|
add += self._add_eqratio(
|
|
|
a,
|
|
|
b,
|
|
|
m,
|
|
|
n,
|
|
|
c,
|
|
|
d,
|
|
|
p,
|
|
|
q,
|
|
|
ab,
|
|
|
mn,
|
|
|
cd,
|
|
|
pq,
|
|
|
deps,
|
|
|
)
|
|
|
return add
|
|
|
|
|
|
def check_rconst(self, points: list[Point], verbose: bool = False) -> bool:
|
|
|
"""Check whether a ratio is equal to some given constant."""
|
|
|
_ = verbose
|
|
|
a, b, c, d, nd = points
|
|
|
if isinstance(nd, str):
|
|
|
name = nd
|
|
|
else:
|
|
|
name = nd.name
|
|
|
num, den = name.split('/')
|
|
|
rat, _ = self.get_or_create_const_rat(int(num), int(den))
|
|
|
|
|
|
ab = self._get_segment(a, b)
|
|
|
cd = self._get_segment(c, d)
|
|
|
|
|
|
if not ab or not cd:
|
|
|
return False
|
|
|
|
|
|
if not (ab.val and cd.val):
|
|
|
return False
|
|
|
|
|
|
for rat1, _, _ in gm.all_ratios(ab._val, cd._val):
|
|
|
if self.is_equal(rat1, rat):
|
|
|
return True
|
|
|
return False
|
|
|
|
|
|
def check_rcompute(self, points: list[Point]) -> bool:
|
|
|
"""Check whether a ratio is equal to some constant."""
|
|
|
a, b, c, d = points
|
|
|
ab = self._get_segment(a, b)
|
|
|
cd = self._get_segment(c, d)
|
|
|
|
|
|
if not ab or not cd:
|
|
|
return False
|
|
|
|
|
|
if not (ab.val and cd.val):
|
|
|
return False
|
|
|
|
|
|
for rat0 in self.rconst.values():
|
|
|
for rat in rat0.val.neighbors(Ratio):
|
|
|
l1, l2 = rat.lengths
|
|
|
if ab.val == l1 and cd.val == l2:
|
|
|
return True
|
|
|
return False
|
|
|
|
|
|
def check_eqratio(self, points: list[Point]) -> bool:
|
|
|
"""Check if 8 points make an eqratio predicate."""
|
|
|
a, b, c, d, m, n, p, q = points
|
|
|
|
|
|
if {a, b} == {c, d} and {m, n} == {p, q}:
|
|
|
return True
|
|
|
if {a, b} == {m, n} and {c, d} == {p, q}:
|
|
|
return True
|
|
|
|
|
|
ab = self._get_segment(a, b)
|
|
|
cd = self._get_segment(c, d)
|
|
|
mn = self._get_segment(m, n)
|
|
|
pq = self._get_segment(p, q)
|
|
|
|
|
|
if {a, b} == {c, d} and mn and pq and self.is_equal(mn, pq):
|
|
|
return True
|
|
|
if {a, b} == {m, n} and cd and pq and self.is_equal(cd, pq):
|
|
|
return True
|
|
|
if {p, q} == {m, n} and ab and cd and self.is_equal(ab, cd):
|
|
|
return True
|
|
|
if {p, q} == {c, d} and ab and mn and self.is_equal(ab, mn):
|
|
|
return True
|
|
|
|
|
|
if not ab or not cd or not mn or not pq:
|
|
|
return False
|
|
|
|
|
|
if self.is_equal(ab, cd) and self.is_equal(mn, pq):
|
|
|
return True
|
|
|
if self.is_equal(ab, mn) and self.is_equal(cd, pq):
|
|
|
return True
|
|
|
|
|
|
if not (ab.val and cd.val and mn.val and pq.val):
|
|
|
return False
|
|
|
|
|
|
if (ab.val, cd.val) == (mn.val, pq.val) or (ab.val, mn.val) == (
|
|
|
cd.val,
|
|
|
pq.val,
|
|
|
):
|
|
|
return True
|
|
|
|
|
|
for rat1, _, _ in gm.all_ratios(ab._val, cd._val):
|
|
|
for rat2, _, _ in gm.all_ratios(mn._val, pq._val):
|
|
|
if self.is_equal(rat1, rat2):
|
|
|
return True
|
|
|
return False
|
|
|
|
|
|
def add_simtri_check(
|
|
|
self, points: list[Point], deps: EmptyDependency
|
|
|
) -> list[Dependency]:
|
|
|
if nm.same_clock(*[p.num for p in points]):
|
|
|
return self.add_simtri(points, deps)
|
|
|
return self.add_simtri2(points, deps)
|
|
|
|
|
|
def add_contri_check(
|
|
|
self, points: list[Point], deps: EmptyDependency
|
|
|
) -> list[Dependency]:
|
|
|
if nm.same_clock(*[p.num for p in points]):
|
|
|
return self.add_contri(points, deps)
|
|
|
return self.add_contri2(points, deps)
|
|
|
|
|
|
def enum_sides(
|
|
|
self, points: list[Point]
|
|
|
) -> Generator[list[Point], None, None]:
|
|
|
a, b, c, x, y, z = points
|
|
|
yield [a, b, x, y]
|
|
|
yield [b, c, y, z]
|
|
|
yield [c, a, z, x]
|
|
|
|
|
|
def enum_triangle(
|
|
|
self, points: list[Point]
|
|
|
) -> Generator[list[Point], None, None]:
|
|
|
a, b, c, x, y, z = points
|
|
|
yield [a, b, a, c, x, y, x, z]
|
|
|
yield [b, a, b, c, y, x, y, z]
|
|
|
yield [c, a, c, b, z, x, z, y]
|
|
|
|
|
|
def enum_triangle2(
|
|
|
self, points: list[Point]
|
|
|
) -> Generator[list[Point], None, None]:
|
|
|
a, b, c, x, y, z = points
|
|
|
yield [a, b, a, c, x, z, x, y]
|
|
|
yield [b, a, b, c, y, z, y, x]
|
|
|
yield [c, a, c, b, z, y, z, x]
|
|
|
|
|
|
def add_simtri(
|
|
|
self, points: list[Point], deps: EmptyDependency
|
|
|
) -> list[Dependency]:
|
|
|
"""Add two similar triangles."""
|
|
|
add = []
|
|
|
hashs = [d.hashed() for d in deps.why]
|
|
|
|
|
|
for args in self.enum_triangle(points):
|
|
|
if problem.hashed('eqangle6', args) in hashs:
|
|
|
continue
|
|
|
add += self.add_eqangle(args, deps=deps)
|
|
|
|
|
|
for args in self.enum_triangle(points):
|
|
|
if problem.hashed('eqratio6', args) in hashs:
|
|
|
continue
|
|
|
add += self.add_eqratio(args, deps=deps)
|
|
|
|
|
|
return add
|
|
|
|
|
|
def check_simtri(self, points: list[Point]) -> bool:
|
|
|
a, b, c, x, y, z = points
|
|
|
return self.check_eqangle([a, b, a, c, x, y, x, z]) and self.check_eqangle(
|
|
|
[b, a, b, c, y, x, y, z]
|
|
|
)
|
|
|
|
|
|
def add_simtri2(
|
|
|
self, points: list[Point], deps: EmptyDependency
|
|
|
) -> list[Dependency]:
|
|
|
"""Add two similar reflected triangles."""
|
|
|
add = []
|
|
|
hashs = [d.hashed() for d in deps.why]
|
|
|
for args in self.enum_triangle2(points):
|
|
|
if problem.hashed('eqangle6', args) in hashs:
|
|
|
continue
|
|
|
add += self.add_eqangle(args, deps=deps)
|
|
|
|
|
|
for args in self.enum_triangle(points):
|
|
|
if problem.hashed('eqratio6', args) in hashs:
|
|
|
continue
|
|
|
add += self.add_eqratio(args, deps=deps)
|
|
|
|
|
|
return add
|
|
|
|
|
|
def add_contri(
|
|
|
self, points: list[Point], deps: EmptyDependency
|
|
|
) -> list[Dependency]:
|
|
|
"""Add two congruent triangles."""
|
|
|
add = []
|
|
|
hashs = [d.hashed() for d in deps.why]
|
|
|
for args in self.enum_triangle(points):
|
|
|
if problem.hashed('eqangle6', args) in hashs:
|
|
|
continue
|
|
|
add += self.add_eqangle(args, deps=deps)
|
|
|
|
|
|
for args in self.enum_sides(points):
|
|
|
if problem.hashed('cong', args) in hashs:
|
|
|
continue
|
|
|
add += self.add_cong(args, deps=deps)
|
|
|
return add
|
|
|
|
|
|
def check_contri(self, points: list[Point]) -> bool:
|
|
|
a, b, c, x, y, z = points
|
|
|
return (
|
|
|
self.check_cong([a, b, x, y])
|
|
|
and self.check_cong([b, c, y, z])
|
|
|
and self.check_cong([c, a, z, x])
|
|
|
)
|
|
|
|
|
|
def add_contri2(
|
|
|
self, points: list[Point], deps: EmptyDependency
|
|
|
) -> list[Dependency]:
|
|
|
"""Add two congruent reflected triangles."""
|
|
|
add = []
|
|
|
hashs = [d.hashed() for d in deps.why]
|
|
|
for args in self.enum_triangle2(points):
|
|
|
if problem.hashed('eqangle6', args) in hashs:
|
|
|
continue
|
|
|
add += self.add_eqangle(args, deps=deps)
|
|
|
|
|
|
for args in self.enum_sides(points):
|
|
|
if problem.hashed('cong', args) in hashs:
|
|
|
continue
|
|
|
add += self.add_cong(args, deps=deps)
|
|
|
|
|
|
return add
|
|
|
|
|
|
def in_cache(self, name: str, args: list[Point]) -> bool:
|
|
|
return problem.hashed(name, args) in self.cache
|
|
|
|
|
|
def cache_dep(
|
|
|
self, name: str, args: list[Point], premises: list[Dependency]
|
|
|
) -> None:
|
|
|
hashed = problem.hashed(name, args)
|
|
|
if hashed in self.cache:
|
|
|
return
|
|
|
self.cache[hashed] = premises
|
|
|
|
|
|
def all_same_line(
|
|
|
self, a: Point, b: Point
|
|
|
) -> Generator[tuple[Point, Point], None, None]:
|
|
|
ab = self._get_line(a, b)
|
|
|
if ab is None:
|
|
|
return
|
|
|
for p1, p2 in utils.comb2(ab.neighbors(Point)):
|
|
|
if {p1, p2} != {a, b}:
|
|
|
yield p1, p2
|
|
|
|
|
|
def all_same_angle(
|
|
|
self, a: Point, b: Point, c: Point, d: Point
|
|
|
) -> Generator[tuple[Point, Point, Point, Point], None, None]:
|
|
|
for x, y in self.all_same_line(a, b):
|
|
|
for m, n in self.all_same_line(c, d):
|
|
|
yield x, y, m, n
|
|
|
|
|
|
def additionally_draw(self, name: str, args: list[Point]) -> None:
|
|
|
"""Draw some extra line/circles for illustration purpose."""
|
|
|
|
|
|
if name in ['circle']:
|
|
|
center, point = args[:2]
|
|
|
circle = self.new_node(Circle, f'({center.name},{point.name})')
|
|
|
circle.num = nm.Circle(center.num, p1=point.num)
|
|
|
circle.points = center, point
|
|
|
|
|
|
if name in ['on_circle', 'tangent']:
|
|
|
center, point = args[-2:]
|
|
|
circle = self.new_node(Circle, f'({center.name},{point.name})')
|
|
|
circle.num = nm.Circle(center.num, p1=point.num)
|
|
|
circle.points = center, point
|
|
|
|
|
|
if name in ['incenter', 'excenter', 'incenter2', 'excenter2']:
|
|
|
d, a, b, c = [x for x in args[-4:]]
|
|
|
a, b, c = sorted([a, b, c], key=lambda x: x.name.lower())
|
|
|
circle = self.new_node(Circle, f'({d.name},h.{a.name}{b.name})')
|
|
|
p = d.num.foot(nm.Line(a.num, b.num))
|
|
|
circle.num = nm.Circle(d.num, p1=p)
|
|
|
circle.points = d, a, b, c
|
|
|
|
|
|
if name in ['cc_tangent']:
|
|
|
o, a, w, b = args[-4:]
|
|
|
c1 = self.new_node(Circle, f'({o.name},{a.name})')
|
|
|
c1.num = nm.Circle(o.num, p1=a.num)
|
|
|
c1.points = o, a
|
|
|
|
|
|
c2 = self.new_node(Circle, f'({w.name},{b.name})')
|
|
|
c2.num = nm.Circle(w.num, p1=b.num)
|
|
|
c2.points = w, b
|
|
|
|
|
|
if name in ['ninepoints']:
|
|
|
a, b, c = args[-3:]
|
|
|
a, b, c = sorted([a, b, c], key=lambda x: x.name.lower())
|
|
|
circle = self.new_node(Circle, f'(,m.{a.name}{b.name}{c.name})')
|
|
|
p1 = (b.num + c.num) * 0.5
|
|
|
p2 = (c.num + a.num) * 0.5
|
|
|
p3 = (a.num + b.num) * 0.5
|
|
|
circle.num = nm.Circle(p1=p1, p2=p2, p3=p3)
|
|
|
circle.points = (None, None, a, b, c)
|
|
|
|
|
|
if name in ['2l1c']:
|
|
|
a, b, c, o = args[:4]
|
|
|
a, b, c = sorted([a, b, c], key=lambda x: x.name.lower())
|
|
|
circle = self.new_node(Circle, f'({o.name},{a.name}{b.name}{c.name})')
|
|
|
circle.num = nm.Circle(p1=a.num, p2=b.num, p3=c.num)
|
|
|
circle.points = (a, b, c)
|
|
|
|
|
|
def add_clause(
|
|
|
self,
|
|
|
clause: problem.Clause,
|
|
|
plevel: int,
|
|
|
definitions: dict[str, problem.Definition],
|
|
|
verbose: int = False,
|
|
|
) -> tuple[list[Dependency], int]:
|
|
|
"""Add a new clause of construction, e.g. a new excenter."""
|
|
|
existing_points = self.all_points()
|
|
|
new_points = [Point(name) for name in clause.points]
|
|
|
|
|
|
new_points_dep_points = set()
|
|
|
new_points_dep = []
|
|
|
|
|
|
|
|
|
for c in clause.constructions:
|
|
|
cdef = definitions[c.name]
|
|
|
|
|
|
if len(cdef.construction.args) != len(c.args):
|
|
|
if len(cdef.construction.args) - len(c.args) == len(clause.points):
|
|
|
c.args = clause.points + c.args
|
|
|
else:
|
|
|
correct_form = ' '.join(cdef.points + ['=', c.name] + cdef.args)
|
|
|
raise ValueError('Argument mismatch. ' + correct_form)
|
|
|
|
|
|
mapping = dict(zip(cdef.construction.args, c.args))
|
|
|
c_name = 'midp' if c.name == 'midpoint' else c.name
|
|
|
deps = EmptyDependency(level=0, rule_name=problem.CONSTRUCTION_RULE)
|
|
|
deps.construction = Dependency(c_name, c.args, rule_name=None, level=0)
|
|
|
|
|
|
for d in cdef.deps.constructions:
|
|
|
args = self.names2points([mapping[a] for a in d.args])
|
|
|
new_points_dep_points.update(args)
|
|
|
if not self.check(d.name, args):
|
|
|
raise DepCheckFailError(
|
|
|
d.name + ' ' + ' '.join([x.name for x in args])
|
|
|
)
|
|
|
deps.why += [
|
|
|
Dependency(
|
|
|
d.name, args, rule_name=problem.CONSTRUCTION_RULE, level=0
|
|
|
)
|
|
|
]
|
|
|
|
|
|
new_points_dep += [deps]
|
|
|
|
|
|
|
|
|
def range_fn() -> (
|
|
|
list[Union[nm.Point, nm.Line, nm.Circle, nm.HalfLine, nm.HoleCircle]]
|
|
|
):
|
|
|
to_be_intersected = []
|
|
|
for c in clause.constructions:
|
|
|
cdef = definitions[c.name]
|
|
|
mapping = dict(zip(cdef.construction.args, c.args))
|
|
|
for n in cdef.numerics:
|
|
|
args = [mapping[a] for a in n.args]
|
|
|
args = list(map(lambda x: self.get(x, lambda: int(x)), args))
|
|
|
to_be_intersected += nm.sketch(n.name, args)
|
|
|
|
|
|
return to_be_intersected
|
|
|
|
|
|
is_total_free = (
|
|
|
len(clause.constructions) == 1 and clause.constructions[0].name in FREE
|
|
|
)
|
|
|
is_semi_free = (
|
|
|
len(clause.constructions) == 1
|
|
|
and clause.constructions[0].name in INTERSECT
|
|
|
)
|
|
|
|
|
|
existing_points = [p.num for p in existing_points]
|
|
|
|
|
|
def draw_fn() -> list[nm.Point]:
|
|
|
to_be_intersected = range_fn()
|
|
|
return nm.reduce(to_be_intersected, existing_points)
|
|
|
|
|
|
rely_on = set()
|
|
|
for c in clause.constructions:
|
|
|
cdef = definitions[c.name]
|
|
|
mapping = dict(zip(cdef.construction.args, c.args))
|
|
|
for n in cdef.numerics:
|
|
|
args = [mapping[a] for a in n.args]
|
|
|
args = list(map(lambda x: self.get(x, lambda: int(x)), args))
|
|
|
rely_on.update([a for a in args if isinstance(a, Point)])
|
|
|
|
|
|
for p in rely_on:
|
|
|
p.change.update(new_points)
|
|
|
|
|
|
nums = draw_fn()
|
|
|
for p, num, num0 in zip(new_points, nums, clause.nums):
|
|
|
p.co_change = new_points
|
|
|
if isinstance(num0, nm.Point):
|
|
|
num = num0
|
|
|
elif isinstance(num0, (tuple, list)):
|
|
|
x, y = num0
|
|
|
num = nm.Point(x, y)
|
|
|
|
|
|
p.num = num
|
|
|
|
|
|
|
|
|
if nm.check_too_close(nums, existing_points):
|
|
|
raise PointTooCloseError()
|
|
|
if nm.check_too_far(nums, existing_points):
|
|
|
raise PointTooFarError()
|
|
|
|
|
|
|
|
|
|
|
|
for p in new_points:
|
|
|
self._name2point[p.name] = p
|
|
|
self._name2node[p.name] = p
|
|
|
self.type2nodes[Point].append(p)
|
|
|
|
|
|
for p in new_points:
|
|
|
p.why = sum([d.why for d in new_points_dep], [])
|
|
|
p.group = new_points
|
|
|
p.dep_points = new_points_dep_points
|
|
|
p.dep_points.update(new_points)
|
|
|
p.plevel = plevel
|
|
|
|
|
|
|
|
|
rely_dict_0 = defaultdict(lambda: [])
|
|
|
|
|
|
for c in clause.constructions:
|
|
|
cdef = definitions[c.name]
|
|
|
mapping = dict(zip(cdef.construction.args, c.args))
|
|
|
for p, ps in cdef.rely.items():
|
|
|
p = mapping[p]
|
|
|
ps = [mapping[x] for x in ps]
|
|
|
rely_dict_0[p].append(ps)
|
|
|
|
|
|
rely_dict = {}
|
|
|
for p, pss in rely_dict_0.items():
|
|
|
ps = sum(pss, [])
|
|
|
if len(pss) > 1:
|
|
|
ps = [x for x in ps if x != p]
|
|
|
|
|
|
p = self._name2point[p]
|
|
|
ps = self.names2nodes(ps)
|
|
|
rely_dict[p] = ps
|
|
|
|
|
|
for p in new_points:
|
|
|
p.rely_on = set(rely_dict.get(p, []))
|
|
|
for x in p.rely_on:
|
|
|
if not hasattr(x, 'base_rely_on'):
|
|
|
x.base_rely_on = set()
|
|
|
p.base_rely_on = set.union(*[x.base_rely_on for x in p.rely_on] + [set()])
|
|
|
if is_total_free or is_semi_free:
|
|
|
p.rely_on.add(p)
|
|
|
p.base_rely_on.add(p)
|
|
|
|
|
|
plevel_done = set()
|
|
|
added = []
|
|
|
basics = []
|
|
|
|
|
|
for c, deps in zip(clause.constructions, new_points_dep):
|
|
|
cdef = definitions[c.name]
|
|
|
mapping = dict(zip(cdef.construction.args, c.args))
|
|
|
|
|
|
|
|
|
c_args = list(map(lambda x: self.get(x, lambda: int(x)), c.args))
|
|
|
self.additionally_draw(c.name, c_args)
|
|
|
|
|
|
for points, bs in cdef.basics:
|
|
|
if points:
|
|
|
points = self.names2nodes([mapping[p] for p in points])
|
|
|
points = [p for p in points if p not in plevel_done]
|
|
|
for p in points:
|
|
|
p.plevel = plevel
|
|
|
plevel_done.update(points)
|
|
|
plevel += 1
|
|
|
else:
|
|
|
continue
|
|
|
|
|
|
for b in bs:
|
|
|
if b.name != 'rconst':
|
|
|
args = [mapping[a] for a in b.args]
|
|
|
else:
|
|
|
num, den = map(int, b.args[-2:])
|
|
|
rat, _ = self.get_or_create_const_rat(num, den)
|
|
|
args = [mapping[a] for a in b.args[:-2]] + [rat.name]
|
|
|
|
|
|
args = list(map(lambda x: self.get(x, lambda: int(x)), args))
|
|
|
|
|
|
adds = self.add_piece(name=b.name, args=args, deps=deps)
|
|
|
basics.append((b.name, args, deps))
|
|
|
if adds:
|
|
|
added += adds
|
|
|
for add in adds:
|
|
|
self.cache_dep(add.name, add.args, add)
|
|
|
|
|
|
assert len(plevel_done) == len(new_points)
|
|
|
for p in new_points:
|
|
|
p.basics = basics
|
|
|
|
|
|
return added, plevel
|
|
|
|
|
|
def all_eqangle_same_lines(self) -> Generator[tuple[Point, ...], None, None]:
|
|
|
for l1, l2 in utils.perm2(self.type2nodes[Line]):
|
|
|
for a, b, c, d, e, f, g, h in utils.all_8points(l1, l2, l1, l2):
|
|
|
if (a, b, c, d) != (e, f, g, h):
|
|
|
yield a, b, c, d, e, f, g, h
|
|
|
|
|
|
def all_eqangles_distinct_linepairss(
|
|
|
self,
|
|
|
) -> Generator[tuple[Line, ...], None, None]:
|
|
|
"""No eqangles betcause para-para, or para-corresponding, or same."""
|
|
|
|
|
|
for measure in self.type2nodes[Measure]:
|
|
|
angs = measure.neighbors(Angle)
|
|
|
line_pairss = []
|
|
|
for ang in angs:
|
|
|
d1, d2 = ang.directions
|
|
|
if d1 is None or d2 is None:
|
|
|
continue
|
|
|
l1s = d1.neighbors(Line)
|
|
|
l2s = d2.neighbors(Line)
|
|
|
|
|
|
para_para = list(utils.cross(l1s, l2s))
|
|
|
line_pairss.append(para_para)
|
|
|
|
|
|
for pairs1, pairs2 in utils.comb2(line_pairss):
|
|
|
for pair1, pair2 in utils.cross(pairs1, pairs2):
|
|
|
(l1, l2), (l3, l4) = pair1, pair2
|
|
|
yield l1, l2, l3, l4
|
|
|
|
|
|
def all_eqangles_8points(self) -> Generator[tuple[Point, ...], None, None]:
|
|
|
"""List all sets of 8 points that make two equal angles."""
|
|
|
|
|
|
angss = []
|
|
|
for measure in self.type2nodes[Measure]:
|
|
|
angs = measure.neighbors(Angle)
|
|
|
angss.append(angs)
|
|
|
|
|
|
|
|
|
angss.extend([[ang] for ang in self.type2nodes[Angle] if ang.val is None])
|
|
|
|
|
|
line_pairss = []
|
|
|
for angs in angss:
|
|
|
line_pairs = set()
|
|
|
for ang in angs:
|
|
|
d1, d2 = ang.directions
|
|
|
if d1 is None or d2 is None:
|
|
|
continue
|
|
|
l1s = d1.neighbors(Line)
|
|
|
l2s = d2.neighbors(Line)
|
|
|
line_pairs.update(set(utils.cross(l1s, l2s)))
|
|
|
line_pairss.append(line_pairs)
|
|
|
|
|
|
|
|
|
noang_ds = [d for d in self.type2nodes[Direction] if not d.neighbors(Angle)]
|
|
|
|
|
|
for d1 in noang_ds:
|
|
|
for d2 in self.type2nodes[Direction]:
|
|
|
if d1 == d2:
|
|
|
continue
|
|
|
l1s = d1.neighbors(Line)
|
|
|
l2s = d2.neighbors(Line)
|
|
|
if len(l1s) < 2 and len(l2s) < 2:
|
|
|
continue
|
|
|
line_pairss.append(set(utils.cross(l1s, l2s)))
|
|
|
line_pairss.append(set(utils.cross(l2s, l1s)))
|
|
|
|
|
|
|
|
|
|
|
|
nodir_ls = [l for l in self.type2nodes[Line] if l.val is None]
|
|
|
|
|
|
for line in nodir_ls:
|
|
|
for d in self.type2nodes[Direction]:
|
|
|
l1s = d.neighbors(Line)
|
|
|
if len(l1s) < 2:
|
|
|
continue
|
|
|
l2s = [line]
|
|
|
line_pairss.append(set(utils.cross(l1s, l2s)))
|
|
|
line_pairss.append(set(utils.cross(l2s, l1s)))
|
|
|
|
|
|
record = set()
|
|
|
for line_pairs in line_pairss:
|
|
|
for pair1, pair2 in utils.perm2(list(line_pairs)):
|
|
|
(l1, l2), (l3, l4) = pair1, pair2
|
|
|
if l1 == l2 or l3 == l4:
|
|
|
continue
|
|
|
if (l1, l2) == (l3, l4):
|
|
|
continue
|
|
|
if (l1, l2, l3, l4) in record:
|
|
|
continue
|
|
|
record.add((l1, l2, l3, l4))
|
|
|
for a, b, c, d, e, f, g, h in utils.all_8points(l1, l2, l3, l4):
|
|
|
yield (a, b, c, d, e, f, g, h)
|
|
|
|
|
|
for a, b, c, d, e, f, g, h in self.all_eqangle_same_lines():
|
|
|
yield a, b, c, d, e, f, g, h
|
|
|
|
|
|
def all_eqangles_6points(self) -> Generator[tuple[Point, ...], None, None]:
|
|
|
"""List all sets of 6 points that make two equal angles."""
|
|
|
record = set()
|
|
|
for a, b, c, d, e, f, g, h in self.all_eqangles_8points():
|
|
|
if (
|
|
|
a not in (c, d)
|
|
|
and b not in (c, d)
|
|
|
or e not in (g, h)
|
|
|
and f not in (g, h)
|
|
|
):
|
|
|
continue
|
|
|
|
|
|
if b in (c, d):
|
|
|
a, b = b, a
|
|
|
if f in (g, h):
|
|
|
e, f = f, e
|
|
|
if a == d:
|
|
|
c, d = d, c
|
|
|
if e == h:
|
|
|
g, h = h, g
|
|
|
if (a, b, c, d, e, f, g, h) in record:
|
|
|
continue
|
|
|
record.add((a, b, c, d, e, f, g, h))
|
|
|
yield a, b, c, d, e, f, g, h
|
|
|
|
|
|
def all_paras(self) -> Generator[tuple[Point, ...], None, None]:
|
|
|
for d in self.type2nodes[Direction]:
|
|
|
for l1, l2 in utils.perm2(d.neighbors(Line)):
|
|
|
for a, b, c, d in utils.all_4points(l1, l2):
|
|
|
yield a, b, c, d
|
|
|
|
|
|
def all_perps(self) -> Generator[tuple[Point, ...], None, None]:
|
|
|
for ang in self.vhalfpi.neighbors(Angle):
|
|
|
d1, d2 = ang.directions
|
|
|
if d1 is None or d2 is None:
|
|
|
continue
|
|
|
if d1 == d2:
|
|
|
continue
|
|
|
for l1, l2 in utils.cross(d1.neighbors(Line), d2.neighbors(Line)):
|
|
|
for a, b, c, d in utils.all_4points(l1, l2):
|
|
|
yield a, b, c, d
|
|
|
|
|
|
def all_congs(self) -> Generator[tuple[Point, ...], None, None]:
|
|
|
for l in self.type2nodes[Length]:
|
|
|
for s1, s2 in utils.perm2(l.neighbors(Segment)):
|
|
|
(a, b), (c, d) = s1.points, s2.points
|
|
|
for x, y in [(a, b), (b, a)]:
|
|
|
for m, n in [(c, d), (d, c)]:
|
|
|
yield x, y, m, n
|
|
|
|
|
|
def all_eqratios_8points(self) -> Generator[tuple[Point, ...], None, None]:
|
|
|
"""List all sets of 8 points that make two equal ratios."""
|
|
|
ratss = []
|
|
|
for value in self.type2nodes[Value]:
|
|
|
rats = value.neighbors(Ratio)
|
|
|
ratss.append(rats)
|
|
|
|
|
|
|
|
|
ratss.extend([[rat] for rat in self.type2nodes[Ratio] if rat.val is None])
|
|
|
|
|
|
seg_pairss = []
|
|
|
for rats in ratss:
|
|
|
seg_pairs = set()
|
|
|
for rat in rats:
|
|
|
l1, l2 = rat.lengths
|
|
|
if l1 is None or l2 is None:
|
|
|
continue
|
|
|
s1s = l1.neighbors(Segment)
|
|
|
s2s = l2.neighbors(Segment)
|
|
|
seg_pairs.update(utils.cross(s1s, s2s))
|
|
|
seg_pairss.append(seg_pairs)
|
|
|
|
|
|
|
|
|
norat_ls = [l for l in self.type2nodes[Length] if not l.neighbors(Ratio)]
|
|
|
|
|
|
for l1 in norat_ls:
|
|
|
for l2 in self.type2nodes[Length]:
|
|
|
if l1 == l2:
|
|
|
continue
|
|
|
s1s = l1.neighbors(Segment)
|
|
|
s2s = l2.neighbors(Segment)
|
|
|
if len(s1s) < 2 and len(s2s) < 2:
|
|
|
continue
|
|
|
seg_pairss.append(set(utils.cross(s1s, s2s)))
|
|
|
seg_pairss.append(set(utils.cross(s2s, s1s)))
|
|
|
|
|
|
|
|
|
nolen_ss = [s for s in self.type2nodes[Segment] if s.val is None]
|
|
|
|
|
|
for seg in nolen_ss:
|
|
|
for l in self.type2nodes[Length]:
|
|
|
s1s = l.neighbors(Segment)
|
|
|
if len(s1s) == 1:
|
|
|
continue
|
|
|
s2s = [seg]
|
|
|
seg_pairss.append(set(utils.cross(s1s, s2s)))
|
|
|
seg_pairss.append(set(utils.cross(s2s, s1s)))
|
|
|
|
|
|
record = set()
|
|
|
for seg_pairs in seg_pairss:
|
|
|
for pair1, pair2 in utils.perm2(list(seg_pairs)):
|
|
|
(s1, s2), (s3, s4) = pair1, pair2
|
|
|
if s1 == s2 or s3 == s4:
|
|
|
continue
|
|
|
if (s1, s2) == (s3, s4):
|
|
|
continue
|
|
|
if (s1, s2, s3, s4) in record:
|
|
|
continue
|
|
|
record.add((s1, s2, s3, s4))
|
|
|
a, b = s1.points
|
|
|
c, d = s2.points
|
|
|
e, f = s3.points
|
|
|
g, h = s4.points
|
|
|
|
|
|
for x, y in [(a, b), (b, a)]:
|
|
|
for z, t in [(c, d), (d, c)]:
|
|
|
for m, n in [(e, f), (f, e)]:
|
|
|
for p, q in [(g, h), (h, g)]:
|
|
|
yield (x, y, z, t, m, n, p, q)
|
|
|
|
|
|
segss = []
|
|
|
|
|
|
for length in self.type2nodes[Length]:
|
|
|
segs = length.neighbors(Segment)
|
|
|
segss.append(segs)
|
|
|
|
|
|
segs_pair = list(utils.perm2(list(segss)))
|
|
|
segs_pair += list(zip(segss, segss))
|
|
|
for segs1, segs2 in segs_pair:
|
|
|
for s1, s2 in utils.perm2(list(segs1)):
|
|
|
for s3, s4 in utils.perm2(list(segs2)):
|
|
|
if (s1, s2) == (s3, s4) or (s1, s3) == (s2, s4):
|
|
|
continue
|
|
|
if (s1, s2, s3, s4) in record:
|
|
|
continue
|
|
|
record.add((s1, s2, s3, s4))
|
|
|
a, b = s1.points
|
|
|
c, d = s2.points
|
|
|
e, f = s3.points
|
|
|
g, h = s4.points
|
|
|
|
|
|
for x, y in [(a, b), (b, a)]:
|
|
|
for z, t in [(c, d), (d, c)]:
|
|
|
for m, n in [(e, f), (f, e)]:
|
|
|
for p, q in [(g, h), (h, g)]:
|
|
|
yield (x, y, z, t, m, n, p, q)
|
|
|
|
|
|
def all_eqratios_6points(self) -> Generator[tuple[Point, ...], None, None]:
|
|
|
"""List all sets of 6 points that make two equal angles."""
|
|
|
record = set()
|
|
|
for a, b, c, d, e, f, g, h in self.all_eqratios_8points():
|
|
|
if (
|
|
|
a not in (c, d)
|
|
|
and b not in (c, d)
|
|
|
or e not in (g, h)
|
|
|
and f not in (g, h)
|
|
|
):
|
|
|
continue
|
|
|
if b in (c, d):
|
|
|
a, b = b, a
|
|
|
if f in (g, h):
|
|
|
e, f = f, e
|
|
|
if a == d:
|
|
|
c, d = d, c
|
|
|
if e == h:
|
|
|
g, h = h, g
|
|
|
if (a, b, c, d, e, f, g, h) in record:
|
|
|
continue
|
|
|
record.add((a, b, c, d, e, f, g, h))
|
|
|
yield a, b, c, d, e, f, g, h
|
|
|
|
|
|
def all_cyclics(self) -> Generator[tuple[Point, ...], None, None]:
|
|
|
for c in self.type2nodes[Circle]:
|
|
|
for x, y, z, t in utils.perm4(c.neighbors(Point)):
|
|
|
yield x, y, z, t
|
|
|
|
|
|
def all_colls(self) -> Generator[tuple[Point, ...], None, None]:
|
|
|
for l in self.type2nodes[Line]:
|
|
|
for x, y, z in utils.perm3(l.neighbors(Point)):
|
|
|
yield x, y, z
|
|
|
|
|
|
def all_midps(self) -> Generator[tuple[Point, ...], None, None]:
|
|
|
for l in self.type2nodes[Line]:
|
|
|
for a, b, c in utils.perm3(l.neighbors(Point)):
|
|
|
if self.check_cong([a, b, a, c]):
|
|
|
yield a, b, c
|
|
|
|
|
|
def all_circles(self) -> Generator[tuple[Point, ...], None, None]:
|
|
|
for l in self.type2nodes[Length]:
|
|
|
p2p = defaultdict(list)
|
|
|
for s in l.neighbors(Segment):
|
|
|
a, b = s.points
|
|
|
p2p[a].append(b)
|
|
|
p2p[b].append(a)
|
|
|
for p, ps in p2p.items():
|
|
|
if len(ps) >= 3:
|
|
|
for a, b, c in utils.perm3(ps):
|
|
|
yield p, a, b, c
|
|
|
|
|
|
def two_points_on_direction(self, d: Direction) -> tuple[Point, Point]:
|
|
|
l = d.neighbors(Line)[0]
|
|
|
p1, p2 = l.neighbors(Point)[:2]
|
|
|
return p1, p2
|
|
|
|
|
|
def two_points_of_length(self, l: Length) -> tuple[Point, Point]:
|
|
|
s = l.neighbors(Segment)[0]
|
|
|
p1, p2 = s.points
|
|
|
return p1, p2
|
|
|
|
|
|
|
|
|
def create_consts_str(g: Graph, s: str) -> Union[Ratio, Angle]:
|
|
|
if 'pi/' in s:
|
|
|
n, d = s.split('pi/')
|
|
|
n, d = int(n), int(d)
|
|
|
p0, _ = g.get_or_create_const_ang(n, d)
|
|
|
else:
|
|
|
n, d = s.split('/')
|
|
|
n, d = int(n), int(d)
|
|
|
p0, _ = g.get_or_create_const_rat(n, d)
|
|
|
return p0
|
|
|
|
|
|
|
|
|
def create_consts(g: Graph, p: gm.Node) -> Union[Ratio, Angle]:
|
|
|
if isinstance(p, Angle):
|
|
|
n, d = p.name.split('pi/')
|
|
|
n, d = int(n), int(d)
|
|
|
p0, _ = g.get_or_create_const_ang(n, d)
|
|
|
if isinstance(p, Ratio):
|
|
|
n, d = p.name.split('/')
|
|
|
n, d = int(n), int(d)
|
|
|
p0, _ = g.get_or_create_const_rat(n, d)
|
|
|
return p0
|
|
|
|