https://github.com/mirefek/geo_logic
Raw File
Tip revision: 80598a9ddc0ad7e7c7c92a49c29def7322372c72 authored by Mirek Olšák on 04 June 2024, 21:34:41 UTC
bugfix
Tip revision: 80598a9
triggers.py
from relstr import RelStr
from stop_watch import StopWatch
from geo_object import *

# general class for triggers, can be also used as "dummy triggers" not doing anything
class RelStrEnv:
    def __init__(self, logic):
        self.logic = logic
        self.num_model = logic.num_model
        self.relstr = RelStr()
        self.to_run = []
        self.discarded = set()
        self.running = False

    # when a new relation is added to the RelStr (lookup table)
    # t = label, data = input + output
    def add(self, t, data):
        pass

    # run all actions accumulated so far
    def run(self):
        if self.running: return
        self.running = True
        while self.to_run:
            action, x_to_y = self.to_run.pop()
            if any(y in self.discarded for y in x_to_y): continue
            action(x_to_y)
        self.running = False

    def discard_node(self, n, store_disc_edges = None):
        self.discarded.add(n)
        self.relstr.discard_node(n, store_disc_edges)

    def glue_nodes(self, glue_dict):
        disc_edges = []
        for src in glue_dict.keys(): self.discard_node(src, disc_edges)
        for t,data in disc_edges:
            self.add(t, tuple(glue_dict.get(x, x) for x in data))

"""
Triggers automatically call the following "axioms" whenever possible:

intersection_uq_ll a:L b:L X:P Y:P ->
  <- not_eq a b
  <- lies_on X a
  <- lies_on X b
  <- lies_on Y a
  <- lies_on Y b
  THEN
  <- == X Y
intersection_uq_lc a:L b:C X:P Y:P ->
  <- intersecting a b
  <- lies_on X a
  <- lies_on X b
  <- lies_on Y a
  <- lies_on Y b
  Z <- intersection_remoter a b X
  <- not_eq Y Z
  THEN
  <- == X Y
intersection_uq_cc a:C b:C X:P Y:P ->
  <- intersecting a b
  <- lies_on X a
  <- lies_on X b
  <- lies_on Y a
  <- lies_on Y b
  Z <- intersection_remoter a b X
  <- not_eq Y Z
  THEN
  <- == X Y
line_uq_pp X:P Y:P a:L b:L ->
  <- not_eq X Y
  <- lies_on X a
  <- lies_on X b
  <- lies_on Y a
  <- lies_on Y b
  THEN
  <- == a b
line_uq_pd X:P a:L b:L ->
  da <- direction_of a
  db <- direction_of b
  <- == da db
  <- lies_on X a
  <- lies_on X b
  THEN
  <- == a b
circle_cr_uq w:C r:D O:P ->
  rw <- radius_of w
  Ow <- center_of O
  <- == rw r
  <- == Ow O
  THEN
  c2 <- circle O r
  <- == c c2
circumcircle_uq A:P B:P C:P a:C b:C ->
  <- lies_on A a
  <- lies_on B a
  <- lies_on C a
  <- lies_on A b
  <- lies_on B b
  <- lies_on C b
  <- not_eq A B
  <- not_eq B C
  <- not_eq C A
  THEN
  <- == a b
"""
class TriggerEnv(RelStrEnv):
    # intersection_uq_ll
    # intersection_uq_lc, not tangent
    # intersection_uq_cc, not tangent
    # line_uq_pp
    # line_uq_pd
    # circle_cr_uq
    # circumcircle_uq
    def __init__(self, rel_names, logic):

        RelStrEnv.__init__(self, logic)

        self.lies_on_l = rel_names.lies_on_l
        self.lies_on_c = rel_names.lies_on_c
        self.direction_of = rel_names.direction_of
        self.radius_of = rel_names.radius_of
        self.center_of = rel_names.center_of
        self.circle = rel_names.circle

        self.label_to_action = {
            self.lies_on_l    : self.lies_on_l_added,
            self.lies_on_c    : self.lies_on_c_added,
            self.direction_of : self.direction_of_added,
            self.radius_of    : self.radius_of_added,
            self.center_of    : self.center_of_added,
        }

    def add(self, t, data):
        if t not in self.label_to_action: return
        if self.relstr.add_rel(t, data):
            self.label_to_action[t](*data)

    def glue_trig(self, pair):
        a,b = pair
        self.logic.glue(a,b)

    def add_circ_trig(self, Orc):
        O,r,c = Orc

        c2_tup = self.logic.get_constr(self.circle, (O, r))
        if c2_tup is None:
            self.logic.add_constr(self.circle, (O, r), (c,))
        else:
            c2, = c2_tup
            if c2 != c: self.glue_trig(self.glue_trig, (c, c2))

    def num_equal(self, a, b):
        return self.num_model[a].identical_to(self.num_model[b])

    def lies_on_l_added(self, p, l):

        # line_uq_pp
        # intersection_uq_ll
        points = self.relstr.tobj_to_nb[self.lies_on_l,l,1]
        passing_l = self.relstr.tobj_to_nb[self.lies_on_l,p,0]
        passing_c = self.relstr.tobj_to_nb[self.lies_on_c,p,0]

        if len(points) < len(passing_l):
            for p2,_ in points:
                if p2 != p: self.ppll_search_ppl(p, p2, l)
        else:
            for _,l2 in passing_l:
                if l2 != l: self.ppll_search_pll(p, l, l2)

        # intersection_uq_lc
        if len(points) <= len(passing_c):
            for p2,_ in points:
                if p2 != p:
                    self.intersection_uq_c_search_ppl(p, p2, l)
        else:
            for _,c in passing_c:
                self.intersection_uq_c_search_pcl(p, c, l)

        # line_uq_pd
        d_tup = self.logic.get_constr(self.direction_of, (l,))
        if d_tup is not None:
            d, = d_tup
            self.line_uq_pd_search_pld(p,l,d)

    def lies_on_c_added(self, p, c):

        points = self.relstr.tobj_to_nb[self.lies_on_c,c,1]
        passing_l = self.relstr.tobj_to_nb[self.lies_on_l,p,0]
        passing_c = self.relstr.tobj_to_nb[self.lies_on_c,p,0]

        # intersection_uq_cc, not tangent
        # intersection_uq_lc, not tangent
        if len(points) <= len(passing_c) + len(passing_l):
            for p2,_ in points:
                if p2 != p:
                    self.intersection_uq_c_search_ppc(p, p2, c) # DONE
        else:
            for _,l in passing_l:
                self.intersection_uq_c_search_pcl(p, c, l) # DONE
            for _,c2 in passing_c:
                if c2 != c:
                    self.intersection_uq_c_search_pcc(p, c, c2) # DONE

        # circumcircle_uq
        if len(points) >= 3:
            for _,c2 in passing_c:
                if c != c2 and self.num_equal(c, c2):
                    self.circumcircle_uq_search(c,c2)

    def direction_of_added(self, l, d):  # search for line_uq_pd

        parallel = self.relstr.tobj_to_nb[self.direction_of,d,1]
        points = self.relstr.tobj_to_nb[self.lies_on_l,l,1]

        if (len(parallel) <= len(points)):
            for l2,_ in parallel:
                if l != l2 and self.num_equal(l, l2):
                    self.line_uq_pd_search_lld(l, l2, d)

        else:
            for p,_ in points:
                self.line_uq_pd_search_pld(p, l, d)

    def radius_of_added(self, c, r):
        # circle_cr_uq
        O_tup = self.logic.get_constr(self.center_of, (c,))
        if O_tup is None: return
        O, = O_tup
        self.to_run.append((self.add_circ_trig, (O, r, c)))

    def center_of_added(self, c, O):
        # circle_cr_uq
        r_tup = self.logic.get_constr(self.radius_of, (c,))
        if r_tup is None: return
        r, = r_tup
        self.to_run.append((self.add_circ_trig, (O, r, c)))

    # -------------------------------------
    # search scripts

    def ppll_search_ppl(self, p, p2, l):
        passing = self.relstr.tobj_to_nb[self.lies_on_l,p,0]
        passing2 = self.relstr.tobj_to_nb[self.lies_on_l,p2,0]
        if len(passing2) < len(passing):
            passing, passing2 = passing2, passing
            p,p2 = p2,p
        for _,l2 in passing:
            if l != l2 and (p2,l2) in passing2:
                if not self.num_equal(p, p2):
                    assert(self.num_equal(l, l2))
                    self.to_run.append((self.glue_trig, (l, l2)))
                    # if there was other line l3 passing through p, p2
                    # we would already know l2 == l3
                    break
                if not self.num_equal(l, l2):
                    assert(self.num_equal(p, p2))
                    self.to_run.append((self.glue_trig, (p, p2)))
                    break # l2 is just a witness

    def ppll_search_pll(self, p, l, l2):
        points = self.relstr.tobj_to_nb[self.lies_on_l,l,1]
        points2 = self.relstr.tobj_to_nb[self.lies_on_l,l2,1]
        if len(points2) < len(points):
            points, points2 = points2, points
            l,l2 = l2,l
        for p2,_ in points:
            if p2 != p and (p2,l2) in points2:
                if not self.num_equal(p, p2):
                    assert(self.num_equal(l, l2))
                    self.to_run.append((self.glue_trig, (l, l2)))
                    break # p2 is lust a witness
                if not self.num_equal(l, l2):
                    assert(self.num_equal(p, p2))
                    self.to_run.append((self.glue_trig, (p, p2)))
                    # if there was other point p3 in the intersection of l and l2,
                    # we would already know p2 == p3
                    break

    def intersection_uq_c_search_ppl(self, p, p2, l):
        if not self.num_equal(p2, p): return
        passing = self.relstr.tobj_to_nb[self.lies_on_c,p,0]
        passing2 = self.relstr.tobj_to_nb[self.lies_on_c,p2,0]
        if len(passing2) < len(passing):
            passing,passing2 = passing2,passing
            p,p2 = p2,p
        for _,c in passing:
            if (p2,c) in passing2 and intersecting_lc(self.num_model[l], self.num_model[c]):
                self.to_run.append((self.glue_trig, (p, p2)))
                break # c is just a witness

    def intersection_uq_c_search_ppc(self, p, p2, c):
        
        if not self.num_equal(p2, p): return

        # lines
        passing = self.relstr.tobj_to_nb[self.lies_on_l,p,0]
        passing2 = self.relstr.tobj_to_nb[self.lies_on_l,p2,0]
        if len(passing2) < len(passing):
            passing,passing2 = passing2,passing
            p,p2 = p2,p
        for _,l in passing:
            if (p2,l) in passing2 and intersecting_lc(self.num_model[l], self.num_model[c]):
                self.to_run.append((self.glue_trig, (p, p2)))
                return # l is just a witness

        # circles
        passing = self.relstr.tobj_to_nb[self.lies_on_c,p,0]
        passing2 = self.relstr.tobj_to_nb[self.lies_on_c,p2,0]
        if len(passing2) < len(passing):
            passing,passing2 = passing2,passing
            p,p2 = p2,p
        for _,c2 in passing:
            if (p2,c2) in passing2 and intersecting_cc(self.num_model[c], self.num_model[c2]):
                assert(self.num_equal(p, p2))
                self.to_run.append((self.glue_trig, (p, p2)))
                return # c2 is just a witness

    def intersection_uq_c_search_pcl(self, p, c, l):

        if not intersecting_lc(self.num_model[l], self.num_model[c]): return
        points_l = self.relstr.tobj_to_nb[self.lies_on_l,l,1]
        points_c = self.relstr.tobj_to_nb[self.lies_on_c,c,1]
        if len(points_l) < len(points_c):
            points = points_l
            points2 = points_c
            lc2 = c
        else:
            points = points_c
            points2 = points_l
            lc2 = l
        for p2,_ in points:
            if p2 != p and (p2,lc2) in points2 and self.num_equal(p, p2):
                self.to_run.append((self.glue_trig, (p, p2)))
                # if there was other point p3 in the intersection of l and c,
                # we would already know p2 == p3
                break

    def intersection_uq_c_search_pcc(self, p, c, c2):

        if not intersecting_cc(self.num_model[c], self.num_model[c2]): return
        points = self.relstr.tobj_to_nb[self.lies_on_c,c,1]
        points2 = self.relstr.tobj_to_nb[self.lies_on_c,c2,1]
        if len(points2) < len(points):
            points, points2 = points2, points
            c,c2 = c2,c
        for p2,_ in points:
            if p2 != p and (p2,c2) in points2 and (self.num_equal(p, p2)):
                self.to_run.append((self.glue_trig, (p, p2)))
                # if there was other point p3 in the intersection of c and c2,
                # we would already know p2 == p3
                break

    def circumcircle_uq_search(self, c,c2):

        points = self.relstr.tobj_to_nb[self.lies_on_c,c,1]
        points2 = self.relstr.tobj_to_nb[self.lies_on_c,c2,1]
        if len(points2) < len(points):
            c,c2 = c2,c
            points,points2 = points2,points
        witnesses = []
        for p,_ in points:
            if (p,c2) in points2 and not any(
                self.num_equal(w, p)
                for w in witnesses
            ):
                witnesses.append(p)
                if len(witnesses) >= 3:
                    assert(self.num_equal(c, c2))
                    self.to_run.append((self.glue_trig, (c, c2)))
                    break

    def line_uq_pd_search_lld(self, l, l2, d):

        points = self.relstr.tobj_to_nb[self.lies_on_l,l,1]
        points2 = self.relstr.tobj_to_nb[self.lies_on_l,l2,1]
        if len(points2) < len(points):
            l,l2 = l2,l
            points,points2 = points2,points
        for p,_ in points:
            if (p,l2) in points2:
                assert(self.num_equal(l, l2))
                self.to_run.append((self.glue_trig, (l, l2)))
                break # p is just a witness

    def line_uq_pd_search_pld(self, p, l, d):

        parallel = self.relstr.tobj_to_nb[self.direction_of,d,1]
        passing = self.relstr.tobj_to_nb[self.lies_on_l,p,0]
        if len(parallel) < len(passing):
            for l2,_ in parallel:
                if l2 != l and (p,l2) in passing:
                    assert(self.num_equal(l, l2))
                    self.to_run.append((self.glue_trig, (l, l2)))
                    # if other parallel passing line l3 was equal to l,
                    # we would already know l2 == l3
                    break

        else:
            for _,l2 in passing:
                if l2 != l and (l2,d) in parallel:
                    assert(self.num_equal(l, l2))
                    self.to_run.append((self.glue_trig, (l, l2)))
                    # if other parallel passing line l3 was equal to l,
                    # we would already know l2 == l3
                    break
back to top