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 Deductive Database (DD).""" | |
# pylint: disable=g-multiple-import,g-importing-member | |
from collections import defaultdict | |
import time | |
from typing import Any, Callable, Generator | |
import geometry as gm | |
import graph as gh | |
import graph_utils as utils | |
import numericals as nm | |
import problem as pr | |
from problem import Dependency, EmptyDependency | |
def intersect1(set1: set[Any], set2: set[Any]) -> Any: | |
for x in set1: | |
if x in set2: | |
return x | |
return None | |
def diff_point(l: gm.Line, a: gm.Point) -> gm.Point: | |
for x in l.neighbors(gm.Point): | |
if x != a: | |
return x | |
return None | |
# pylint: disable=protected-access | |
# pylint: disable=unused-argument | |
def match_eqratio_eqratio_eqratio( | |
g: gh.Graph, | |
g_matcher: Callable[str, list[tuple[gm.Point, ...]]], | |
theorem: pr.Theorem, | |
) -> Generator[dict[str, gm.Point], None, None]: | |
"""Match eqratio a b c d m n p q, eqratio c d e f p q r u => eqratio a b e f m n r u.""" | |
for m1 in g.type2nodes[gm.Value]: | |
for m2 in g.type2nodes[gm.Value]: | |
rats1 = [] | |
for rat in m1.neighbors(gm.Ratio): | |
l1, l2 = rat.lengths | |
if l1 is None or l2 is None: | |
continue | |
rats1.append((l1, l2)) | |
rats2 = [] | |
for rat in m2.neighbors(gm.Ratio): | |
l1, l2 = rat.lengths | |
if l1 is None or l2 is None: | |
continue | |
rats2.append((l1, l2)) | |
pairs = [] | |
for (l1, l2), (l3, l4) in utils.cross(rats1, rats2): | |
if l2 == l3: | |
pairs.append((l1, l2, l4)) | |
for (l1, l12, l2), (l3, l34, l4) in utils.comb2(pairs): | |
if (l1, l12, l2) == (l3, l34, l4): | |
continue | |
if l1 == l2 or l3 == l4: | |
continue | |
if l1 == l12 or l12 == l2 or l3 == l34 or l4 == l34: | |
continue | |
# d12 - d1 = d34 - d3 = m1 | |
# d2 - d12 = d4 - d34 = m2 | |
# => d2 - d1 = d4 - d3 (= m1+m2) | |
a, b = g.two_points_of_length(l1) | |
c, d = g.two_points_of_length(l12) | |
m, n = g.two_points_of_length(l3) | |
p, q = g.two_points_of_length(l34) | |
# eqangle a b c d m n p q | |
e, f = g.two_points_of_length(l2) | |
r, u = g.two_points_of_length(l4) | |
yield dict(zip('abcdefmnpqru', [a, b, c, d, e, f, m, n, p, q, r, u])) | |
def match_eqangle_eqangle_eqangle( | |
g: gh.Graph, | |
g_matcher: Callable[str, list[tuple[gm.Point, ...]]], | |
theorem: pr.Theorem, | |
) -> Generator[dict[str, gm.Point], None, None]: | |
"""Match eqangle a b c d m n p q, eqangle c d e f p q r u => eqangle a b e f m n r u.""" | |
for m1 in g.type2nodes[gm.Measure]: | |
for m2 in g.type2nodes[gm.Measure]: | |
angs1 = [] | |
for ang in m1.neighbors(gm.Angle): | |
d1, d2 = ang.directions | |
if d1 is None or d2 is None: | |
continue | |
angs1.append((d1, d2)) | |
angs2 = [] | |
for ang in m2.neighbors(gm.Angle): | |
d1, d2 = ang.directions | |
if d1 is None or d2 is None: | |
continue | |
angs2.append((d1, d2)) | |
pairs = [] | |
for (d1, d2), (d3, d4) in utils.cross(angs1, angs2): | |
if d2 == d3: | |
pairs.append((d1, d2, d4)) | |
for (d1, d12, d2), (d3, d34, d4) in utils.comb2(pairs): | |
if (d1, d12, d2) == (d3, d34, d4): | |
continue | |
if d1 == d2 or d3 == d4: | |
continue | |
if d1 == d12 or d12 == d2 or d3 == d34 or d4 == d34: | |
continue | |
# d12 - d1 = d34 - d3 = m1 | |
# d2 - d12 = d4 - d34 = m2 | |
# => d2 - d1 = d4 - d3 | |
a, b = g.two_points_on_direction(d1) | |
c, d = g.two_points_on_direction(d12) | |
m, n = g.two_points_on_direction(d3) | |
p, q = g.two_points_on_direction(d34) | |
# eqangle a b c d m n p q | |
e, f = g.two_points_on_direction(d2) | |
r, u = g.two_points_on_direction(d4) | |
yield dict(zip('abcdefmnpqru', [a, b, c, d, e, f, m, n, p, q, r, u])) | |
def match_perp_perp_npara_eqangle( | |
g: gh.Graph, | |
g_matcher: Callable[str, list[tuple[gm.Point, ...]]], | |
theorem: pr.Theorem, | |
) -> Generator[dict[str, gm.Point], None, None]: | |
"""Match perp A B C D, perp E F G H, npara A B E F => eqangle A B E F C D G H.""" | |
dpairs = [] | |
for ang in g.vhalfpi.neighbors(gm.Angle): | |
d1, d2 = ang.directions | |
if d1 is None or d2 is None: | |
continue | |
dpairs.append((d1, d2)) | |
for (d1, d2), (d3, d4) in utils.comb2(dpairs): | |
a, b = g.two_points_on_direction(d1) | |
c, d = g.two_points_on_direction(d2) | |
m, n = g.two_points_on_direction(d3) | |
p, q = g.two_points_on_direction(d4) | |
if g.check_npara([a, b, m, n]): | |
if ({a, b}, {c, d}) == ({m, n}, {p, q}): | |
continue | |
if ({a, b}, {c, d}) == ({p, q}, {m, n}): | |
continue | |
yield dict(zip('ABCDEFGH', [a, b, c, d, m, n, p, q])) | |
def match_circle_coll_eqangle_midp( | |
g: gh.Graph, | |
g_matcher: Callable[str, list[tuple[gm.Point, ...]]], | |
theorem: pr.Theorem, | |
) -> Generator[dict[str, gm.Point], None, None]: | |
"""Match circle O A B C, coll M B C, eqangle A B A C O B O M => midp M B C.""" | |
for p, a, b, c in g.all_circles(): | |
ab = g._get_line(a, b) | |
if ab is None: | |
continue | |
if ab.val is None: | |
continue | |
ac = g._get_line(a, c) | |
if ac is None: | |
continue | |
if ac.val is None: | |
continue | |
pb = g._get_line(p, b) | |
if pb is None: | |
continue | |
if pb.val is None: | |
continue | |
bc = g._get_line(b, c) | |
if bc is None: | |
continue | |
bc_points = bc.neighbors(gm.Point, return_set=True) | |
anga, _ = g._get_angle(ab.val, ac.val) | |
for angp in pb.val.neighbors(gm.Angle): | |
if not g.is_equal(anga, angp): | |
continue | |
_, d = angp.directions | |
for l in d.neighbors(gm.Line): | |
l_points = l.neighbors(gm.Point, return_set=True) | |
m = intersect1(bc_points, l_points) | |
if m is not None: | |
yield dict(zip('ABCMO', [a, b, c, m, p])) | |
def match_midp_perp_cong( | |
g: gh.Graph, | |
g_matcher: Callable[str, list[tuple[gm.Point, ...]]], | |
theorem: pr.Theorem, | |
) -> Generator[dict[str, gm.Point], None, None]: | |
"""Match midp M A B, perp O M A B => cong O A O B.""" | |
for m, a, b in g.all_midps(): | |
ab = g._get_line(a, b) | |
for l in m.neighbors(gm.Line): | |
if g.check_perpl(l, ab): | |
for o in l.neighbors(gm.Point): | |
if o != m: | |
yield dict(zip('ABMO', [a, b, m, o])) | |
def match_cyclic_eqangle_cong( | |
g: gh.Graph, | |
g_matcher: Callable[str, list[tuple[gm.Point, ...]]], | |
theorem: pr.Theorem, | |
) -> Generator[dict[str, gm.Point], None, None]: | |
"""Match cyclic A B C P Q R, eqangle C A C B R P R Q => cong A B P Q.""" | |
for c in g.type2nodes[gm.Circle]: | |
ps = c.neighbors(gm.Point) | |
for (a, b, c), (x, y, z) in utils.comb2(list(utils.perm3(ps))): | |
if {a, b, c} == {x, y, z}: | |
continue | |
if g.check_eqangle([c, a, c, b, z, x, z, y]): | |
yield dict(zip('ABCPQR', [a, b, c, x, y, z])) | |
def match_circle_eqangle_perp( | |
g: gh.Graph, | |
g_matcher: Callable[str, list[tuple[gm.Point, ...]]], | |
theorem: pr.Theorem, | |
) -> Generator[dict[str, gm.Point], None, None]: | |
"""Match circle O A B C, eqangle A X A B C A C B => perp O A A X.""" | |
for p, a, b, c in g.all_circles(): | |
ca = g._get_line(c, a) | |
if ca is None: | |
continue | |
cb = g._get_line(c, b) | |
if cb is None: | |
continue | |
ab = g._get_line(a, b) | |
if ab is None: | |
continue | |
if ca.val is None: | |
continue | |
if cb.val is None: | |
continue | |
if ab.val is None: | |
continue | |
c_ang, _ = g._get_angle(cb.val, ca.val) | |
if c_ang is None: | |
continue | |
for ang in ab.val.neighbors(gm.Angle): | |
if g.is_equal(ang, c_ang): | |
_, d = ang.directions | |
for l in d.neighbors(gm.Line): | |
if a not in l.neighbors(gm.Point): | |
continue | |
x = diff_point(l, a) | |
if x is None: | |
continue | |
yield dict(zip('OABCX', [p, a, b, c, x])) | |
break | |
def match_circle_perp_eqangle( | |
g: gh.Graph, | |
g_matcher: Callable[str, list[tuple[gm.Point, ...]]], | |
theorem: pr.Theorem, | |
) -> Generator[dict[str, gm.Point], None, None]: | |
"""Match circle O A B C, perp O A A X => eqangle A X A B C A C B.""" | |
for p, a, b, c in g.all_circles(): | |
pa = g._get_line(p, a) | |
if pa is None: | |
continue | |
if pa.val is None: | |
continue | |
for l in a.neighbors(gm.Line): | |
if g.check_perpl(pa, l): | |
x = diff_point(l, a) | |
if x is not None: | |
yield dict(zip('OABCX', [p, a, b, c, x])) | |
def match_perp_perp_ncoll_para( | |
g: gh.Graph, | |
g_matcher: Callable[str, list[tuple[gm.Point, ...]]], | |
theorem: pr.Theorem, | |
) -> Generator[dict[str, gm.Point], None, None]: | |
"""Match perp A B C D, perp C D E F, ncoll A B E => para A B E F.""" | |
d2d = defaultdict(list) | |
for ang in g.vhalfpi.neighbors(gm.Angle): | |
d1, d2 = ang.directions | |
if d1 is None or d2 is None: | |
continue | |
d2d[d1] += [d2] | |
d2d[d2] += [d1] | |
for x, ys in d2d.items(): | |
if len(ys) < 2: | |
continue | |
c, d = g.two_points_on_direction(x) | |
for y1, y2 in utils.comb2(ys): | |
a, b = g.two_points_on_direction(y1) | |
e, f = g.two_points_on_direction(y2) | |
if nm.check_ncoll([a.num, b.num, e.num]): | |
yield dict(zip('ABCDEF', [a, b, c, d, e, f])) | |
def match_eqangle6_ncoll_cong( | |
g: gh.Graph, | |
g_matcher: Callable[str, list[tuple[gm.Point, ...]]], | |
theorem: pr.Theorem, | |
) -> Generator[dict[str, gm.Point], None, None]: | |
"""Match eqangle6 A O A B B A B O, ncoll O A B => cong O A O B.""" | |
for a in g.type2nodes[gm.Point]: | |
for b, c in utils.comb2(g.type2nodes[gm.Point]): | |
if a == b or a == c: | |
continue | |
if g.check_eqangle([b, a, b, c, c, b, c, a]): | |
if g.check_ncoll([a, b, c]): | |
yield dict(zip('OAB', [a, b, c])) | |
def match_eqangle_perp_perp( | |
g: gh.Graph, | |
g_matcher: Callable[str, list[tuple[gm.Point, ...]]], | |
theorem: pr.Theorem, | |
) -> Generator[dict[str, gm.Point], None, None]: | |
"""Match eqangle A B P Q C D U V, perp P Q U V => perp A B C D.""" | |
for ang in g.vhalfpi.neighbors(gm.Angle): | |
# d1 perp d2 | |
d1, d2 = ang.directions | |
if d1 is None or d2 is None: | |
continue | |
for d3, d4 in utils.comb2(g.type2nodes[gm.Direction]): | |
if d1 == d3 or d2 == d4: | |
continue | |
# if d1 - d3 = d2 - d4 => d3 perp d4 | |
a13, a31 = g._get_angle(d1, d3) | |
a24, a42 = g._get_angle(d2, d4) | |
if a13 is None or a31 is None or a24 is None or a42 is None: | |
continue | |
if g.is_equal(a13, a24) and g.is_equal(a31, a42): | |
a, b = g.two_points_on_direction(d1) | |
c, d = g.two_points_on_direction(d2) | |
m, n = g.two_points_on_direction(d3) | |
p, q = g.two_points_on_direction(d4) | |
yield dict(zip('ABCDPQUV', [m, n, p, q, a, b, c, d])) | |
def match_eqangle_ncoll_cyclic( | |
g: gh.Graph, | |
g_matcher: Callable[str, list[tuple[gm.Point, ...]]], | |
theorem: pr.Theorem, | |
) -> Generator[dict[str, gm.Point], None, None]: | |
"""Match eqangle6 P A P B Q A Q B, ncoll P Q A B => cyclic A B P Q.""" | |
for l1, l2, l3, l4 in g.all_eqangles_distinct_linepairss(): | |
if len(set([l1, l2, l3, l4])) < 4: | |
continue # they all must be distinct. | |
p1s = l1.neighbors(gm.Point, return_set=True) | |
p2s = l2.neighbors(gm.Point, return_set=True) | |
p3s = l3.neighbors(gm.Point, return_set=True) | |
p4s = l4.neighbors(gm.Point, return_set=True) | |
p = intersect1(p1s, p2s) | |
if not p: | |
continue | |
q = intersect1(p3s, p4s) | |
if not q: | |
continue | |
a = intersect1(p1s, p3s) | |
if not a: | |
continue | |
b = intersect1(p2s, p4s) | |
if not b: | |
continue | |
if len(set([a, b, p, q])) < 4: | |
continue | |
if not g.check_ncoll([a, b, p, q]): | |
continue | |
yield dict(zip('ABPQ', [a, b, p, q])) | |
def match_eqangle_para( | |
g: gh.Graph, | |
g_matcher: Callable[str, list[tuple[gm.Point, ...]]], | |
theorem: pr.Theorem, | |
) -> Generator[dict[str, gm.Point], None, None]: | |
"""Match eqangle A B P Q C D P Q => para A B C D.""" | |
for measure in g.type2nodes[gm.Measure]: | |
angs = measure.neighbors(gm.Angle) | |
d12, d21 = defaultdict(list), defaultdict(list) | |
for ang in angs: | |
d1, d2 = ang.directions | |
if d1 is None or d2 is None: | |
continue | |
d12[d1].append(d2) | |
d21[d2].append(d1) | |
for d1, d2s in d12.items(): | |
a, b = g.two_points_on_direction(d1) | |
for d2, d3 in utils.comb2(d2s): | |
c, d = g.two_points_on_direction(d2) | |
e, f = g.two_points_on_direction(d3) | |
yield dict(zip('ABCDPQ', [c, d, e, f, a, b])) | |
def match_cyclic_eqangle( | |
g: gh.Graph, | |
g_matcher: Callable[str, list[tuple[gm.Point, ...]]], | |
theorem: pr.Theorem, | |
) -> Generator[dict[str, gm.Point], None, None]: | |
"""Match cyclic A B P Q => eqangle P A P B Q A Q B.""" | |
record = set() | |
for a, b, c, d in g_matcher('cyclic'): | |
if (a, b, c, d) in record: | |
continue | |
record.add((a, b, c, d)) | |
record.add((a, b, d, c)) | |
record.add((b, a, c, d)) | |
record.add((b, a, d, c)) | |
yield dict(zip('ABPQ', [a, b, c, d])) | |
def rotate_simtri( | |
a: gm.Point, b: gm.Point, c: gm.Point, x: gm.Point, y: gm.Point, z: gm.Point | |
) -> Generator[tuple[gm.Point, ...], None, None]: | |
"""Rotate points around for similar triangle predicates.""" | |
yield (z, y, x, c, b, a) | |
for p in [ | |
(b, c, a, y, z, x), | |
(c, a, b, z, x, y), | |
(x, y, z, a, b, c), | |
(y, z, x, b, c, a), | |
(z, x, y, c, a, b), | |
]: | |
yield p | |
yield p[::-1] | |
def match_cong_cong_cong_cyclic( | |
g: gh.Graph, | |
g_matcher: Callable[str, list[tuple[gm.Point, ...]]], | |
theorem: pr.Theorem, | |
) -> Generator[dict[str, gm.Point], None, None]: | |
"""Match cong O A O B, cong O B O C, cong O C O D => cyclic A B C D.""" | |
for l in g.type2nodes[gm.Length]: | |
p2p = defaultdict(list) | |
for s in l.neighbors(gm.Segment): | |
a, b = s.points | |
p2p[a].append(b) | |
p2p[b].append(a) | |
for p, ps in p2p.items(): | |
if len(ps) >= 4: | |
for a, b, c, d in utils.comb4(ps): | |
yield dict(zip('OABCD', [p, a, b, c, d])) | |
def match_cong_cong_cong_ncoll_contri( | |
g: gh.Graph, | |
g_matcher: Callable[str, list[tuple[gm.Point, ...]]], | |
theorem: pr.Theorem, | |
) -> Generator[dict[str, gm.Point], None, None]: | |
"""Match cong A B P Q, cong B C Q R, cong C A R P, ncoll A B C => contri* A B C P Q R.""" | |
record = set() | |
for a, b, p, q in g_matcher('cong'): | |
for c in g.type2nodes[gm.Point]: | |
for r in g.type2nodes[gm.Point]: | |
if any([x in record for x in rotate_simtri(a, b, c, p, q, r)]): | |
continue | |
if not g.check_ncoll([a, b, c]): | |
continue | |
if g.check_cong([b, c, q, r]) and g.check_cong([c, a, r, p]): | |
record.add((a, b, c, p, q, r)) | |
yield dict(zip('ABCPQR', [a, b, c, p, q, r])) | |
def match_cong_cong_eqangle6_ncoll_contri( | |
g: gh.Graph, | |
g_matcher: Callable[str, list[tuple[gm.Point, ...]]], | |
theorem: pr.Theorem, | |
) -> Generator[dict[str, gm.Point], None, None]: | |
"""Match cong A B P Q, cong B C Q R, eqangle6 B A B C Q P Q R, ncoll A B C => contri* A B C P Q R.""" | |
record = set() | |
for a, b, p, q in g_matcher('cong'): | |
for c in g.type2nodes[gm.Point]: | |
if c in (a, b): | |
continue | |
for r in g.type2nodes[gm.Point]: | |
if r in (p, q): | |
continue | |
in_record = False | |
for x in [ | |
(c, b, a, r, q, p), | |
(p, q, r, a, b, c), | |
(r, q, p, c, b, a), | |
]: | |
if x in record: | |
in_record = True | |
break | |
if in_record: | |
continue | |
if not g.check_cong([b, c, q, r]): | |
continue | |
if not g.check_ncoll([a, b, c]): | |
continue | |
if nm.same_clock(a.num, b.num, c.num, p.num, q.num, r.num): | |
if g.check_eqangle([b, a, b, c, q, p, q, r]): | |
record.add((a, b, c, p, q, r)) | |
yield dict(zip('ABCPQR', [a, b, c, p, q, r])) | |
else: | |
if g.check_eqangle([b, a, b, c, q, r, q, p]): | |
record.add((a, b, c, p, q, r)) | |
yield dict(zip('ABCPQR', [a, b, c, p, q, r])) | |
def match_eqratio6_eqangle6_ncoll_simtri( | |
g: gh.Graph, | |
g_matcher: Callable[str, list[tuple[gm.Point, ...]]], | |
theorem: pr.Theorem, | |
) -> Generator[dict[str, gm.Point], None, None]: | |
"""Match eqratio6 B A B C Q P Q R, eqratio6 C A C B R P R Q, ncoll A B C => simtri* A B C P Q R.""" | |
enums = g_matcher('eqratio6') | |
record = set() | |
for b, a, b, c, q, p, q, r in enums: # pylint: disable=redeclared-assigned-name,unused-variable | |
if (a, b, c) == (p, q, r): | |
continue | |
if any([x in record for x in rotate_simtri(a, b, c, p, q, r)]): | |
continue | |
if not g.check_ncoll([a, b, c]): | |
continue | |
if nm.same_clock(a.num, b.num, c.num, p.num, q.num, r.num): | |
if g.check_eqangle([b, a, b, c, q, p, q, r]): | |
record.add((a, b, c, p, q, r)) | |
yield dict(zip('ABCPQR', [a, b, c, p, q, r])) | |
elif g.check_eqangle([b, a, b, c, q, r, q, p]): | |
record.add((a, b, c, p, q, r)) | |
yield dict(zip('ABCPQR', [a, b, c, p, q, r])) | |
def match_eqangle6_eqangle6_ncoll_simtri( | |
g: gh.Graph, | |
g_matcher: Callable[str, list[tuple[gm.Point, ...]]], | |
theorem: pr.Theorem, | |
) -> Generator[dict[str, gm.Point], None, None]: | |
"""Match eqangle6 B A B C Q P Q R, eqangle6 C A C B R P R Q, ncoll A B C => simtri A B C P Q R.""" | |
enums = g_matcher('eqangle6') | |
record = set() | |
for b, a, b, c, q, p, q, r in enums: # pylint: disable=redeclared-assigned-name,unused-variable | |
if (a, b, c) == (p, q, r): | |
continue | |
if any([x in record for x in rotate_simtri(a, b, c, p, q, r)]): | |
continue | |
if not g.check_eqangle([c, a, c, b, r, p, r, q]): | |
continue | |
if not g.check_ncoll([a, b, c]): | |
continue | |
mapping = dict(zip('ABCPQR', [a, b, c, p, q, r])) | |
record.add((a, b, c, p, q, r)) | |
yield mapping | |
def match_eqratio6_eqratio6_ncoll_simtri( | |
g: gh.Graph, | |
g_matcher: Callable[str, list[tuple[gm.Point, ...]]], | |
theorem: pr.Theorem, | |
) -> Generator[dict[str, gm.Point], None, None]: | |
"""Match eqratio6 B A B C Q P Q R, eqratio6 C A C B R P R Q, ncoll A B C => simtri* A B C P Q R.""" | |
enums = g_matcher('eqratio6') | |
record = set() | |
for b, a, b, c, q, p, q, r in enums: # pylint: disable=redeclared-assigned-name,unused-variable | |
if (a, b, c) == (p, q, r): | |
continue | |
if any([x in record for x in rotate_simtri(a, b, c, p, q, r)]): | |
continue | |
if not g.check_eqratio([c, a, c, b, r, p, r, q]): | |
continue | |
if not g.check_ncoll([a, b, c]): | |
continue | |
mapping = dict(zip('ABCPQR', [a, b, c, p, q, r])) | |
record.add((a, b, c, p, q, r)) | |
yield mapping | |
def match_eqangle6_eqangle6_ncoll_simtri2( | |
g: gh.Graph, | |
g_matcher: Callable[str, list[tuple[gm.Point, ...]]], | |
theorem: pr.Theorem, | |
) -> Generator[dict[str, gm.Point], None, None]: | |
"""Match eqangle6 B A B C Q R Q P, eqangle6 C A C B R Q R P, ncoll A B C => simtri2 A B C P Q R.""" | |
enums = g_matcher('eqangle6') | |
record = set() | |
for b, a, b, c, q, r, q, p in enums: # pylint: disable=redeclared-assigned-name,unused-variable | |
if (a, b, c) == (p, q, r): | |
continue | |
if any([x in record for x in rotate_simtri(a, b, c, p, q, r)]): | |
continue | |
if not g.check_eqangle([c, a, c, b, r, q, r, p]): | |
continue | |
if not g.check_ncoll([a, b, c]): | |
continue | |
mapping = dict(zip('ABCPQR', [a, b, c, p, q, r])) | |
record.add((a, b, c, p, q, r)) | |
yield mapping | |
def rotate_contri( | |
a: gm.Point, b: gm.Point, c: gm.Point, x: gm.Point, y: gm.Point, z: gm.Point | |
) -> Generator[tuple[gm.Point, ...], None, None]: | |
for p in [(b, a, c, y, x, z), (x, y, z, a, b, c), (y, x, z, b, a, c)]: | |
yield p | |
def match_eqangle6_eqangle6_ncoll_cong_contri( | |
g: gh.Graph, | |
g_matcher: Callable[str, list[tuple[gm.Point, ...]]], | |
theorem: pr.Theorem, | |
) -> Generator[dict[str, gm.Point], None, None]: | |
"""Match eqangle6 B A B C Q P Q R, eqangle6 C A C B R P R Q, ncoll A B C, cong A B P Q => contri A B C P Q R.""" | |
enums = g_matcher('eqangle6') | |
record = set() | |
for b, a, b, c, q, p, q, r in enums: # pylint: disable=redeclared-assigned-name,unused-variable | |
if not g.check_cong([a, b, p, q]): | |
continue | |
if (a, b, c) == (p, q, r): | |
continue | |
if any([x in record for x in rotate_contri(a, b, c, p, q, r)]): | |
continue | |
if not g.check_eqangle([c, a, c, b, r, p, r, q]): | |
continue | |
if not g.check_ncoll([a, b, c]): | |
continue | |
mapping = dict(zip('ABCPQR', [a, b, c, p, q, r])) | |
record.add((a, b, c, p, q, r)) | |
yield mapping | |
def match_eqratio6_eqratio6_ncoll_cong_contri( | |
g: gh.Graph, | |
g_matcher: Callable[str, list[tuple[gm.Point, ...]]], | |
theorem: pr.Theorem, | |
) -> Generator[dict[str, gm.Point], None, None]: | |
"""Match eqratio6 B A B C Q P Q R, eqratio6 C A C B R P R Q, ncoll A B C, cong A B P Q => contri* A B C P Q R.""" | |
enums = g_matcher('eqratio6') | |
record = set() | |
for b, a, b, c, q, p, q, r in enums: # pylint: disable=redeclared-assigned-name,unused-variable | |
if not g.check_cong([a, b, p, q]): | |
continue | |
if (a, b, c) == (p, q, r): | |
continue | |
if any([x in record for x in rotate_contri(a, b, c, p, q, r)]): | |
continue | |
if not g.check_eqratio([c, a, c, b, r, p, r, q]): | |
continue | |
if not g.check_ncoll([a, b, c]): | |
continue | |
mapping = dict(zip('ABCPQR', [a, b, c, p, q, r])) | |
record.add((a, b, c, p, q, r)) | |
yield mapping | |
def match_eqangle6_eqangle6_ncoll_cong_contri2( | |
g: gh.Graph, | |
g_matcher: Callable[str, list[tuple[gm.Point, ...]]], | |
theorem: pr.Theorem, | |
) -> Generator[dict[str, gm.Point], None, None]: | |
"""Match eqangle6 B A B C Q R Q P, eqangle6 C A C B R Q R P, ncoll A B C, cong A B P Q => contri2 A B C P Q R.""" | |
enums = g_matcher('eqangle6') | |
record = set() | |
for b, a, b, c, q, r, q, p in enums: # pylint: disable=redeclared-assigned-name,unused-variable | |
if not g.check_cong([a, b, p, q]): | |
continue | |
if (a, b, c) == (p, q, r): | |
continue | |
if any([x in record for x in rotate_contri(a, b, c, p, q, r)]): | |
continue | |
if not g.check_eqangle([c, a, c, b, r, q, r, p]): | |
continue | |
if not g.check_ncoll([a, b, c]): | |
continue | |
mapping = dict(zip('ABCPQR', [a, b, c, p, q, r])) | |
record.add((a, b, c, p, q, r)) | |
yield mapping | |
def match_eqratio6_coll_ncoll_eqangle6( | |
g: gh.Graph, | |
g_matcher: Callable[str, list[tuple[gm.Point, ...]]], | |
theorem: pr.Theorem, | |
) -> Generator[dict[str, gm.Point], None, None]: | |
"""Match eqratio6 d b d c a b a c, coll d b c, ncoll a b c => eqangle6 a b a d a d a c.""" | |
records = set() | |
for b, d, c in g_matcher('coll'): | |
for a in g.all_points(): | |
if g.check_coll([a, b, c]): | |
continue | |
if (a, b, d, c) in records or (a, c, d, b) in records: | |
continue | |
records.add((a, b, d, c)) | |
if g.check_eqratio([d, b, d, c, a, b, a, c]): | |
yield dict(zip('abcd', [a, b, c, d])) | |
def match_eqangle6_coll_ncoll_eqratio6( | |
g: gh.Graph, | |
g_matcher: Callable[str, list[tuple[gm.Point, ...]]], | |
theorem: pr.Theorem, | |
) -> Generator[dict[str, gm.Point], None, None]: | |
"""Match eqangle6 a b a d a d a c, coll d b c, ncoll a b c => eqratio6 d b d c a b a c.""" | |
records = set() | |
for b, d, c in g_matcher('coll'): | |
for a in g.all_points(): | |
if g.check_coll([a, b, c]): | |
continue | |
if (a, b, d, c) in records or (a, c, d, b) in records: | |
continue | |
records.add((a, b, d, c)) | |
if g.check_eqangle([a, b, a, d, a, d, a, c]): | |
yield dict(zip('abcd', [a, b, c, d])) | |
def match_eqangle6_ncoll_cyclic( | |
g: gh.Graph, | |
g_matcher: Callable[str, list[tuple[gm.Point, ...]]], | |
theorem: pr.Theorem, | |
) -> Generator[dict[str, gm.Point], None, None]: | |
"""Match eqangle6 P A P B Q A Q B, ncoll P Q A B => cyclic A B P Q.""" | |
for a, b, a, c, x, y, x, z in g_matcher('eqangle6'): # pylint: disable=redeclared-assigned-name,unused-variable | |
if (b, c) != (y, z) or a == x: | |
continue | |
if nm.check_ncoll([x.num for x in [a, b, c, x]]): | |
yield dict(zip('ABPQ', [b, c, a, x])) | |
def match_all( | |
name: str, g: gh.Graph | |
) -> Generator[tuple[gm.Point, ...], None, None]: | |
"""Match all instances of a certain relation.""" | |
if name in ['ncoll', 'npara', 'nperp']: | |
return [] | |
if name == 'coll': | |
return g.all_colls() | |
if name == 'para': | |
return g.all_paras() | |
if name == 'perp': | |
return g.all_perps() | |
if name == 'cong': | |
return g.all_congs() | |
if name == 'eqangle': | |
return g.all_eqangles_8points() | |
if name == 'eqangle6': | |
return g.all_eqangles_6points() | |
if name == 'eqratio': | |
return g.all_eqratios_8points() | |
if name == 'eqratio6': | |
return g.all_eqratios_6points() | |
if name == 'cyclic': | |
return g.all_cyclics() | |
if name == 'midp': | |
return g.all_midps() | |
if name == 'circle': | |
return g.all_circles() | |
raise ValueError(f'Unrecognize {name}') | |
def cache_match( | |
graph: gh.Graph, | |
) -> Callable[str, list[tuple[gm.Point, ...]]]: | |
"""Cache throughout one single BFS level.""" | |
cache = {} | |
def match_fn(name: str) -> list[tuple[gm.Point, ...]]: | |
if name in cache: | |
return cache[name] | |
result = list(match_all(name, graph)) | |
cache[name] = result | |
return result | |
return match_fn | |
def try_to_map( | |
clause_enum: list[tuple[pr.Clause, list[tuple[gm.Point, ...]]]], | |
mapping: dict[str, gm.Point], | |
) -> Generator[dict[str, gm.Point], None, None]: | |
"""Recursively try to match the remaining points given current mapping.""" | |
if not clause_enum: | |
yield mapping | |
return | |
clause, enum = clause_enum[0] | |
for points in enum: | |
mpcpy = dict(mapping) | |
fail = False | |
for p, a in zip(points, clause.args): | |
if a in mpcpy and mpcpy[a] != p or p in mpcpy and mpcpy[p] != a: | |
fail = True | |
break | |
mpcpy[a] = p | |
mpcpy[p] = a | |
if fail: | |
continue | |
for m in try_to_map(clause_enum[1:], mpcpy): | |
yield m | |
def match_generic( | |
g: gh.Graph, | |
cache: Callable[str, list[tuple[gm.Point, ...]]], | |
theorem: pr.Theorem | |
) -> Generator[dict[str, gm.Point], None, None]: | |
"""Match any generic rule that is not one of the above match_*() rules.""" | |
clause2enum = {} | |
clauses = [] | |
numerical_checks = [] | |
for clause in theorem.premise: | |
if clause.name in ['ncoll', 'npara', 'nperp', 'sameside']: | |
numerical_checks.append(clause) | |
continue | |
enum = cache(clause.name) | |
if len(enum) == 0: # pylint: disable=g-explicit-length-test | |
return 0 | |
clause2enum[clause] = enum | |
clauses.append((len(set(clause.args)), clause)) | |
clauses = sorted(clauses, key=lambda x: x[0], reverse=True) | |
_, clauses = zip(*clauses) | |
for mapping in try_to_map([(c, clause2enum[c]) for c in clauses], {}): | |
if not mapping: | |
continue | |
checks_ok = True | |
for check in numerical_checks: | |
args = [mapping[a] for a in check.args] | |
if check.name == 'ncoll': | |
checks_ok = g.check_ncoll(args) | |
elif check.name == 'npara': | |
checks_ok = g.check_npara(args) | |
elif check.name == 'nperp': | |
checks_ok = g.check_nperp(args) | |
elif check.name == 'sameside': | |
checks_ok = g.check_sameside(args) | |
if not checks_ok: | |
break | |
if not checks_ok: | |
continue | |
yield mapping | |
BUILT_IN_FNS = { | |
'cong_cong_cong_cyclic': match_cong_cong_cong_cyclic, | |
'cong_cong_cong_ncoll_contri*': match_cong_cong_cong_ncoll_contri, | |
'cong_cong_eqangle6_ncoll_contri*': match_cong_cong_eqangle6_ncoll_contri, | |
'eqangle6_eqangle6_ncoll_simtri': match_eqangle6_eqangle6_ncoll_simtri, | |
'eqangle6_eqangle6_ncoll_cong_contri': ( | |
match_eqangle6_eqangle6_ncoll_cong_contri | |
), # pylint: disable=line-too-long | |
'eqangle6_eqangle6_ncoll_simtri2': match_eqangle6_eqangle6_ncoll_simtri2, | |
'eqangle6_eqangle6_ncoll_cong_contri2': ( | |
match_eqangle6_eqangle6_ncoll_cong_contri2 | |
), # pylint: disable=line-too-long | |
'eqratio6_eqratio6_ncoll_simtri*': match_eqratio6_eqratio6_ncoll_simtri, | |
'eqratio6_eqratio6_ncoll_cong_contri*': ( | |
match_eqratio6_eqratio6_ncoll_cong_contri | |
), # pylint: disable=line-too-long | |
'eqangle_para': match_eqangle_para, | |
'eqangle_ncoll_cyclic': match_eqangle_ncoll_cyclic, | |
'eqratio6_eqangle6_ncoll_simtri*': match_eqratio6_eqangle6_ncoll_simtri, | |
'eqangle_perp_perp': match_eqangle_perp_perp, | |
'eqangle6_ncoll_cong': match_eqangle6_ncoll_cong, | |
'perp_perp_ncoll_para': match_perp_perp_ncoll_para, | |
'circle_perp_eqangle': match_circle_perp_eqangle, | |
'circle_eqangle_perp': match_circle_eqangle_perp, | |
'cyclic_eqangle_cong': match_cyclic_eqangle_cong, | |
'midp_perp_cong': match_midp_perp_cong, | |
'perp_perp_npara_eqangle': match_perp_perp_npara_eqangle, | |
'cyclic_eqangle': match_cyclic_eqangle, | |
'eqangle_eqangle_eqangle': match_eqangle_eqangle_eqangle, | |
'eqratio_eqratio_eqratio': match_eqratio_eqratio_eqratio, | |
'eqratio6_coll_ncoll_eqangle6': match_eqratio6_coll_ncoll_eqangle6, | |
'eqangle6_coll_ncoll_eqratio6': match_eqangle6_coll_ncoll_eqratio6, | |
'eqangle6_ncoll_cyclic': match_eqangle6_ncoll_cyclic, | |
} | |
SKIP_THEOREMS = set() | |
def set_skip_theorems(theorems: set[str]) -> None: | |
SKIP_THEOREMS.update(theorems) | |
MAX_BRANCH = 50_000 | |
def match_one_theorem( | |
g: gh.Graph, | |
cache: Callable[str, list[tuple[gm.Point, ...]]], | |
theorem: pr.Theorem | |
) -> Generator[dict[str, gm.Point], None, None]: | |
"""Match all instances of a single theorem (rule).""" | |
if cache is None: | |
cache = cache_match(g) | |
if theorem.name in SKIP_THEOREMS: | |
return [] | |
if theorem.name.split('_')[-1] in SKIP_THEOREMS: | |
return [] | |
if theorem.name in BUILT_IN_FNS: | |
mps = BUILT_IN_FNS[theorem.name](g, cache, theorem) | |
else: | |
mps = match_generic(g, cache, theorem) | |
mappings = [] | |
for mp in mps: | |
mappings.append(mp) | |
if len(mappings) > MAX_BRANCH: # cap branching at this number. | |
break | |
return mappings | |
def match_all_theorems( | |
g: gh.Graph, theorems: list[pr.Theorem], goal: pr.Clause | |
) -> dict[pr.Theorem, dict[pr.Theorem, dict[str, gm.Point]]]: | |
"""Match all instances of all theorems (rules).""" | |
cache = cache_match(g) | |
# for BFS, collect all potential matches | |
# and then do it at the same time | |
theorem2mappings = {} | |
# Step 1: list all matches | |
for _, theorem in theorems.items(): | |
name = theorem.name | |
if name.split('_')[-1] in [ | |
'acompute', | |
'rcompute', | |
'fixl', | |
'fixc', | |
'fixb', | |
'fixt', | |
'fixp', | |
]: | |
if goal and goal.name != name: | |
continue | |
mappings = match_one_theorem(g, cache, theorem) | |
if len(mappings): # pylint: disable=g-explicit-length-test | |
theorem2mappings[theorem] = list(mappings) | |
return theorem2mappings | |
def bfs_one_level( | |
g: gh.Graph, | |
theorems: list[pr.Theorem], | |
level: int, | |
controller: pr.Problem, | |
verbose: bool = False, | |
nm_check: bool = False, | |
timeout: int = 600, | |
) -> tuple[ | |
list[pr.Dependency], | |
dict[str, list[tuple[gm.Point, ...]]], | |
dict[str, list[tuple[gm.Point, ...]]], | |
int, | |
]: | |
"""Forward deduce one breadth-first level.""" | |
# Step 1: match all theorems: | |
theorem2mappings = match_all_theorems(g, theorems, controller.goal) | |
# Step 2: traceback for each deduce: | |
theorem2deps = {} | |
t0 = time.time() | |
for theorem, mappings in theorem2mappings.items(): | |
if time.time() - t0 > timeout: | |
break | |
mp_deps = [] | |
for mp in mappings: | |
deps = EmptyDependency(level=level, rule_name=theorem.rule_name) | |
fail = False # finding why deps might fail. | |
for p in theorem.premise: | |
p_args = [mp[a] for a in p.args] | |
# Trivial deps. | |
if p.name == 'cong': | |
a, b, c, d = p_args | |
if {a, b} == {c, d}: | |
continue | |
if p.name == 'para': | |
a, b, c, d = p_args | |
if {a, b} == {c, d}: | |
continue | |
if theorem.name in [ | |
'cong_cong_eqangle6_ncoll_contri*', | |
'eqratio6_eqangle6_ncoll_simtri*', | |
]: | |
if p.name in ['eqangle', 'eqangle6']: # SAS or RAR | |
b, a, b, c, y, x, y, z = ( # pylint: disable=redeclared-assigned-name,unused-variable | |
p_args | |
) | |
if not nm.same_clock(a.num, b.num, c.num, x.num, y.num, z.num): | |
p_args = b, a, b, c, y, z, y, x | |
dep = Dependency(p.name, p_args, rule_name='', level=level) | |
try: | |
dep = dep.why_me_or_cache(g, level) | |
except: # pylint: disable=bare-except | |
fail = True | |
break | |
if dep.why is None: | |
fail = True | |
break | |
g.cache_dep(p.name, p_args, dep) | |
deps.why.append(dep) | |
if fail: | |
continue | |
mp_deps.append((mp, deps)) | |
theorem2deps[theorem] = mp_deps | |
theorem2deps = list(theorem2deps.items()) | |
# Step 3: add conclusions to graph. | |
# Note that we do NOT mix step 2 and 3, strictly going for BFS. | |
added = [] | |
for theorem, mp_deps in theorem2deps: | |
for mp, deps in mp_deps: | |
if time.time() - t0 > timeout: | |
break | |
name, args = theorem.conclusion_name_args(mp) | |
hash_conclusion = pr.hashed(name, args) | |
if hash_conclusion in g.cache: | |
continue | |
add = g.add_piece(name, args, deps=deps) | |
added += add | |
branching = len(added) | |
# Check if goal is found | |
if controller.goal: | |
args = [] | |
for a in controller.goal.args: | |
if a in g._name2node: | |
a = g._name2node[a] | |
elif '/' in a: | |
a = create_consts_str(g, a) | |
elif a.isdigit(): | |
a = int(a) | |
args.append(a) | |
if g.check(controller.goal.name, args): | |
return added, {}, {}, branching | |
# Run AR, but do NOT apply to the proof state (yet). | |
for dep in added: | |
g.add_algebra(dep, level) | |
derives, eq4s = g.derive_algebra(level, verbose=False) | |
branching += sum([len(x) for x in derives.values()]) | |
branching += sum([len(x) for x in eq4s.values()]) | |
return added, derives, eq4s, branching | |
def create_consts_str(g: gh.Graph, s: str) -> gm.Angle | gm.Ratio: | |
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 do_algebra( | |
g: gh.Graph, added: list[pr.Dependency], verbose: bool = False | |
) -> None: | |
for add in added: | |
g.add_algebra(add, None) | |
derives, eq4s = g.derive_algebra(level=None, verbose=verbose) | |
apply_derivations(g, derives) | |
apply_derivations(g, eq4s) | |
def apply_derivations( | |
g: gh.Graph, derives: dict[str, list[tuple[gm.Point, ...]]] | |
) -> list[pr.Dependency]: | |
applied = [] | |
all_derives = list(derives.items()) | |
for name, args in all_derives: | |
for arg in args: | |
applied += g.do_algebra(name, arg) | |
return applied | |