def index(self, indexer: Indexer): ret = [] triangles = indexer.index_by_type(Triangle) exist_relations = set() two_sums = indexer.index_by_type(TwoSum) for two_sum in two_sums: exist_relations.add(two_sum.relationship.id) for tri in triangles: known_angles = [] for angle in tri.angles: angle_A_cond = indexer.index_value_condition(angle, 'angle') if angle_A_cond.attr_value is not None: known_angles.append(angle_A_cond) if len(known_angles) >= 2: pairs = itertools.product(known_angles, known_angles) for angle_B_cond, angle_C_cond in pairs: id_ = f'{angle_B_cond.obj.id}.angle_{angle_C_cond.obj.id}.angle' if id_ in exist_relations: continue two_sum_relation = TwoSum( id_, angle_B_cond.obj, 'angle', angle_C_cond.obj, 'angle', angle_B_cond.attr_value + angle_C_cond.attr_value) r = RelationshipBased(two_sum_relation) ret.append([[angle_B_cond, angle_C_cond], r]) return ret
def _find_common_vertex_angle(self, cva_cond: RelationshipBased, indexer: Indexer): r = cva_cond.relationship vertex = r.vertex ends = r.ends size = len(ends) for i in range(size): for j in range(i + 1, size): for k in range(j + 1, size): angle_ij = indexer.index_angle_by_points( ends[i], vertex, ends[j]) angle_jk = indexer.index_angle_by_points( ends[j], vertex, ends[k]) angle_ik = indexer.index_angle_by_points( ends[i], vertex, ends[k]) cond_ik = indexer.index_value_condition(angle_ik, 'angle') if cond_ik.attr_value is None: continue eq_cond = index_equivalent_value(indexer, angle_ij, 'angle', angle_jk, 'angle') if eq_cond is None: continue cond_ij = indexer.index_value_condition(angle_ij, 'angle') cond_jk = indexer.index_value_condition(angle_jk, 'angle') unkown_cond = [ c for c in [cond_ij, cond_jk] if c.attr_value is None ] if not unkown_cond: continue return [[eq_cond, cond_ik], unkown_cond] return None
def _find_common_vertex_angle(self, cva_cond: RelationshipBased, indexer: Indexer): r = cva_cond.relationship vertex = r.vertex ends = r.ends size = len(ends) for i in range(size): for j in range(i + 1, size): for k in range(j + 1, size): angle_ij = indexer.index_angle_by_points( ends[i], vertex, ends[j]) angle_jk = indexer.index_angle_by_points( ends[j], vertex, ends[k]) angle_ik = indexer.index_angle_by_points( ends[i], vertex, ends[k]) cond_ij = indexer.index_value_condition(angle_ij, 'angle') cond_jk = indexer.index_value_condition(angle_jk, 'angle') cond_ik = indexer.index_value_condition(angle_ik, 'angle') if sum([ cond_ij.attr_value is None, cond_jk.attr_value is None, cond_ik.attr_value is None ]) == 1: return [[cva_cond, cond_ij, cond_jk], (cond_ik)] return None
def __init__(self, entity: Entity, conditions: List[Condition], target: Target): if len(entity.children) == 0: raise ValueError("Problem entity is empty!") self.entity = entity self.conditions = conditions self.target = target # Initialize deduction graph. self.graph = DeductionGraph(conditions, target) # Build indexer self.indexer = Indexer(entity, self.graph)
def index(self, indexer: Indexer): ret = [] rt_conds = indexer.index_by_type(IsRightTriangle) for cond in rt_conds: r = cond.relationship angle = r.right_angle angle_cond = indexer.index_value_condition(angle, 'angle') if angle_cond.attr_value is None: ret.append([[cond], angle_cond]) return ret
def basic_pattern(): entity, target, conditions = get_problem() graph = DeductionGraph(conditions, target) indexer = Indexer(entity, graph) pattern = TrianglePattern(angle_A=AttributeState.KNOWN, angle_B=AttributeState.KNOWN, angle_C=AttributeState.KNOWN) conditions, entitis = indexer.index_by_pattern(pattern, True) print(entitis) print(conditions)
def index(self, indexer: Indexer): """Find interior angles on the same side from parallel relationship.""" ret = [] def find_alternate_angles(link_col, col1, col2, p1, p2): p1_index = link_col.index(p1) p2_index = link_col.index(p2) reverse = lambda x: -1 if x == 0 else 0 compare = p1_index < p2_index p1_direction = -1 if compare else 0 for k in [0, -1]: if link_col[p1_direction] == p1 or col1[k] == p1: continue if link_col[reverse(p1_direction)] == p2 or col2[k] == p2: continue angle1 = indexer.index_angle_by_points(\ link_col[p1_direction], p1, col1[k]) angle2 = indexer.index_angle_by_points(\ link_col[reverse(p1_direction)], p2, col2[k]) a1_cond = indexer.index_value_condition(angle1, 'angle') a2_cond = indexer.index_value_condition(angle2, 'angle') if a1_cond.attr_value is None \ and a2_cond.attr_value is not None: ret.append([[a2_cond], a1_cond]) elif a1_cond.attr_value is not None \ and a2_cond.attr_value is None: ret.append([[a1_cond], a2_cond]) conds = indexer.index_by_type(Parallel) for cond in conds: r = cond.relationship line1, line2 = r.line1, r.line2 # col is a list of point entity. col1 = indexer.index_collineation_by_line(line1) col2 = indexer.index_collineation_by_line(line2) if r.reverse: col2 = list(reversed(col2)) for p1 in col1: for p2 in col2: # Link line is the line links two parallel lines. link_line = indexer.index_line_by_points(p1, p2) # One link line can generate four corresponding angles at most. if link_line is None: continue link_col = indexer.index_collineation_by_line(link_line) find_alternate_angles(link_col, col1, col2, p1, p2) return ret
def index(self, indexer: Indexer): ret = [] rt_conds = indexer.index_by_type(IsRightTriangle) for cond in rt_conds: r = cond.relationship hypotenuse = r.hypotenuse legs = r.legs a = indexer.index_value_condition(legs[0], 'length') b = indexer.index_value_condition(legs[1], 'length') c = indexer.index_value_condition(hypotenuse, 'length') if attr_value_known_num([a, b, c]) == 2: ret.append([[cond, a, b], c]) return ret
def index(self, indexer: Indexer): ret = [] e_conds = indexer.index_by_type(IsEquilateralTriangle) for cond in e_conds: r = cond.relationship tg = [] for angle in r.triangle.angles: angle_cond = indexer.index_value_condition(angle, 'angle') if angle_cond.attr_value is None: tg.append(angle_cond) for t in tg: ret.append([[cond], t]) return ret
class Problem(object): def __init__(self, entity: Entity, conditions: List[Condition], target: Target): if len(entity.children) == 0: raise ValueError("Problem entity is empty!") self.entity = entity self.conditions = conditions self.target = target # Initialize deduction graph. self.graph = DeductionGraph(conditions, target) # Build indexer self.indexer = Indexer(entity, self.graph) def set_entity(self, entity: Entity) -> None: self.entity = entity def set_conditions(self, conditions: List[Condition]) -> None: self.conditions = conditions @property def solved(self): return self.graph.solved def deduct(self, theorem): # Traverse all [sources, target] pair that meet requirement of theorem. for srcs, tg in theorem.index(self.indexer): srcs, tg = theorem.deduct(srcs, tg) # There can be multiple targets. if not isinstance(tg, list): tg = [tg] for tg_ in tg: self.graph.expand(tg_) tg_.from_conditions = srcs tg_.from_theorem = theorem self.indexer.update_index(tg_) def is_valid(self, theorem): """Determine whether the given theorem is valid.""" return True if theorem.index(self.indexer) else False def solving_path(self): return self.graph.solving_path() def solving_steps(self): return self.graph.solving_steps() def plain_word_answer(self): return self.graph.plain_word_answer() def show_graph(self): self.graph.show_graph()
def index(self, indexer: Indexer): ret = [] nls_conds = indexer.index_by_type(NLineSector) for cond in nls_conds: r = cond.relationship # Existence of ratio condition is guaranteed. ratio_cond = indexer.index_value_condition(r, 'ratio') ratio = ratio_cond.attr_value near_point, split_point, far_point = r.three_points line_near = indexer.index_line_by_points(near_point, split_point) line_far = indexer.index_line_by_points(far_point, split_point) line_full = indexer.index_line_by_points(near_point, far_point) near_cond = indexer.index_value_condition(line_near, 'length') far_cond = indexer.index_value_condition(line_far, 'length') full_cond = indexer.index_value_condition(line_full, 'length') if full_cond.attr_value is None: if near_cond.attr_value is not None: ret.append([[near_cond, ratio_cond, 1/ratio], full_cond]) elif far_cond.attr_value is not None: ret.append([[far_cond, ratio_cond, 1/(1-ratio)], full_cond]) if near_cond.attr_value is None: if full_cond.attr_value is not None: ret.append([[full_cond, ratio_cond, ratio], near_cond]) elif far_cond.attr_value is not None: ret.append([[far_cond, ratio_cond, ratio/(1-ratio)], near_cond]) if far_cond.attr_value is None: if full_cond.attr_value is not None: ret.append([[full_cond, ratio_cond, 1-ratio], far_cond]) elif near_cond.attr_value is not None: ret.append([[near_cond, ratio_cond, (1-ratio)/ratio], far_cond]) return ret
def index(self, indexer: Indexer): ret = [] conds = indexer.index_by_type(OppositeVerticalAngle) for cond in conds: r = cond.relationship angle1_cond = indexer.index_value_condition(r.angle1, 'angle') angle2_cond = indexer.index_value_condition(r.angle2, 'angle') if angle1_cond.attr_value is not None and \ angle2_cond.attr_value is None: ret.append([[cond, angle1_cond], angle2_cond]) elif angle1_cond.attr_value is None and \ angle2_cond.attr_value is not None: ret.append([[cond, angle2_cond], angle1_cond]) return ret
def index(self, indexer: Indexer): ret = [] two_sums = indexer.index_by_type(TwoSum) for two_sum in two_sums: r = two_sum.relationship cond1 = indexer.index_value_condition(r.entity1, r.attr1, False) cond2 = indexer.index_value_condition(r.entity2, r.attr2, False) if cond1 is None and cond2 is not None: cond1 = indexer.index_value_condition(r.entity1, r.attr1) ret.append([[two_sum, cond2], cond1]) elif cond1 is not None and cond2 is None: cond2 = indexer.index_value_condition(r.entity2, r.attr2) ret.append([[two_sum, cond1], cond2]) return ret
def index(self, indexer: Indexer): ret = [] st_conds = indexer.index_by_type(SimilarTriangle) for cond in st_conds: r = cond.relationship for angle1, angle2 in r.cor_angle: a1_cond = indexer.index_value_condition(angle1, 'angle') a2_cond = indexer.index_value_condition(angle2, 'angle') if a1_cond.attr_value is None \ and a2_cond.attr_value is not None: ret.append([[cond, a2_cond], a1_cond]) elif a1_cond.attr_value is not None \ and a2_cond.attr_value is None: ret.append([[cond, a1_cond], a2_cond]) return ret
def index(self, indexer: Indexer): ret = [] i_conds = indexer.index_by_type(IsIsoscelesTriangle) for cond in i_conds: r = cond.relationship base_angle = r.base_angle a0_cond = indexer.index_value_condition(base_angle[0], 'angle') a1_cond = indexer.index_value_condition(base_angle[1], 'angle') if a0_cond.attr_value is None \ and a1_cond.attr_value is not None: ret.append([[cond, a1_cond], a0_cond]) elif a0_cond.attr_value is not None \ and a1_cond.attr_value is None: ret.append([[cond, a0_cond], a1_cond]) return ret
def index(self, indexer: Indexer): ret = [] st_conds = indexer.index_by_type(SimilarTriangle) for cond in st_conds: r = cond.relationship for side1, side2 in r.cor_sides: side1_cond = indexer.index_value_condition(side1, 'length') side2_cond = indexer.index_value_condition(side2, 'length') if side1_cond.attr_value is not None \ and side2_cond.attr_value is not None: ratio = indexer.index_value_condition(r, 'ratio') if ratio.attr_value is None: ret.append([[cond, side1_cond, side2_cond], ratio]) break return ret
def index(self, indexer: Indexer): ret = [] triangles = indexer.index_by_type(Triangle) for th in triangles: side1 = indexer.index_value_condition(th.side1, 'length') if side1.attr_value is None: continue side2 = indexer.index_value_condition(th.side2, 'length') if side2.attr_value is None: continue side3 = indexer.index_value_condition(th.side3, 'length') if side3.attr_value is None: continue ret.append([[side1, side2, side3], AttributeValue(th, **{'circumference': None})]) return ret
def index(self, indexer: Indexer): ret = [] i_conds = indexer.index_by_type(IsIsoscelesTriangle) for cond in i_conds: r = cond.relationship h = r.hypotenuse h0_cond = indexer.index_value_condition(h[0], 'length') h1_cond = indexer.index_value_condition(h[1], 'length') if h0_cond.attr_value is None \ and h1_cond.attr_value is not None: ret.append([[cond, h1_cond], h0_cond]) elif h0_cond.attr_value is not None \ and h1_cond.attr_value is None: ret.append([[cond, h0_cond], h1_cond]) return ret
def index(self, indexer: Indexer): ret = [] st_conds = indexer.index_by_type(SimilarTriangle) for cond in st_conds: r = cond.relationship ratio_cond = indexer.index_value_condition(r, 'ratio') if ratio_cond.attr_value is None: continue for side1, side2 in r.cor_sides: s1_cond = indexer.index_value_condition(side1, 'length') s2_cond = indexer.index_value_condition(side2, 'length') if sum( [s1_cond.attr_value is None, s2_cond.attr_value is None]) == 1: ret.append([[cond, s1_cond, ratio_cond], s2_cond]) return ret
def index(self, indexer: Indexer): ret = [] triangles = indexer.index_by_type(Triangle) for th in triangles: side1 = indexer.index_value_condition(th.side1, 'length') if side1.attr_value is None: continue side2 = indexer.index_value_condition(th.side2, 'length') if side2.attr_value is None: continue side3 = indexer.index_value_condition(th.side3, 'length') if side3.attr_value is None: continue area = indexer.index_value_condition(th, 'area') if area.attr_value is None: ret.append([[side1, side2, side3], area]) return ret
def index(self, indexer: Indexer): ret = [] r_conds = indexer.index_by_type(IsRightTriangle) triangles = indexer.index_by_type(Triangle) right_triangles = [cond.relationship.triangle for cond in r_conds] for th in triangles: if th in right_triangles: continue for angle in [th.angle1, th.angle2, th.angle3]: a_cond = indexer.index_value_condition(angle, 'angle') if a_cond.attr_value is not None and a_cond.attr_value == 90: rt = IsRightTriangle(th.id, th, angle) rt_cond = RelationshipBased(rt) ret.append([[a_cond], rt_cond]) break return ret
def index(self, indexer: Indexer): conditions = [] cvas = indexer.index_by_type(CommonVertexAngle) for cva_cond in cvas: cond = self._find_common_vertex_angle(cva_cond, indexer) if cond is not None: conditions.append(cond) return conditions
def index(self, indexer: Indexer): conditions = [] cols = indexer.index_by_type(Collineation) for col_cond in cols: cond = self._find_lines_by_collineation(col_cond, indexer) if cond is not None: conditions.append(cond) return conditions
def index(self, indexer: Indexer): ret = [] triangles = indexer.index_by_type(Triangle) exist_relations = {} two_sums = indexer.index_by_type(TwoSum) for two_sum in two_sums: exist_relations[two_sum.relationship.id] = two_sum for tri in triangles: for angle in tri.angles: angle_A_cond = indexer.index_value_condition(angle, 'angle') if angle_A_cond.attr_value is None: angle_B, angle_C = [a for a in tri.angles if a != angle] id_ = f'{angle_B.id}.angle_{angle_C.id}.angle' if id_ in exist_relations: two_angle_sum = exist_relations[id_] ret.append([[two_angle_sum], angle_A_cond]) return ret
def index(self, indexer: Indexer): pattern = TrianglePattern(line_AB=AttributeState.KNOWN, line_AC=AttributeState.KNOWN, line_BC=AttributeState.KNOWN, angle_A=AttributeState.UNKNOWN) repleced_patterns = indexer.index_by_pattern(pattern) return [([p.line_AB, p.line_AC, p.line_BC], p.angle_A) \ for p in repleced_patterns]
def index(self, indexer: Indexer): ret = [] e_conds = indexer.index_by_type(IsEquilateralTriangle) for cond in e_conds: r = cond.relationship pre = [] tg = [] for side in r.triangle.sides: side_cond = indexer.index_value_condition(side, 'length') if side_cond.attr_value is None: tg.append(side_cond) else: pre.append(side_cond) if pre: pre = [cond] + pre for t in tg: ret.append([pre, t]) return ret
def index(self, indexer: Indexer): ret = [] rt_conds = indexer.index_by_type(IsRightTriangle) for cond in rt_conds: r = cond.relationship triangle = r.triangle area_cond = indexer.index_value_condition(triangle, 'area') # If area is already known, skip. if area_cond.attr_value is not None: continue leg1_cond = indexer.index_value_condition(r.legs[0], 'length') if leg1_cond.attr_value is None: continue leg2_cond = indexer.index_value_condition(r.legs[1], 'length') if leg2_cond.attr_value is None: continue ret.append([[cond, leg1_cond, leg2_cond], area_cond]) return ret
def index(self, indexer: Indexer): """Find corresponding angles from parallel relationship.""" ret = [] def find_corresponding_angles(link_col, col1, col2, p1, p2): for i in [0, -1]: for j in [0, -1]: if link_col[i] == p1 or col1[j] == p1: continue if link_col[i] == p2 or col2[j] == p2: continue angle1 = indexer.index_angle_by_points(\ link_col[i], p1, col1[j]) angle2 = indexer.index_angle_by_points(\ link_col[i], p2, col2[j]) a1_cond = indexer.index_value_condition(angle1, 'angle') a2_cond = indexer.index_value_condition(angle2, 'angle') if a1_cond.attr_value is None \ and a2_cond.attr_value is not None: ret.append([[a2_cond], a1_cond]) elif a1_cond.attr_value is not None \ and a2_cond.attr_value is None: ret.append([[a1_cond], a2_cond]) conds = indexer.index_by_type(Parallel) for cond in conds: r = cond.relationship line1, line2 = r.line1, r.line2 # col is a list of point entity. col1 = indexer.index_collineation_by_line(line1) col2 = indexer.index_collineation_by_line(line2) if r.reverse: col2 = list(reversed(col2)) for p1 in col1: for p2 in col2: # Link line is the line links two parallel lines. link_line = indexer.index_line_by_points(p1, p2) # One link line can generate four corresponding angles at most. if link_line is None: continue link_col = indexer.index_collineation_by_line(link_line) find_corresponding_angles(link_col, col1, col2, p1, p2) return ret
def index(self, indexer: Indexer): ret = [] conds = indexer.index_by_type(Perpendicular) for cond in conds: r = cond.relationship line1 = r.line1 line2 = r.line2 if r.foot_point is None: r.foot_point = indexer.index_line_intersection(line1, line2) foot_point = r.foot_point for p1 in [line1.end1, line1.end2]: for p2 in [line2.end1, line2.end2]: if p1 == foot_point or p2 == foot_point: continue angle = indexer.index_angle_by_points(p1, foot_point, p2) angle_cond = indexer.index_value_condition(angle, 'angle') if angle_cond.attr_value is None: ret.append([[cond], AttributeValue(angle, **{'angle': None})]) return ret
def index(self, indexer: Indexer): ret = [] conds = indexer.index_by_type(SupplementaryAngle) exist_relations = set() two_sums = indexer.index_by_type(TwoSum) for two_sum in two_sums: exist_relations.add(two_sum.relationship.id) for cond in conds: r = cond.relationship angle1_cond = indexer.index_value_condition(r.angle1, 'angle') angle2_cond = indexer.index_value_condition(r.angle2, 'angle') id_ = f'{angle1_cond.obj.id}.angle_{angle2_cond.obj.id}.angle' if id_ in exist_relations: continue two_sum_relation = TwoSum(id_, angle1_cond.obj, 'angle', angle2_cond.obj, 'angle', 180) r = RelationshipBased(two_sum_relation) ret.append([[cond], r]) return ret