Spaces:
Running
Running
Upload 96 files
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- ag4masses/alphageometry/__pycache__/alphageometry.cpython-310.pyc +0 -0
- ag4masses/alphageometry/__pycache__/ar.cpython-310.pyc +0 -0
- ag4masses/alphageometry/__pycache__/beam_search.cpython-310.pyc +0 -0
- ag4masses/alphageometry/__pycache__/dd.cpython-310.pyc +0 -0
- ag4masses/alphageometry/__pycache__/ddar.cpython-310.pyc +0 -0
- ag4masses/alphageometry/__pycache__/decoder_stack.cpython-310.pyc +0 -0
- ag4masses/alphageometry/__pycache__/geometry.cpython-310.pyc +0 -0
- ag4masses/alphageometry/__pycache__/graph.cpython-310.pyc +0 -0
- ag4masses/alphageometry/__pycache__/graph_utils.cpython-310.pyc +0 -0
- ag4masses/alphageometry/__pycache__/lm_inference.cpython-310.pyc +0 -0
- ag4masses/alphageometry/__pycache__/models.cpython-310.pyc +0 -0
- ag4masses/alphageometry/__pycache__/numericals.cpython-310.pyc +0 -0
- ag4masses/alphageometry/__pycache__/pretty.cpython-310.pyc +0 -0
- ag4masses/alphageometry/__pycache__/problem.cpython-310.pyc +0 -0
- ag4masses/alphageometry/__pycache__/trace_back.cpython-310.pyc +0 -0
- ag4masses/alphageometry/__pycache__/transformer_layer.cpython-310.pyc +0 -0
- ag4masses/alphageometry/alphageometry.py +755 -0
- ag4masses/alphageometry/alphageometry_test.py +103 -0
- ag4masses/alphageometry/ar.py +752 -0
- ag4masses/alphageometry/ar_test.py +204 -0
- ag4masses/alphageometry/beam_search.py +463 -0
- ag4masses/alphageometry/dd.py +1156 -0
- ag4masses/alphageometry/dd_test.py +79 -0
- ag4masses/alphageometry/ddar.py +159 -0
- ag4masses/alphageometry/ddar_test.py +65 -0
- ag4masses/alphageometry/decoder_stack.py +55 -0
- ag4masses/alphageometry/defs.txt +407 -0
- ag4masses/alphageometry/download.sh +17 -0
- ag4masses/alphageometry/examples.txt +8 -0
- ag4masses/alphageometry/fig1.svg +0 -0
- ag4masses/alphageometry/geometry.py +578 -0
- ag4masses/alphageometry/geometry_150M_generate.gin +47 -0
- ag4masses/alphageometry/geometry_test.py +80 -0
- ag4masses/alphageometry/graph.py +3057 -0
- ag4masses/alphageometry/graph_test.py +164 -0
- ag4masses/alphageometry/graph_utils.py +132 -0
- ag4masses/alphageometry/graph_utils_test.py +145 -0
- ag4masses/alphageometry/imo_ag_30.txt +60 -0
- ag4masses/alphageometry/jgex_ag_231.txt +462 -0
- ag4masses/alphageometry/lm_inference.py +189 -0
- ag4masses/alphageometry/lm_inference_test.py +89 -0
- ag4masses/alphageometry/models.py +178 -0
- ag4masses/alphageometry/numericals.py +1923 -0
- ag4masses/alphageometry/numericals_test.py +313 -0
- ag4masses/alphageometry/pretty.py +216 -0
- ag4masses/alphageometry/problem.py +1133 -0
- ag4masses/alphageometry/problem_test.py +61 -0
- ag4masses/alphageometry/rules.txt +43 -0
- ag4masses/alphageometry/trace_back.py +374 -0
- ag4masses/alphageometry/trace_back_test.py +61 -0
ag4masses/alphageometry/__pycache__/alphageometry.cpython-310.pyc
ADDED
Binary file (17.8 kB). View file
|
|
ag4masses/alphageometry/__pycache__/ar.cpython-310.pyc
ADDED
Binary file (22.2 kB). View file
|
|
ag4masses/alphageometry/__pycache__/beam_search.cpython-310.pyc
ADDED
Binary file (9.45 kB). View file
|
|
ag4masses/alphageometry/__pycache__/dd.cpython-310.pyc
ADDED
Binary file (28.1 kB). View file
|
|
ag4masses/alphageometry/__pycache__/ddar.cpython-310.pyc
ADDED
Binary file (3.44 kB). View file
|
|
ag4masses/alphageometry/__pycache__/decoder_stack.cpython-310.pyc
ADDED
Binary file (1.44 kB). View file
|
|
ag4masses/alphageometry/__pycache__/geometry.cpython-310.pyc
ADDED
Binary file (15.7 kB). View file
|
|
ag4masses/alphageometry/__pycache__/graph.cpython-310.pyc
ADDED
Binary file (77.3 kB). View file
|
|
ag4masses/alphageometry/__pycache__/graph_utils.cpython-310.pyc
ADDED
Binary file (3.41 kB). View file
|
|
ag4masses/alphageometry/__pycache__/lm_inference.cpython-310.pyc
ADDED
Binary file (5.45 kB). View file
|
|
ag4masses/alphageometry/__pycache__/models.cpython-310.pyc
ADDED
Binary file (5.1 kB). View file
|
|
ag4masses/alphageometry/__pycache__/numericals.cpython-310.pyc
ADDED
Binary file (54.4 kB). View file
|
|
ag4masses/alphageometry/__pycache__/pretty.cpython-310.pyc
ADDED
Binary file (5.25 kB). View file
|
|
ag4masses/alphageometry/__pycache__/problem.cpython-310.pyc
ADDED
Binary file (31.4 kB). View file
|
|
ag4masses/alphageometry/__pycache__/trace_back.cpython-310.pyc
ADDED
Binary file (8.75 kB). View file
|
|
ag4masses/alphageometry/__pycache__/transformer_layer.cpython-310.pyc
ADDED
Binary file (10.5 kB). View file
|
|
ag4masses/alphageometry/alphageometry.py
ADDED
@@ -0,0 +1,755 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright 2023 DeepMind Technologies Limited
|
2 |
+
#
|
3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4 |
+
# you may not use this file except in compliance with the License.
|
5 |
+
# You may obtain a copy of the License at
|
6 |
+
#
|
7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8 |
+
#
|
9 |
+
# Unless required by applicable law or agreed to in writing, software
|
10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
+
# See the License for the specific language governing permissions and
|
13 |
+
# limitations under the License.
|
14 |
+
# ==============================================================================
|
15 |
+
|
16 |
+
"""Run DD+AR or AlphaGeometry solver.
|
17 |
+
|
18 |
+
Please refer to README.md for detailed instructions.
|
19 |
+
"""
|
20 |
+
|
21 |
+
import time
|
22 |
+
import traceback
|
23 |
+
|
24 |
+
from absl import app
|
25 |
+
from absl import flags
|
26 |
+
from absl import logging
|
27 |
+
import ddar
|
28 |
+
import graph as gh
|
29 |
+
import lm_inference as lm
|
30 |
+
import pretty as pt
|
31 |
+
import problem as pr
|
32 |
+
|
33 |
+
#=============
|
34 |
+
import sys, os, math, re
|
35 |
+
import multiprocessing
|
36 |
+
import warnings
|
37 |
+
warnings.filterwarnings("ignore")
|
38 |
+
model = None # global variable used in multi-processing workers
|
39 |
+
|
40 |
+
_GIN_SEARCH_PATHS = flags.DEFINE_list(
|
41 |
+
'gin_search_paths',
|
42 |
+
['third_party/py/meliad/transformer/configs'],
|
43 |
+
'List of paths where the Gin config files are located.',
|
44 |
+
)
|
45 |
+
_GIN_FILE = flags.DEFINE_multi_string(
|
46 |
+
'gin_file', ['base_htrans.gin'], 'List of Gin config files.'
|
47 |
+
)
|
48 |
+
_GIN_PARAM = flags.DEFINE_multi_string(
|
49 |
+
'gin_param', None, 'Newline separated list of Gin parameter bindings.'
|
50 |
+
)
|
51 |
+
|
52 |
+
_PROBLEMS_FILE = flags.DEFINE_string(
|
53 |
+
'problems_file',
|
54 |
+
'imo_ag_30.txt',
|
55 |
+
'text file contains the problem strings. See imo_ag_30.txt for example.',
|
56 |
+
)
|
57 |
+
_PROBLEM_NAME = flags.DEFINE_string(
|
58 |
+
'problem_name',
|
59 |
+
'imo_2000_p1',
|
60 |
+
'name of the problem to solve, must be in the problem_file.',
|
61 |
+
)
|
62 |
+
_MODE = flags.DEFINE_string(
|
63 |
+
'mode', 'ddar', 'either `ddar` (DD+AR) or `alphageometry`')
|
64 |
+
_DEFS_FILE = flags.DEFINE_string(
|
65 |
+
'defs_file',
|
66 |
+
'defs.txt',
|
67 |
+
'definitions of available constructions to state a problem.',
|
68 |
+
)
|
69 |
+
_RULES_FILE = flags.DEFINE_string(
|
70 |
+
'rules_file', 'rules.txt', 'list of deduction rules used by DD.'
|
71 |
+
)
|
72 |
+
_CKPT_PATH = flags.DEFINE_string('ckpt_path', '', 'checkpoint of the LM model.')
|
73 |
+
_VOCAB_PATH = flags.DEFINE_string(
|
74 |
+
'vocab_path', '', 'path to the LM vocab file.'
|
75 |
+
)
|
76 |
+
_OUT_FILE = flags.DEFINE_string(
|
77 |
+
'out_file', '', 'path to the solution output file.'
|
78 |
+
) # pylint: disable=line-too-long
|
79 |
+
_BEAM_SIZE = flags.DEFINE_integer(
|
80 |
+
'beam_size', 1, 'beam size of the proof search.'
|
81 |
+
) # pylint: disable=line-too-long
|
82 |
+
_SEARCH_DEPTH = flags.DEFINE_integer(
|
83 |
+
'search_depth', 1, 'search depth of the proof search.'
|
84 |
+
) # pylint: disable=line-too-long
|
85 |
+
|
86 |
+
#===================================
|
87 |
+
_N_WORKSERS = flags.DEFINE_integer(
|
88 |
+
'n_workers', 1, 'number of workers'
|
89 |
+
)# pylint: disable=line-too-long
|
90 |
+
|
91 |
+
DEFINITIONS = None # contains definitions of construction actions
|
92 |
+
RULES = None # contains rules of deductions
|
93 |
+
|
94 |
+
|
95 |
+
def natural_language_statement(logical_statement: pr.Dependency) -> str:
|
96 |
+
"""Convert logical_statement to natural language.
|
97 |
+
|
98 |
+
Args:
|
99 |
+
logical_statement: pr.Dependency with .name and .args
|
100 |
+
|
101 |
+
Returns:
|
102 |
+
a string of (pseudo) natural language of the predicate for human reader.
|
103 |
+
"""
|
104 |
+
names = [a.name.upper() for a in logical_statement.args]
|
105 |
+
names = [(n[0] + '_' + n[1:]) if len(n) > 1 else n for n in names]
|
106 |
+
return pt.pretty_nl(logical_statement.name, names)
|
107 |
+
|
108 |
+
|
109 |
+
def proof_step_string(
|
110 |
+
proof_step: pr.Dependency, refs: dict[tuple[str, ...], int], last_step: bool
|
111 |
+
) -> str:
|
112 |
+
"""Translate proof to natural language.
|
113 |
+
|
114 |
+
Args:
|
115 |
+
proof_step: pr.Dependency with .name and .args
|
116 |
+
refs: dict(hash: int) to keep track of derived predicates
|
117 |
+
last_step: boolean to keep track whether this is the last step.
|
118 |
+
|
119 |
+
Returns:
|
120 |
+
a string of (pseudo) natural language of the proof step for human reader.
|
121 |
+
"""
|
122 |
+
premises, [conclusion] = proof_step
|
123 |
+
|
124 |
+
premises_nl = ' & '.join(
|
125 |
+
[
|
126 |
+
natural_language_statement(p) + ' [{:02}]'.format(refs[p.hashed()])
|
127 |
+
for p in premises
|
128 |
+
]
|
129 |
+
)
|
130 |
+
|
131 |
+
if not premises:
|
132 |
+
premises_nl = 'similarly'
|
133 |
+
|
134 |
+
refs[conclusion.hashed()] = len(refs)
|
135 |
+
|
136 |
+
conclusion_nl = natural_language_statement(conclusion)
|
137 |
+
if not last_step:
|
138 |
+
conclusion_nl += ' [{:02}]'.format(refs[conclusion.hashed()])
|
139 |
+
|
140 |
+
return f'{premises_nl} \u21d2 {conclusion_nl}'
|
141 |
+
|
142 |
+
|
143 |
+
def write_solution(g: gh.Graph, p: pr.Problem, out_file: str) -> None:
|
144 |
+
"""Output the solution to out_file.
|
145 |
+
|
146 |
+
Args:
|
147 |
+
g: gh.Graph object, containing the proof state.
|
148 |
+
p: pr.Problem object, containing the theorem.
|
149 |
+
out_file: file to write to, empty string to skip writing to file.
|
150 |
+
"""
|
151 |
+
setup, aux, proof_steps, refs = ddar.get_proof_steps(
|
152 |
+
g, p.goal, merge_trivials=False
|
153 |
+
)
|
154 |
+
|
155 |
+
solution = ''
|
156 |
+
solution += 'Theo đề bài ta có:\n'
|
157 |
+
premises_nl = []
|
158 |
+
for premises, [points] in setup:
|
159 |
+
solution += ' '.join([p.name.upper() for p in points]) + ' '
|
160 |
+
if not premises:
|
161 |
+
continue
|
162 |
+
premises_nl += [
|
163 |
+
natural_language_statement(p) + ' [{:02}]'.format(refs[p.hashed()])
|
164 |
+
for p in premises
|
165 |
+
]
|
166 |
+
solution += ': Points\n' + '\n'.join(premises_nl)
|
167 |
+
|
168 |
+
solution += '\n\nCác điểm cần dựng thêm:\n'
|
169 |
+
aux_premises_nl = []
|
170 |
+
if len(aux) == 0:
|
171 |
+
solution += 'Không cần dựng thêm điểm nào.'
|
172 |
+
else:
|
173 |
+
for premises, [points] in aux:
|
174 |
+
solution += ' '.join([p.name.upper() for p in points]) + ' '
|
175 |
+
aux_premises_nl += [
|
176 |
+
natural_language_statement(p) + ' [{:02}]'.format(refs[p.hashed()])
|
177 |
+
for p in premises
|
178 |
+
]
|
179 |
+
solution += ': Points\n' + '\n'.join(aux_premises_nl)
|
180 |
+
|
181 |
+
# some special case where the deduction rule has a well known name.
|
182 |
+
r2name = {
|
183 |
+
'r32': '(SSS)',
|
184 |
+
'r33': '(SAS)',
|
185 |
+
'r34': '(Similar Triangles)',
|
186 |
+
'r35': '(Similar Triangles)',
|
187 |
+
'r36': '(ASA)',
|
188 |
+
'r37': '(ASA)',
|
189 |
+
'r38': '(Similar Triangles)',
|
190 |
+
'r39': '(Similar Triangles)',
|
191 |
+
'r40': '(Congruent Triangles)',
|
192 |
+
'a00': '(Distance chase)',
|
193 |
+
'a01': '(Ratio chase)',
|
194 |
+
'a02': '(Angle chase)',
|
195 |
+
}
|
196 |
+
|
197 |
+
solution += '\n\nCác bước chứng minh:\n'
|
198 |
+
for i, step in enumerate(proof_steps):
|
199 |
+
_, [con] = step
|
200 |
+
nl = proof_step_string(step, refs, last_step=i == len(proof_steps) - 1)
|
201 |
+
rule_name = r2name.get(con.rule_name, '')
|
202 |
+
nl = nl.replace('\u21d2', f'{rule_name}\u21d2 ')
|
203 |
+
solution += '{:03}. '.format(i + 1) + nl + '\n'
|
204 |
+
logging.info(solution)
|
205 |
+
if out_file:
|
206 |
+
with open(out_file, 'w') as f:
|
207 |
+
f.write(solution)
|
208 |
+
logging.info('Solution written to %s.', out_file)
|
209 |
+
|
210 |
+
def get_lm(ckpt_init: str, vocab_path: str) -> lm.LanguageModelInference:
|
211 |
+
lm.parse_gin_configuration(
|
212 |
+
_GIN_FILE.value, _GIN_PARAM.value, gin_paths=_GIN_SEARCH_PATHS.value
|
213 |
+
)
|
214 |
+
|
215 |
+
return lm.LanguageModelInference(vocab_path, ckpt_init, mode='beam_search')
|
216 |
+
|
217 |
+
|
218 |
+
def run_ddar(g: gh.Graph, p: pr.Problem, out_file: str) -> bool:
|
219 |
+
"""Run DD+AR.
|
220 |
+
|
221 |
+
Args:
|
222 |
+
g: gh.Graph object, containing the proof state.
|
223 |
+
p: pr.Problem object, containing the problem statement.
|
224 |
+
out_file: path to output file if solution is found.
|
225 |
+
|
226 |
+
Returns:
|
227 |
+
Boolean, whether DD+AR finishes successfully.
|
228 |
+
"""
|
229 |
+
ddar.solve(g, RULES, p, max_level=1000)
|
230 |
+
|
231 |
+
goal_args = g.names2nodes(p.goal.args)
|
232 |
+
if not g.check(p.goal.name, goal_args):
|
233 |
+
logging.info('DD+AR failed to solve the problem.')
|
234 |
+
return False
|
235 |
+
|
236 |
+
write_solution(g, p, out_file)
|
237 |
+
|
238 |
+
gh.nm.draw(
|
239 |
+
g.type2nodes[gh.Point],
|
240 |
+
g.type2nodes[gh.Line],
|
241 |
+
g.type2nodes[gh.Circle],
|
242 |
+
g.type2nodes[gh.Segment],
|
243 |
+
save_to="ag4mout/output.png",)
|
244 |
+
return True
|
245 |
+
|
246 |
+
|
247 |
+
def translate_constrained_to_constructive(
|
248 |
+
point: str, name: str, args: list[str]
|
249 |
+
) -> tuple[str, list[str]]:
|
250 |
+
"""Translate a predicate from constraint-based to construction-based.
|
251 |
+
|
252 |
+
Args:
|
253 |
+
point: str: name of the new point
|
254 |
+
name: str: name of the predicate, e.g., perp, para, etc.
|
255 |
+
args: list[str]: list of predicate args.
|
256 |
+
|
257 |
+
Returns:
|
258 |
+
(name, args): translated to constructive predicate.
|
259 |
+
"""
|
260 |
+
if name in ['T', 'perp']:
|
261 |
+
a, b, c, d = args
|
262 |
+
if point in [c, d]:
|
263 |
+
a, b, c, d = c, d, a, b
|
264 |
+
if point == b:
|
265 |
+
a, b = b, a
|
266 |
+
if point == d:
|
267 |
+
c, d = d, c
|
268 |
+
if a == c and a == point:
|
269 |
+
return 'on_dia', [a, b, d]
|
270 |
+
return 'on_tline', [a, b, c, d]
|
271 |
+
|
272 |
+
elif name in ['P', 'para']:
|
273 |
+
a, b, c, d = args
|
274 |
+
if point in [c, d]:
|
275 |
+
a, b, c, d = c, d, a, b
|
276 |
+
if point == b:
|
277 |
+
a, b = b, a
|
278 |
+
return 'on_pline', [a, b, c, d]
|
279 |
+
|
280 |
+
elif name in ['D', 'cong']:
|
281 |
+
a, b, c, d = args
|
282 |
+
if point in [c, d]:
|
283 |
+
a, b, c, d = c, d, a, b
|
284 |
+
if point == b:
|
285 |
+
a, b = b, a
|
286 |
+
if point == d:
|
287 |
+
c, d = d, c
|
288 |
+
if a == c and a == point:
|
289 |
+
return 'on_bline', [a, b, d]
|
290 |
+
if b in [c, d]:
|
291 |
+
if b == d:
|
292 |
+
c, d = d, c # pylint: disable=unused-variable
|
293 |
+
return 'on_circle', [a, b, d]
|
294 |
+
return 'eqdistance', [a, b, c, d]
|
295 |
+
|
296 |
+
elif name in ['C', 'coll']:
|
297 |
+
a, b, c = args
|
298 |
+
if point == b:
|
299 |
+
a, b = b, a
|
300 |
+
if point == c:
|
301 |
+
a, b, c = c, a, b
|
302 |
+
return 'on_line', [a, b, c]
|
303 |
+
|
304 |
+
elif name in ['^', 'eqangle']:
|
305 |
+
a, b, c, d, e, f = args
|
306 |
+
|
307 |
+
if point in [d, e, f]:
|
308 |
+
a, b, c, d, e, f = d, e, f, a, b, c
|
309 |
+
|
310 |
+
x, b, y, c, d = b, c, e, d, f
|
311 |
+
if point == b:
|
312 |
+
a, b, c, d = b, a, d, c
|
313 |
+
|
314 |
+
if point == d and x == y: # x p x b = x c x p
|
315 |
+
return 'angle_bisector', [point, b, x, c]
|
316 |
+
|
317 |
+
if point == x:
|
318 |
+
return 'eqangle3', [x, a, b, y, c, d]
|
319 |
+
|
320 |
+
return 'on_aline', [a, x, b, c, y, d]
|
321 |
+
|
322 |
+
elif name in ['cyclic', 'O']:
|
323 |
+
a, b, c = [x for x in args if x != point]
|
324 |
+
return 'on_circum', [point, a, b, c]
|
325 |
+
|
326 |
+
return name, args
|
327 |
+
|
328 |
+
|
329 |
+
def check_valid_args(name: str, args: list[str]) -> bool:
|
330 |
+
"""Check whether a predicate is grammarically correct.
|
331 |
+
|
332 |
+
Args:
|
333 |
+
name: str: name of the predicate
|
334 |
+
args: list[str]: args of the predicate
|
335 |
+
|
336 |
+
Returns:
|
337 |
+
bool: whether the predicate arg count is valid.
|
338 |
+
"""
|
339 |
+
if name == 'perp':
|
340 |
+
if len(args) != 4:
|
341 |
+
return False
|
342 |
+
a, b, c, d = args
|
343 |
+
if len({a, b}) < 2:
|
344 |
+
return False
|
345 |
+
if len({c, d}) < 2:
|
346 |
+
return False
|
347 |
+
elif name == 'para':
|
348 |
+
if len(args) != 4:
|
349 |
+
return False
|
350 |
+
a, b, c, d = args
|
351 |
+
if len({a, b, c, d}) < 4:
|
352 |
+
return False
|
353 |
+
elif name == 'cong':
|
354 |
+
if len(args) != 4:
|
355 |
+
return False
|
356 |
+
a, b, c, d = args
|
357 |
+
if len({a, b}) < 2:
|
358 |
+
return False
|
359 |
+
if len({c, d}) < 2:
|
360 |
+
return False
|
361 |
+
elif name == 'coll':
|
362 |
+
if len(args) != 3:
|
363 |
+
return False
|
364 |
+
a, b, c = args
|
365 |
+
if len({a, b, c}) < 3:
|
366 |
+
return False
|
367 |
+
elif name == 'cyclic':
|
368 |
+
if len(args) != 4:
|
369 |
+
return False
|
370 |
+
a, b, c, d = args
|
371 |
+
if len({a, b, c, d}) < 4:
|
372 |
+
return False
|
373 |
+
elif name == 'eqangle':
|
374 |
+
if len(args) != 8:
|
375 |
+
return False
|
376 |
+
a, b, c, d, e, f, g, h = args
|
377 |
+
if len({a, b, c, d}) < 3:
|
378 |
+
return False
|
379 |
+
if len({e, f, g, h}) < 3:
|
380 |
+
return False
|
381 |
+
return True
|
382 |
+
|
383 |
+
|
384 |
+
def try_translate_constrained_to_construct(string: str, g: gh.Graph) -> str:
|
385 |
+
"""Whether a string of aux construction can be constructed.
|
386 |
+
|
387 |
+
Args:
|
388 |
+
string: str: the string describing aux construction.
|
389 |
+
g: gh.Graph: the current proof state.
|
390 |
+
|
391 |
+
Returns:
|
392 |
+
str: whether this construction is valid. If not, starts with "ERROR:".
|
393 |
+
"""
|
394 |
+
if string[-1] != ';':
|
395 |
+
return 'ERROR: must end with ;'
|
396 |
+
|
397 |
+
logging.info(f'PID={os.getpid()}: !! try_translate_constrained_to_construct: string=%s', string)
|
398 |
+
|
399 |
+
# sometimes the LM may return ill-formed result with multiple colons.
|
400 |
+
# example:
|
401 |
+
#
|
402 |
+
# napoleon2
|
403 |
+
# a1 a2 a3 = triangle; c3 = s_angle a1 a2 c3 30, s_angle a2 a1 c3 150; c1 = s_angle a2 a3 c1 30, s_angle a3 a2 c1 150; c2 = s_angle a3 a1 c2 30, s_angle a1 a3 c2 150 ? cong c1 c2 c1 c3
|
404 |
+
#
|
405 |
+
# in the process,
|
406 |
+
# I0210 17:58:01.513668 140016515833856 alphageometry.py:550] Decoding from {S} a : ; b : ; c : ; d : ^ a d a b 5. pi / 6. 00 ^ b d b a 1. pi / 6. 01 ; e : ^ b e b c 5. pi / 6. 02 ^ c e c b 1. pi / 6. 03 ; f : ^ a f a c 1. pi / 6. 04 ^ c f c a 5. pi / 6. 05 ? D e f e d {F1} x00 g : C a b g 06 D a g b g 07 ; x00 h : C c b h 08 D c h b h 09 ; x00
|
407 |
+
# I0210 18:01:38.182158 140016515833856 alphageometry.py:384] !! try_translate_constrained_to_construct: string=i : C a c i 10 D a i c i 11 ? V d f {F1} x00 j : D g j h j 12 D h j i j 13 ;
|
408 |
+
|
409 |
+
#XXX
|
410 |
+
# str_parts = string.split(' : ')
|
411 |
+
# if len(str_parts) != 2:
|
412 |
+
# return f'ERROR: string has multiple colons: |{string}|'
|
413 |
+
mch = re.match('(.*?)( \? | \. \{)', string)
|
414 |
+
if mch :
|
415 |
+
strFixed = mch.group(1) + ';'
|
416 |
+
logging.info(f'ID={os.getpid()}: Bad LM output: {string}. Changed to {strFixed}')
|
417 |
+
string = strFixed
|
418 |
+
|
419 |
+
# sometimes the constraint in string is empty:
|
420 |
+
# 0407 17:11:35.470240 126383800963072 alphageometry.py:394] !! try_translate_constrained_to_construct: string=j : ;
|
421 |
+
hdprem = string.split(' : ')
|
422 |
+
if len(hdprem) !=2 or hdprem[1].strip()==';' :
|
423 |
+
logging.info(f'ID={os.getpid()}: Bad LM output: {string}. ERROR')
|
424 |
+
return f'ERROR: Bad LM output: {string}'
|
425 |
+
head, prem_str = hdprem
|
426 |
+
point = head.strip()
|
427 |
+
|
428 |
+
if len(point) != 1 or point == ' ':
|
429 |
+
return f'ERROR: invalid point name {point}'
|
430 |
+
|
431 |
+
existing_points = [p.name for p in g.all_points()]
|
432 |
+
if point in existing_points:
|
433 |
+
return f'ERROR: point {point} already exists.'
|
434 |
+
|
435 |
+
prem_toks = prem_str.split()[:-1] # remove the EOS ' ;'
|
436 |
+
prems = [[]]
|
437 |
+
|
438 |
+
for i, tok in enumerate(prem_toks):
|
439 |
+
if tok.isdigit():
|
440 |
+
if i < len(prem_toks) - 1:
|
441 |
+
prems.append([])
|
442 |
+
else:
|
443 |
+
prems[-1].append(tok)
|
444 |
+
|
445 |
+
if len(prems) > 2:
|
446 |
+
return 'ERROR: there cannot be more than two predicates.'
|
447 |
+
|
448 |
+
clause_txt = point + ' = '
|
449 |
+
constructions = []
|
450 |
+
|
451 |
+
for prem in prems:
|
452 |
+
name, *args = prem
|
453 |
+
|
454 |
+
if point not in args:
|
455 |
+
return f'ERROR: {point} not found in predicate args.'
|
456 |
+
|
457 |
+
if not check_valid_args(pt.map_symbol(name), args):
|
458 |
+
return 'ERROR: Invalid predicate ' + name + ' ' + ' '.join(args)
|
459 |
+
|
460 |
+
for a in args:
|
461 |
+
if a != point and a not in existing_points:
|
462 |
+
return f'ERROR: point {a} does not exist.'
|
463 |
+
|
464 |
+
try:
|
465 |
+
name, args = translate_constrained_to_constructive(point, name, args)
|
466 |
+
except: # pylint: disable=bare-except
|
467 |
+
return 'ERROR: Invalid predicate ' + name + ' ' + ' '.join(args)
|
468 |
+
|
469 |
+
if name == 'on_aline':
|
470 |
+
if args.count(point) > 1:
|
471 |
+
return f'ERROR: on_aline involves twice {point}'
|
472 |
+
|
473 |
+
constructions += [name + ' ' + ' '.join(args)]
|
474 |
+
|
475 |
+
clause_txt += ', '.join(constructions)
|
476 |
+
clause = pr.Clause.from_txt(clause_txt)
|
477 |
+
|
478 |
+
try:
|
479 |
+
g.copy().add_clause(clause, 0, DEFINITIONS)
|
480 |
+
except: # pylint: disable=bare-except
|
481 |
+
return 'ERROR: ' + traceback.format_exc()
|
482 |
+
|
483 |
+
return clause_txt
|
484 |
+
|
485 |
+
|
486 |
+
def insert_aux_to_premise(pstring: str, auxstring: str) -> str:
|
487 |
+
"""Insert auxiliary constructs from proof to premise.
|
488 |
+
|
489 |
+
Args:
|
490 |
+
pstring: str: describing the problem to solve.
|
491 |
+
auxstring: str: describing the auxiliar construction.
|
492 |
+
|
493 |
+
Returns:
|
494 |
+
str: new pstring with auxstring inserted before the conclusion.
|
495 |
+
"""
|
496 |
+
setup, goal = pstring.split(' ? ')
|
497 |
+
return setup + '; ' + auxstring + ' ? ' + goal
|
498 |
+
|
499 |
+
|
500 |
+
class BeamQueue:
|
501 |
+
"""Keep only the top k objects according to their values."""
|
502 |
+
|
503 |
+
def __init__(self, max_size: int = 512):
|
504 |
+
self.queue = []
|
505 |
+
self.max_size = max_size
|
506 |
+
|
507 |
+
def add(self, node: object, val: float) -> None:
|
508 |
+
"""Add a new node to this queue."""
|
509 |
+
|
510 |
+
if len(self.queue) < self.max_size:
|
511 |
+
self.queue.append((val, node))
|
512 |
+
return
|
513 |
+
|
514 |
+
# Find the minimum node:
|
515 |
+
min_idx, (min_val, _) = min(enumerate(self.queue), key=lambda x: x[1])
|
516 |
+
|
517 |
+
# replace it if the new node has higher value.
|
518 |
+
if val > min_val:
|
519 |
+
self.queue[min_idx] = (val, node)
|
520 |
+
|
521 |
+
def __iter__(self):
|
522 |
+
for val, node in self.queue:
|
523 |
+
yield val, node
|
524 |
+
|
525 |
+
def __len__(self) -> int:
|
526 |
+
return len(self.queue)
|
527 |
+
|
528 |
+
#XXX
|
529 |
+
def bqsearch_init():
|
530 |
+
global model
|
531 |
+
logging.info('Worker initializing. PID=%d', os.getpid())
|
532 |
+
model = get_lm(_CKPT_PATH.value, _VOCAB_PATH.value)
|
533 |
+
|
534 |
+
def bqsearch(i_nd, srch_inputs, out_file) -> tuple[int, bool, list]: # ( iNode, solved, [ (node, score) ] )
|
535 |
+
pid = os.getpid()
|
536 |
+
logging.info(f'Worker PID={pid} called for beam search node {i_nd}')
|
537 |
+
|
538 |
+
prev_score, (g, string, pstring) = srch_inputs
|
539 |
+
logging.info(f'Worker PID={pid}: Decoding from {string}')
|
540 |
+
outputs = model.beam_decode(string, eos_tokens=[';'])
|
541 |
+
|
542 |
+
# translate lm output to the constructive language.
|
543 |
+
# so that we can update the graph representing proof states:
|
544 |
+
translations = [
|
545 |
+
try_translate_constrained_to_construct(o, g)
|
546 |
+
for o in outputs['seqs_str']
|
547 |
+
]
|
548 |
+
|
549 |
+
# couple the lm outputs with its translations
|
550 |
+
candidates = zip(outputs['seqs_str'], translations, outputs['scores'])
|
551 |
+
|
552 |
+
# bring the highest scoring candidate first
|
553 |
+
candidates = reversed(list(candidates))
|
554 |
+
|
555 |
+
ret = []
|
556 |
+
for lm_out, translation, score in candidates:
|
557 |
+
logging.info(f'Worker PID={pid}: LM output (score={score}): "{lm_out}"')
|
558 |
+
logging.info(f'Worker PID={pid}: Translation: "{translation}"')
|
559 |
+
|
560 |
+
if translation.startswith('ERROR:'):
|
561 |
+
# the construction is invalid.
|
562 |
+
continue
|
563 |
+
|
564 |
+
# Update the constructive statement of the problem with the aux point:
|
565 |
+
candidate_pstring = insert_aux_to_premise(pstring, translation)
|
566 |
+
|
567 |
+
#XXX
|
568 |
+
logging.info(f'Worker PID={pid}: string=|{string}| lm_out=|{lm_out}|')
|
569 |
+
logging.info(f'Worker PID={pid}: Solving: "{candidate_pstring}"')
|
570 |
+
p_new = pr.Problem.from_txt(candidate_pstring)
|
571 |
+
|
572 |
+
# This is the new proof state graph representation:
|
573 |
+
g_new, _ = gh.Graph.build_problem(p_new, DEFINITIONS)
|
574 |
+
|
575 |
+
try:
|
576 |
+
if run_ddar(g_new, p_new, out_file):
|
577 |
+
logging.info('Worker PID={pid}: Solved.')
|
578 |
+
return (i_nd, True, None)
|
579 |
+
except Exception as e:
|
580 |
+
logging.info(f'Worker PID={pid}: Error in run_ddar: {e}')
|
581 |
+
|
582 |
+
# Add the candidate to the beam queue.
|
583 |
+
ret.append( [
|
584 |
+
# The string for the new node is old_string + lm output +
|
585 |
+
# the special token asking for a new auxiliary point ' x00':
|
586 |
+
# node
|
587 |
+
(g_new, string + ' ' + lm_out + ' x00', candidate_pstring),
|
588 |
+
# the score of each node is sum of score of all nodes
|
589 |
+
# on the path to itself. For beam search, there is no need to
|
590 |
+
# normalize according to path length because all nodes in beam
|
591 |
+
# is of the same path length.
|
592 |
+
# val
|
593 |
+
prev_score + score ]
|
594 |
+
)
|
595 |
+
|
596 |
+
logging.info(f'Worker PID={pid} beam search node {i_nd}: returning')
|
597 |
+
return (i_nd, False, ret)
|
598 |
+
|
599 |
+
def run_alphageometry(
|
600 |
+
#XX model: lm.LanguageModelInference,
|
601 |
+
p: pr.Problem,
|
602 |
+
search_depth: int,
|
603 |
+
beam_size: int,
|
604 |
+
out_file: str,
|
605 |
+
) -> bool:
|
606 |
+
"""Simplified code to run AlphaGeometry proof search.
|
607 |
+
|
608 |
+
We removed all optimizations that are infrastructure-dependent, e.g.
|
609 |
+
parallelized model inference on multi GPUs,
|
610 |
+
parallelized DD+AR on multiple CPUs,
|
611 |
+
parallel execution of LM and DD+AR,
|
612 |
+
shared pool of CPU workers across different problems, etc.
|
613 |
+
|
614 |
+
Many other speed optimizations and abstractions are also removed to
|
615 |
+
better present the core structure of the proof search.
|
616 |
+
|
617 |
+
Args:
|
618 |
+
model: Interface with inference-related endpoints to JAX's model.
|
619 |
+
p: pr.Problem object describing the problem to solve.
|
620 |
+
search_depth: max proof search depth.
|
621 |
+
beam_size: beam size of the proof search.
|
622 |
+
out_file: path to output file if solution is found.
|
623 |
+
|
624 |
+
Returns:
|
625 |
+
boolean of whether this is solved.
|
626 |
+
"""
|
627 |
+
# translate the problem to a string of grammar that the LM is trained on.
|
628 |
+
string = p.setup_str_from_problem(DEFINITIONS)
|
629 |
+
# special tokens prompting the LM to generate auxiliary points.
|
630 |
+
string += ' {F1} x00'
|
631 |
+
# the graph to represent the proof state.
|
632 |
+
g, _ = gh.Graph.build_problem(p, DEFINITIONS)
|
633 |
+
|
634 |
+
# First we run the symbolic engine DD+AR:
|
635 |
+
if run_ddar(g, p, out_file):
|
636 |
+
return True
|
637 |
+
|
638 |
+
# ?? when pickling graph for some problems, the default recursion limit 1000 is not enough,
|
639 |
+
# got 'maximum recursion depth exceeded while pickling an object' error
|
640 |
+
sys.setrecursionlimit(10000)
|
641 |
+
|
642 |
+
# beam search for the proof
|
643 |
+
# each node in the search tree is a 3-tuple:
|
644 |
+
# (<graph representation of proof state>,
|
645 |
+
# <string for LM to decode from>,
|
646 |
+
# <original problem string>)
|
647 |
+
beam_queue = BeamQueue(max_size=beam_size)
|
648 |
+
# originally the beam search tree starts with a single node (a 3-tuple):
|
649 |
+
beam_queue.add(
|
650 |
+
node=(g, string, p.txt()), val=0.0 # value of the root node is simply 0.
|
651 |
+
)
|
652 |
+
|
653 |
+
pool = None
|
654 |
+
if _N_WORKSERS.value == 1:
|
655 |
+
bqsearch_init()
|
656 |
+
else:
|
657 |
+
pool = multiprocessing.Pool(_N_WORKSERS.value, bqsearch_init)
|
658 |
+
|
659 |
+
for depth in range(search_depth):
|
660 |
+
logging.info(
|
661 |
+
'Depth %s. There are %i nodes to expand:', depth, len(beam_queue)
|
662 |
+
)
|
663 |
+
for _, (_, string, _) in beam_queue:
|
664 |
+
logging.info(string)
|
665 |
+
|
666 |
+
new_queue = BeamQueue(max_size=beam_size) # to replace beam_queue.
|
667 |
+
if _N_WORKSERS.value==1:
|
668 |
+
for i, srch_inputs in enumerate(beam_queue):
|
669 |
+
_, solved, res = bqsearch(i, srch_inputs, out_file)
|
670 |
+
if solved:
|
671 |
+
return True
|
672 |
+
for node, val in res:
|
673 |
+
# Add the candidate to the beam queue.
|
674 |
+
new_queue.add(node, val)
|
675 |
+
# Note that the queue only maintain at most beam_size nodes
|
676 |
+
# so this new node might possibly be dropped depending on its value.
|
677 |
+
else:
|
678 |
+
jobs = [pool.apply_async(bqsearch, (i, srch_inputs, out_file)) for i, srch_inputs in enumerate(beam_queue)]
|
679 |
+
|
680 |
+
n_done = 0
|
681 |
+
while n_done < len(beam_queue):
|
682 |
+
for i, jobres in enumerate(jobs):
|
683 |
+
if jobres and jobres.ready():
|
684 |
+
n_done += 1
|
685 |
+
jobs[i] = None
|
686 |
+
_, solved, res = jobres.get()
|
687 |
+
if solved:
|
688 |
+
# Clean up resources
|
689 |
+
pool.terminate()
|
690 |
+
pool.join()
|
691 |
+
return True
|
692 |
+
for node, val in res:
|
693 |
+
# Add the candidate to the beam queue.
|
694 |
+
new_queue.add(node, val)
|
695 |
+
# Note that the queue only maintain at most beam_size nodes
|
696 |
+
# so this new node might possibly be dropped depending on its value.
|
697 |
+
time.sleep(1) # Adjust wait time as needed
|
698 |
+
|
699 |
+
# replace the old queue with new queue before the new proof search depth.
|
700 |
+
beam_queue = new_queue
|
701 |
+
|
702 |
+
# Clean up resources
|
703 |
+
if pool:
|
704 |
+
pool.terminate()
|
705 |
+
pool.join()
|
706 |
+
return False
|
707 |
+
|
708 |
+
def main(_):
|
709 |
+
global DEFINITIONS
|
710 |
+
global RULES
|
711 |
+
|
712 |
+
# definitions of terms used in our domain-specific language.
|
713 |
+
DEFINITIONS = pr.Definition.from_txt_file(_DEFS_FILE.value, to_dict=True)
|
714 |
+
# load inference rules used in DD.
|
715 |
+
RULES = pr.Theorem.from_txt_file(_RULES_FILE.value, to_dict=True)
|
716 |
+
|
717 |
+
# when using the language model,
|
718 |
+
# point names will be renamed to alphabetical a, b, c, d, e, ...
|
719 |
+
# instead of staying with their original names,
|
720 |
+
# in order to match the synthetic training data generation.
|
721 |
+
need_rename = _MODE.value != 'ddar'
|
722 |
+
|
723 |
+
# load problems from the problems_file,
|
724 |
+
problems = pr.Problem.from_txt_file(
|
725 |
+
_PROBLEMS_FILE.value, to_dict=True, translate=need_rename
|
726 |
+
)
|
727 |
+
|
728 |
+
if _PROBLEM_NAME.value not in problems:
|
729 |
+
raise ValueError(
|
730 |
+
f'Problem name `{_PROBLEM_NAME.value}` '
|
731 |
+
+ f'not found in `{_PROBLEMS_FILE.value}`'
|
732 |
+
)
|
733 |
+
|
734 |
+
this_problem = problems[_PROBLEM_NAME.value]
|
735 |
+
|
736 |
+
if _MODE.value == 'ddar':
|
737 |
+
g, _ = gh.Graph.build_problem(this_problem, DEFINITIONS)
|
738 |
+
run_ddar(g, this_problem, _OUT_FILE.value)
|
739 |
+
|
740 |
+
elif _MODE.value == 'alphageometry':
|
741 |
+
#XX model = get_lm(_CKPT_PATH.value, _VOCAB_PATH.value)
|
742 |
+
run_alphageometry(
|
743 |
+
#XX model,
|
744 |
+
this_problem,
|
745 |
+
_SEARCH_DEPTH.value,
|
746 |
+
_BEAM_SIZE.value,
|
747 |
+
_OUT_FILE.value,
|
748 |
+
)
|
749 |
+
|
750 |
+
else:
|
751 |
+
raise ValueError(f'Unknown FLAGS.mode: {_MODE.value}')
|
752 |
+
|
753 |
+
|
754 |
+
if __name__ == '__main__':
|
755 |
+
app.run(main)
|
ag4masses/alphageometry/alphageometry_test.py
ADDED
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright 2023 DeepMind Technologies Limited
|
2 |
+
#
|
3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4 |
+
# you may not use this file except in compliance with the License.
|
5 |
+
# You may obtain a copy of the License at
|
6 |
+
#
|
7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8 |
+
#
|
9 |
+
# Unless required by applicable law or agreed to in writing, software
|
10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
+
# See the License for the specific language governing permissions and
|
13 |
+
# limitations under the License.
|
14 |
+
# ==============================================================================
|
15 |
+
|
16 |
+
"""Unit tests for alphageometry.py."""
|
17 |
+
|
18 |
+
import unittest
|
19 |
+
|
20 |
+
from absl.testing import absltest
|
21 |
+
import alphageometry
|
22 |
+
|
23 |
+
|
24 |
+
class AlphaGeometryTest(unittest.TestCase):
|
25 |
+
|
26 |
+
def test_translate_constrained_to_constructive(self):
|
27 |
+
self.assertEqual(
|
28 |
+
alphageometry.translate_constrained_to_constructive(
|
29 |
+
'd', 'T', list('addb')
|
30 |
+
),
|
31 |
+
('on_dia', ['d', 'b', 'a']),
|
32 |
+
)
|
33 |
+
self.assertEqual(
|
34 |
+
alphageometry.translate_constrained_to_constructive(
|
35 |
+
'd', 'T', list('adbc')
|
36 |
+
),
|
37 |
+
('on_tline', ['d', 'a', 'b', 'c']),
|
38 |
+
)
|
39 |
+
self.assertEqual(
|
40 |
+
alphageometry.translate_constrained_to_constructive(
|
41 |
+
'd', 'P', list('bcda')
|
42 |
+
),
|
43 |
+
('on_pline', ['d', 'a', 'b', 'c']),
|
44 |
+
)
|
45 |
+
self.assertEqual(
|
46 |
+
alphageometry.translate_constrained_to_constructive(
|
47 |
+
'd', 'D', list('bdcd')
|
48 |
+
),
|
49 |
+
('on_bline', ['d', 'c', 'b']),
|
50 |
+
)
|
51 |
+
self.assertEqual(
|
52 |
+
alphageometry.translate_constrained_to_constructive(
|
53 |
+
'd', 'D', list('bdcb')
|
54 |
+
),
|
55 |
+
('on_circle', ['d', 'b', 'c']),
|
56 |
+
)
|
57 |
+
self.assertEqual(
|
58 |
+
alphageometry.translate_constrained_to_constructive(
|
59 |
+
'd', 'D', list('bacd')
|
60 |
+
),
|
61 |
+
('eqdistance', ['d', 'c', 'b', 'a']),
|
62 |
+
)
|
63 |
+
self.assertEqual(
|
64 |
+
alphageometry.translate_constrained_to_constructive(
|
65 |
+
'd', 'C', list('bad')
|
66 |
+
),
|
67 |
+
('on_line', ['d', 'b', 'a']),
|
68 |
+
)
|
69 |
+
self.assertEqual(
|
70 |
+
alphageometry.translate_constrained_to_constructive(
|
71 |
+
'd', 'C', list('bad')
|
72 |
+
),
|
73 |
+
('on_line', ['d', 'b', 'a']),
|
74 |
+
)
|
75 |
+
self.assertEqual(
|
76 |
+
alphageometry.translate_constrained_to_constructive(
|
77 |
+
'd', 'O', list('abcd')
|
78 |
+
),
|
79 |
+
('on_circum', ['d', 'a', 'b', 'c']),
|
80 |
+
)
|
81 |
+
|
82 |
+
def test_insert_aux_to_premise(self):
|
83 |
+
pstring = 'a b c = triangle a b c; d = on_tline d b a c, on_tline d c a b ? perp a d b c' # pylint: disable=line-too-long
|
84 |
+
auxstring = 'e = on_line e a c, on_line e b d'
|
85 |
+
|
86 |
+
target = 'a b c = triangle a b c; d = on_tline d b a c, on_tline d c a b; e = on_line e a c, on_line e b d ? perp a d b c' # pylint: disable=line-too-long
|
87 |
+
self.assertEqual(
|
88 |
+
alphageometry.insert_aux_to_premise(pstring, auxstring), target
|
89 |
+
)
|
90 |
+
|
91 |
+
def test_beam_queue(self):
|
92 |
+
beam_queue = alphageometry.BeamQueue(max_size=2)
|
93 |
+
|
94 |
+
beam_queue.add('a', 1)
|
95 |
+
beam_queue.add('b', 2)
|
96 |
+
beam_queue.add('c', 3)
|
97 |
+
|
98 |
+
beam_queue = list(beam_queue)
|
99 |
+
self.assertEqual(beam_queue, [(3, 'c'), (2, 'b')])
|
100 |
+
|
101 |
+
|
102 |
+
if __name__ == '__main__':
|
103 |
+
absltest.main()
|
ag4masses/alphageometry/ar.py
ADDED
@@ -0,0 +1,752 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright 2023 DeepMind Technologies Limited
|
2 |
+
#
|
3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4 |
+
# you may not use this file except in compliance with the License.
|
5 |
+
# You may obtain a copy of the License at
|
6 |
+
#
|
7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8 |
+
#
|
9 |
+
# Unless required by applicable law or agreed to in writing, software
|
10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
+
# See the License for the specific language governing permissions and
|
13 |
+
# limitations under the License.
|
14 |
+
# ==============================================================================
|
15 |
+
|
16 |
+
"""Implementing Algebraic Reasoning (AR)."""
|
17 |
+
|
18 |
+
from collections import defaultdict # pylint: disable=g-importing-member
|
19 |
+
from fractions import Fraction as frac # pylint: disable=g-importing-member
|
20 |
+
from typing import Any, Generator
|
21 |
+
|
22 |
+
import geometry as gm
|
23 |
+
import numpy as np
|
24 |
+
import problem as pr
|
25 |
+
from scipy import optimize
|
26 |
+
|
27 |
+
|
28 |
+
class InfQuotientError(Exception):
|
29 |
+
pass
|
30 |
+
|
31 |
+
|
32 |
+
def _gcd(x: int, y: int) -> int:
|
33 |
+
while y:
|
34 |
+
x, y = y, x % y
|
35 |
+
return x
|
36 |
+
|
37 |
+
|
38 |
+
def simplify(n: int, d: int) -> tuple[int, int]:
|
39 |
+
g = _gcd(n, d)
|
40 |
+
return (n // g, d // g)
|
41 |
+
|
42 |
+
|
43 |
+
# maximum denominator for a fraction.
|
44 |
+
MAX_DENOMINATOR = 1000000
|
45 |
+
|
46 |
+
# tolerance for fraction approximation
|
47 |
+
TOL = 1e-15
|
48 |
+
|
49 |
+
|
50 |
+
def get_quotient(v: float) -> tuple[int, int]:
|
51 |
+
n = v
|
52 |
+
d = 1
|
53 |
+
while abs(n - round(n)) > TOL:
|
54 |
+
d += 1
|
55 |
+
n += v
|
56 |
+
if d > MAX_DENOMINATOR:
|
57 |
+
e = InfQuotientError(v)
|
58 |
+
raise e
|
59 |
+
|
60 |
+
n = int(round(n))
|
61 |
+
return simplify(n, d)
|
62 |
+
|
63 |
+
|
64 |
+
def fix_v(v: float) -> float:
|
65 |
+
n, d = get_quotient(v)
|
66 |
+
return n / d
|
67 |
+
|
68 |
+
|
69 |
+
def fix(e: dict[str, float]) -> dict[str, float]:
|
70 |
+
return {k: fix_v(v) for k, v in e.items()}
|
71 |
+
|
72 |
+
|
73 |
+
def frac_string(f: frac) -> str:
|
74 |
+
n, d = get_quotient(f)
|
75 |
+
return f'{n}/{d}'
|
76 |
+
|
77 |
+
|
78 |
+
def hashed(e: dict[str, float]) -> tuple[tuple[str, float], ...]:
|
79 |
+
return tuple(sorted(list(e.items())))
|
80 |
+
|
81 |
+
|
82 |
+
def is_zero(e: dict[str, float]) -> bool:
|
83 |
+
return len(strip(e)) == 0 # pylint: disable=g-explicit-length-test
|
84 |
+
|
85 |
+
|
86 |
+
def strip(e: dict[str, float]) -> dict[str, float]:
|
87 |
+
return {v: c for v, c in e.items() if c != 0}
|
88 |
+
|
89 |
+
|
90 |
+
def plus(e1: dict[str, float], e2: dict[str, float]) -> dict[str, float]:
|
91 |
+
e = dict(e1)
|
92 |
+
for v, c in e2.items():
|
93 |
+
if v in e:
|
94 |
+
e[v] += c
|
95 |
+
else:
|
96 |
+
e[v] = c
|
97 |
+
return strip(e)
|
98 |
+
|
99 |
+
|
100 |
+
def plus_all(*es: list[dict[str, float]]) -> dict[str, float]:
|
101 |
+
result = {}
|
102 |
+
for e in es:
|
103 |
+
result = plus(result, e)
|
104 |
+
return result
|
105 |
+
|
106 |
+
|
107 |
+
def mult(e: dict[str, float], m: float) -> dict[str, float]:
|
108 |
+
return {v: m * c for v, c in e.items()}
|
109 |
+
|
110 |
+
|
111 |
+
def minus(e1: dict[str, float], e2: dict[str, float]) -> dict[str, float]:
|
112 |
+
return plus(e1, mult(e2, -1))
|
113 |
+
|
114 |
+
|
115 |
+
def div(e1: dict[str, float], e2: dict[str, float]) -> float:
|
116 |
+
"""Divide e1 by e2."""
|
117 |
+
e1 = strip(e1)
|
118 |
+
e2 = strip(e2)
|
119 |
+
if set(e1.keys()) != set(e2.keys()):
|
120 |
+
return None
|
121 |
+
|
122 |
+
n, d = None, None
|
123 |
+
|
124 |
+
for v, c1 in e1.items():
|
125 |
+
c2 = e2[v] # we want c1/c2 = n/d => c1*d=c2*n
|
126 |
+
if n is not None and c1 * d != c2 * n:
|
127 |
+
return None
|
128 |
+
n, d = c1, c2
|
129 |
+
return frac(n) / frac(d)
|
130 |
+
|
131 |
+
|
132 |
+
def recon(e: dict[str, float], const: str) -> tuple[str, dict[str, float]]:
|
133 |
+
"""Reconcile one variable in the expression e=0, given const."""
|
134 |
+
e = strip(e)
|
135 |
+
if len(e) == 0: # pylint: disable=g-explicit-length-test
|
136 |
+
return None
|
137 |
+
|
138 |
+
v0 = None
|
139 |
+
for v in e:
|
140 |
+
if v != const:
|
141 |
+
v0 = v
|
142 |
+
break
|
143 |
+
if v0 is None:
|
144 |
+
return v0
|
145 |
+
|
146 |
+
c0 = e.pop(v0)
|
147 |
+
return v0, {v: -c / c0 for v, c in e.items()}
|
148 |
+
|
149 |
+
|
150 |
+
def replace(
|
151 |
+
e: dict[str, float], v0: str, e0: dict[str, float]
|
152 |
+
) -> dict[str, float]:
|
153 |
+
if v0 not in e:
|
154 |
+
return e
|
155 |
+
e = dict(e)
|
156 |
+
m = e.pop(v0)
|
157 |
+
return plus(e, mult(e0, m))
|
158 |
+
|
159 |
+
|
160 |
+
def comb2(elems: list[Any]) -> Generator[tuple[Any, Any], None, None]:
|
161 |
+
if len(elems) < 1:
|
162 |
+
return
|
163 |
+
for i, e1 in enumerate(elems[:-1]):
|
164 |
+
for e2 in elems[i + 1 :]:
|
165 |
+
yield e1, e2
|
166 |
+
|
167 |
+
|
168 |
+
def perm2(elems: list[Any]) -> Generator[tuple[Any, Any], None, None]:
|
169 |
+
for e1, e2 in comb2(elems):
|
170 |
+
yield e1, e2
|
171 |
+
yield e2, e1
|
172 |
+
|
173 |
+
|
174 |
+
def chain2(elems: list[Any]) -> Generator[tuple[Any, Any], None, None]:
|
175 |
+
if len(elems) < 2:
|
176 |
+
return
|
177 |
+
for i, e1 in enumerate(elems[:-1]):
|
178 |
+
yield e1, elems[i + 1]
|
179 |
+
|
180 |
+
|
181 |
+
def update_groups(
|
182 |
+
groups1: list[Any], groups2: list[Any]
|
183 |
+
) -> tuple[list[Any], list[tuple[Any, Any]], list[list[Any]]]:
|
184 |
+
"""Update groups of equivalent elements.
|
185 |
+
|
186 |
+
Given groups1 = [set1, set2, set3, ..]
|
187 |
+
where all elems within each set_i is defined to be "equivalent" to each other.
|
188 |
+
(but not across the sets)
|
189 |
+
|
190 |
+
Incoming groups2 = [set1, set2, ...] similar to set1 - it is the
|
191 |
+
additional equivalent information on elements in groups1.
|
192 |
+
|
193 |
+
Return the new updated groups1 and the set of links
|
194 |
+
that make it that way.
|
195 |
+
|
196 |
+
Example:
|
197 |
+
groups1 = [{1, 2}, {3, 4, 5}, {6, 7}]
|
198 |
+
groups2 = [{2, 3, 8}, {9, 10, 11}]
|
199 |
+
|
200 |
+
=> new groups1 and links:
|
201 |
+
groups1 = [{1, 2, 3, 4, 5, 8}, {6, 7}, {9, 10, 11}]
|
202 |
+
links = (2, 3), (3, 8), (9, 10), (10, 11)
|
203 |
+
|
204 |
+
Explain: since groups2 says 2 and 3 are equivalent (with {2, 3, 8}),
|
205 |
+
then {1, 2} and {3, 4, 5} in groups1 will be merged,
|
206 |
+
because 2 and 3 each belong to those 2 groups.
|
207 |
+
Additionally 8 also belong to this same group.
|
208 |
+
{3, 4, 5} is left alone, while {9, 10, 11} is a completely new set.
|
209 |
+
|
210 |
+
The links to make this all happens is:
|
211 |
+
(2, 3): to merge {1, 2} and {3, 4, 5}
|
212 |
+
(3, 8): to link 8 into the merged({1, 2, 3, 4, 5})
|
213 |
+
(9, 10) and (10, 11): to make the new group {9, 10, 11}
|
214 |
+
|
215 |
+
Args:
|
216 |
+
groups1: a list of sets.
|
217 |
+
groups2: a list of sets.
|
218 |
+
|
219 |
+
Returns:
|
220 |
+
groups1, links, history: result of the update.
|
221 |
+
"""
|
222 |
+
history = []
|
223 |
+
links = []
|
224 |
+
for g2 in groups2:
|
225 |
+
joins = [None] * len(groups1) # mark which one in groups1 is merged
|
226 |
+
merged_g1 = set() # merge them into this.
|
227 |
+
old = None # any elem in g2 that belong to any set in groups1 (old)
|
228 |
+
new = [] # all elem in g2 that is new
|
229 |
+
|
230 |
+
for e in g2:
|
231 |
+
found = False
|
232 |
+
for i, g1 in enumerate(groups1):
|
233 |
+
if e not in g1:
|
234 |
+
continue
|
235 |
+
|
236 |
+
found = True
|
237 |
+
if joins[i]:
|
238 |
+
continue
|
239 |
+
|
240 |
+
joins[i] = True
|
241 |
+
merged_g1.update(g1)
|
242 |
+
|
243 |
+
if old is not None:
|
244 |
+
links.append((old, e)) # link to make merging happen.
|
245 |
+
old = e
|
246 |
+
|
247 |
+
if not found: # e is new!
|
248 |
+
new.append(e)
|
249 |
+
|
250 |
+
# now chain elems in new together.
|
251 |
+
if old is not None and new:
|
252 |
+
links.append((old, new[0]))
|
253 |
+
merged_g1.update(new)
|
254 |
+
|
255 |
+
links += chain2(new)
|
256 |
+
|
257 |
+
new_groups1 = []
|
258 |
+
if merged_g1: # put the merged_g1 in first
|
259 |
+
new_groups1.append(merged_g1)
|
260 |
+
|
261 |
+
# put the remaining (unjoined) groups in
|
262 |
+
new_groups1 += [g1 for j, g1 in zip(joins, groups1) if not j]
|
263 |
+
|
264 |
+
if old is None and new:
|
265 |
+
new_groups1 += [set(new)]
|
266 |
+
|
267 |
+
groups1 = new_groups1
|
268 |
+
history.append(groups1)
|
269 |
+
|
270 |
+
return groups1, links, history
|
271 |
+
|
272 |
+
|
273 |
+
class Table:
|
274 |
+
"""The coefficient matrix."""
|
275 |
+
|
276 |
+
def __init__(self, const: str = '1'):
|
277 |
+
self.const = const
|
278 |
+
self.v2e = {}
|
279 |
+
self.add_free(const) # the table {var: expression}
|
280 |
+
|
281 |
+
# to cache what is already derived/inputted
|
282 |
+
self.eqs = set()
|
283 |
+
self.groups = [] # groups of equal pairs.
|
284 |
+
|
285 |
+
# for why (linprog)
|
286 |
+
self.c = []
|
287 |
+
self.v2i = {} # v -> index of row in A.
|
288 |
+
self.deps = [] # equal number of columns.
|
289 |
+
self.A = np.zeros([0, 0]) # pylint: disable=invalid-name
|
290 |
+
self.do_why = True
|
291 |
+
|
292 |
+
def add_free(self, v: str) -> None:
|
293 |
+
self.v2e[v] = {v: frac(1)}
|
294 |
+
|
295 |
+
def replace(self, v0: str, e0: dict[str, float]) -> None:
|
296 |
+
for v, e in list(self.v2e.items()):
|
297 |
+
self.v2e[v] = replace(e, v0, e0)
|
298 |
+
|
299 |
+
def add_expr(self, vc: list[tuple[str, float]]) -> bool:
|
300 |
+
"""Add a new equality, represented by the list of tuples vc=[(v, c), ..]."""
|
301 |
+
result = {}
|
302 |
+
free = []
|
303 |
+
|
304 |
+
for v, c in vc:
|
305 |
+
c = frac(c)
|
306 |
+
if v in self.v2e:
|
307 |
+
result = plus(result, mult(self.v2e[v], c))
|
308 |
+
else:
|
309 |
+
free += [(v, c)]
|
310 |
+
|
311 |
+
if free == []: # pylint: disable=g-explicit-bool-comparison
|
312 |
+
if is_zero(self.modulo(result)):
|
313 |
+
return False
|
314 |
+
result = recon(result, self.const)
|
315 |
+
if result is None:
|
316 |
+
return False
|
317 |
+
v, e = result
|
318 |
+
self.replace(v, e)
|
319 |
+
|
320 |
+
elif len(free) == 1:
|
321 |
+
v, m = free[0]
|
322 |
+
self.v2e[v] = mult(result, frac(-1, m))
|
323 |
+
|
324 |
+
else:
|
325 |
+
dependent_v = None
|
326 |
+
for v, m in free:
|
327 |
+
if dependent_v is None and v != self.const:
|
328 |
+
dependent_v = (v, m)
|
329 |
+
continue
|
330 |
+
|
331 |
+
self.add_free(v)
|
332 |
+
result = plus(result, {v: m})
|
333 |
+
|
334 |
+
v, m = dependent_v
|
335 |
+
self.v2e[v] = mult(result, frac(-1, m))
|
336 |
+
|
337 |
+
return True
|
338 |
+
|
339 |
+
def register(self, vc: list[tuple[str, float]], dep: pr.Dependency) -> None:
|
340 |
+
"""Register a new equality vc=[(v, c), ..] with traceback dependency dep."""
|
341 |
+
result = plus_all(*[{v: c} for v, c in vc])
|
342 |
+
if is_zero(result):
|
343 |
+
return
|
344 |
+
|
345 |
+
vs, _ = zip(*vc)
|
346 |
+
for v in vs:
|
347 |
+
if v not in self.v2i:
|
348 |
+
self.v2i[v] = len(self.v2i)
|
349 |
+
|
350 |
+
(m, n), l = self.A.shape, len(self.v2i)
|
351 |
+
if l > m:
|
352 |
+
self.A = np.concatenate([self.A, np.zeros([l - m, n])], 0)
|
353 |
+
|
354 |
+
new_column = np.zeros([len(self.v2i), 2]) # N, 2
|
355 |
+
for v, c in vc:
|
356 |
+
new_column[self.v2i[v], 0] += float(c)
|
357 |
+
new_column[self.v2i[v], 1] -= float(c)
|
358 |
+
|
359 |
+
self.A = np.concatenate([self.A, new_column], 1)
|
360 |
+
self.c += [1.0, -1.0]
|
361 |
+
self.deps += [dep]
|
362 |
+
|
363 |
+
def register2(
|
364 |
+
self, a: str, b: str, m: float, n: float, dep: pr.Dependency
|
365 |
+
) -> None:
|
366 |
+
self.register([(a, m), (b, -n)], dep)
|
367 |
+
|
368 |
+
def register3(self, a: str, b: str, f: float, dep: pr.Dependency) -> None:
|
369 |
+
self.register([(a, 1), (b, -1), (self.const, -f)], dep)
|
370 |
+
|
371 |
+
def register4(
|
372 |
+
self, a: str, b: str, c: str, d: str, dep: pr.Dependency
|
373 |
+
) -> None:
|
374 |
+
self.register([(a, 1), (b, -1), (c, -1), (d, 1)], dep)
|
375 |
+
|
376 |
+
def why(self, e: dict[str, float]) -> list[Any]:
|
377 |
+
"""AR traceback == MILP."""
|
378 |
+
if not self.do_why:
|
379 |
+
return []
|
380 |
+
# why expr == 0?
|
381 |
+
# Solve min(c^Tx) s.t. A_eq * x = b_eq, x >= 0
|
382 |
+
e = strip(e)
|
383 |
+
if not e:
|
384 |
+
return []
|
385 |
+
|
386 |
+
b_eq = [0] * len(self.v2i)
|
387 |
+
for v, c in e.items():
|
388 |
+
b_eq[self.v2i[v]] += float(c)
|
389 |
+
|
390 |
+
try:
|
391 |
+
x = optimize.linprog(c=self.c, A_eq=self.A, b_eq=b_eq, method='highs')[
|
392 |
+
'x'
|
393 |
+
]
|
394 |
+
except: # pylint: disable=bare-except
|
395 |
+
x = optimize.linprog(
|
396 |
+
c=self.c,
|
397 |
+
A_eq=self.A,
|
398 |
+
b_eq=b_eq,
|
399 |
+
)['x']
|
400 |
+
|
401 |
+
deps = []
|
402 |
+
for i, dep in enumerate(self.deps):
|
403 |
+
if x[2 * i] > 1e-12 or x[2 * i + 1] > 1e-12:
|
404 |
+
if dep not in deps:
|
405 |
+
deps.append(dep)
|
406 |
+
return deps
|
407 |
+
|
408 |
+
def record_eq(self, v1: str, v2: str, v3: str, v4: str) -> None:
|
409 |
+
self.eqs.add((v1, v2, v3, v4))
|
410 |
+
self.eqs.add((v2, v1, v4, v3))
|
411 |
+
self.eqs.add((v3, v4, v1, v2))
|
412 |
+
self.eqs.add((v4, v3, v2, v1))
|
413 |
+
|
414 |
+
def check_record_eq(self, v1: str, v2: str, v3: str, v4: str) -> bool:
|
415 |
+
if (v1, v2, v3, v4) in self.eqs:
|
416 |
+
return True
|
417 |
+
if (v2, v1, v4, v3) in self.eqs:
|
418 |
+
return True
|
419 |
+
if (v3, v4, v1, v2) in self.eqs:
|
420 |
+
return True
|
421 |
+
if (v4, v3, v2, v1) in self.eqs:
|
422 |
+
return True
|
423 |
+
return False
|
424 |
+
|
425 |
+
def add_eq2(
|
426 |
+
self, a: str, b: str, m: float, n: float, dep: pr.Dependency
|
427 |
+
) -> None:
|
428 |
+
# a/b = m/n
|
429 |
+
if not self.add_expr([(a, n), (b, -m)]):
|
430 |
+
return []
|
431 |
+
self.register2(a, b, m, n, dep)
|
432 |
+
|
433 |
+
def add_eq3(self, a: str, b: str, f: float, dep: pr.Dependency) -> None:
|
434 |
+
# a - b = f * constant
|
435 |
+
self.eqs.add((a, b, frac(f)))
|
436 |
+
self.eqs.add((b, a, frac(1 - f)))
|
437 |
+
|
438 |
+
if not self.add_expr([(a, 1), (b, -1), (self.const, -f)]):
|
439 |
+
return []
|
440 |
+
|
441 |
+
self.register3(a, b, f, dep)
|
442 |
+
|
443 |
+
def add_eq4(self, a: str, b: str, c: str, d: str, dep: pr.Dependency) -> None:
|
444 |
+
# a - b = c - d
|
445 |
+
self.record_eq(a, b, c, d)
|
446 |
+
self.record_eq(a, c, b, d)
|
447 |
+
|
448 |
+
expr = list(minus({a: 1, b: -1}, {c: 1, d: -1}).items())
|
449 |
+
|
450 |
+
if not self.add_expr(expr):
|
451 |
+
return []
|
452 |
+
|
453 |
+
self.register4(a, b, c, d, dep)
|
454 |
+
self.groups, _, _ = update_groups(
|
455 |
+
self.groups, [{(a, b), (c, d)}, {(b, a), (d, c)}]
|
456 |
+
)
|
457 |
+
|
458 |
+
def pairs(self) -> Generator[list[tuple[str, str]], None, None]:
|
459 |
+
for v1, v2 in perm2(list(self.v2e.keys())): # pylint: disable=g-builtin-op
|
460 |
+
if v1 == self.const or v2 == self.const:
|
461 |
+
continue
|
462 |
+
yield v1, v2
|
463 |
+
|
464 |
+
def modulo(self, e: dict[str, float]) -> dict[str, float]:
|
465 |
+
return strip(e)
|
466 |
+
|
467 |
+
def get_all_eqs(
|
468 |
+
self,
|
469 |
+
) -> dict[tuple[tuple[str, float], ...], list[tuple[str, str]]]:
|
470 |
+
h2pairs = defaultdict(list)
|
471 |
+
for v1, v2 in self.pairs():
|
472 |
+
e1, e2 = self.v2e[v1], self.v2e[v2]
|
473 |
+
e12 = minus(e1, e2)
|
474 |
+
h12 = hashed(self.modulo(e12))
|
475 |
+
h2pairs[h12].append((v1, v2))
|
476 |
+
return h2pairs
|
477 |
+
|
478 |
+
def get_all_eqs_and_why(
|
479 |
+
self, return_quads: bool = True
|
480 |
+
) -> Generator[Any, None, None]:
|
481 |
+
"""Check all 4/3/2-permutations for new equalities."""
|
482 |
+
groups = []
|
483 |
+
|
484 |
+
for h, vv in self.get_all_eqs().items():
|
485 |
+
if h == (): # pylint: disable=g-explicit-bool-comparison
|
486 |
+
for v1, v2 in vv:
|
487 |
+
if (v1, v2) in self.eqs or (v2, v1) in self.eqs:
|
488 |
+
continue
|
489 |
+
self.eqs.add((v1, v2))
|
490 |
+
# why v1 - v2 = e12 ? (note modulo(e12) == 0)
|
491 |
+
why_dict = minus({v1: 1, v2: -1}, minus(self.v2e[v1], self.v2e[v2]))
|
492 |
+
yield v1, v2, self.why(why_dict)
|
493 |
+
continue
|
494 |
+
|
495 |
+
if len(h) == 1 and h[0][0] == self.const:
|
496 |
+
for v1, v2 in vv:
|
497 |
+
frac = h[0][1] # pylint: disable=redefined-outer-name
|
498 |
+
if (v1, v2, frac) in self.eqs:
|
499 |
+
continue
|
500 |
+
self.eqs.add((v1, v2, frac))
|
501 |
+
# why v1 - v2 = e12 ? (note modulo(e12) == 0)
|
502 |
+
why_dict = minus({v1: 1, v2: -1}, minus(self.v2e[v1], self.v2e[v2]))
|
503 |
+
value = simplify(frac.numerator, frac.denominator)
|
504 |
+
yield v1, v2, value, self.why(why_dict)
|
505 |
+
continue
|
506 |
+
|
507 |
+
groups.append(vv)
|
508 |
+
|
509 |
+
if not return_quads:
|
510 |
+
return
|
511 |
+
|
512 |
+
self.groups, links, _ = update_groups(self.groups, groups)
|
513 |
+
for (v1, v2), (v3, v4) in links:
|
514 |
+
if self.check_record_eq(v1, v2, v3, v4):
|
515 |
+
continue
|
516 |
+
e12 = minus(self.v2e[v1], self.v2e[v2])
|
517 |
+
e34 = minus(self.v2e[v3], self.v2e[v4])
|
518 |
+
|
519 |
+
why_dict = minus( # why (v1-v2)-(v3-v4)=e12-e34?
|
520 |
+
minus({v1: 1, v2: -1}, {v3: 1, v4: -1}), minus(e12, e34)
|
521 |
+
)
|
522 |
+
self.record_eq(v1, v2, v3, v4)
|
523 |
+
yield v1, v2, v3, v4, self.why(why_dict)
|
524 |
+
|
525 |
+
|
526 |
+
class GeometricTable(Table):
|
527 |
+
"""Abstract class representing the coefficient matrix (table) A."""
|
528 |
+
|
529 |
+
def __init__(self, name: str = ''):
|
530 |
+
super().__init__(name)
|
531 |
+
self.v2obj = {}
|
532 |
+
|
533 |
+
def get_name(self, objs: list[Any]) -> list[str]:
|
534 |
+
self.v2obj.update({o.name: o for o in objs})
|
535 |
+
return [o.name for o in objs]
|
536 |
+
|
537 |
+
def map2obj(self, names: list[str]) -> list[Any]:
|
538 |
+
return [self.v2obj[n] for n in names]
|
539 |
+
|
540 |
+
def get_all_eqs_and_why(
|
541 |
+
self, return_quads: bool
|
542 |
+
) -> Generator[Any, None, None]:
|
543 |
+
for out in super().get_all_eqs_and_why(return_quads):
|
544 |
+
if len(out) == 3:
|
545 |
+
x, y, why = out
|
546 |
+
x, y = self.map2obj([x, y])
|
547 |
+
yield x, y, why
|
548 |
+
if len(out) == 4:
|
549 |
+
x, y, f, why = out
|
550 |
+
x, y = self.map2obj([x, y])
|
551 |
+
yield x, y, f, why
|
552 |
+
if len(out) == 5:
|
553 |
+
a, b, x, y, why = out
|
554 |
+
a, b, x, y = self.map2obj([a, b, x, y])
|
555 |
+
yield a, b, x, y, why
|
556 |
+
|
557 |
+
|
558 |
+
class RatioTable(GeometricTable):
|
559 |
+
"""Coefficient matrix A for log(distance)."""
|
560 |
+
|
561 |
+
def __init__(self, name: str = ''):
|
562 |
+
name = name or '1'
|
563 |
+
super().__init__(name)
|
564 |
+
self.one = self.const
|
565 |
+
|
566 |
+
def add_eq(self, l1: gm.Length, l2: gm.Length, dep: pr.Dependency) -> None:
|
567 |
+
l1, l2 = self.get_name([l1, l2])
|
568 |
+
return super().add_eq3(l1, l2, 0.0, dep)
|
569 |
+
|
570 |
+
def add_const_ratio(
|
571 |
+
self, l1: gm.Length, l2: gm.Length, m: float, n: float, dep: pr.Dependency
|
572 |
+
) -> None:
|
573 |
+
l1, l2 = self.get_name([l1, l2])
|
574 |
+
return super().add_eq2(l1, l2, m, n, dep)
|
575 |
+
|
576 |
+
def add_eqratio(
|
577 |
+
self,
|
578 |
+
l1: gm.Length,
|
579 |
+
l2: gm.Length,
|
580 |
+
l3: gm.Length,
|
581 |
+
l4: gm.Length,
|
582 |
+
dep: pr.Dependency,
|
583 |
+
) -> None:
|
584 |
+
l1, l2, l3, l4 = self.get_name([l1, l2, l3, l4])
|
585 |
+
return self.add_eq4(l1, l2, l3, l4, dep)
|
586 |
+
|
587 |
+
def get_all_eqs_and_why(self) -> Generator[Any, None, None]:
|
588 |
+
return super().get_all_eqs_and_why(True)
|
589 |
+
|
590 |
+
|
591 |
+
class AngleTable(GeometricTable):
|
592 |
+
"""Coefficient matrix A for slope(direction)."""
|
593 |
+
|
594 |
+
def __init__(self, name: str = ''):
|
595 |
+
name = name or 'pi'
|
596 |
+
super().__init__(name)
|
597 |
+
self.pi = self.const
|
598 |
+
|
599 |
+
def modulo(self, e: dict[str, float]) -> dict[str, float]:
|
600 |
+
e = strip(e)
|
601 |
+
if self.pi not in e:
|
602 |
+
return super().modulo(e)
|
603 |
+
|
604 |
+
e[self.pi] = e[self.pi] % 1
|
605 |
+
return strip(e)
|
606 |
+
|
607 |
+
def add_para(
|
608 |
+
self, d1: gm.Direction, d2: gm.Direction, dep: pr.Dependency
|
609 |
+
) -> None:
|
610 |
+
return self.add_const_angle(d1, d2, 0, dep)
|
611 |
+
|
612 |
+
def add_const_angle(
|
613 |
+
self, d1: gm.Direction, d2: gm.Direction, ang: float, dep: pr.Dependency
|
614 |
+
) -> None:
|
615 |
+
if ang and d2._obj.num > d1._obj.num: # pylint: disable=protected-access
|
616 |
+
d1, d2 = d2, d1
|
617 |
+
ang = 180 - ang
|
618 |
+
|
619 |
+
d1, d2 = self.get_name([d1, d2])
|
620 |
+
|
621 |
+
num, den = simplify(ang, 180)
|
622 |
+
ang = frac(int(num), int(den))
|
623 |
+
return super().add_eq3(d1, d2, ang, dep)
|
624 |
+
|
625 |
+
def add_eqangle(
|
626 |
+
self,
|
627 |
+
d1: gm.Direction,
|
628 |
+
d2: gm.Direction,
|
629 |
+
d3: gm.Direction,
|
630 |
+
d4: gm.Direction,
|
631 |
+
dep: pr.Dependency,
|
632 |
+
) -> None:
|
633 |
+
"""Add the inequality d1-d2=d3-d4."""
|
634 |
+
# Use string as variables.
|
635 |
+
l1, l2, l3, l4 = [d._obj.num for d in [d1, d2, d3, d4]] # pylint: disable=protected-access
|
636 |
+
d1, d2, d3, d4 = self.get_name([d1, d2, d3, d4])
|
637 |
+
ang1 = {d1: 1, d2: -1}
|
638 |
+
ang2 = {d3: 1, d4: -1}
|
639 |
+
|
640 |
+
if l2 > l1:
|
641 |
+
ang1 = plus({self.pi: 1}, ang1)
|
642 |
+
if l4 > l3:
|
643 |
+
ang2 = plus({self.pi: 1}, ang2)
|
644 |
+
|
645 |
+
ang12 = minus(ang1, ang2)
|
646 |
+
self.record_eq(d1, d2, d3, d4)
|
647 |
+
self.record_eq(d1, d3, d2, d4)
|
648 |
+
|
649 |
+
expr = list(ang12.items())
|
650 |
+
if not self.add_expr(expr):
|
651 |
+
return []
|
652 |
+
|
653 |
+
self.register(expr, dep)
|
654 |
+
|
655 |
+
def get_all_eqs_and_why(self) -> Generator[Any, None, None]:
|
656 |
+
return super().get_all_eqs_and_why(True)
|
657 |
+
|
658 |
+
|
659 |
+
class DistanceTable(GeometricTable):
|
660 |
+
"""Coefficient matrix A for position(point, line)."""
|
661 |
+
|
662 |
+
def __init__(self, name: str = ''):
|
663 |
+
name = name or '1:1'
|
664 |
+
self.merged = {}
|
665 |
+
self.ratios = set()
|
666 |
+
super().__init__(name)
|
667 |
+
|
668 |
+
def pairs(self) -> Generator[tuple[str, str], None, None]:
|
669 |
+
l2vs = defaultdict(list)
|
670 |
+
for v in list(self.v2e.keys()): # pylint: disable=g-builtin-op
|
671 |
+
if v == self.const:
|
672 |
+
continue
|
673 |
+
l, p = v.split(':')
|
674 |
+
l2vs[l].append(p)
|
675 |
+
|
676 |
+
for l, ps in l2vs.items():
|
677 |
+
for p1, p2 in perm2(ps):
|
678 |
+
yield l + ':' + p1, l + ':' + p2
|
679 |
+
|
680 |
+
def name(self, l: gm.Line, p: gm.Point) -> str:
|
681 |
+
v = l.name + ':' + p.name
|
682 |
+
self.v2obj[v] = (l, p)
|
683 |
+
return v
|
684 |
+
|
685 |
+
def map2obj(self, names: list[str]) -> list[gm.Point]:
|
686 |
+
return [self.v2obj[n][1] for n in names]
|
687 |
+
|
688 |
+
def add_cong(
|
689 |
+
self,
|
690 |
+
l12: gm.Line,
|
691 |
+
l34: gm.Line,
|
692 |
+
p1: gm.Point,
|
693 |
+
p2: gm.Point,
|
694 |
+
p3: gm.Point,
|
695 |
+
p4: gm.Point,
|
696 |
+
dep: pr.Dependency,
|
697 |
+
) -> None:
|
698 |
+
"""Add that distance between p1 and p2 (on l12) == p3 and p4 (on l34)."""
|
699 |
+
if p2.num > p1.num:
|
700 |
+
p1, p2 = p2, p1
|
701 |
+
if p4.num > p3.num:
|
702 |
+
p3, p4 = p4, p3
|
703 |
+
|
704 |
+
p1 = self.name(l12, p1)
|
705 |
+
p2 = self.name(l12, p2)
|
706 |
+
p3 = self.name(l34, p3)
|
707 |
+
p4 = self.name(l34, p4)
|
708 |
+
return super().add_eq4(p1, p2, p3, p4, dep)
|
709 |
+
|
710 |
+
def get_all_eqs_and_why(self) -> Generator[Any, None, None]:
|
711 |
+
for x in super().get_all_eqs_and_why(True):
|
712 |
+
yield x
|
713 |
+
|
714 |
+
# Now we figure out all the const ratios.
|
715 |
+
h2pairs = defaultdict(list)
|
716 |
+
for v1, v2 in self.pairs():
|
717 |
+
if (v1, v2) in self.merged:
|
718 |
+
continue
|
719 |
+
e1, e2 = self.v2e[v1], self.v2e[v2]
|
720 |
+
e12 = minus(e1, e2)
|
721 |
+
h12 = hashed(e12)
|
722 |
+
h2pairs[h12].append((v1, v2, e12))
|
723 |
+
|
724 |
+
for (_, vves1), (_, vves2) in perm2(list(h2pairs.items())):
|
725 |
+
v1, v2, e12 = vves1[0]
|
726 |
+
for v1_, v2_, _ in vves1[1:]:
|
727 |
+
self.merged[(v1_, v2_)] = (v1, v2)
|
728 |
+
|
729 |
+
v3, v4, e34 = vves2[0]
|
730 |
+
for v3_, v4_, _ in vves2[1:]:
|
731 |
+
self.merged[(v3_, v4_)] = (v3, v4)
|
732 |
+
|
733 |
+
if (v1, v2, v3, v4) in self.ratios:
|
734 |
+
continue
|
735 |
+
|
736 |
+
d12 = div(e12, e34)
|
737 |
+
if d12 is None or d12 > 1 or d12 < 0:
|
738 |
+
continue
|
739 |
+
|
740 |
+
self.ratios.add((v1, v2, v3, v4))
|
741 |
+
self.ratios.add((v2, v1, v4, v3))
|
742 |
+
|
743 |
+
n, d = d12.numerator, d12.denominator
|
744 |
+
|
745 |
+
# (v1 - v2) * d = (v3 - v4) * n
|
746 |
+
why_dict = minus(
|
747 |
+
minus({v1: d, v2: -d}, {v3: n, v4: -n}),
|
748 |
+
minus(mult(e12, d), mult(e34, n)), # there is no modulo, so this is 0
|
749 |
+
)
|
750 |
+
|
751 |
+
v1, v2, v3, v4 = self.map2obj([v1, v2, v3, v4])
|
752 |
+
yield v1, v2, v3, v4, abs(n), abs(d), self.why(why_dict)
|
ag4masses/alphageometry/ar_test.py
ADDED
@@ -0,0 +1,204 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright 2023 DeepMind Technologies Limited
|
2 |
+
#
|
3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4 |
+
# you may not use this file except in compliance with the License.
|
5 |
+
# You may obtain a copy of the License at
|
6 |
+
#
|
7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8 |
+
#
|
9 |
+
# Unless required by applicable law or agreed to in writing, software
|
10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
+
# See the License for the specific language governing permissions and
|
13 |
+
# limitations under the License.
|
14 |
+
# ==============================================================================
|
15 |
+
|
16 |
+
"""Unit tests for ar.py."""
|
17 |
+
import unittest
|
18 |
+
|
19 |
+
from absl.testing import absltest
|
20 |
+
import ar
|
21 |
+
import graph as gh
|
22 |
+
import problem as pr
|
23 |
+
|
24 |
+
|
25 |
+
class ARTest(unittest.TestCase):
|
26 |
+
|
27 |
+
@classmethod
|
28 |
+
def setUpClass(cls):
|
29 |
+
super().setUpClass()
|
30 |
+
cls.defs = pr.Definition.from_txt_file('defs.txt', to_dict=True)
|
31 |
+
cls.rules = pr.Theorem.from_txt_file('rules.txt', to_dict=True)
|
32 |
+
|
33 |
+
def test_update_groups(self):
|
34 |
+
"""Test for update_groups."""
|
35 |
+
groups1 = [{1, 2}, {3, 4, 5}, {6, 7}]
|
36 |
+
groups2 = [{2, 3, 8}, {9, 10, 11}]
|
37 |
+
|
38 |
+
_, links, history = ar.update_groups(groups1, groups2)
|
39 |
+
self.assertEqual(
|
40 |
+
history,
|
41 |
+
[
|
42 |
+
[{1, 2, 3, 4, 5, 8}, {6, 7}],
|
43 |
+
[{1, 2, 3, 4, 5, 8}, {6, 7}, {9, 10, 11}],
|
44 |
+
],
|
45 |
+
)
|
46 |
+
self.assertEqual(links, [(2, 3), (3, 8), (9, 10), (10, 11)])
|
47 |
+
|
48 |
+
groups1 = [{1, 2}, {3, 4}, {5, 6}, {7, 8}]
|
49 |
+
groups2 = [{2, 3, 8, 9, 10}, {3, 6, 11}]
|
50 |
+
|
51 |
+
_, links, history = ar.update_groups(groups1, groups2)
|
52 |
+
self.assertEqual(
|
53 |
+
history,
|
54 |
+
[
|
55 |
+
[{1, 2, 3, 4, 7, 8, 9, 10}, {5, 6}],
|
56 |
+
[{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}],
|
57 |
+
],
|
58 |
+
)
|
59 |
+
self.assertEqual(links, [(2, 3), (3, 8), (8, 9), (9, 10), (3, 6), (6, 11)])
|
60 |
+
|
61 |
+
groups1 = []
|
62 |
+
groups2 = [{1, 2}, {3, 4}, {5, 6}, {2, 3}]
|
63 |
+
|
64 |
+
_, links, history = ar.update_groups(groups1, groups2)
|
65 |
+
self.assertEqual(
|
66 |
+
history,
|
67 |
+
[
|
68 |
+
[{1, 2}],
|
69 |
+
[{1, 2}, {3, 4}],
|
70 |
+
[{1, 2}, {3, 4}, {5, 6}],
|
71 |
+
[{1, 2, 3, 4}, {5, 6}],
|
72 |
+
],
|
73 |
+
)
|
74 |
+
self.assertEqual(links, [(1, 2), (3, 4), (5, 6), (2, 3)])
|
75 |
+
|
76 |
+
def test_generic_table_simple(self):
|
77 |
+
tb = ar.Table()
|
78 |
+
|
79 |
+
# If a-b = b-c & d-a = c-d
|
80 |
+
tb.add_eq4('a', 'b', 'b', 'c', 'fact1')
|
81 |
+
tb.add_eq4('d', 'a', 'c', 'd', 'fact2')
|
82 |
+
tb.add_eq4('x', 'y', 'z', 't', 'fact3') # distractor fact
|
83 |
+
|
84 |
+
# Then b=d, because {fact1, fact2} but not fact3.
|
85 |
+
result = list(tb.get_all_eqs_and_why())
|
86 |
+
self.assertIn(('b', 'd', ['fact1', 'fact2']), result)
|
87 |
+
|
88 |
+
def test_angle_table_inbisector_exbisector(self):
|
89 |
+
"""Test that AR can figure out bisector & ex-bisector are perpendicular."""
|
90 |
+
# Load the scenario that we have cd is bisector of acb and
|
91 |
+
# ce is the ex-bisector of acb.
|
92 |
+
p = pr.Problem.from_txt(
|
93 |
+
'a b c = triangle a b c; d = incenter d a b c; e = excenter e a b c ?'
|
94 |
+
' perp d c c e'
|
95 |
+
)
|
96 |
+
g, _ = gh.Graph.build_problem(p, ARTest.defs)
|
97 |
+
|
98 |
+
# Create an external angle table:
|
99 |
+
tb = ar.AngleTable('pi')
|
100 |
+
|
101 |
+
# Add bisector & ex-bisector facts into the table:
|
102 |
+
ca, cd, cb, ce = g.names2nodes(['d(ac)', 'd(cd)', 'd(bc)', 'd(ce)'])
|
103 |
+
tb.add_eqangle(ca, cd, cd, cb, 'fact1')
|
104 |
+
tb.add_eqangle(ce, ca, cb, ce, 'fact2')
|
105 |
+
|
106 |
+
# Add a distractor fact to make sure traceback does not include this fact
|
107 |
+
ab = g.names2nodes(['d(ab)'])[0]
|
108 |
+
tb.add_eqangle(ab, cb, cb, ca, 'fact3')
|
109 |
+
|
110 |
+
# Check for all new equalities
|
111 |
+
result = list(tb.get_all_eqs_and_why())
|
112 |
+
|
113 |
+
# halfpi is represented as a tuple (1, 2)
|
114 |
+
halfpi = (1, 2)
|
115 |
+
|
116 |
+
# check that cd-ce == halfpi and this is because fact1 & fact2, not fact3
|
117 |
+
self.assertCountEqual(
|
118 |
+
result,
|
119 |
+
[
|
120 |
+
(cd, ce, halfpi, ['fact1', 'fact2']),
|
121 |
+
(ce, cd, halfpi, ['fact1', 'fact2']),
|
122 |
+
],
|
123 |
+
)
|
124 |
+
|
125 |
+
def test_angle_table_equilateral_triangle(self):
|
126 |
+
"""Test that AR can figure out triangles with 3 equal angles => each is pi/3."""
|
127 |
+
# Load an equaliteral scenario
|
128 |
+
p = pr.Problem.from_txt('a b c = ieq_triangle ? cong a b a c')
|
129 |
+
g, _ = gh.Graph.build_problem(p, ARTest.defs)
|
130 |
+
|
131 |
+
# Add two eqangles facts because ieq_triangle only add congruent sides
|
132 |
+
a, b, c = g.names2nodes('abc')
|
133 |
+
g.add_eqangle([a, b, b, c, b, c, c, a], pr.EmptyDependency(0, None))
|
134 |
+
g.add_eqangle([b, c, c, a, c, a, a, b], pr.EmptyDependency(0, None))
|
135 |
+
|
136 |
+
# Create an external angle table:
|
137 |
+
tb = ar.AngleTable('pi')
|
138 |
+
|
139 |
+
# Add the fact that there are three equal angles
|
140 |
+
ab, bc, ca = g.names2nodes(['d(ab)', 'd(bc)', 'd(ac)'])
|
141 |
+
tb.add_eqangle(ab, bc, bc, ca, 'fact1')
|
142 |
+
tb.add_eqangle(bc, ca, ca, ab, 'fact2')
|
143 |
+
|
144 |
+
# Now check for all new equalities
|
145 |
+
result = list(tb.get_all_eqs_and_why())
|
146 |
+
result = [(x.name, y.name, z, t) for x, y, z, t in result]
|
147 |
+
|
148 |
+
# 1/3 pi is represented as a tuple angle_60
|
149 |
+
angle_60 = (1, 3)
|
150 |
+
angle_120 = (2, 3)
|
151 |
+
|
152 |
+
# check that angles constants are created and figured out:
|
153 |
+
self.assertCountEqual(
|
154 |
+
result,
|
155 |
+
[
|
156 |
+
('d(bc)', 'd(ac)', angle_120, ['fact1', 'fact2']),
|
157 |
+
('d(ab)', 'd(bc)', angle_120, ['fact1', 'fact2']),
|
158 |
+
('d(ac)', 'd(ab)', angle_120, ['fact1', 'fact2']),
|
159 |
+
('d(ac)', 'd(bc)', angle_60, ['fact1', 'fact2']),
|
160 |
+
('d(bc)', 'd(ab)', angle_60, ['fact1', 'fact2']),
|
161 |
+
('d(ab)', 'd(ac)', angle_60, ['fact1', 'fact2']),
|
162 |
+
],
|
163 |
+
)
|
164 |
+
|
165 |
+
def test_incenter_excenter_touchpoints(self):
|
166 |
+
"""Test that AR can figure out incenter/excenter touchpoints are equidistant to midpoint."""
|
167 |
+
|
168 |
+
p = pr.Problem.from_txt(
|
169 |
+
'a b c = triangle a b c; d1 d2 d3 d = incenter2 a b c; e1 e2 e3 e ='
|
170 |
+
' excenter2 a b c ? perp d c c e',
|
171 |
+
translate=False,
|
172 |
+
)
|
173 |
+
g, _ = gh.Graph.build_problem(p, ARTest.defs)
|
174 |
+
|
175 |
+
a, b, c, ab, bc, ca, d1, d2, d3, e1, e2, e3 = g.names2nodes(
|
176 |
+
['a', 'b', 'c', 'ab', 'bc', 'ac', 'd1', 'd2', 'd3', 'e1', 'e2', 'e3']
|
177 |
+
)
|
178 |
+
|
179 |
+
# Create an external distance table:
|
180 |
+
tb = ar.DistanceTable()
|
181 |
+
|
182 |
+
# DD can figure out the following facts,
|
183 |
+
# we manually add them to AR.
|
184 |
+
tb.add_cong(ab, ca, a, d3, a, d2, 'fact1')
|
185 |
+
tb.add_cong(ab, ca, a, e3, a, e2, 'fact2')
|
186 |
+
tb.add_cong(ca, bc, c, d2, c, d1, 'fact5')
|
187 |
+
tb.add_cong(ca, bc, c, e2, c, e1, 'fact6')
|
188 |
+
tb.add_cong(bc, ab, b, d1, b, d3, 'fact3')
|
189 |
+
tb.add_cong(bc, ab, b, e1, b, e3, 'fact4')
|
190 |
+
|
191 |
+
# Now we check whether tb has figured out that
|
192 |
+
# distance(b, d1) == distance(e1, c)
|
193 |
+
|
194 |
+
# linear comb exprssion of each variables:
|
195 |
+
b = tb.v2e['bc:b']
|
196 |
+
c = tb.v2e['bc:c']
|
197 |
+
d1 = tb.v2e['bc:d1']
|
198 |
+
e1 = tb.v2e['bc:e1']
|
199 |
+
|
200 |
+
self.assertEqual(ar.minus(d1, b), ar.minus(c, e1))
|
201 |
+
|
202 |
+
|
203 |
+
if __name__ == '__main__':
|
204 |
+
absltest.main()
|
ag4masses/alphageometry/beam_search.py
ADDED
@@ -0,0 +1,463 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright 2023 DeepMind Technologies Limited
|
2 |
+
#
|
3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4 |
+
# you may not use this file except in compliance with the License.
|
5 |
+
# You may obtain a copy of the License at
|
6 |
+
#
|
7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8 |
+
#
|
9 |
+
# Unless required by applicable law or agreed to in writing, software
|
10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
+
# See the License for the specific language governing permissions and
|
13 |
+
# limitations under the License.
|
14 |
+
# ==============================================================================
|
15 |
+
|
16 |
+
"""Fast decoding routines for inference from a trained model.
|
17 |
+
|
18 |
+
Modified https://github.com/google/flax/blob/main/examples/wmt/decode.py
|
19 |
+
to acommodate
|
20 |
+
|
21 |
+
(a) continued decoding from a previous beam cache.
|
22 |
+
(b) init with with a single beam and then expand into beam_size beams.
|
23 |
+
"""
|
24 |
+
|
25 |
+
from typing import Any
|
26 |
+
|
27 |
+
import flax
|
28 |
+
import jax
|
29 |
+
from jax import lax
|
30 |
+
import jax.numpy as jnp
|
31 |
+
import numpy as np
|
32 |
+
|
33 |
+
|
34 |
+
# Constants
|
35 |
+
# "Effective negative infinity" constant for masking in beam search.
|
36 |
+
NEG_INF = np.array(-1.0e7)
|
37 |
+
|
38 |
+
# Beam search parameters
|
39 |
+
BEAM_SEARCH_DEFAULT_ALPHA = 0.6
|
40 |
+
MAX_DECODE_LEN = 32
|
41 |
+
|
42 |
+
# Brevity penalty parameters
|
43 |
+
BREVITY_LEN_BIAS_NUMERATOR = 5.0
|
44 |
+
BREVITY_LEN_BIAS_DENOMINATOR = 6.0
|
45 |
+
|
46 |
+
|
47 |
+
def brevity_penalty(alpha: float, length: int):
|
48 |
+
"""Brevity penalty function for beam search penalizing short sequences.
|
49 |
+
|
50 |
+
Args:
|
51 |
+
alpha: float: brevity-penalty scaling parameter.
|
52 |
+
length: int: length of considered sequence.
|
53 |
+
|
54 |
+
Returns:
|
55 |
+
Brevity penalty score as jax scalar.
|
56 |
+
"""
|
57 |
+
return jnp.power(
|
58 |
+
((BREVITY_LEN_BIAS_NUMERATOR + length) / BREVITY_LEN_BIAS_DENOMINATOR),
|
59 |
+
alpha,
|
60 |
+
)
|
61 |
+
|
62 |
+
|
63 |
+
# Beam handling utility functions:
|
64 |
+
|
65 |
+
|
66 |
+
def add_beam_dim(x: jnp.ndarray, beam_size: int) -> jnp.ndarray:
|
67 |
+
"""Creates new beam dimension in non-scalar array and tiles into it."""
|
68 |
+
if x.ndim == 0: # ignore scalars (e.g. cache index)
|
69 |
+
return x
|
70 |
+
x = jnp.expand_dims(x, axis=1)
|
71 |
+
tile_dims = [1] * x.ndim
|
72 |
+
tile_dims[1] = beam_size
|
73 |
+
return jnp.tile(x, tile_dims)
|
74 |
+
|
75 |
+
|
76 |
+
def add_beam_dim_cache(
|
77 |
+
cache: tuple[dict[str, jnp.ndarray], ...], beam_size: int
|
78 |
+
) -> tuple[dict[str, jnp.ndarray], ...]:
|
79 |
+
"""Creates new beam dimension in non-scalar array and tiles into it."""
|
80 |
+
new_cache = []
|
81 |
+
|
82 |
+
for layer in cache:
|
83 |
+
new_layer = {}
|
84 |
+
for key, x in layer.items():
|
85 |
+
if key in ['keys', 'vals']:
|
86 |
+
x = add_beam_dim(x, beam_size)
|
87 |
+
new_layer[key] = x
|
88 |
+
new_cache.append(new_layer)
|
89 |
+
|
90 |
+
return tuple(new_cache)
|
91 |
+
|
92 |
+
|
93 |
+
def flatten_beam_dim(x):
|
94 |
+
"""Flattens the first two dimensions of a non-scalar array."""
|
95 |
+
if x.ndim < 2: # ignore scalars (e.g. cache index)
|
96 |
+
return x
|
97 |
+
return x.reshape((x.shape[0] * x.shape[1],) + x.shape[2:])
|
98 |
+
|
99 |
+
|
100 |
+
def unflatten_beam_dim(x, batch_size, beam_size):
|
101 |
+
"""Unflattens the first, flat batch*beam dimension of a non-scalar array."""
|
102 |
+
if x.ndim == 0: # ignore scalars (e.g. cache index)
|
103 |
+
return x
|
104 |
+
assert batch_size * beam_size == x.shape[0]
|
105 |
+
return x.reshape((batch_size, beam_size) + x.shape[1:])
|
106 |
+
|
107 |
+
|
108 |
+
def flat_batch_beam_expand(x, beam_size):
|
109 |
+
"""Expands the each batch item by beam_size in batch_dimension."""
|
110 |
+
return flatten_beam_dim(add_beam_dim(x, beam_size))
|
111 |
+
|
112 |
+
|
113 |
+
def gather_beams(nested, beam_indices, batch_size, new_beam_size):
|
114 |
+
"""Gathers the beam slices indexed by beam_indices into new beam array.
|
115 |
+
|
116 |
+
Args:
|
117 |
+
nested: pytree of arrays or scalars (the latter ignored).
|
118 |
+
beam_indices: array of beam_indices
|
119 |
+
batch_size: int: size of batch.
|
120 |
+
new_beam_size: int: size of _new_ beam dimension.
|
121 |
+
|
122 |
+
Returns:
|
123 |
+
New pytree with new beam arrays.
|
124 |
+
[batch_size, old_beam_size, ...] --> [batch_size, new_beam_size, ...]
|
125 |
+
"""
|
126 |
+
batch_indices = jnp.reshape(
|
127 |
+
jnp.arange(batch_size * new_beam_size) // new_beam_size,
|
128 |
+
(batch_size, new_beam_size),
|
129 |
+
)
|
130 |
+
|
131 |
+
def gather_fn(x):
|
132 |
+
if x.ndim == 0: # ignore scalars (e.g. cache index)
|
133 |
+
return x
|
134 |
+
else:
|
135 |
+
return x[batch_indices, beam_indices]
|
136 |
+
|
137 |
+
return jax.tree_util.tree_map(gather_fn, nested)
|
138 |
+
|
139 |
+
|
140 |
+
def gather_topk_beams(nested, score_or_log_prob, batch_size, new_beam_size):
|
141 |
+
"""Gathers the top-k beam slices given by score_or_log_prob array.
|
142 |
+
|
143 |
+
Args:
|
144 |
+
nested: pytree of arrays or scalars (the latter ignored).
|
145 |
+
score_or_log_prob: [batch_size, old_beam_size] array of values to sort by
|
146 |
+
for top-k selection of beam slices.
|
147 |
+
batch_size: int: size of batch.
|
148 |
+
new_beam_size: int: size of _new_ top-k selected beam dimension
|
149 |
+
|
150 |
+
Returns:
|
151 |
+
New pytree with new beam arrays containing top k new_beam_size slices.
|
152 |
+
[batch_size, old_beam_size, ...] --> [batch_size, new_beam_size, ...]
|
153 |
+
"""
|
154 |
+
_, topk_indices = lax.top_k(score_or_log_prob, k=new_beam_size)
|
155 |
+
topk_indices = jnp.flip(topk_indices, axis=1)
|
156 |
+
return gather_beams(nested, topk_indices, batch_size, new_beam_size)
|
157 |
+
|
158 |
+
|
159 |
+
def apply_on_cache(fn, cache, *args, **kwargs):
|
160 |
+
"""Apply fn(val) only when key is 'keys' or 'val'."""
|
161 |
+
new_cache = []
|
162 |
+
for layer in cache:
|
163 |
+
new_layer = {}
|
164 |
+
for key, val in layer.items():
|
165 |
+
if key in ['keys', 'values', 'current_index', 'relative_position_bias']:
|
166 |
+
val = fn(val, *args, **kwargs)
|
167 |
+
new_layer[key] = val
|
168 |
+
new_cache.append(new_layer)
|
169 |
+
return tuple(new_cache)
|
170 |
+
|
171 |
+
|
172 |
+
# Beam search state:
|
173 |
+
|
174 |
+
|
175 |
+
@flax.struct.dataclass
|
176 |
+
class BeamState:
|
177 |
+
"""Holds beam search state data."""
|
178 |
+
|
179 |
+
# The position of the decoding loop in the length dimension.
|
180 |
+
cur_index: jax.Array # scalar int32: current decoded length index
|
181 |
+
# The active sequence log probabilities and finished sequence scores.
|
182 |
+
live_logprobs: jax.Array # float32: [batch_size, beam_size]
|
183 |
+
finished_scores: jax.Array # float32: [batch_size, beam_size]
|
184 |
+
# The current active-beam-searching and finished sequences.
|
185 |
+
live_seqs: jax.Array # int32: [batch_size, beam_size, max_decode_len]
|
186 |
+
finished_seqs: jax.Array # int32: [batch_size, beam_size,
|
187 |
+
# max_decode_len]
|
188 |
+
# Records which of the 'finished_seqs' is occupied and not a filler slot.
|
189 |
+
finished_flags: jax.Array # bool: [batch_size, beam_size]
|
190 |
+
# The current state of the autoregressive decoding caches.
|
191 |
+
cache: Any # Any pytree of arrays, e.g. flax attention Cache object
|
192 |
+
|
193 |
+
|
194 |
+
def beam_init(seed_token, batch_size, beam_size, max_decode_len, cache):
|
195 |
+
"""Initializes the beam search state data structure."""
|
196 |
+
cur_index0 = jnp.array(0)
|
197 |
+
live_logprobs0 = jnp.tile(
|
198 |
+
jnp.array([0.0] + [NEG_INF] * (beam_size - 1)), [batch_size, 1]
|
199 |
+
)
|
200 |
+
finished_scores0 = jnp.ones((batch_size, beam_size)) * NEG_INF
|
201 |
+
|
202 |
+
live_seqs0 = jnp.concatenate(
|
203 |
+
[
|
204 |
+
jnp.reshape(seed_token, (batch_size, beam_size, 1)),
|
205 |
+
jnp.zeros((batch_size, beam_size, max_decode_len - 1), jnp.int32),
|
206 |
+
],
|
207 |
+
axis=-1,
|
208 |
+
) # (batch, beam, max_decode_len)
|
209 |
+
|
210 |
+
finished_seqs0 = jnp.zeros((batch_size, beam_size, max_decode_len), jnp.int32)
|
211 |
+
finished_flags0 = jnp.zeros((batch_size, beam_size), jnp.bool_)
|
212 |
+
beam_cache0 = apply_on_cache(lambda x: jnp.expand_dims(x, axis=0), cache)
|
213 |
+
return BeamState(
|
214 |
+
cur_index=cur_index0,
|
215 |
+
live_logprobs=live_logprobs0,
|
216 |
+
finished_scores=finished_scores0,
|
217 |
+
live_seqs=live_seqs0,
|
218 |
+
finished_seqs=finished_seqs0,
|
219 |
+
finished_flags=finished_flags0,
|
220 |
+
cache=beam_cache0,
|
221 |
+
)
|
222 |
+
|
223 |
+
|
224 |
+
# Beam search routine:
|
225 |
+
|
226 |
+
|
227 |
+
def beam_search_flat(
|
228 |
+
seed_token,
|
229 |
+
cache,
|
230 |
+
tokens_to_logits,
|
231 |
+
alpha=BEAM_SEARCH_DEFAULT_ALPHA,
|
232 |
+
eos=None,
|
233 |
+
max_decode_len=MAX_DECODE_LEN,
|
234 |
+
mask=None,
|
235 |
+
):
|
236 |
+
"""Beam search for LM.
|
237 |
+
|
238 |
+
inputs and cache is already flat! i.e. first dimention == batch*beam.
|
239 |
+
|
240 |
+
Args:
|
241 |
+
seed_token: array: [beam_size, 1] int32 sequence of tokens.
|
242 |
+
cache: flax attention cache.
|
243 |
+
tokens_to_logits: fast autoregressive decoder function taking single token
|
244 |
+
slices and cache and returning next-token logits and updated cache.
|
245 |
+
alpha: float: scaling factor for brevity penalty.
|
246 |
+
eos: array: [vocab] 1 for end-of-sentence tokens, 0 for not.
|
247 |
+
max_decode_len: int: maximum length of decoded translations.
|
248 |
+
mask: array: [vocab] binary mask for vocab. 1 to keep the prob, 0 to set the
|
249 |
+
prob := 0.
|
250 |
+
|
251 |
+
Returns:
|
252 |
+
Tuple of:
|
253 |
+
[beam_size, max_decode_len] top-scoring sequences
|
254 |
+
[beam_size] beam-search scores.
|
255 |
+
"""
|
256 |
+
# We liberally annotate shape information for clarity below.
|
257 |
+
batch_size, beam_size = 1, seed_token.shape[0]
|
258 |
+
mask = mask.reshape((1, 1, -1))
|
259 |
+
eos = eos.reshape((1, 1, -1))
|
260 |
+
mask_bias = (1 - mask) * NEG_INF
|
261 |
+
|
262 |
+
# initialize beam search state
|
263 |
+
beam_search_init_state = beam_init(
|
264 |
+
seed_token, batch_size, beam_size, max_decode_len, cache
|
265 |
+
)
|
266 |
+
|
267 |
+
def beam_search_loop_cond_fn(state):
|
268 |
+
"""Beam search loop termination condition."""
|
269 |
+
# Have we reached max decoding length?
|
270 |
+
not_at_end = state.cur_index < max_decode_len - 1
|
271 |
+
|
272 |
+
# Is no further progress in the beam search possible?
|
273 |
+
# Get the best possible scores from alive sequences.
|
274 |
+
min_brevity_penalty = brevity_penalty(alpha, max_decode_len)
|
275 |
+
best_live_scores = state.live_logprobs[:, -1:] / min_brevity_penalty
|
276 |
+
# Get the worst scores from finished sequences.
|
277 |
+
worst_finished_scores = jnp.min(
|
278 |
+
state.finished_scores, axis=1, keepdims=True
|
279 |
+
)
|
280 |
+
# Mask out scores from slots without any actual finished sequences.
|
281 |
+
worst_finished_scores = jnp.where(
|
282 |
+
state.finished_flags, worst_finished_scores, NEG_INF
|
283 |
+
)
|
284 |
+
# If no best possible live score is better than current worst finished
|
285 |
+
# scores, the search cannot improve the finished set further.
|
286 |
+
search_terminated = jnp.all(worst_finished_scores > best_live_scores)
|
287 |
+
|
288 |
+
# If we're not at the max decode length, and the search hasn't terminated,
|
289 |
+
# continue looping.
|
290 |
+
return not_at_end & (~search_terminated)
|
291 |
+
|
292 |
+
def beam_search_loop_body_fn(state):
|
293 |
+
"""Beam search loop state update function."""
|
294 |
+
# Collect the current position slice along length to feed the fast
|
295 |
+
# autoregressive decoder model. Flatten the beam dimension into batch
|
296 |
+
# dimension for feeding into the model.
|
297 |
+
# --> [batch * beam, 1]
|
298 |
+
flat_ids = flatten_beam_dim(
|
299 |
+
lax.dynamic_slice(
|
300 |
+
state.live_seqs, (0, 0, state.cur_index), (batch_size, beam_size, 1)
|
301 |
+
)
|
302 |
+
)
|
303 |
+
# Flatten beam dimension into batch to be compatible with model.
|
304 |
+
# {[batch, beam, ...], ...} --> {[batch * beam, ...], ...}
|
305 |
+
flat_cache = apply_on_cache(flatten_beam_dim, state.cache)
|
306 |
+
|
307 |
+
# Call fast-decoder model on current tokens to get next-position logits.
|
308 |
+
# --> [batch * beam, vocab]
|
309 |
+
flat_logits, new_flat_cache = tokens_to_logits(flat_ids, flat_cache)
|
310 |
+
|
311 |
+
# unflatten beam dimension
|
312 |
+
# [batch * beam, vocab] --> [batch, beam, vocab]
|
313 |
+
logits = unflatten_beam_dim(flat_logits, batch_size, beam_size)
|
314 |
+
|
315 |
+
# Unflatten beam dimension in attention cache arrays
|
316 |
+
# {[batch * beam, ...], ...} --> {[batch, beam, ...], ...}
|
317 |
+
new_cache = apply_on_cache(
|
318 |
+
unflatten_beam_dim, new_flat_cache, batch_size, beam_size
|
319 |
+
)
|
320 |
+
|
321 |
+
# Gather log probabilities from logits
|
322 |
+
candidate_log_probs = jax.nn.log_softmax(logits)
|
323 |
+
# Add new logprobs to existing prefix logprobs.
|
324 |
+
# --> [batch, beam, vocab]
|
325 |
+
log_probs = candidate_log_probs + jnp.expand_dims(
|
326 |
+
state.live_logprobs, axis=2
|
327 |
+
)
|
328 |
+
|
329 |
+
# We'll need the vocab size, gather it from the log probability dimension.
|
330 |
+
vocab_size = log_probs.shape[2]
|
331 |
+
|
332 |
+
# mask away some tokens.
|
333 |
+
log_probs += mask_bias # [batch,beam,vocab]+[1,1,vocab]
|
334 |
+
|
335 |
+
# Each item in batch has beam_size * vocab_size candidate sequences.
|
336 |
+
# For each item, get the top 2*k candidates with the highest log-
|
337 |
+
# probabilities. We gather the top 2*K beams here so that even if the best
|
338 |
+
# K sequences reach EOS simultaneously, we have another K sequences
|
339 |
+
# remaining to continue the live beam search.
|
340 |
+
beams_to_keep = 2 * beam_size
|
341 |
+
# Flatten beam and vocab dimensions.
|
342 |
+
flat_log_probs = log_probs.reshape((batch_size, beam_size * vocab_size))
|
343 |
+
# Gather the top 2*K scores from _all_ beams.
|
344 |
+
# --> [batch, 2*beams], [batch, 2*beams]
|
345 |
+
topk_log_probs, topk_indices = lax.top_k(flat_log_probs, k=beams_to_keep)
|
346 |
+
# Recover the beam index by floor division.
|
347 |
+
topk_beam_indices = topk_indices // vocab_size
|
348 |
+
# Gather 2*k top beams.
|
349 |
+
# --> [batch, 2*beams, length]
|
350 |
+
topk_seq = gather_beams(
|
351 |
+
state.live_seqs, topk_beam_indices, batch_size, beams_to_keep
|
352 |
+
)
|
353 |
+
|
354 |
+
# Append the most probable 2*K token IDs to the top 2*K sequences
|
355 |
+
# Recover token id by modulo division and expand Id array for broadcasting.
|
356 |
+
# --> [batch, 2*beams, 1]
|
357 |
+
topk_ids = jnp.expand_dims(topk_indices % vocab_size, axis=2)
|
358 |
+
# Update sequences for the 2*K top-k new sequences.
|
359 |
+
# --> [batch, 2*beams, length]
|
360 |
+
topk_seq = lax.dynamic_update_slice(
|
361 |
+
topk_seq, topk_ids, (0, 0, state.cur_index + 1)
|
362 |
+
)
|
363 |
+
|
364 |
+
# Update LIVE (in-progress) sequences:
|
365 |
+
# Did any of these sequences reach an end marker?
|
366 |
+
# --> [batch, 2*beams]
|
367 |
+
last_token = topk_seq[:, :, state.cur_index + 1]
|
368 |
+
last_token = jax.nn.one_hot(last_token, vocab_size, dtype=jnp.bfloat16)
|
369 |
+
|
370 |
+
# any([batch, 2b, vocab] * [1, 1, vocab], axis=-1) == [batch, 2b]
|
371 |
+
newly_finished = jnp.any(last_token * eos, axis=-1)
|
372 |
+
|
373 |
+
# To prevent these newly finished sequences from being added to the LIVE
|
374 |
+
# set of active beam search sequences, set their log probs to a very large
|
375 |
+
# negative value.
|
376 |
+
new_log_probs = topk_log_probs + newly_finished * NEG_INF
|
377 |
+
# Determine the top k beam indices (from top 2*k beams) from log probs.
|
378 |
+
# --> [batch, beams]
|
379 |
+
_, new_topk_indices = lax.top_k(new_log_probs, k=beam_size)
|
380 |
+
new_topk_indices = jnp.flip(new_topk_indices, axis=1)
|
381 |
+
# Gather the top k beams (from top 2*k beams).
|
382 |
+
# --> [batch, beams, length], [batch, beams]
|
383 |
+
top_alive_seq, top_alive_log_probs = gather_beams(
|
384 |
+
[topk_seq, new_log_probs], new_topk_indices, batch_size, beam_size
|
385 |
+
)
|
386 |
+
|
387 |
+
# Determine the top k beam indices from the original set of all beams.
|
388 |
+
# --> [batch, beams]
|
389 |
+
top_alive_indices = gather_beams(
|
390 |
+
topk_beam_indices, new_topk_indices, batch_size, beam_size
|
391 |
+
)
|
392 |
+
# With these, gather the top k beam-associated caches.
|
393 |
+
# --> {[batch, beams, ...], ...}
|
394 |
+
top_alive_cache = apply_on_cache(
|
395 |
+
gather_beams, new_cache, top_alive_indices, batch_size, beam_size
|
396 |
+
)
|
397 |
+
|
398 |
+
# Update FINISHED (reached end of sentence) sequences:
|
399 |
+
# Calculate new seq scores from log probabilities.
|
400 |
+
new_scores = topk_log_probs / brevity_penalty(alpha, state.cur_index + 1)
|
401 |
+
# Mask out the still unfinished sequences by adding large negative value.
|
402 |
+
# --> [batch, 2*beams]
|
403 |
+
new_scores += (~newly_finished) * NEG_INF
|
404 |
+
|
405 |
+
# Combine sequences, scores, and flags along the beam dimension and compare
|
406 |
+
# new finished sequence scores to existing finished scores and select the
|
407 |
+
# best from the new set of beams.
|
408 |
+
finished_seqs = jnp.concatenate( # --> [batch, 3*beams, length]
|
409 |
+
[state.finished_seqs, topk_seq], axis=1
|
410 |
+
)
|
411 |
+
finished_scores = jnp.concatenate( # --> [batch, 3*beams]
|
412 |
+
[state.finished_scores, new_scores], axis=1
|
413 |
+
)
|
414 |
+
finished_flags = jnp.concatenate( # --> [batch, 3*beams]
|
415 |
+
[state.finished_flags, newly_finished], axis=1
|
416 |
+
)
|
417 |
+
# --> [batch, beams, length], [batch, beams], [batch, beams]
|
418 |
+
top_finished_seq, top_finished_scores, top_finished_flags = (
|
419 |
+
gather_topk_beams(
|
420 |
+
[finished_seqs, finished_scores, finished_flags],
|
421 |
+
finished_scores,
|
422 |
+
batch_size,
|
423 |
+
beam_size,
|
424 |
+
)
|
425 |
+
)
|
426 |
+
|
427 |
+
return BeamState(
|
428 |
+
cur_index=state.cur_index + 1,
|
429 |
+
live_logprobs=top_alive_log_probs,
|
430 |
+
finished_scores=top_finished_scores,
|
431 |
+
live_seqs=top_alive_seq,
|
432 |
+
finished_seqs=top_finished_seq,
|
433 |
+
finished_flags=top_finished_flags,
|
434 |
+
cache=top_alive_cache,
|
435 |
+
)
|
436 |
+
|
437 |
+
# Run while loop and get final beam search state.
|
438 |
+
final_state = lax.while_loop(
|
439 |
+
beam_search_loop_cond_fn, beam_search_loop_body_fn, beam_search_init_state
|
440 |
+
)
|
441 |
+
|
442 |
+
# Account for the edge-case where there are no finished sequences for a
|
443 |
+
# particular batch item. If so, return live sequences for that batch item.
|
444 |
+
# --> [batch]
|
445 |
+
none_finished = jnp.any(final_state.finished_flags, axis=1)
|
446 |
+
# --> [batch, beams, length]
|
447 |
+
finished_seqs = jnp.where(
|
448 |
+
none_finished[:, None, None],
|
449 |
+
final_state.finished_seqs,
|
450 |
+
final_state.live_seqs,
|
451 |
+
)
|
452 |
+
# --> [batch, beams]
|
453 |
+
finished_scores = jnp.where(
|
454 |
+
none_finished[:, None],
|
455 |
+
final_state.finished_scores,
|
456 |
+
final_state.live_logprobs,
|
457 |
+
)
|
458 |
+
|
459 |
+
finished_seqs = jnp.reshape(finished_seqs, (beam_size, max_decode_len))
|
460 |
+
finished_scores = jnp.reshape(finished_scores, (beam_size,))
|
461 |
+
|
462 |
+
final_cache = apply_on_cache(flatten_beam_dim, final_state.cache)
|
463 |
+
return finished_seqs, finished_scores, final_cache
|
ag4masses/alphageometry/dd.py
ADDED
@@ -0,0 +1,1156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright 2023 DeepMind Technologies Limited
|
2 |
+
#
|
3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4 |
+
# you may not use this file except in compliance with the License.
|
5 |
+
# You may obtain a copy of the License at
|
6 |
+
#
|
7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8 |
+
#
|
9 |
+
# Unless required by applicable law or agreed to in writing, software
|
10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
+
# See the License for the specific language governing permissions and
|
13 |
+
# limitations under the License.
|
14 |
+
# ==============================================================================
|
15 |
+
|
16 |
+
"""Implements Deductive Database (DD)."""
|
17 |
+
|
18 |
+
# pylint: disable=g-multiple-import,g-importing-member
|
19 |
+
from collections import defaultdict
|
20 |
+
import time
|
21 |
+
from typing import Any, Callable, Generator
|
22 |
+
|
23 |
+
import geometry as gm
|
24 |
+
import graph as gh
|
25 |
+
import graph_utils as utils
|
26 |
+
import numericals as nm
|
27 |
+
import problem as pr
|
28 |
+
from problem import Dependency, EmptyDependency
|
29 |
+
|
30 |
+
|
31 |
+
def intersect1(set1: set[Any], set2: set[Any]) -> Any:
|
32 |
+
for x in set1:
|
33 |
+
if x in set2:
|
34 |
+
return x
|
35 |
+
return None
|
36 |
+
|
37 |
+
|
38 |
+
def diff_point(l: gm.Line, a: gm.Point) -> gm.Point:
|
39 |
+
for x in l.neighbors(gm.Point):
|
40 |
+
if x != a:
|
41 |
+
return x
|
42 |
+
return None
|
43 |
+
|
44 |
+
|
45 |
+
# pylint: disable=protected-access
|
46 |
+
# pylint: disable=unused-argument
|
47 |
+
|
48 |
+
|
49 |
+
def match_eqratio_eqratio_eqratio(
|
50 |
+
g: gh.Graph,
|
51 |
+
g_matcher: Callable[str, list[tuple[gm.Point, ...]]],
|
52 |
+
theorem: pr.Theorem,
|
53 |
+
) -> Generator[dict[str, gm.Point], None, None]:
|
54 |
+
"""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."""
|
55 |
+
for m1 in g.type2nodes[gm.Value]:
|
56 |
+
for m2 in g.type2nodes[gm.Value]:
|
57 |
+
rats1 = []
|
58 |
+
for rat in m1.neighbors(gm.Ratio):
|
59 |
+
l1, l2 = rat.lengths
|
60 |
+
if l1 is None or l2 is None:
|
61 |
+
continue
|
62 |
+
rats1.append((l1, l2))
|
63 |
+
|
64 |
+
rats2 = []
|
65 |
+
for rat in m2.neighbors(gm.Ratio):
|
66 |
+
l1, l2 = rat.lengths
|
67 |
+
if l1 is None or l2 is None:
|
68 |
+
continue
|
69 |
+
rats2.append((l1, l2))
|
70 |
+
|
71 |
+
pairs = []
|
72 |
+
for (l1, l2), (l3, l4) in utils.cross(rats1, rats2):
|
73 |
+
if l2 == l3:
|
74 |
+
pairs.append((l1, l2, l4))
|
75 |
+
|
76 |
+
for (l1, l12, l2), (l3, l34, l4) in utils.comb2(pairs):
|
77 |
+
if (l1, l12, l2) == (l3, l34, l4):
|
78 |
+
continue
|
79 |
+
if l1 == l2 or l3 == l4:
|
80 |
+
continue
|
81 |
+
if l1 == l12 or l12 == l2 or l3 == l34 or l4 == l34:
|
82 |
+
continue
|
83 |
+
# d12 - d1 = d34 - d3 = m1
|
84 |
+
# d2 - d12 = d4 - d34 = m2
|
85 |
+
# => d2 - d1 = d4 - d3 (= m1+m2)
|
86 |
+
a, b = g.two_points_of_length(l1)
|
87 |
+
c, d = g.two_points_of_length(l12)
|
88 |
+
m, n = g.two_points_of_length(l3)
|
89 |
+
p, q = g.two_points_of_length(l34)
|
90 |
+
# eqangle a b c d m n p q
|
91 |
+
e, f = g.two_points_of_length(l2)
|
92 |
+
r, u = g.two_points_of_length(l4)
|
93 |
+
yield dict(zip('abcdefmnpqru', [a, b, c, d, e, f, m, n, p, q, r, u]))
|
94 |
+
|
95 |
+
|
96 |
+
def match_eqangle_eqangle_eqangle(
|
97 |
+
g: gh.Graph,
|
98 |
+
g_matcher: Callable[str, list[tuple[gm.Point, ...]]],
|
99 |
+
theorem: pr.Theorem,
|
100 |
+
) -> Generator[dict[str, gm.Point], None, None]:
|
101 |
+
"""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."""
|
102 |
+
for m1 in g.type2nodes[gm.Measure]:
|
103 |
+
for m2 in g.type2nodes[gm.Measure]:
|
104 |
+
angs1 = []
|
105 |
+
for ang in m1.neighbors(gm.Angle):
|
106 |
+
d1, d2 = ang.directions
|
107 |
+
if d1 is None or d2 is None:
|
108 |
+
continue
|
109 |
+
angs1.append((d1, d2))
|
110 |
+
|
111 |
+
angs2 = []
|
112 |
+
for ang in m2.neighbors(gm.Angle):
|
113 |
+
d1, d2 = ang.directions
|
114 |
+
if d1 is None or d2 is None:
|
115 |
+
continue
|
116 |
+
angs2.append((d1, d2))
|
117 |
+
|
118 |
+
pairs = []
|
119 |
+
for (d1, d2), (d3, d4) in utils.cross(angs1, angs2):
|
120 |
+
if d2 == d3:
|
121 |
+
pairs.append((d1, d2, d4))
|
122 |
+
|
123 |
+
for (d1, d12, d2), (d3, d34, d4) in utils.comb2(pairs):
|
124 |
+
if (d1, d12, d2) == (d3, d34, d4):
|
125 |
+
continue
|
126 |
+
if d1 == d2 or d3 == d4:
|
127 |
+
continue
|
128 |
+
if d1 == d12 or d12 == d2 or d3 == d34 or d4 == d34:
|
129 |
+
continue
|
130 |
+
# d12 - d1 = d34 - d3 = m1
|
131 |
+
# d2 - d12 = d4 - d34 = m2
|
132 |
+
# => d2 - d1 = d4 - d3
|
133 |
+
a, b = g.two_points_on_direction(d1)
|
134 |
+
c, d = g.two_points_on_direction(d12)
|
135 |
+
m, n = g.two_points_on_direction(d3)
|
136 |
+
p, q = g.two_points_on_direction(d34)
|
137 |
+
# eqangle a b c d m n p q
|
138 |
+
e, f = g.two_points_on_direction(d2)
|
139 |
+
r, u = g.two_points_on_direction(d4)
|
140 |
+
yield dict(zip('abcdefmnpqru', [a, b, c, d, e, f, m, n, p, q, r, u]))
|
141 |
+
|
142 |
+
|
143 |
+
def match_perp_perp_npara_eqangle(
|
144 |
+
g: gh.Graph,
|
145 |
+
g_matcher: Callable[str, list[tuple[gm.Point, ...]]],
|
146 |
+
theorem: pr.Theorem,
|
147 |
+
) -> Generator[dict[str, gm.Point], None, None]:
|
148 |
+
"""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."""
|
149 |
+
dpairs = []
|
150 |
+
for ang in g.vhalfpi.neighbors(gm.Angle):
|
151 |
+
d1, d2 = ang.directions
|
152 |
+
if d1 is None or d2 is None:
|
153 |
+
continue
|
154 |
+
dpairs.append((d1, d2))
|
155 |
+
|
156 |
+
for (d1, d2), (d3, d4) in utils.comb2(dpairs):
|
157 |
+
a, b = g.two_points_on_direction(d1)
|
158 |
+
c, d = g.two_points_on_direction(d2)
|
159 |
+
m, n = g.two_points_on_direction(d3)
|
160 |
+
p, q = g.two_points_on_direction(d4)
|
161 |
+
if g.check_npara([a, b, m, n]):
|
162 |
+
if ({a, b}, {c, d}) == ({m, n}, {p, q}):
|
163 |
+
continue
|
164 |
+
if ({a, b}, {c, d}) == ({p, q}, {m, n}):
|
165 |
+
continue
|
166 |
+
|
167 |
+
yield dict(zip('ABCDEFGH', [a, b, c, d, m, n, p, q]))
|
168 |
+
|
169 |
+
|
170 |
+
def match_circle_coll_eqangle_midp(
|
171 |
+
g: gh.Graph,
|
172 |
+
g_matcher: Callable[str, list[tuple[gm.Point, ...]]],
|
173 |
+
theorem: pr.Theorem,
|
174 |
+
) -> Generator[dict[str, gm.Point], None, None]:
|
175 |
+
"""Match circle O A B C, coll M B C, eqangle A B A C O B O M => midp M B C."""
|
176 |
+
for p, a, b, c in g.all_circles():
|
177 |
+
ab = g._get_line(a, b)
|
178 |
+
if ab is None:
|
179 |
+
continue
|
180 |
+
if ab.val is None:
|
181 |
+
continue
|
182 |
+
ac = g._get_line(a, c)
|
183 |
+
if ac is None:
|
184 |
+
continue
|
185 |
+
if ac.val is None:
|
186 |
+
continue
|
187 |
+
pb = g._get_line(p, b)
|
188 |
+
if pb is None:
|
189 |
+
continue
|
190 |
+
if pb.val is None:
|
191 |
+
continue
|
192 |
+
|
193 |
+
bc = g._get_line(b, c)
|
194 |
+
if bc is None:
|
195 |
+
continue
|
196 |
+
bc_points = bc.neighbors(gm.Point, return_set=True)
|
197 |
+
|
198 |
+
anga, _ = g._get_angle(ab.val, ac.val)
|
199 |
+
|
200 |
+
for angp in pb.val.neighbors(gm.Angle):
|
201 |
+
if not g.is_equal(anga, angp):
|
202 |
+
continue
|
203 |
+
|
204 |
+
_, d = angp.directions
|
205 |
+
for l in d.neighbors(gm.Line):
|
206 |
+
l_points = l.neighbors(gm.Point, return_set=True)
|
207 |
+
m = intersect1(bc_points, l_points)
|
208 |
+
if m is not None:
|
209 |
+
yield dict(zip('ABCMO', [a, b, c, m, p]))
|
210 |
+
|
211 |
+
|
212 |
+
def match_midp_perp_cong(
|
213 |
+
g: gh.Graph,
|
214 |
+
g_matcher: Callable[str, list[tuple[gm.Point, ...]]],
|
215 |
+
theorem: pr.Theorem,
|
216 |
+
) -> Generator[dict[str, gm.Point], None, None]:
|
217 |
+
"""Match midp M A B, perp O M A B => cong O A O B."""
|
218 |
+
for m, a, b in g.all_midps():
|
219 |
+
ab = g._get_line(a, b)
|
220 |
+
for l in m.neighbors(gm.Line):
|
221 |
+
if g.check_perpl(l, ab):
|
222 |
+
for o in l.neighbors(gm.Point):
|
223 |
+
if o != m:
|
224 |
+
yield dict(zip('ABMO', [a, b, m, o]))
|
225 |
+
|
226 |
+
|
227 |
+
def match_cyclic_eqangle_cong(
|
228 |
+
g: gh.Graph,
|
229 |
+
g_matcher: Callable[str, list[tuple[gm.Point, ...]]],
|
230 |
+
theorem: pr.Theorem,
|
231 |
+
) -> Generator[dict[str, gm.Point], None, None]:
|
232 |
+
"""Match cyclic A B C P Q R, eqangle C A C B R P R Q => cong A B P Q."""
|
233 |
+
for c in g.type2nodes[gm.Circle]:
|
234 |
+
ps = c.neighbors(gm.Point)
|
235 |
+
for (a, b, c), (x, y, z) in utils.comb2(list(utils.perm3(ps))):
|
236 |
+
if {a, b, c} == {x, y, z}:
|
237 |
+
continue
|
238 |
+
if g.check_eqangle([c, a, c, b, z, x, z, y]):
|
239 |
+
yield dict(zip('ABCPQR', [a, b, c, x, y, z]))
|
240 |
+
|
241 |
+
|
242 |
+
def match_circle_eqangle_perp(
|
243 |
+
g: gh.Graph,
|
244 |
+
g_matcher: Callable[str, list[tuple[gm.Point, ...]]],
|
245 |
+
theorem: pr.Theorem,
|
246 |
+
) -> Generator[dict[str, gm.Point], None, None]:
|
247 |
+
"""Match circle O A B C, eqangle A X A B C A C B => perp O A A X."""
|
248 |
+
for p, a, b, c in g.all_circles():
|
249 |
+
ca = g._get_line(c, a)
|
250 |
+
if ca is None:
|
251 |
+
continue
|
252 |
+
cb = g._get_line(c, b)
|
253 |
+
if cb is None:
|
254 |
+
continue
|
255 |
+
ab = g._get_line(a, b)
|
256 |
+
if ab is None:
|
257 |
+
continue
|
258 |
+
|
259 |
+
if ca.val is None:
|
260 |
+
continue
|
261 |
+
if cb.val is None:
|
262 |
+
continue
|
263 |
+
if ab.val is None:
|
264 |
+
continue
|
265 |
+
|
266 |
+
c_ang, _ = g._get_angle(cb.val, ca.val)
|
267 |
+
if c_ang is None:
|
268 |
+
continue
|
269 |
+
|
270 |
+
for ang in ab.val.neighbors(gm.Angle):
|
271 |
+
if g.is_equal(ang, c_ang):
|
272 |
+
_, d = ang.directions
|
273 |
+
for l in d.neighbors(gm.Line):
|
274 |
+
if a not in l.neighbors(gm.Point):
|
275 |
+
continue
|
276 |
+
x = diff_point(l, a)
|
277 |
+
if x is None:
|
278 |
+
continue
|
279 |
+
yield dict(zip('OABCX', [p, a, b, c, x]))
|
280 |
+
break
|
281 |
+
|
282 |
+
|
283 |
+
def match_circle_perp_eqangle(
|
284 |
+
g: gh.Graph,
|
285 |
+
g_matcher: Callable[str, list[tuple[gm.Point, ...]]],
|
286 |
+
theorem: pr.Theorem,
|
287 |
+
) -> Generator[dict[str, gm.Point], None, None]:
|
288 |
+
"""Match circle O A B C, perp O A A X => eqangle A X A B C A C B."""
|
289 |
+
for p, a, b, c in g.all_circles():
|
290 |
+
pa = g._get_line(p, a)
|
291 |
+
if pa is None:
|
292 |
+
continue
|
293 |
+
if pa.val is None:
|
294 |
+
continue
|
295 |
+
for l in a.neighbors(gm.Line):
|
296 |
+
if g.check_perpl(pa, l):
|
297 |
+
x = diff_point(l, a)
|
298 |
+
if x is not None:
|
299 |
+
yield dict(zip('OABCX', [p, a, b, c, x]))
|
300 |
+
|
301 |
+
|
302 |
+
def match_perp_perp_ncoll_para(
|
303 |
+
g: gh.Graph,
|
304 |
+
g_matcher: Callable[str, list[tuple[gm.Point, ...]]],
|
305 |
+
theorem: pr.Theorem,
|
306 |
+
) -> Generator[dict[str, gm.Point], None, None]:
|
307 |
+
"""Match perp A B C D, perp C D E F, ncoll A B E => para A B E F."""
|
308 |
+
d2d = defaultdict(list)
|
309 |
+
for ang in g.vhalfpi.neighbors(gm.Angle):
|
310 |
+
d1, d2 = ang.directions
|
311 |
+
if d1 is None or d2 is None:
|
312 |
+
continue
|
313 |
+
d2d[d1] += [d2]
|
314 |
+
d2d[d2] += [d1]
|
315 |
+
|
316 |
+
for x, ys in d2d.items():
|
317 |
+
if len(ys) < 2:
|
318 |
+
continue
|
319 |
+
c, d = g.two_points_on_direction(x)
|
320 |
+
for y1, y2 in utils.comb2(ys):
|
321 |
+
a, b = g.two_points_on_direction(y1)
|
322 |
+
e, f = g.two_points_on_direction(y2)
|
323 |
+
if nm.check_ncoll([a.num, b.num, e.num]):
|
324 |
+
yield dict(zip('ABCDEF', [a, b, c, d, e, f]))
|
325 |
+
|
326 |
+
|
327 |
+
def match_eqangle6_ncoll_cong(
|
328 |
+
g: gh.Graph,
|
329 |
+
g_matcher: Callable[str, list[tuple[gm.Point, ...]]],
|
330 |
+
theorem: pr.Theorem,
|
331 |
+
) -> Generator[dict[str, gm.Point], None, None]:
|
332 |
+
"""Match eqangle6 A O A B B A B O, ncoll O A B => cong O A O B."""
|
333 |
+
for a in g.type2nodes[gm.Point]:
|
334 |
+
for b, c in utils.comb2(g.type2nodes[gm.Point]):
|
335 |
+
if a == b or a == c:
|
336 |
+
continue
|
337 |
+
if g.check_eqangle([b, a, b, c, c, b, c, a]):
|
338 |
+
if g.check_ncoll([a, b, c]):
|
339 |
+
yield dict(zip('OAB', [a, b, c]))
|
340 |
+
|
341 |
+
|
342 |
+
def match_eqangle_perp_perp(
|
343 |
+
g: gh.Graph,
|
344 |
+
g_matcher: Callable[str, list[tuple[gm.Point, ...]]],
|
345 |
+
theorem: pr.Theorem,
|
346 |
+
) -> Generator[dict[str, gm.Point], None, None]:
|
347 |
+
"""Match eqangle A B P Q C D U V, perp P Q U V => perp A B C D."""
|
348 |
+
for ang in g.vhalfpi.neighbors(gm.Angle):
|
349 |
+
# d1 perp d2
|
350 |
+
d1, d2 = ang.directions
|
351 |
+
if d1 is None or d2 is None:
|
352 |
+
continue
|
353 |
+
for d3, d4 in utils.comb2(g.type2nodes[gm.Direction]):
|
354 |
+
if d1 == d3 or d2 == d4:
|
355 |
+
continue
|
356 |
+
# if d1 - d3 = d2 - d4 => d3 perp d4
|
357 |
+
a13, a31 = g._get_angle(d1, d3)
|
358 |
+
a24, a42 = g._get_angle(d2, d4)
|
359 |
+
if a13 is None or a31 is None or a24 is None or a42 is None:
|
360 |
+
continue
|
361 |
+
if g.is_equal(a13, a24) and g.is_equal(a31, a42):
|
362 |
+
a, b = g.two_points_on_direction(d1)
|
363 |
+
c, d = g.two_points_on_direction(d2)
|
364 |
+
m, n = g.two_points_on_direction(d3)
|
365 |
+
p, q = g.two_points_on_direction(d4)
|
366 |
+
yield dict(zip('ABCDPQUV', [m, n, p, q, a, b, c, d]))
|
367 |
+
|
368 |
+
|
369 |
+
def match_eqangle_ncoll_cyclic(
|
370 |
+
g: gh.Graph,
|
371 |
+
g_matcher: Callable[str, list[tuple[gm.Point, ...]]],
|
372 |
+
theorem: pr.Theorem,
|
373 |
+
) -> Generator[dict[str, gm.Point], None, None]:
|
374 |
+
"""Match eqangle6 P A P B Q A Q B, ncoll P Q A B => cyclic A B P Q."""
|
375 |
+
for l1, l2, l3, l4 in g.all_eqangles_distinct_linepairss():
|
376 |
+
if len(set([l1, l2, l3, l4])) < 4:
|
377 |
+
continue # they all must be distinct.
|
378 |
+
|
379 |
+
p1s = l1.neighbors(gm.Point, return_set=True)
|
380 |
+
p2s = l2.neighbors(gm.Point, return_set=True)
|
381 |
+
p3s = l3.neighbors(gm.Point, return_set=True)
|
382 |
+
p4s = l4.neighbors(gm.Point, return_set=True)
|
383 |
+
|
384 |
+
p = intersect1(p1s, p2s)
|
385 |
+
if not p:
|
386 |
+
continue
|
387 |
+
q = intersect1(p3s, p4s)
|
388 |
+
if not q:
|
389 |
+
continue
|
390 |
+
a = intersect1(p1s, p3s)
|
391 |
+
if not a:
|
392 |
+
continue
|
393 |
+
b = intersect1(p2s, p4s)
|
394 |
+
if not b:
|
395 |
+
continue
|
396 |
+
if len(set([a, b, p, q])) < 4:
|
397 |
+
continue
|
398 |
+
|
399 |
+
if not g.check_ncoll([a, b, p, q]):
|
400 |
+
continue
|
401 |
+
|
402 |
+
yield dict(zip('ABPQ', [a, b, p, q]))
|
403 |
+
|
404 |
+
|
405 |
+
def match_eqangle_para(
|
406 |
+
g: gh.Graph,
|
407 |
+
g_matcher: Callable[str, list[tuple[gm.Point, ...]]],
|
408 |
+
theorem: pr.Theorem,
|
409 |
+
) -> Generator[dict[str, gm.Point], None, None]:
|
410 |
+
"""Match eqangle A B P Q C D P Q => para A B C D."""
|
411 |
+
for measure in g.type2nodes[gm.Measure]:
|
412 |
+
angs = measure.neighbors(gm.Angle)
|
413 |
+
d12, d21 = defaultdict(list), defaultdict(list)
|
414 |
+
for ang in angs:
|
415 |
+
d1, d2 = ang.directions
|
416 |
+
if d1 is None or d2 is None:
|
417 |
+
continue
|
418 |
+
d12[d1].append(d2)
|
419 |
+
d21[d2].append(d1)
|
420 |
+
|
421 |
+
for d1, d2s in d12.items():
|
422 |
+
a, b = g.two_points_on_direction(d1)
|
423 |
+
for d2, d3 in utils.comb2(d2s):
|
424 |
+
c, d = g.two_points_on_direction(d2)
|
425 |
+
e, f = g.two_points_on_direction(d3)
|
426 |
+
yield dict(zip('ABCDPQ', [c, d, e, f, a, b]))
|
427 |
+
|
428 |
+
|
429 |
+
def match_cyclic_eqangle(
|
430 |
+
g: gh.Graph,
|
431 |
+
g_matcher: Callable[str, list[tuple[gm.Point, ...]]],
|
432 |
+
theorem: pr.Theorem,
|
433 |
+
) -> Generator[dict[str, gm.Point], None, None]:
|
434 |
+
"""Match cyclic A B P Q => eqangle P A P B Q A Q B."""
|
435 |
+
record = set()
|
436 |
+
for a, b, c, d in g_matcher('cyclic'):
|
437 |
+
if (a, b, c, d) in record:
|
438 |
+
continue
|
439 |
+
record.add((a, b, c, d))
|
440 |
+
record.add((a, b, d, c))
|
441 |
+
record.add((b, a, c, d))
|
442 |
+
record.add((b, a, d, c))
|
443 |
+
yield dict(zip('ABPQ', [a, b, c, d]))
|
444 |
+
|
445 |
+
|
446 |
+
def rotate_simtri(
|
447 |
+
a: gm.Point, b: gm.Point, c: gm.Point, x: gm.Point, y: gm.Point, z: gm.Point
|
448 |
+
) -> Generator[tuple[gm.Point, ...], None, None]:
|
449 |
+
"""Rotate points around for similar triangle predicates."""
|
450 |
+
yield (z, y, x, c, b, a)
|
451 |
+
for p in [
|
452 |
+
(b, c, a, y, z, x),
|
453 |
+
(c, a, b, z, x, y),
|
454 |
+
(x, y, z, a, b, c),
|
455 |
+
(y, z, x, b, c, a),
|
456 |
+
(z, x, y, c, a, b),
|
457 |
+
]:
|
458 |
+
yield p
|
459 |
+
yield p[::-1]
|
460 |
+
|
461 |
+
|
462 |
+
def match_cong_cong_cong_cyclic(
|
463 |
+
g: gh.Graph,
|
464 |
+
g_matcher: Callable[str, list[tuple[gm.Point, ...]]],
|
465 |
+
theorem: pr.Theorem,
|
466 |
+
) -> Generator[dict[str, gm.Point], None, None]:
|
467 |
+
"""Match cong O A O B, cong O B O C, cong O C O D => cyclic A B C D."""
|
468 |
+
for l in g.type2nodes[gm.Length]:
|
469 |
+
p2p = defaultdict(list)
|
470 |
+
for s in l.neighbors(gm.Segment):
|
471 |
+
a, b = s.points
|
472 |
+
p2p[a].append(b)
|
473 |
+
p2p[b].append(a)
|
474 |
+
|
475 |
+
for p, ps in p2p.items():
|
476 |
+
if len(ps) >= 4:
|
477 |
+
for a, b, c, d in utils.comb4(ps):
|
478 |
+
yield dict(zip('OABCD', [p, a, b, c, d]))
|
479 |
+
|
480 |
+
|
481 |
+
def match_cong_cong_cong_ncoll_contri(
|
482 |
+
g: gh.Graph,
|
483 |
+
g_matcher: Callable[str, list[tuple[gm.Point, ...]]],
|
484 |
+
theorem: pr.Theorem,
|
485 |
+
) -> Generator[dict[str, gm.Point], None, None]:
|
486 |
+
"""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."""
|
487 |
+
record = set()
|
488 |
+
for a, b, p, q in g_matcher('cong'):
|
489 |
+
for c in g.type2nodes[gm.Point]:
|
490 |
+
for r in g.type2nodes[gm.Point]:
|
491 |
+
if any([x in record for x in rotate_simtri(a, b, c, p, q, r)]):
|
492 |
+
continue
|
493 |
+
if not g.check_ncoll([a, b, c]):
|
494 |
+
continue
|
495 |
+
if g.check_cong([b, c, q, r]) and g.check_cong([c, a, r, p]):
|
496 |
+
record.add((a, b, c, p, q, r))
|
497 |
+
yield dict(zip('ABCPQR', [a, b, c, p, q, r]))
|
498 |
+
|
499 |
+
|
500 |
+
def match_cong_cong_eqangle6_ncoll_contri(
|
501 |
+
g: gh.Graph,
|
502 |
+
g_matcher: Callable[str, list[tuple[gm.Point, ...]]],
|
503 |
+
theorem: pr.Theorem,
|
504 |
+
) -> Generator[dict[str, gm.Point], None, None]:
|
505 |
+
"""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."""
|
506 |
+
record = set()
|
507 |
+
for a, b, p, q in g_matcher('cong'):
|
508 |
+
for c in g.type2nodes[gm.Point]:
|
509 |
+
if c in (a, b):
|
510 |
+
continue
|
511 |
+
for r in g.type2nodes[gm.Point]:
|
512 |
+
if r in (p, q):
|
513 |
+
continue
|
514 |
+
|
515 |
+
in_record = False
|
516 |
+
for x in [
|
517 |
+
(c, b, a, r, q, p),
|
518 |
+
(p, q, r, a, b, c),
|
519 |
+
(r, q, p, c, b, a),
|
520 |
+
]:
|
521 |
+
if x in record:
|
522 |
+
in_record = True
|
523 |
+
break
|
524 |
+
|
525 |
+
if in_record:
|
526 |
+
continue
|
527 |
+
|
528 |
+
if not g.check_cong([b, c, q, r]):
|
529 |
+
continue
|
530 |
+
if not g.check_ncoll([a, b, c]):
|
531 |
+
continue
|
532 |
+
|
533 |
+
if nm.same_clock(a.num, b.num, c.num, p.num, q.num, r.num):
|
534 |
+
if g.check_eqangle([b, a, b, c, q, p, q, r]):
|
535 |
+
record.add((a, b, c, p, q, r))
|
536 |
+
yield dict(zip('ABCPQR', [a, b, c, p, q, r]))
|
537 |
+
else:
|
538 |
+
if g.check_eqangle([b, a, b, c, q, r, q, p]):
|
539 |
+
record.add((a, b, c, p, q, r))
|
540 |
+
yield dict(zip('ABCPQR', [a, b, c, p, q, r]))
|
541 |
+
|
542 |
+
|
543 |
+
def match_eqratio6_eqangle6_ncoll_simtri(
|
544 |
+
g: gh.Graph,
|
545 |
+
g_matcher: Callable[str, list[tuple[gm.Point, ...]]],
|
546 |
+
theorem: pr.Theorem,
|
547 |
+
) -> Generator[dict[str, gm.Point], None, None]:
|
548 |
+
"""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."""
|
549 |
+
enums = g_matcher('eqratio6')
|
550 |
+
|
551 |
+
record = set()
|
552 |
+
for b, a, b, c, q, p, q, r in enums: # pylint: disable=redeclared-assigned-name,unused-variable
|
553 |
+
if (a, b, c) == (p, q, r):
|
554 |
+
continue
|
555 |
+
if any([x in record for x in rotate_simtri(a, b, c, p, q, r)]):
|
556 |
+
continue
|
557 |
+
if not g.check_ncoll([a, b, c]):
|
558 |
+
continue
|
559 |
+
|
560 |
+
if nm.same_clock(a.num, b.num, c.num, p.num, q.num, r.num):
|
561 |
+
if g.check_eqangle([b, a, b, c, q, p, q, r]):
|
562 |
+
record.add((a, b, c, p, q, r))
|
563 |
+
yield dict(zip('ABCPQR', [a, b, c, p, q, r]))
|
564 |
+
elif g.check_eqangle([b, a, b, c, q, r, q, p]):
|
565 |
+
record.add((a, b, c, p, q, r))
|
566 |
+
yield dict(zip('ABCPQR', [a, b, c, p, q, r]))
|
567 |
+
|
568 |
+
|
569 |
+
def match_eqangle6_eqangle6_ncoll_simtri(
|
570 |
+
g: gh.Graph,
|
571 |
+
g_matcher: Callable[str, list[tuple[gm.Point, ...]]],
|
572 |
+
theorem: pr.Theorem,
|
573 |
+
) -> Generator[dict[str, gm.Point], None, None]:
|
574 |
+
"""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."""
|
575 |
+
enums = g_matcher('eqangle6')
|
576 |
+
|
577 |
+
record = set()
|
578 |
+
for b, a, b, c, q, p, q, r in enums: # pylint: disable=redeclared-assigned-name,unused-variable
|
579 |
+
if (a, b, c) == (p, q, r):
|
580 |
+
continue
|
581 |
+
if any([x in record for x in rotate_simtri(a, b, c, p, q, r)]):
|
582 |
+
continue
|
583 |
+
if not g.check_eqangle([c, a, c, b, r, p, r, q]):
|
584 |
+
continue
|
585 |
+
if not g.check_ncoll([a, b, c]):
|
586 |
+
continue
|
587 |
+
|
588 |
+
mapping = dict(zip('ABCPQR', [a, b, c, p, q, r]))
|
589 |
+
record.add((a, b, c, p, q, r))
|
590 |
+
yield mapping
|
591 |
+
|
592 |
+
|
593 |
+
def match_eqratio6_eqratio6_ncoll_simtri(
|
594 |
+
g: gh.Graph,
|
595 |
+
g_matcher: Callable[str, list[tuple[gm.Point, ...]]],
|
596 |
+
theorem: pr.Theorem,
|
597 |
+
) -> Generator[dict[str, gm.Point], None, None]:
|
598 |
+
"""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."""
|
599 |
+
enums = g_matcher('eqratio6')
|
600 |
+
|
601 |
+
record = set()
|
602 |
+
for b, a, b, c, q, p, q, r in enums: # pylint: disable=redeclared-assigned-name,unused-variable
|
603 |
+
if (a, b, c) == (p, q, r):
|
604 |
+
continue
|
605 |
+
if any([x in record for x in rotate_simtri(a, b, c, p, q, r)]):
|
606 |
+
continue
|
607 |
+
if not g.check_eqratio([c, a, c, b, r, p, r, q]):
|
608 |
+
continue
|
609 |
+
if not g.check_ncoll([a, b, c]):
|
610 |
+
continue
|
611 |
+
|
612 |
+
mapping = dict(zip('ABCPQR', [a, b, c, p, q, r]))
|
613 |
+
record.add((a, b, c, p, q, r))
|
614 |
+
yield mapping
|
615 |
+
|
616 |
+
|
617 |
+
def match_eqangle6_eqangle6_ncoll_simtri2(
|
618 |
+
g: gh.Graph,
|
619 |
+
g_matcher: Callable[str, list[tuple[gm.Point, ...]]],
|
620 |
+
theorem: pr.Theorem,
|
621 |
+
) -> Generator[dict[str, gm.Point], None, None]:
|
622 |
+
"""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."""
|
623 |
+
enums = g_matcher('eqangle6')
|
624 |
+
|
625 |
+
record = set()
|
626 |
+
for b, a, b, c, q, r, q, p in enums: # pylint: disable=redeclared-assigned-name,unused-variable
|
627 |
+
if (a, b, c) == (p, q, r):
|
628 |
+
continue
|
629 |
+
if any([x in record for x in rotate_simtri(a, b, c, p, q, r)]):
|
630 |
+
continue
|
631 |
+
if not g.check_eqangle([c, a, c, b, r, q, r, p]):
|
632 |
+
continue
|
633 |
+
if not g.check_ncoll([a, b, c]):
|
634 |
+
continue
|
635 |
+
|
636 |
+
mapping = dict(zip('ABCPQR', [a, b, c, p, q, r]))
|
637 |
+
record.add((a, b, c, p, q, r))
|
638 |
+
yield mapping
|
639 |
+
|
640 |
+
|
641 |
+
def rotate_contri(
|
642 |
+
a: gm.Point, b: gm.Point, c: gm.Point, x: gm.Point, y: gm.Point, z: gm.Point
|
643 |
+
) -> Generator[tuple[gm.Point, ...], None, None]:
|
644 |
+
for p in [(b, a, c, y, x, z), (x, y, z, a, b, c), (y, x, z, b, a, c)]:
|
645 |
+
yield p
|
646 |
+
|
647 |
+
|
648 |
+
def match_eqangle6_eqangle6_ncoll_cong_contri(
|
649 |
+
g: gh.Graph,
|
650 |
+
g_matcher: Callable[str, list[tuple[gm.Point, ...]]],
|
651 |
+
theorem: pr.Theorem,
|
652 |
+
) -> Generator[dict[str, gm.Point], None, None]:
|
653 |
+
"""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."""
|
654 |
+
enums = g_matcher('eqangle6')
|
655 |
+
|
656 |
+
record = set()
|
657 |
+
for b, a, b, c, q, p, q, r in enums: # pylint: disable=redeclared-assigned-name,unused-variable
|
658 |
+
if not g.check_cong([a, b, p, q]):
|
659 |
+
continue
|
660 |
+
if (a, b, c) == (p, q, r):
|
661 |
+
continue
|
662 |
+
if any([x in record for x in rotate_contri(a, b, c, p, q, r)]):
|
663 |
+
continue
|
664 |
+
if not g.check_eqangle([c, a, c, b, r, p, r, q]):
|
665 |
+
continue
|
666 |
+
|
667 |
+
if not g.check_ncoll([a, b, c]):
|
668 |
+
continue
|
669 |
+
|
670 |
+
mapping = dict(zip('ABCPQR', [a, b, c, p, q, r]))
|
671 |
+
record.add((a, b, c, p, q, r))
|
672 |
+
yield mapping
|
673 |
+
|
674 |
+
|
675 |
+
def match_eqratio6_eqratio6_ncoll_cong_contri(
|
676 |
+
g: gh.Graph,
|
677 |
+
g_matcher: Callable[str, list[tuple[gm.Point, ...]]],
|
678 |
+
theorem: pr.Theorem,
|
679 |
+
) -> Generator[dict[str, gm.Point], None, None]:
|
680 |
+
"""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."""
|
681 |
+
enums = g_matcher('eqratio6')
|
682 |
+
|
683 |
+
record = set()
|
684 |
+
for b, a, b, c, q, p, q, r in enums: # pylint: disable=redeclared-assigned-name,unused-variable
|
685 |
+
if not g.check_cong([a, b, p, q]):
|
686 |
+
continue
|
687 |
+
if (a, b, c) == (p, q, r):
|
688 |
+
continue
|
689 |
+
if any([x in record for x in rotate_contri(a, b, c, p, q, r)]):
|
690 |
+
continue
|
691 |
+
if not g.check_eqratio([c, a, c, b, r, p, r, q]):
|
692 |
+
continue
|
693 |
+
|
694 |
+
if not g.check_ncoll([a, b, c]):
|
695 |
+
continue
|
696 |
+
|
697 |
+
mapping = dict(zip('ABCPQR', [a, b, c, p, q, r]))
|
698 |
+
record.add((a, b, c, p, q, r))
|
699 |
+
yield mapping
|
700 |
+
|
701 |
+
|
702 |
+
def match_eqangle6_eqangle6_ncoll_cong_contri2(
|
703 |
+
g: gh.Graph,
|
704 |
+
g_matcher: Callable[str, list[tuple[gm.Point, ...]]],
|
705 |
+
theorem: pr.Theorem,
|
706 |
+
) -> Generator[dict[str, gm.Point], None, None]:
|
707 |
+
"""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."""
|
708 |
+
enums = g_matcher('eqangle6')
|
709 |
+
|
710 |
+
record = set()
|
711 |
+
for b, a, b, c, q, r, q, p in enums: # pylint: disable=redeclared-assigned-name,unused-variable
|
712 |
+
if not g.check_cong([a, b, p, q]):
|
713 |
+
continue
|
714 |
+
if (a, b, c) == (p, q, r):
|
715 |
+
continue
|
716 |
+
if any([x in record for x in rotate_contri(a, b, c, p, q, r)]):
|
717 |
+
continue
|
718 |
+
if not g.check_eqangle([c, a, c, b, r, q, r, p]):
|
719 |
+
continue
|
720 |
+
if not g.check_ncoll([a, b, c]):
|
721 |
+
continue
|
722 |
+
|
723 |
+
mapping = dict(zip('ABCPQR', [a, b, c, p, q, r]))
|
724 |
+
record.add((a, b, c, p, q, r))
|
725 |
+
yield mapping
|
726 |
+
|
727 |
+
|
728 |
+
def match_eqratio6_coll_ncoll_eqangle6(
|
729 |
+
g: gh.Graph,
|
730 |
+
g_matcher: Callable[str, list[tuple[gm.Point, ...]]],
|
731 |
+
theorem: pr.Theorem,
|
732 |
+
) -> Generator[dict[str, gm.Point], None, None]:
|
733 |
+
"""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."""
|
734 |
+
records = set()
|
735 |
+
for b, d, c in g_matcher('coll'):
|
736 |
+
for a in g.all_points():
|
737 |
+
if g.check_coll([a, b, c]):
|
738 |
+
continue
|
739 |
+
if (a, b, d, c) in records or (a, c, d, b) in records:
|
740 |
+
continue
|
741 |
+
records.add((a, b, d, c))
|
742 |
+
|
743 |
+
if g.check_eqratio([d, b, d, c, a, b, a, c]):
|
744 |
+
yield dict(zip('abcd', [a, b, c, d]))
|
745 |
+
|
746 |
+
|
747 |
+
def match_eqangle6_coll_ncoll_eqratio6(
|
748 |
+
g: gh.Graph,
|
749 |
+
g_matcher: Callable[str, list[tuple[gm.Point, ...]]],
|
750 |
+
theorem: pr.Theorem,
|
751 |
+
) -> Generator[dict[str, gm.Point], None, None]:
|
752 |
+
"""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."""
|
753 |
+
records = set()
|
754 |
+
for b, d, c in g_matcher('coll'):
|
755 |
+
for a in g.all_points():
|
756 |
+
if g.check_coll([a, b, c]):
|
757 |
+
continue
|
758 |
+
if (a, b, d, c) in records or (a, c, d, b) in records:
|
759 |
+
continue
|
760 |
+
records.add((a, b, d, c))
|
761 |
+
|
762 |
+
if g.check_eqangle([a, b, a, d, a, d, a, c]):
|
763 |
+
yield dict(zip('abcd', [a, b, c, d]))
|
764 |
+
|
765 |
+
|
766 |
+
def match_eqangle6_ncoll_cyclic(
|
767 |
+
g: gh.Graph,
|
768 |
+
g_matcher: Callable[str, list[tuple[gm.Point, ...]]],
|
769 |
+
theorem: pr.Theorem,
|
770 |
+
) -> Generator[dict[str, gm.Point], None, None]:
|
771 |
+
"""Match eqangle6 P A P B Q A Q B, ncoll P Q A B => cyclic A B P Q."""
|
772 |
+
for a, b, a, c, x, y, x, z in g_matcher('eqangle6'): # pylint: disable=redeclared-assigned-name,unused-variable
|
773 |
+
if (b, c) != (y, z) or a == x:
|
774 |
+
continue
|
775 |
+
if nm.check_ncoll([x.num for x in [a, b, c, x]]):
|
776 |
+
yield dict(zip('ABPQ', [b, c, a, x]))
|
777 |
+
|
778 |
+
|
779 |
+
def match_all(
|
780 |
+
name: str, g: gh.Graph
|
781 |
+
) -> Generator[tuple[gm.Point, ...], None, None]:
|
782 |
+
"""Match all instances of a certain relation."""
|
783 |
+
if name in ['ncoll', 'npara', 'nperp']:
|
784 |
+
return []
|
785 |
+
if name == 'coll':
|
786 |
+
return g.all_colls()
|
787 |
+
if name == 'para':
|
788 |
+
return g.all_paras()
|
789 |
+
if name == 'perp':
|
790 |
+
return g.all_perps()
|
791 |
+
if name == 'cong':
|
792 |
+
return g.all_congs()
|
793 |
+
if name == 'eqangle':
|
794 |
+
return g.all_eqangles_8points()
|
795 |
+
if name == 'eqangle6':
|
796 |
+
return g.all_eqangles_6points()
|
797 |
+
if name == 'eqratio':
|
798 |
+
return g.all_eqratios_8points()
|
799 |
+
if name == 'eqratio6':
|
800 |
+
return g.all_eqratios_6points()
|
801 |
+
if name == 'cyclic':
|
802 |
+
return g.all_cyclics()
|
803 |
+
if name == 'midp':
|
804 |
+
return g.all_midps()
|
805 |
+
if name == 'circle':
|
806 |
+
return g.all_circles()
|
807 |
+
raise ValueError(f'Unrecognize {name}')
|
808 |
+
|
809 |
+
|
810 |
+
def cache_match(
|
811 |
+
graph: gh.Graph,
|
812 |
+
) -> Callable[str, list[tuple[gm.Point, ...]]]:
|
813 |
+
"""Cache throughout one single BFS level."""
|
814 |
+
cache = {}
|
815 |
+
|
816 |
+
def match_fn(name: str) -> list[tuple[gm.Point, ...]]:
|
817 |
+
if name in cache:
|
818 |
+
return cache[name]
|
819 |
+
|
820 |
+
result = list(match_all(name, graph))
|
821 |
+
cache[name] = result
|
822 |
+
return result
|
823 |
+
|
824 |
+
return match_fn
|
825 |
+
|
826 |
+
|
827 |
+
def try_to_map(
|
828 |
+
clause_enum: list[tuple[pr.Clause, list[tuple[gm.Point, ...]]]],
|
829 |
+
mapping: dict[str, gm.Point],
|
830 |
+
) -> Generator[dict[str, gm.Point], None, None]:
|
831 |
+
"""Recursively try to match the remaining points given current mapping."""
|
832 |
+
if not clause_enum:
|
833 |
+
yield mapping
|
834 |
+
return
|
835 |
+
|
836 |
+
clause, enum = clause_enum[0]
|
837 |
+
for points in enum:
|
838 |
+
mpcpy = dict(mapping)
|
839 |
+
|
840 |
+
fail = False
|
841 |
+
for p, a in zip(points, clause.args):
|
842 |
+
if a in mpcpy and mpcpy[a] != p or p in mpcpy and mpcpy[p] != a:
|
843 |
+
fail = True
|
844 |
+
break
|
845 |
+
mpcpy[a] = p
|
846 |
+
mpcpy[p] = a
|
847 |
+
|
848 |
+
if fail:
|
849 |
+
continue
|
850 |
+
|
851 |
+
for m in try_to_map(clause_enum[1:], mpcpy):
|
852 |
+
yield m
|
853 |
+
|
854 |
+
|
855 |
+
def match_generic(
|
856 |
+
g: gh.Graph,
|
857 |
+
cache: Callable[str, list[tuple[gm.Point, ...]]],
|
858 |
+
theorem: pr.Theorem
|
859 |
+
) -> Generator[dict[str, gm.Point], None, None]:
|
860 |
+
"""Match any generic rule that is not one of the above match_*() rules."""
|
861 |
+
clause2enum = {}
|
862 |
+
|
863 |
+
clauses = []
|
864 |
+
numerical_checks = []
|
865 |
+
for clause in theorem.premise:
|
866 |
+
if clause.name in ['ncoll', 'npara', 'nperp', 'sameside']:
|
867 |
+
numerical_checks.append(clause)
|
868 |
+
continue
|
869 |
+
|
870 |
+
enum = cache(clause.name)
|
871 |
+
if len(enum) == 0: # pylint: disable=g-explicit-length-test
|
872 |
+
return 0
|
873 |
+
|
874 |
+
clause2enum[clause] = enum
|
875 |
+
clauses.append((len(set(clause.args)), clause))
|
876 |
+
|
877 |
+
clauses = sorted(clauses, key=lambda x: x[0], reverse=True)
|
878 |
+
_, clauses = zip(*clauses)
|
879 |
+
|
880 |
+
for mapping in try_to_map([(c, clause2enum[c]) for c in clauses], {}):
|
881 |
+
if not mapping:
|
882 |
+
continue
|
883 |
+
|
884 |
+
checks_ok = True
|
885 |
+
for check in numerical_checks:
|
886 |
+
args = [mapping[a] for a in check.args]
|
887 |
+
if check.name == 'ncoll':
|
888 |
+
checks_ok = g.check_ncoll(args)
|
889 |
+
elif check.name == 'npara':
|
890 |
+
checks_ok = g.check_npara(args)
|
891 |
+
elif check.name == 'nperp':
|
892 |
+
checks_ok = g.check_nperp(args)
|
893 |
+
elif check.name == 'sameside':
|
894 |
+
checks_ok = g.check_sameside(args)
|
895 |
+
if not checks_ok:
|
896 |
+
break
|
897 |
+
if not checks_ok:
|
898 |
+
continue
|
899 |
+
|
900 |
+
yield mapping
|
901 |
+
|
902 |
+
|
903 |
+
BUILT_IN_FNS = {
|
904 |
+
'cong_cong_cong_cyclic': match_cong_cong_cong_cyclic,
|
905 |
+
'cong_cong_cong_ncoll_contri*': match_cong_cong_cong_ncoll_contri,
|
906 |
+
'cong_cong_eqangle6_ncoll_contri*': match_cong_cong_eqangle6_ncoll_contri,
|
907 |
+
'eqangle6_eqangle6_ncoll_simtri': match_eqangle6_eqangle6_ncoll_simtri,
|
908 |
+
'eqangle6_eqangle6_ncoll_cong_contri': (
|
909 |
+
match_eqangle6_eqangle6_ncoll_cong_contri
|
910 |
+
), # pylint: disable=line-too-long
|
911 |
+
'eqangle6_eqangle6_ncoll_simtri2': match_eqangle6_eqangle6_ncoll_simtri2,
|
912 |
+
'eqangle6_eqangle6_ncoll_cong_contri2': (
|
913 |
+
match_eqangle6_eqangle6_ncoll_cong_contri2
|
914 |
+
), # pylint: disable=line-too-long
|
915 |
+
'eqratio6_eqratio6_ncoll_simtri*': match_eqratio6_eqratio6_ncoll_simtri,
|
916 |
+
'eqratio6_eqratio6_ncoll_cong_contri*': (
|
917 |
+
match_eqratio6_eqratio6_ncoll_cong_contri
|
918 |
+
), # pylint: disable=line-too-long
|
919 |
+
'eqangle_para': match_eqangle_para,
|
920 |
+
'eqangle_ncoll_cyclic': match_eqangle_ncoll_cyclic,
|
921 |
+
'eqratio6_eqangle6_ncoll_simtri*': match_eqratio6_eqangle6_ncoll_simtri,
|
922 |
+
'eqangle_perp_perp': match_eqangle_perp_perp,
|
923 |
+
'eqangle6_ncoll_cong': match_eqangle6_ncoll_cong,
|
924 |
+
'perp_perp_ncoll_para': match_perp_perp_ncoll_para,
|
925 |
+
'circle_perp_eqangle': match_circle_perp_eqangle,
|
926 |
+
'circle_eqangle_perp': match_circle_eqangle_perp,
|
927 |
+
'cyclic_eqangle_cong': match_cyclic_eqangle_cong,
|
928 |
+
'midp_perp_cong': match_midp_perp_cong,
|
929 |
+
'perp_perp_npara_eqangle': match_perp_perp_npara_eqangle,
|
930 |
+
'cyclic_eqangle': match_cyclic_eqangle,
|
931 |
+
'eqangle_eqangle_eqangle': match_eqangle_eqangle_eqangle,
|
932 |
+
'eqratio_eqratio_eqratio': match_eqratio_eqratio_eqratio,
|
933 |
+
'eqratio6_coll_ncoll_eqangle6': match_eqratio6_coll_ncoll_eqangle6,
|
934 |
+
'eqangle6_coll_ncoll_eqratio6': match_eqangle6_coll_ncoll_eqratio6,
|
935 |
+
'eqangle6_ncoll_cyclic': match_eqangle6_ncoll_cyclic,
|
936 |
+
}
|
937 |
+
|
938 |
+
|
939 |
+
SKIP_THEOREMS = set()
|
940 |
+
|
941 |
+
|
942 |
+
def set_skip_theorems(theorems: set[str]) -> None:
|
943 |
+
SKIP_THEOREMS.update(theorems)
|
944 |
+
|
945 |
+
|
946 |
+
MAX_BRANCH = 50_000
|
947 |
+
|
948 |
+
|
949 |
+
def match_one_theorem(
|
950 |
+
g: gh.Graph,
|
951 |
+
cache: Callable[str, list[tuple[gm.Point, ...]]],
|
952 |
+
theorem: pr.Theorem
|
953 |
+
) -> Generator[dict[str, gm.Point], None, None]:
|
954 |
+
"""Match all instances of a single theorem (rule)."""
|
955 |
+
if cache is None:
|
956 |
+
cache = cache_match(g)
|
957 |
+
|
958 |
+
if theorem.name in SKIP_THEOREMS:
|
959 |
+
return []
|
960 |
+
|
961 |
+
if theorem.name.split('_')[-1] in SKIP_THEOREMS:
|
962 |
+
return []
|
963 |
+
|
964 |
+
if theorem.name in BUILT_IN_FNS:
|
965 |
+
mps = BUILT_IN_FNS[theorem.name](g, cache, theorem)
|
966 |
+
else:
|
967 |
+
mps = match_generic(g, cache, theorem)
|
968 |
+
|
969 |
+
mappings = []
|
970 |
+
for mp in mps:
|
971 |
+
mappings.append(mp)
|
972 |
+
if len(mappings) > MAX_BRANCH: # cap branching at this number.
|
973 |
+
break
|
974 |
+
|
975 |
+
return mappings
|
976 |
+
|
977 |
+
|
978 |
+
def match_all_theorems(
|
979 |
+
g: gh.Graph, theorems: list[pr.Theorem], goal: pr.Clause
|
980 |
+
) -> dict[pr.Theorem, dict[pr.Theorem, dict[str, gm.Point]]]:
|
981 |
+
"""Match all instances of all theorems (rules)."""
|
982 |
+
cache = cache_match(g)
|
983 |
+
# for BFS, collect all potential matches
|
984 |
+
# and then do it at the same time
|
985 |
+
theorem2mappings = {}
|
986 |
+
|
987 |
+
# Step 1: list all matches
|
988 |
+
for _, theorem in theorems.items():
|
989 |
+
name = theorem.name
|
990 |
+
if name.split('_')[-1] in [
|
991 |
+
'acompute',
|
992 |
+
'rcompute',
|
993 |
+
'fixl',
|
994 |
+
'fixc',
|
995 |
+
'fixb',
|
996 |
+
'fixt',
|
997 |
+
'fixp',
|
998 |
+
]:
|
999 |
+
if goal and goal.name != name:
|
1000 |
+
continue
|
1001 |
+
|
1002 |
+
mappings = match_one_theorem(g, cache, theorem)
|
1003 |
+
if len(mappings): # pylint: disable=g-explicit-length-test
|
1004 |
+
theorem2mappings[theorem] = list(mappings)
|
1005 |
+
return theorem2mappings
|
1006 |
+
|
1007 |
+
|
1008 |
+
def bfs_one_level(
|
1009 |
+
g: gh.Graph,
|
1010 |
+
theorems: list[pr.Theorem],
|
1011 |
+
level: int,
|
1012 |
+
controller: pr.Problem,
|
1013 |
+
verbose: bool = False,
|
1014 |
+
nm_check: bool = False,
|
1015 |
+
timeout: int = 600,
|
1016 |
+
) -> tuple[
|
1017 |
+
list[pr.Dependency],
|
1018 |
+
dict[str, list[tuple[gm.Point, ...]]],
|
1019 |
+
dict[str, list[tuple[gm.Point, ...]]],
|
1020 |
+
int,
|
1021 |
+
]:
|
1022 |
+
"""Forward deduce one breadth-first level."""
|
1023 |
+
|
1024 |
+
# Step 1: match all theorems:
|
1025 |
+
theorem2mappings = match_all_theorems(g, theorems, controller.goal)
|
1026 |
+
|
1027 |
+
# Step 2: traceback for each deduce:
|
1028 |
+
theorem2deps = {}
|
1029 |
+
t0 = time.time()
|
1030 |
+
for theorem, mappings in theorem2mappings.items():
|
1031 |
+
if time.time() - t0 > timeout:
|
1032 |
+
break
|
1033 |
+
mp_deps = []
|
1034 |
+
for mp in mappings:
|
1035 |
+
deps = EmptyDependency(level=level, rule_name=theorem.rule_name)
|
1036 |
+
fail = False # finding why deps might fail.
|
1037 |
+
|
1038 |
+
for p in theorem.premise:
|
1039 |
+
p_args = [mp[a] for a in p.args]
|
1040 |
+
# Trivial deps.
|
1041 |
+
if p.name == 'cong':
|
1042 |
+
a, b, c, d = p_args
|
1043 |
+
if {a, b} == {c, d}:
|
1044 |
+
continue
|
1045 |
+
if p.name == 'para':
|
1046 |
+
a, b, c, d = p_args
|
1047 |
+
if {a, b} == {c, d}:
|
1048 |
+
continue
|
1049 |
+
|
1050 |
+
if theorem.name in [
|
1051 |
+
'cong_cong_eqangle6_ncoll_contri*',
|
1052 |
+
'eqratio6_eqangle6_ncoll_simtri*',
|
1053 |
+
]:
|
1054 |
+
if p.name in ['eqangle', 'eqangle6']: # SAS or RAR
|
1055 |
+
b, a, b, c, y, x, y, z = ( # pylint: disable=redeclared-assigned-name,unused-variable
|
1056 |
+
p_args
|
1057 |
+
)
|
1058 |
+
if not nm.same_clock(a.num, b.num, c.num, x.num, y.num, z.num):
|
1059 |
+
p_args = b, a, b, c, y, z, y, x
|
1060 |
+
|
1061 |
+
dep = Dependency(p.name, p_args, rule_name='', level=level)
|
1062 |
+
try:
|
1063 |
+
dep = dep.why_me_or_cache(g, level)
|
1064 |
+
except: # pylint: disable=bare-except
|
1065 |
+
fail = True
|
1066 |
+
break
|
1067 |
+
|
1068 |
+
if dep.why is None:
|
1069 |
+
fail = True
|
1070 |
+
break
|
1071 |
+
g.cache_dep(p.name, p_args, dep)
|
1072 |
+
deps.why.append(dep)
|
1073 |
+
|
1074 |
+
if fail:
|
1075 |
+
continue
|
1076 |
+
|
1077 |
+
mp_deps.append((mp, deps))
|
1078 |
+
theorem2deps[theorem] = mp_deps
|
1079 |
+
|
1080 |
+
theorem2deps = list(theorem2deps.items())
|
1081 |
+
|
1082 |
+
# Step 3: add conclusions to graph.
|
1083 |
+
# Note that we do NOT mix step 2 and 3, strictly going for BFS.
|
1084 |
+
added = []
|
1085 |
+
for theorem, mp_deps in theorem2deps:
|
1086 |
+
for mp, deps in mp_deps:
|
1087 |
+
if time.time() - t0 > timeout:
|
1088 |
+
break
|
1089 |
+
name, args = theorem.conclusion_name_args(mp)
|
1090 |
+
hash_conclusion = pr.hashed(name, args)
|
1091 |
+
if hash_conclusion in g.cache:
|
1092 |
+
continue
|
1093 |
+
|
1094 |
+
add = g.add_piece(name, args, deps=deps)
|
1095 |
+
added += add
|
1096 |
+
|
1097 |
+
branching = len(added)
|
1098 |
+
|
1099 |
+
# Check if goal is found
|
1100 |
+
if controller.goal:
|
1101 |
+
args = []
|
1102 |
+
|
1103 |
+
for a in controller.goal.args:
|
1104 |
+
if a in g._name2node:
|
1105 |
+
a = g._name2node[a]
|
1106 |
+
elif '/' in a:
|
1107 |
+
a = create_consts_str(g, a)
|
1108 |
+
elif a.isdigit():
|
1109 |
+
a = int(a)
|
1110 |
+
args.append(a)
|
1111 |
+
|
1112 |
+
if g.check(controller.goal.name, args):
|
1113 |
+
return added, {}, {}, branching
|
1114 |
+
|
1115 |
+
# Run AR, but do NOT apply to the proof state (yet).
|
1116 |
+
for dep in added:
|
1117 |
+
g.add_algebra(dep, level)
|
1118 |
+
derives, eq4s = g.derive_algebra(level, verbose=False)
|
1119 |
+
|
1120 |
+
branching += sum([len(x) for x in derives.values()])
|
1121 |
+
branching += sum([len(x) for x in eq4s.values()])
|
1122 |
+
|
1123 |
+
return added, derives, eq4s, branching
|
1124 |
+
|
1125 |
+
|
1126 |
+
def create_consts_str(g: gh.Graph, s: str) -> gm.Angle | gm.Ratio:
|
1127 |
+
if 'pi/' in s:
|
1128 |
+
n, d = s.split('pi/')
|
1129 |
+
n, d = int(n), int(d)
|
1130 |
+
p0, _ = g.get_or_create_const_ang(n, d)
|
1131 |
+
else:
|
1132 |
+
n, d = s.split('/')
|
1133 |
+
n, d = int(n), int(d)
|
1134 |
+
p0, _ = g.get_or_create_const_rat(n, d)
|
1135 |
+
return p0
|
1136 |
+
|
1137 |
+
|
1138 |
+
def do_algebra(
|
1139 |
+
g: gh.Graph, added: list[pr.Dependency], verbose: bool = False
|
1140 |
+
) -> None:
|
1141 |
+
for add in added:
|
1142 |
+
g.add_algebra(add, None)
|
1143 |
+
derives, eq4s = g.derive_algebra(level=None, verbose=verbose)
|
1144 |
+
apply_derivations(g, derives)
|
1145 |
+
apply_derivations(g, eq4s)
|
1146 |
+
|
1147 |
+
|
1148 |
+
def apply_derivations(
|
1149 |
+
g: gh.Graph, derives: dict[str, list[tuple[gm.Point, ...]]]
|
1150 |
+
) -> list[pr.Dependency]:
|
1151 |
+
applied = []
|
1152 |
+
all_derives = list(derives.items())
|
1153 |
+
for name, args in all_derives:
|
1154 |
+
for arg in args:
|
1155 |
+
applied += g.do_algebra(name, arg)
|
1156 |
+
return applied
|
ag4masses/alphageometry/dd_test.py
ADDED
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright 2023 DeepMind Technologies Limited
|
2 |
+
#
|
3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4 |
+
# you may not use this file except in compliance with the License.
|
5 |
+
# You may obtain a copy of the License at
|
6 |
+
#
|
7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8 |
+
#
|
9 |
+
# Unless required by applicable law or agreed to in writing, software
|
10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
+
# See the License for the specific language governing permissions and
|
13 |
+
# limitations under the License.
|
14 |
+
# ==============================================================================
|
15 |
+
|
16 |
+
"""Unit tests for dd."""
|
17 |
+
import unittest
|
18 |
+
|
19 |
+
from absl.testing import absltest
|
20 |
+
import dd
|
21 |
+
import graph as gh
|
22 |
+
import problem as pr
|
23 |
+
|
24 |
+
|
25 |
+
MAX_LEVEL = 1000
|
26 |
+
|
27 |
+
|
28 |
+
class DDTest(unittest.TestCase):
|
29 |
+
|
30 |
+
@classmethod
|
31 |
+
def setUpClass(cls):
|
32 |
+
super().setUpClass()
|
33 |
+
cls.defs = pr.Definition.from_txt_file('defs.txt', to_dict=True)
|
34 |
+
cls.rules = pr.Theorem.from_txt_file('rules.txt', to_dict=True)
|
35 |
+
|
36 |
+
def test_imo_2022_p4_should_succeed(self):
|
37 |
+
p = pr.Problem.from_txt(
|
38 |
+
'a b = segment a b; g1 = on_tline g1 a a b; g2 = on_tline g2 b b a; m ='
|
39 |
+
' on_circle m g1 a, on_circle m g2 b; n = on_circle n g1 a, on_circle n'
|
40 |
+
' g2 b; c = on_pline c m a b, on_circle c g1 a; d = on_pline d m a b,'
|
41 |
+
' on_circle d g2 b; e = on_line e a c, on_line e b d; p = on_line p a'
|
42 |
+
' n, on_line p c d; q = on_line q b n, on_line q c d ? cong e p e q'
|
43 |
+
)
|
44 |
+
g, _ = gh.Graph.build_problem(p, DDTest.defs)
|
45 |
+
goal_args = g.names2nodes(p.goal.args)
|
46 |
+
|
47 |
+
success = False
|
48 |
+
for level in range(MAX_LEVEL):
|
49 |
+
added, _, _, _ = dd.bfs_one_level(g, DDTest.rules, level, p)
|
50 |
+
if g.check(p.goal.name, goal_args):
|
51 |
+
success = True
|
52 |
+
break
|
53 |
+
if not added: # saturated
|
54 |
+
break
|
55 |
+
|
56 |
+
self.assertTrue(success)
|
57 |
+
|
58 |
+
def test_incenter_excenter_should_fail(self):
|
59 |
+
p = pr.Problem.from_txt(
|
60 |
+
'a b c = triangle a b c; d = incenter d a b c; e = excenter e a b c ?'
|
61 |
+
' perp d c c e'
|
62 |
+
)
|
63 |
+
g, _ = gh.Graph.build_problem(p, DDTest.defs)
|
64 |
+
goal_args = g.names2nodes(p.goal.args)
|
65 |
+
|
66 |
+
success = False
|
67 |
+
for level in range(MAX_LEVEL):
|
68 |
+
added, _, _, _ = dd.bfs_one_level(g, DDTest.rules, level, p)
|
69 |
+
if g.check(p.goal.name, goal_args):
|
70 |
+
success = True
|
71 |
+
break
|
72 |
+
if not added: # saturated
|
73 |
+
break
|
74 |
+
|
75 |
+
self.assertFalse(success)
|
76 |
+
|
77 |
+
|
78 |
+
if __name__ == '__main__':
|
79 |
+
absltest.main()
|
ag4masses/alphageometry/ddar.py
ADDED
@@ -0,0 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright 2023 DeepMind Technologies Limited
|
2 |
+
#
|
3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4 |
+
# you may not use this file except in compliance with the License.
|
5 |
+
# You may obtain a copy of the License at
|
6 |
+
#
|
7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8 |
+
#
|
9 |
+
# Unless required by applicable law or agreed to in writing, software
|
10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
+
# See the License for the specific language governing permissions and
|
13 |
+
# limitations under the License.
|
14 |
+
# ==============================================================================
|
15 |
+
|
16 |
+
"""Implements the combination DD+AR."""
|
17 |
+
import time
|
18 |
+
|
19 |
+
from absl import logging
|
20 |
+
import dd
|
21 |
+
import graph as gh
|
22 |
+
import problem as pr
|
23 |
+
from problem import Dependency # pylint: disable=g-importing-member
|
24 |
+
import trace_back
|
25 |
+
|
26 |
+
|
27 |
+
def saturate_or_goal(
|
28 |
+
g: gh.Graph,
|
29 |
+
theorems: list[pr.Theorem],
|
30 |
+
level_times: list[float],
|
31 |
+
p: pr.Problem,
|
32 |
+
max_level: int = 100,
|
33 |
+
timeout: int = 600,
|
34 |
+
) -> tuple[
|
35 |
+
list[dict[str, list[tuple[gh.Point, ...]]]],
|
36 |
+
list[dict[str, list[tuple[gh.Point, ...]]]],
|
37 |
+
list[int],
|
38 |
+
list[pr.Dependency],
|
39 |
+
]:
|
40 |
+
"""Run DD until saturation or goal found."""
|
41 |
+
derives = []
|
42 |
+
eq4s = []
|
43 |
+
branching = []
|
44 |
+
all_added = []
|
45 |
+
|
46 |
+
while len(level_times) < max_level:
|
47 |
+
level = len(level_times) + 1
|
48 |
+
|
49 |
+
t = time.time()
|
50 |
+
added, derv, eq4, n_branching = dd.bfs_one_level(
|
51 |
+
g, theorems, level, p, verbose=False, nm_check=True, timeout=timeout
|
52 |
+
)
|
53 |
+
all_added += added
|
54 |
+
branching.append(n_branching)
|
55 |
+
|
56 |
+
derives.append(derv)
|
57 |
+
eq4s.append(eq4)
|
58 |
+
level_time = time.time() - t
|
59 |
+
|
60 |
+
logging.info(f'Depth {level}/{max_level} time = {level_time}') # pylint: disable=logging-fstring-interpolation
|
61 |
+
level_times.append(level_time)
|
62 |
+
|
63 |
+
if p.goal is not None:
|
64 |
+
goal_args = list(map(lambda x: g.get(x, lambda: int(x)), p.goal.args))
|
65 |
+
if g.check(p.goal.name, goal_args): # found goal
|
66 |
+
break
|
67 |
+
|
68 |
+
if not added: # saturated
|
69 |
+
break
|
70 |
+
|
71 |
+
if level_time > timeout:
|
72 |
+
break
|
73 |
+
|
74 |
+
return derives, eq4s, branching, all_added
|
75 |
+
|
76 |
+
|
77 |
+
def solve(
|
78 |
+
g: gh.Graph,
|
79 |
+
theorems: list[pr.Problem],
|
80 |
+
controller: pr.Problem,
|
81 |
+
max_level: int = 1000,
|
82 |
+
timeout: int = 600,
|
83 |
+
) -> tuple[gh.Graph, list[float], str, list[int], list[pr.Dependency]]:
|
84 |
+
"""Alternate between DD and AR until goal is found."""
|
85 |
+
status = 'saturated'
|
86 |
+
level_times = []
|
87 |
+
|
88 |
+
dervs, eq4 = g.derive_algebra(level=0, verbose=False)
|
89 |
+
derives = [dervs]
|
90 |
+
eq4s = [eq4]
|
91 |
+
branches = []
|
92 |
+
all_added = []
|
93 |
+
|
94 |
+
while len(level_times) < max_level:
|
95 |
+
dervs, eq4, next_branches, added = saturate_or_goal(
|
96 |
+
g, theorems, level_times, controller, max_level, timeout=timeout
|
97 |
+
)
|
98 |
+
all_added += added
|
99 |
+
|
100 |
+
derives += dervs
|
101 |
+
eq4s += eq4
|
102 |
+
branches += next_branches
|
103 |
+
|
104 |
+
# Now, it is either goal or saturated
|
105 |
+
if controller.goal is not None:
|
106 |
+
goal_args = g.names2points(controller.goal.args)
|
107 |
+
if g.check(controller.goal.name, goal_args): # found goal
|
108 |
+
status = 'solved'
|
109 |
+
break
|
110 |
+
|
111 |
+
if not derives: # officially saturated.
|
112 |
+
logging.info("derives empty, breaking")
|
113 |
+
break
|
114 |
+
|
115 |
+
# Now we resort to algebra derivations.
|
116 |
+
added = []
|
117 |
+
while derives and not added:
|
118 |
+
added += dd.apply_derivations(g, derives.pop(0))
|
119 |
+
|
120 |
+
if added:
|
121 |
+
continue
|
122 |
+
|
123 |
+
# Final help from AR.
|
124 |
+
while eq4s and not added:
|
125 |
+
added += dd.apply_derivations(g, eq4s.pop(0))
|
126 |
+
|
127 |
+
all_added += added
|
128 |
+
|
129 |
+
if not added: # Nothing left. saturated.
|
130 |
+
logging.info("Nothing added, breaking")
|
131 |
+
break
|
132 |
+
|
133 |
+
return g, level_times, status, branches, all_added
|
134 |
+
|
135 |
+
|
136 |
+
def get_proof_steps(
|
137 |
+
g: gh.Graph, goal: pr.Clause, merge_trivials: bool = False
|
138 |
+
) -> tuple[
|
139 |
+
list[pr.Dependency],
|
140 |
+
list[pr.Dependency],
|
141 |
+
list[tuple[list[pr.Dependency], list[pr.Dependency]]],
|
142 |
+
dict[tuple[str, ...], int],
|
143 |
+
]:
|
144 |
+
"""Extract proof steps from the built DAG."""
|
145 |
+
goal_args = g.names2nodes(goal.args)
|
146 |
+
query = Dependency(goal.name, goal_args, None, None)
|
147 |
+
|
148 |
+
setup, aux, log, setup_points = trace_back.get_logs(
|
149 |
+
query, g, merge_trivials=merge_trivials
|
150 |
+
)
|
151 |
+
|
152 |
+
refs = {}
|
153 |
+
setup = trace_back.point_log(setup, refs, set())
|
154 |
+
aux = trace_back.point_log(aux, refs, setup_points)
|
155 |
+
|
156 |
+
setup = [(prems, [tuple(p)]) for p, prems in setup]
|
157 |
+
aux = [(prems, [tuple(p)]) for p, prems in aux]
|
158 |
+
|
159 |
+
return setup, aux, log, refs
|
ag4masses/alphageometry/ddar_test.py
ADDED
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright 2023 DeepMind Technologies Limited
|
2 |
+
#
|
3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4 |
+
# you may not use this file except in compliance with the License.
|
5 |
+
# You may obtain a copy of the License at
|
6 |
+
#
|
7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8 |
+
#
|
9 |
+
# Unless required by applicable law or agreed to in writing, software
|
10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
+
# See the License for the specific language governing permissions and
|
13 |
+
# limitations under the License.
|
14 |
+
# ==============================================================================
|
15 |
+
|
16 |
+
"""Unit tests for ddar.py."""
|
17 |
+
import unittest
|
18 |
+
|
19 |
+
from absl.testing import absltest
|
20 |
+
import ddar
|
21 |
+
import graph as gh
|
22 |
+
import problem as pr
|
23 |
+
|
24 |
+
|
25 |
+
class DDARTest(unittest.TestCase):
|
26 |
+
|
27 |
+
@classmethod
|
28 |
+
def setUpClass(cls):
|
29 |
+
super().setUpClass()
|
30 |
+
cls.defs = pr.Definition.from_txt_file('defs.txt', to_dict=True)
|
31 |
+
cls.rules = pr.Theorem.from_txt_file('rules.txt', to_dict=True)
|
32 |
+
|
33 |
+
def test_orthocenter_should_fail(self):
|
34 |
+
txt = 'a b c = triangle a b c; d = on_tline d b a c, on_tline d c a b ? perp a d b c' # pylint: disable=line-too-long
|
35 |
+
p = pr.Problem.from_txt(txt)
|
36 |
+
g, _ = gh.Graph.build_problem(p, DDARTest.defs)
|
37 |
+
|
38 |
+
ddar.solve(g, DDARTest.rules, p, max_level=1000)
|
39 |
+
goal_args = g.names2nodes(p.goal.args)
|
40 |
+
self.assertFalse(g.check(p.goal.name, goal_args))
|
41 |
+
|
42 |
+
def test_orthocenter_aux_should_succeed(self):
|
43 |
+
txt = 'a b c = triangle a b c; d = on_tline d b a c, on_tline d c a b; e = on_line e a c, on_line e b d ? perp a d b c' # pylint: disable=line-too-long
|
44 |
+
p = pr.Problem.from_txt(txt)
|
45 |
+
g, _ = gh.Graph.build_problem(p, DDARTest.defs)
|
46 |
+
|
47 |
+
ddar.solve(g, DDARTest.rules, p, max_level=1000)
|
48 |
+
goal_args = g.names2nodes(p.goal.args)
|
49 |
+
self.assertTrue(g.check(p.goal.name, goal_args))
|
50 |
+
|
51 |
+
def test_incenter_excenter_should_succeed(self):
|
52 |
+
# Note that this same problem should fail in dd_test.py
|
53 |
+
p = pr.Problem.from_txt(
|
54 |
+
'a b c = triangle a b c; d = incenter d a b c; e = excenter e a b c ?'
|
55 |
+
' perp d c c e'
|
56 |
+
) # pylint: disable=line-too-long
|
57 |
+
g, _ = gh.Graph.build_problem(p, DDARTest.defs)
|
58 |
+
|
59 |
+
ddar.solve(g, DDARTest.rules, p, max_level=1000)
|
60 |
+
goal_args = g.names2nodes(p.goal.args)
|
61 |
+
self.assertTrue(g.check(p.goal.name, goal_args))
|
62 |
+
|
63 |
+
|
64 |
+
if __name__ == '__main__':
|
65 |
+
absltest.main()
|
ag4masses/alphageometry/decoder_stack.py
ADDED
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright 2023 DeepMind Technologies Limited
|
2 |
+
#
|
3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4 |
+
# you may not use this file except in compliance with the License.
|
5 |
+
# You may obtain a copy of the License at
|
6 |
+
#
|
7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8 |
+
#
|
9 |
+
# Unless required by applicable law or agreed to in writing, software
|
10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
+
# See the License for the specific language governing permissions and
|
13 |
+
# limitations under the License.
|
14 |
+
# ==============================================================================
|
15 |
+
|
16 |
+
"""The decoder stack in inference mode."""
|
17 |
+
|
18 |
+
from typing import Any, Tuple
|
19 |
+
|
20 |
+
import gin
|
21 |
+
from transformer import decoder_stack
|
22 |
+
import transformer_layer as tl
|
23 |
+
|
24 |
+
|
25 |
+
struct = decoder_stack.struct
|
26 |
+
nn_components = decoder_stack.nn_components
|
27 |
+
position = decoder_stack.position
|
28 |
+
jnp = decoder_stack.jnp
|
29 |
+
attention = decoder_stack.attention
|
30 |
+
|
31 |
+
DStackWindowState = decoder_stack.DStackWindowState
|
32 |
+
|
33 |
+
Array = Any
|
34 |
+
|
35 |
+
TransformerTaskConfig = decoder_stack.TransformerTaskConfig
|
36 |
+
|
37 |
+
DStackDecoderState = Tuple[tl.DecoderState, ...]
|
38 |
+
|
39 |
+
|
40 |
+
@gin.configurable
|
41 |
+
class DecoderStackGenerate(decoder_stack.DecoderStack):
|
42 |
+
"""Stack of transformer decoder layers."""
|
43 |
+
|
44 |
+
layer_factory = tl.TransformerLayerGenerate
|
45 |
+
|
46 |
+
def init_decoder_state_vanilla(
|
47 |
+
self, sequence_length: int, start_of_sequence: Array
|
48 |
+
) -> DStackDecoderState:
|
49 |
+
"""Return initial state for autoregressive generation."""
|
50 |
+
return tuple(
|
51 |
+
[
|
52 |
+
layer.init_decoder_state_vanilla(sequence_length, start_of_sequence)
|
53 |
+
for layer in self.transformer_layers
|
54 |
+
]
|
55 |
+
)
|
ag4masses/alphageometry/defs.txt
ADDED
@@ -0,0 +1,407 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
angle_bisector x a b c
|
2 |
+
x : a b c x
|
3 |
+
a b c = ncoll a b c
|
4 |
+
x : eqangle b a b x b x b c
|
5 |
+
bisect a b c
|
6 |
+
|
7 |
+
angle_mirror x a b c
|
8 |
+
x : a b c x
|
9 |
+
a b c = ncoll a b c
|
10 |
+
x : eqangle b a b c b c b x
|
11 |
+
amirror a b c
|
12 |
+
|
13 |
+
circle x a b c
|
14 |
+
x : a b c
|
15 |
+
a b c = ncoll a b c
|
16 |
+
x : cong x a x b, cong x b x c
|
17 |
+
bline a b, bline a c
|
18 |
+
|
19 |
+
circumcenter x a b c
|
20 |
+
x : a b c
|
21 |
+
a b c = ncoll a b c
|
22 |
+
x : cong x a x b, cong x b x c
|
23 |
+
bline a b, bline a c
|
24 |
+
|
25 |
+
eq_quadrangle a b c d
|
26 |
+
d : a b c d
|
27 |
+
=
|
28 |
+
a : ; b : ; c : ; d : cong d a b c
|
29 |
+
eq_quadrangle
|
30 |
+
|
31 |
+
eq_trapezoid a b c d
|
32 |
+
d : a b c
|
33 |
+
=
|
34 |
+
a : ; b : ; c : ; d : para d c a b, cong d a b c
|
35 |
+
eq_trapezoid
|
36 |
+
|
37 |
+
eq_triangle x b c
|
38 |
+
x : b c
|
39 |
+
b c = diff b c
|
40 |
+
x : cong x b b c, cong b c c x; eqangle b x b c c b c x, eqangle x c x b b x b c
|
41 |
+
circle b b c, circle c b c
|
42 |
+
|
43 |
+
eqangle2 x a b c
|
44 |
+
x : a b c x
|
45 |
+
a b c = ncoll a b c
|
46 |
+
x : eqangle a b a x c x c b
|
47 |
+
eqangle2 a b c
|
48 |
+
|
49 |
+
eqdia_quadrangle a b c d
|
50 |
+
d : a b c d
|
51 |
+
=
|
52 |
+
a : ; b : ; c : ; d : cong d b a c
|
53 |
+
eqdia_quadrangle
|
54 |
+
|
55 |
+
eqdistance x a b c
|
56 |
+
x : a b c x
|
57 |
+
a b c = diff b c
|
58 |
+
x : cong x a b c
|
59 |
+
circle a b c
|
60 |
+
|
61 |
+
foot x a b c
|
62 |
+
x : a b c
|
63 |
+
a b c = ncoll a b c
|
64 |
+
x : perp x a b c, coll x b c
|
65 |
+
tline a b c, line b c
|
66 |
+
|
67 |
+
free a
|
68 |
+
a : a
|
69 |
+
=
|
70 |
+
a :
|
71 |
+
free
|
72 |
+
|
73 |
+
incenter x a b c
|
74 |
+
x : a b c
|
75 |
+
a b c = ncoll a b c
|
76 |
+
x : eqangle a b a x a x a c, eqangle c a c x c x c b; eqangle b c b x b x b a
|
77 |
+
bisect a b c, bisect b c a
|
78 |
+
|
79 |
+
incenter2 x y z i a b c
|
80 |
+
i : a b c, x : i b c, y : i c a, z : i a b
|
81 |
+
a b c = ncoll a b c
|
82 |
+
i : eqangle a b a i a i a c, eqangle c a c i c i c b; eqangle b c b i b i b a; x : coll x b c, perp i x b c; y : coll y c a, perp i y c a; z : coll z a b, perp i z a b; cong i x i y, cong i y i z
|
83 |
+
incenter2 a b c
|
84 |
+
|
85 |
+
excenter x a b c
|
86 |
+
x : a b c
|
87 |
+
a b c = ncoll a b c
|
88 |
+
x : eqangle a b a x a x a c, eqangle c a c x c x c b; eqangle b c b x b x b a
|
89 |
+
bisect b a c, exbisect b c a
|
90 |
+
|
91 |
+
excenter2 x y z i a b c
|
92 |
+
i : a b c, x : i b c, y : i c a, z : i a b
|
93 |
+
a b c = ncoll a b c
|
94 |
+
i : eqangle a b a i a i a c, eqangle c a c i c i c b; eqangle b c b i b i b a; x : coll x b c, perp i x b c; y : coll y c a, perp i y c a; z : coll z a b, perp i z a b; cong i x i y, cong i y i z
|
95 |
+
excenter2 a b c
|
96 |
+
|
97 |
+
centroid x y z i a b c
|
98 |
+
x : b c, y : c a, z : a b, i : a x b y
|
99 |
+
a b c = ncoll a b c
|
100 |
+
x : coll x b c, cong x b x c; y : coll y c a, cong y c y a; z : coll z a b, cong z a z b; i : coll a x i, coll b y i; coll c z i
|
101 |
+
centroid a b c
|
102 |
+
|
103 |
+
ninepoints x y z i a b c
|
104 |
+
x : b c, y : c a, z : a b, i : x y z
|
105 |
+
a b c = ncoll a b c
|
106 |
+
x : coll x b c, cong x b x c; y : coll y c a, cong y c y a; z : coll z a b, cong z a z b; i : cong i x i y, cong i y i z
|
107 |
+
ninepoints a b c
|
108 |
+
|
109 |
+
intersection_cc x o w a
|
110 |
+
x : o w a
|
111 |
+
o w a = ncoll o w a
|
112 |
+
x : cong o a o x, cong w a w x
|
113 |
+
circle o o a, circle w w a
|
114 |
+
|
115 |
+
intersection_lc x a o b
|
116 |
+
x : a o b
|
117 |
+
a o b = diff a b, diff o b, nperp b o b a
|
118 |
+
x : coll x a b, cong o b o x
|
119 |
+
line b a, circle o o b
|
120 |
+
|
121 |
+
intersection_ll x a b c d
|
122 |
+
x : a b c d
|
123 |
+
a b c d = npara a b c d, ncoll a b c d
|
124 |
+
x : coll x a b, coll x c d
|
125 |
+
line a b, line c d
|
126 |
+
|
127 |
+
intersection_lp x a b c m n
|
128 |
+
x : a b c m n
|
129 |
+
a b c m n = npara m n a b, ncoll a b c, ncoll c m n
|
130 |
+
x : coll x a b, para c x m n
|
131 |
+
line a b, pline c m n
|
132 |
+
|
133 |
+
intersection_lt x a b c d e
|
134 |
+
x : a b c d e
|
135 |
+
a b c d e = ncoll a b c, nperp a b d e
|
136 |
+
x : coll x a b, perp x c d e
|
137 |
+
line a b, tline c d e
|
138 |
+
|
139 |
+
intersection_pp x a b c d e f
|
140 |
+
x : a b c d e f
|
141 |
+
a b c d e f = diff a d, npara b c e f
|
142 |
+
x : para x a b c, para x d e f
|
143 |
+
pline a b c, pline d e f
|
144 |
+
|
145 |
+
intersection_tt x a b c d e f
|
146 |
+
x : a b c d e f
|
147 |
+
a b c d e f = diff a d, npara b c e f
|
148 |
+
x : perp x a b c, perp x d e f
|
149 |
+
tline a b c, tline d e f
|
150 |
+
|
151 |
+
iso_triangle a b c
|
152 |
+
c : a b c
|
153 |
+
=
|
154 |
+
a : ; b : ; c : eqangle b a b c c b c a, cong a b a c
|
155 |
+
isos
|
156 |
+
|
157 |
+
lc_tangent x a o
|
158 |
+
x : x a o
|
159 |
+
a o = diff a o
|
160 |
+
x : perp a x a o
|
161 |
+
tline a a o
|
162 |
+
|
163 |
+
midpoint x a b
|
164 |
+
x : a b
|
165 |
+
a b = diff a b
|
166 |
+
x : coll x a b, cong x a x b
|
167 |
+
midp a b
|
168 |
+
|
169 |
+
mirror x a b
|
170 |
+
x : a b
|
171 |
+
a b = diff a b
|
172 |
+
x : coll x a b, cong b a b x
|
173 |
+
pmirror a b
|
174 |
+
|
175 |
+
nsquare x a b
|
176 |
+
x : a b
|
177 |
+
a b = diff a b
|
178 |
+
x : cong x a a b, perp x a a b
|
179 |
+
rotaten90 a b
|
180 |
+
|
181 |
+
on_aline x a b c d e
|
182 |
+
x : x a b c d e
|
183 |
+
a b c d e = ncoll c d e
|
184 |
+
x : eqangle a x a b d c d e
|
185 |
+
aline e d c b a
|
186 |
+
|
187 |
+
on_aline2 x a b c d e
|
188 |
+
x : x a b c d e
|
189 |
+
a b c d e = ncoll c d e
|
190 |
+
x : eqangle x a x b d c d e
|
191 |
+
aline2 e d c b a
|
192 |
+
|
193 |
+
on_bline x a b
|
194 |
+
x : x a b
|
195 |
+
a b = diff a b
|
196 |
+
x : cong x a x b, eqangle a x a b b a b x
|
197 |
+
bline a b
|
198 |
+
|
199 |
+
on_circle x o a
|
200 |
+
x : x o a
|
201 |
+
o a = diff o a
|
202 |
+
x : cong o x o a
|
203 |
+
circle o o a
|
204 |
+
|
205 |
+
on_line x a b
|
206 |
+
x : x a b
|
207 |
+
a b = diff a b
|
208 |
+
x : coll x a b
|
209 |
+
line a b
|
210 |
+
|
211 |
+
on_pline x a b c
|
212 |
+
x : x a b c
|
213 |
+
a b c = diff b c, ncoll a b c
|
214 |
+
x : para x a b c
|
215 |
+
pline a b c
|
216 |
+
|
217 |
+
on_tline x a b c
|
218 |
+
x : x a b c
|
219 |
+
a b c = diff b c
|
220 |
+
x : perp x a b c
|
221 |
+
tline a b c
|
222 |
+
|
223 |
+
orthocenter x a b c
|
224 |
+
x : a b c
|
225 |
+
a b c = ncoll a b c
|
226 |
+
x : perp x a b c, perp x b c a; perp x c a b
|
227 |
+
tline a b c, tline b c a
|
228 |
+
|
229 |
+
parallelogram a b c x
|
230 |
+
x : a b c
|
231 |
+
a b c = ncoll a b c
|
232 |
+
x : para a b c x, para a x b c; cong a b c x, cong a x b c
|
233 |
+
pline a b c, pline c a b
|
234 |
+
|
235 |
+
pentagon a b c d e
|
236 |
+
|
237 |
+
=
|
238 |
+
a : ; b : ; c : ; d : ; e :
|
239 |
+
pentagon
|
240 |
+
|
241 |
+
psquare x a b
|
242 |
+
x : a b
|
243 |
+
a b = diff a b
|
244 |
+
x : cong x a a b, perp x a a b
|
245 |
+
rotatep90 a b
|
246 |
+
|
247 |
+
quadrangle a b c d
|
248 |
+
|
249 |
+
=
|
250 |
+
a : ; b : ; c : ; d :
|
251 |
+
quadrangle
|
252 |
+
|
253 |
+
r_trapezoid a b c d
|
254 |
+
d : a b c
|
255 |
+
=
|
256 |
+
a : ; b : ; c : ; d : para a b c d, perp a b a d
|
257 |
+
r_trapezoid
|
258 |
+
|
259 |
+
r_triangle a b c
|
260 |
+
c : a b c
|
261 |
+
=
|
262 |
+
a : ; b : ; c : perp a b a c
|
263 |
+
r_triangle
|
264 |
+
|
265 |
+
rectangle a b c d
|
266 |
+
c : a b c , d : a b c
|
267 |
+
=
|
268 |
+
a : ; b : ; c : perp a b b c ; d : para a b c d, para a d b c; perp a b a d, cong a b c d, cong a d b c, cong a c b d
|
269 |
+
rectangle
|
270 |
+
|
271 |
+
reflect x a b c
|
272 |
+
x : a b c
|
273 |
+
a b c = diff b c, ncoll a b c
|
274 |
+
x : cong b a b x, cong c a c x; perp b c a x
|
275 |
+
reflect a b c
|
276 |
+
|
277 |
+
risos a b c
|
278 |
+
c : a b
|
279 |
+
=
|
280 |
+
a : ; b : ; c : perp a b a c, cong a b a c; eqangle b a b c c b c a
|
281 |
+
risos
|
282 |
+
|
283 |
+
s_angle a b x y
|
284 |
+
x : a b x
|
285 |
+
a b = diff a b
|
286 |
+
x : s_angle a b x y
|
287 |
+
s_angle a b y
|
288 |
+
|
289 |
+
segment a b
|
290 |
+
|
291 |
+
=
|
292 |
+
a : ; b :
|
293 |
+
segment
|
294 |
+
|
295 |
+
shift x b c d
|
296 |
+
x : b c d
|
297 |
+
b c d = diff d b
|
298 |
+
x : cong x b c d, cong x c b d
|
299 |
+
shift d c b
|
300 |
+
|
301 |
+
square a b x y
|
302 |
+
x : a b, y : a b x
|
303 |
+
a b = diff a b
|
304 |
+
x : perp a b b x, cong a b b x; y : para a b x y, para a y b x; perp a y y x, cong b x x y, cong x y y a, perp a x b y, cong a x b y
|
305 |
+
square a b
|
306 |
+
|
307 |
+
isquare a b c d
|
308 |
+
c : a b , d : a b c
|
309 |
+
=
|
310 |
+
a : ; b : ; c : perp a b b c, cong a b b c; d : para a b c d, para a d b c; perp a d d c, cong b c c d, cong c d d a, perp a c b d, cong a c b d
|
311 |
+
isquare
|
312 |
+
|
313 |
+
trapezoid a b c d
|
314 |
+
d : a b c d
|
315 |
+
=
|
316 |
+
a : ; b : ; c : ; d : para a b c d
|
317 |
+
trapezoid
|
318 |
+
|
319 |
+
triangle a b c
|
320 |
+
|
321 |
+
=
|
322 |
+
a : ; b : ; c :
|
323 |
+
triangle
|
324 |
+
|
325 |
+
triangle12 a b c
|
326 |
+
c : a b c
|
327 |
+
=
|
328 |
+
a : ; b : ; c : rconst a b a c 1 2
|
329 |
+
triangle12
|
330 |
+
|
331 |
+
2l1c x y z i a b c o
|
332 |
+
x : a b c o y z i, y : a b c o x z i, z : a b c o x y i, i : a b c o x y z
|
333 |
+
a b c o = cong o a o b, ncoll a b c
|
334 |
+
x y z i : coll x a c, coll y b c, cong o a o z, coll i o z, cong i x i y, cong i y i z, perp i x a c, perp i y b c
|
335 |
+
2l1c a b c o
|
336 |
+
|
337 |
+
e5128 x y a b c d
|
338 |
+
x : a b c d y, y : a b c d x
|
339 |
+
a b c d = cong c b c d, perp b c b a
|
340 |
+
x y : cong c b c x, coll y a b, coll x y d, eqangle a b a d x a x y
|
341 |
+
e5128 a b c d
|
342 |
+
|
343 |
+
3peq x y z a b c
|
344 |
+
z : b c z , x : a b c z y, y : a b c z x
|
345 |
+
a b c = ncoll a b c
|
346 |
+
z : coll z b c ; x y : coll x a b, coll y a c, coll x y z, cong z x z y
|
347 |
+
3peq a b c
|
348 |
+
|
349 |
+
trisect x y a b c
|
350 |
+
x : a b c y, y : a b c x
|
351 |
+
a b c = ncoll a b c
|
352 |
+
x y : coll x a c, coll y a c, eqangle b a b x b x b y, eqangle b x b y b y b c
|
353 |
+
trisect a b c
|
354 |
+
|
355 |
+
trisegment x y a b
|
356 |
+
x : a b y, y : a b x
|
357 |
+
a b = diff a b
|
358 |
+
x y : coll x a b, coll y a b, cong x a x y, cong y x y b
|
359 |
+
trisegment a b
|
360 |
+
|
361 |
+
on_dia x a b
|
362 |
+
x : x a b
|
363 |
+
a b = diff a b
|
364 |
+
x : perp x a x b
|
365 |
+
dia a b
|
366 |
+
|
367 |
+
ieq_triangle a b c
|
368 |
+
c : a b
|
369 |
+
=
|
370 |
+
a : ; b : ; c : cong a b b c, cong b c c a; eqangle a b a c c a c b, eqangle c a c b b c b a
|
371 |
+
ieq_triangle
|
372 |
+
|
373 |
+
on_opline x a b
|
374 |
+
x : x a b
|
375 |
+
a b = diff a b
|
376 |
+
x : coll x a b
|
377 |
+
on_opline a b
|
378 |
+
|
379 |
+
cc_tangent0 x y o a w b
|
380 |
+
x : o a w b y, y : o a w b x
|
381 |
+
o a w b = diff o a, diff w b, diff o w
|
382 |
+
x y : cong o x o a, cong w y w b, perp x o x y, perp y w y x
|
383 |
+
cc_tangent0 o a w b
|
384 |
+
|
385 |
+
cc_tangent x y z i o a w b
|
386 |
+
x : o a w b y, y : o a w b x, z : o a w b i, i : o a w b z
|
387 |
+
o a w b = diff o a, diff w b, diff o w
|
388 |
+
x y : cong o x o a, cong w y w b, perp x o x y, perp y w y x; z i : cong o z o a, cong w i w b, perp z o z i, perp i w i z
|
389 |
+
cc_tangent o a w b
|
390 |
+
|
391 |
+
eqangle3 x a b d e f
|
392 |
+
x : x a b d e f
|
393 |
+
a b d e f = ncoll d e f, diff a b, diff d e, diff e f
|
394 |
+
x : eqangle x a x b d e d f
|
395 |
+
eqangle3 a b d e f
|
396 |
+
|
397 |
+
tangent x y a o b
|
398 |
+
x y : o a b
|
399 |
+
a o b = diff o a, diff o b, diff a b
|
400 |
+
x : cong o x o b, perp a x o x; y : cong o y o b, perp a y o y
|
401 |
+
tangent a o b
|
402 |
+
|
403 |
+
on_circum x a b c
|
404 |
+
x : a b c
|
405 |
+
a b c = ncoll a b c
|
406 |
+
x : cyclic a b c x
|
407 |
+
cyclic a b c
|
ag4masses/alphageometry/download.sh
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright 2023 DeepMind Technologies Limited
|
2 |
+
#
|
3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4 |
+
# you may not use this file except in compliance with the License.
|
5 |
+
# You may obtain a copy of the License at
|
6 |
+
#
|
7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8 |
+
#
|
9 |
+
# Unless required by applicable law or agreed to in writing, software
|
10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
+
# See the License for the specific language governing permissions and
|
13 |
+
# limitations under the License.
|
14 |
+
# ==============================================================================
|
15 |
+
|
16 |
+
gdown --folder https://bit.ly/alphageometry
|
17 |
+
export DATA=ag_ckpt_vocab
|
ag4masses/alphageometry/examples.txt
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
orthocenter
|
2 |
+
a b c = triangle; h = on_tline b a c, on_tline c a b ? perp a h b c
|
3 |
+
orthocenter_aux
|
4 |
+
a b c = triangle; d = on_tline d b a c, on_tline d c a b; e = on_line e a c, on_line e b d ? perp a d b c
|
5 |
+
incenter_excenter
|
6 |
+
a b c = triangle a b c; d1 d2 d3 d = incenter2 a b c; e1 e2 e3 e = excenter2 a b c ? perp d c c e
|
7 |
+
euler
|
8 |
+
a b c = triangle a b c; h = orthocenter a b c; h1 = foot a b c; h2 = foot b c a; h3 = foot c a b; g1 g2 g3 g = centroid g1 g2 g3 g a b c; o = circle a b c ? coll h g o
|
ag4masses/alphageometry/fig1.svg
ADDED
ag4masses/alphageometry/geometry.py
ADDED
@@ -0,0 +1,578 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright 2023 DeepMind Technologies Limited
|
2 |
+
#
|
3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4 |
+
# you may not use this file except in compliance with the License.
|
5 |
+
# You may obtain a copy of the License at
|
6 |
+
#
|
7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8 |
+
#
|
9 |
+
# Unless required by applicable law or agreed to in writing, software
|
10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
+
# See the License for the specific language governing permissions and
|
13 |
+
# limitations under the License.
|
14 |
+
# ==============================================================================
|
15 |
+
|
16 |
+
"""Implements geometric objects used in the graph representation."""
|
17 |
+
from __future__ import annotations
|
18 |
+
from collections import defaultdict # pylint: disable=g-importing-member
|
19 |
+
from typing import Any, Type
|
20 |
+
|
21 |
+
# pylint: disable=protected-access
|
22 |
+
|
23 |
+
|
24 |
+
class Node:
|
25 |
+
r"""Node in the proof state graph.
|
26 |
+
|
27 |
+
Can be Point, Line, Circle, etc.
|
28 |
+
|
29 |
+
Each node maintains a merge history to
|
30 |
+
other nodes if they are (found out to be) equivalent
|
31 |
+
|
32 |
+
a -> b -
|
33 |
+
\
|
34 |
+
c -> d -> e -> f -> g
|
35 |
+
|
36 |
+
d.merged_to = e
|
37 |
+
d.rep = g
|
38 |
+
d.merged_from = {a, b, c, d}
|
39 |
+
d.equivs = {a, b, c, d, e, f, g}
|
40 |
+
"""
|
41 |
+
|
42 |
+
def __init__(self, name: str = '', graph: Any = None):
|
43 |
+
self.name = name or str(self)
|
44 |
+
self.graph = graph
|
45 |
+
|
46 |
+
self.edge_graph = {}
|
47 |
+
# Edge graph: what other nodes is connected to this node.
|
48 |
+
# edge graph = {
|
49 |
+
# other1: {self1: deps, self2: deps},
|
50 |
+
# other2: {self2: deps, self3: deps}
|
51 |
+
# }
|
52 |
+
|
53 |
+
self.merge_graph = {}
|
54 |
+
# Merge graph: history of merges with other nodes.
|
55 |
+
# merge_graph = {self1: {self2: deps1, self3: deps2}}
|
56 |
+
|
57 |
+
self.rep_by = None # represented by.
|
58 |
+
self.members = {self}
|
59 |
+
|
60 |
+
self._val = None
|
61 |
+
self._obj = None
|
62 |
+
|
63 |
+
self.deps = []
|
64 |
+
|
65 |
+
# numerical representation.
|
66 |
+
self.num = None
|
67 |
+
self.change = set() # what other nodes' num rely on this node?
|
68 |
+
|
69 |
+
def set_rep(self, node: Node) -> None:
|
70 |
+
if node == self:
|
71 |
+
return
|
72 |
+
self.rep_by = node
|
73 |
+
node.merge_edge_graph(self.edge_graph)
|
74 |
+
node.members.update(self.members)
|
75 |
+
|
76 |
+
def rep(self) -> Node:
|
77 |
+
x = self
|
78 |
+
while x.rep_by:
|
79 |
+
x = x.rep_by
|
80 |
+
return x
|
81 |
+
|
82 |
+
def why_rep(self) -> list[Any]:
|
83 |
+
return self.why_equal([self.rep()], None)
|
84 |
+
|
85 |
+
def rep_and_why(self) -> tuple[Node, list[Any]]:
|
86 |
+
rep = self.rep()
|
87 |
+
return rep, self.why_equal([rep], None)
|
88 |
+
|
89 |
+
def neighbors(
|
90 |
+
self, oftype: Type[Node], return_set: bool = False, do_rep: bool = True
|
91 |
+
) -> list[Node]:
|
92 |
+
"""Neighbors of this node in the proof state graph."""
|
93 |
+
if do_rep:
|
94 |
+
rep = self.rep()
|
95 |
+
else:
|
96 |
+
rep = self
|
97 |
+
result = set()
|
98 |
+
|
99 |
+
for n in rep.edge_graph:
|
100 |
+
if oftype is None or oftype and isinstance(n, oftype):
|
101 |
+
if do_rep:
|
102 |
+
result.add(n.rep())
|
103 |
+
else:
|
104 |
+
result.add(n)
|
105 |
+
|
106 |
+
if return_set:
|
107 |
+
return result
|
108 |
+
return list(result)
|
109 |
+
|
110 |
+
def merge_edge_graph(
|
111 |
+
self, new_edge_graph: dict[Node, dict[Node, list[Node]]]
|
112 |
+
) -> None:
|
113 |
+
for x, xdict in new_edge_graph.items():
|
114 |
+
if x in self.edge_graph:
|
115 |
+
self.edge_graph[x].update(dict(xdict))
|
116 |
+
else:
|
117 |
+
self.edge_graph[x] = dict(xdict)
|
118 |
+
|
119 |
+
def merge(self, nodes: list[Node], deps: list[Any]) -> None:
|
120 |
+
for node in nodes:
|
121 |
+
self.merge_one(node, deps)
|
122 |
+
|
123 |
+
def merge_one(self, node: Node, deps: list[Any]) -> None:
|
124 |
+
node.rep().set_rep(self.rep())
|
125 |
+
|
126 |
+
if node in self.merge_graph:
|
127 |
+
return
|
128 |
+
|
129 |
+
self.merge_graph[node] = deps
|
130 |
+
node.merge_graph[self] = deps
|
131 |
+
|
132 |
+
def is_val(self, node: Node) -> bool:
|
133 |
+
return (
|
134 |
+
isinstance(self, Line)
|
135 |
+
and isinstance(node, Direction)
|
136 |
+
or isinstance(self, Segment)
|
137 |
+
and isinstance(node, Length)
|
138 |
+
or isinstance(self, Angle)
|
139 |
+
and isinstance(node, Measure)
|
140 |
+
or isinstance(self, Ratio)
|
141 |
+
and isinstance(node, Value)
|
142 |
+
)
|
143 |
+
|
144 |
+
def set_val(self, node: Node) -> None:
|
145 |
+
self._val = node
|
146 |
+
|
147 |
+
def set_obj(self, node: Node) -> None:
|
148 |
+
self._obj = node
|
149 |
+
|
150 |
+
@property
|
151 |
+
def val(self) -> Node:
|
152 |
+
if self._val is None:
|
153 |
+
return None
|
154 |
+
return self._val.rep()
|
155 |
+
|
156 |
+
@property
|
157 |
+
def obj(self) -> Node:
|
158 |
+
if self._obj is None:
|
159 |
+
return None
|
160 |
+
return self._obj.rep()
|
161 |
+
|
162 |
+
def equivs(self) -> set[Node]:
|
163 |
+
return self.rep().members
|
164 |
+
|
165 |
+
def connect_to(self, node: Node, deps: list[Any] = None) -> None:
|
166 |
+
rep = self.rep()
|
167 |
+
|
168 |
+
if node in rep.edge_graph:
|
169 |
+
rep.edge_graph[node].update({self: deps})
|
170 |
+
else:
|
171 |
+
rep.edge_graph[node] = {self: deps}
|
172 |
+
|
173 |
+
if self.is_val(node):
|
174 |
+
self.set_val(node)
|
175 |
+
node.set_obj(self)
|
176 |
+
|
177 |
+
def equivs_upto(self, level: int) -> dict[Node, Node]:
|
178 |
+
"""What are the equivalent nodes up to a certain level."""
|
179 |
+
parent = {self: None}
|
180 |
+
visited = set()
|
181 |
+
queue = [self]
|
182 |
+
i = 0
|
183 |
+
|
184 |
+
while i < len(queue):
|
185 |
+
current = queue[i]
|
186 |
+
i += 1
|
187 |
+
visited.add(current)
|
188 |
+
|
189 |
+
for neighbor in current.merge_graph:
|
190 |
+
if (
|
191 |
+
level is not None
|
192 |
+
and current.merge_graph[neighbor].level is not None
|
193 |
+
and current.merge_graph[neighbor].level >= level
|
194 |
+
):
|
195 |
+
continue
|
196 |
+
if neighbor not in visited:
|
197 |
+
queue.append(neighbor)
|
198 |
+
parent[neighbor] = current
|
199 |
+
|
200 |
+
return parent
|
201 |
+
|
202 |
+
def why_equal(self, others: list[Node], level: int) -> list[Any]:
|
203 |
+
"""BFS why this node is equal to other nodes."""
|
204 |
+
others = set(others)
|
205 |
+
found = 0
|
206 |
+
|
207 |
+
parent = {}
|
208 |
+
queue = [self]
|
209 |
+
i = 0
|
210 |
+
|
211 |
+
while i < len(queue):
|
212 |
+
current = queue[i]
|
213 |
+
if current in others:
|
214 |
+
found += 1
|
215 |
+
if found == len(others):
|
216 |
+
break
|
217 |
+
|
218 |
+
i += 1
|
219 |
+
|
220 |
+
for neighbor in current.merge_graph:
|
221 |
+
if (
|
222 |
+
level is not None
|
223 |
+
and current.merge_graph[neighbor].level is not None
|
224 |
+
and current.merge_graph[neighbor].level >= level
|
225 |
+
):
|
226 |
+
continue
|
227 |
+
if neighbor not in parent:
|
228 |
+
queue.append(neighbor)
|
229 |
+
parent[neighbor] = current
|
230 |
+
|
231 |
+
return bfs_backtrack(self, others, parent)
|
232 |
+
|
233 |
+
def why_equal_groups(
|
234 |
+
self, groups: list[list[Node]], level: int
|
235 |
+
) -> tuple[list[Any], list[Node]]:
|
236 |
+
"""BFS for why self is equal to at least one member of each group."""
|
237 |
+
others = [None for _ in groups]
|
238 |
+
found = 0
|
239 |
+
|
240 |
+
parent = {}
|
241 |
+
queue = [self]
|
242 |
+
i = 0
|
243 |
+
|
244 |
+
while i < len(queue):
|
245 |
+
current = queue[i]
|
246 |
+
|
247 |
+
for j, grp in enumerate(groups):
|
248 |
+
if others[j] is None and current in grp:
|
249 |
+
others[j] = current
|
250 |
+
found += 1
|
251 |
+
|
252 |
+
if found == len(others):
|
253 |
+
break
|
254 |
+
|
255 |
+
i += 1
|
256 |
+
|
257 |
+
for neighbor in current.merge_graph:
|
258 |
+
if (
|
259 |
+
level is not None
|
260 |
+
and current.merge_graph[neighbor].level is not None
|
261 |
+
and current.merge_graph[neighbor].level >= level
|
262 |
+
):
|
263 |
+
continue
|
264 |
+
if neighbor not in parent:
|
265 |
+
queue.append(neighbor)
|
266 |
+
parent[neighbor] = current
|
267 |
+
|
268 |
+
return bfs_backtrack(self, others, parent), others
|
269 |
+
|
270 |
+
def why_val(self, level: int) -> list[Any]:
|
271 |
+
return self._val.why_equal([self.val], level)
|
272 |
+
|
273 |
+
def why_connect(self, node: Node, level: int = None) -> list[Any]:
|
274 |
+
rep = self.rep()
|
275 |
+
equivs = list(rep.edge_graph[node].keys())
|
276 |
+
if not equivs:
|
277 |
+
return None
|
278 |
+
equiv = equivs[0]
|
279 |
+
dep = rep.edge_graph[node][equiv]
|
280 |
+
return [dep] + self.why_equal(equiv, level)
|
281 |
+
|
282 |
+
|
283 |
+
def why_connect(*pairs: list[tuple[Node, Node]]) -> list[Any]:
|
284 |
+
result = []
|
285 |
+
for node1, node2 in pairs:
|
286 |
+
result += node1.why_connect(node2)
|
287 |
+
return result
|
288 |
+
|
289 |
+
|
290 |
+
def is_equiv(x: Node, y: Node, level: int = None) -> bool:
|
291 |
+
level = level or float('inf')
|
292 |
+
return x.why_equal([y], level) is not None
|
293 |
+
|
294 |
+
|
295 |
+
def is_equal(x: Node, y: Node, level: int = None) -> bool:
|
296 |
+
if x == y:
|
297 |
+
return True
|
298 |
+
if x._val is None or y._val is None:
|
299 |
+
return False
|
300 |
+
if x.val != y.val:
|
301 |
+
return False
|
302 |
+
return is_equiv(x._val, y._val, level)
|
303 |
+
|
304 |
+
|
305 |
+
def bfs_backtrack(
|
306 |
+
root: Node, leafs: list[Node], parent: dict[Node, Node]
|
307 |
+
) -> list[Any]:
|
308 |
+
"""Return the path given BFS trace of parent nodes."""
|
309 |
+
backtracked = {root} # no need to backtrack further when touching this set.
|
310 |
+
deps = []
|
311 |
+
for node in leafs:
|
312 |
+
if node is None:
|
313 |
+
return None
|
314 |
+
if node in backtracked:
|
315 |
+
continue
|
316 |
+
if node not in parent:
|
317 |
+
return None
|
318 |
+
while node not in backtracked:
|
319 |
+
backtracked.add(node)
|
320 |
+
deps.append(node.merge_graph[parent[node]])
|
321 |
+
node = parent[node]
|
322 |
+
|
323 |
+
return deps
|
324 |
+
|
325 |
+
|
326 |
+
class Point(Node):
|
327 |
+
pass
|
328 |
+
|
329 |
+
|
330 |
+
class Line(Node):
|
331 |
+
"""Node of type Line."""
|
332 |
+
|
333 |
+
def new_val(self) -> Direction:
|
334 |
+
return Direction()
|
335 |
+
|
336 |
+
def why_coll(self, points: list[Point], level: int = None) -> list[Any]:
|
337 |
+
"""Why points are connected to self."""
|
338 |
+
level = level or float('inf')
|
339 |
+
|
340 |
+
groups = []
|
341 |
+
for p in points:
|
342 |
+
group = [
|
343 |
+
l
|
344 |
+
for l, d in self.edge_graph[p].items()
|
345 |
+
if d is None or d.level < level
|
346 |
+
]
|
347 |
+
if not group:
|
348 |
+
return None
|
349 |
+
groups.append(group)
|
350 |
+
|
351 |
+
min_deps = None
|
352 |
+
for line in groups[0]:
|
353 |
+
deps, others = line.why_equal_groups(groups[1:], level)
|
354 |
+
if deps is None:
|
355 |
+
continue
|
356 |
+
for p, o in zip(points, [line] + others):
|
357 |
+
deps.append(self.edge_graph[p][o])
|
358 |
+
if min_deps is None or len(deps) < len(min_deps):
|
359 |
+
min_deps = deps
|
360 |
+
|
361 |
+
if min_deps is None:
|
362 |
+
return None
|
363 |
+
return [d for d in min_deps if d is not None]
|
364 |
+
|
365 |
+
|
366 |
+
class Segment(Node):
|
367 |
+
|
368 |
+
def new_val(self) -> Length:
|
369 |
+
return Length()
|
370 |
+
|
371 |
+
|
372 |
+
class Circle(Node):
|
373 |
+
"""Node of type Circle."""
|
374 |
+
|
375 |
+
def why_cyclic(self, points: list[Point], level: int = None) -> list[Any]:
|
376 |
+
"""Why points are connected to self."""
|
377 |
+
level = level or float('inf')
|
378 |
+
|
379 |
+
groups = []
|
380 |
+
for p in points:
|
381 |
+
group = [
|
382 |
+
c
|
383 |
+
for c, d in self.edge_graph[p].items()
|
384 |
+
if d is None or d.level < level
|
385 |
+
]
|
386 |
+
if not group:
|
387 |
+
return None
|
388 |
+
groups.append(group)
|
389 |
+
|
390 |
+
min_deps = None
|
391 |
+
for circle in groups[0]:
|
392 |
+
deps, others = circle.why_equal_groups(groups[1:], level)
|
393 |
+
if deps is None:
|
394 |
+
continue
|
395 |
+
for p, o in zip(points, [circle] + others):
|
396 |
+
deps.append(self.edge_graph[p][o])
|
397 |
+
|
398 |
+
if min_deps is None or len(deps) < len(min_deps):
|
399 |
+
min_deps = deps
|
400 |
+
|
401 |
+
if min_deps is None:
|
402 |
+
return None
|
403 |
+
return [d for d in min_deps if d is not None]
|
404 |
+
|
405 |
+
|
406 |
+
def why_equal(x: Node, y: Node, level: int = None) -> list[Any]:
|
407 |
+
if x == y:
|
408 |
+
return []
|
409 |
+
if not x._val or not y._val:
|
410 |
+
return None
|
411 |
+
if x._val == y._val:
|
412 |
+
return []
|
413 |
+
return x._val.why_equal([y._val], level)
|
414 |
+
|
415 |
+
|
416 |
+
class Direction(Node):
|
417 |
+
pass
|
418 |
+
|
419 |
+
|
420 |
+
def get_lines_thru_all(*points: list[Point]) -> list[Line]:
|
421 |
+
line2count = defaultdict(lambda: 0)
|
422 |
+
points = set(points)
|
423 |
+
for p in points:
|
424 |
+
for l in p.neighbors(Line):
|
425 |
+
line2count[l] += 1
|
426 |
+
return [l for l, count in line2count.items() if count == len(points)]
|
427 |
+
|
428 |
+
|
429 |
+
def line_of_and_why(
|
430 |
+
points: list[Point], level: int = None
|
431 |
+
) -> tuple[Line, list[Any]]:
|
432 |
+
"""Why points are collinear."""
|
433 |
+
for l0 in get_lines_thru_all(*points):
|
434 |
+
for l in l0.equivs():
|
435 |
+
if all([p in l.edge_graph for p in points]):
|
436 |
+
x, y = l.points
|
437 |
+
colls = list({x, y} | set(points))
|
438 |
+
# if len(colls) < 3:
|
439 |
+
# return l, []
|
440 |
+
why = l.why_coll(colls, level)
|
441 |
+
if why is not None:
|
442 |
+
return l, why
|
443 |
+
|
444 |
+
return None, None
|
445 |
+
|
446 |
+
|
447 |
+
def get_circles_thru_all(*points: list[Point]) -> list[Circle]:
|
448 |
+
circle2count = defaultdict(lambda: 0)
|
449 |
+
points = set(points)
|
450 |
+
for p in points:
|
451 |
+
for c in p.neighbors(Circle):
|
452 |
+
circle2count[c] += 1
|
453 |
+
return [c for c, count in circle2count.items() if count == len(points)]
|
454 |
+
|
455 |
+
|
456 |
+
def circle_of_and_why(
|
457 |
+
points: list[Point], level: int = None
|
458 |
+
) -> tuple[Circle, list[Any]]:
|
459 |
+
"""Why points are concyclic."""
|
460 |
+
for c0 in get_circles_thru_all(*points):
|
461 |
+
for c in c0.equivs():
|
462 |
+
if all([p in c.edge_graph for p in points]):
|
463 |
+
cycls = list(set(points))
|
464 |
+
why = c.why_cyclic(cycls, level)
|
465 |
+
if why is not None:
|
466 |
+
return c, why
|
467 |
+
|
468 |
+
return None, None
|
469 |
+
|
470 |
+
|
471 |
+
def name_map(struct: Any) -> Any:
|
472 |
+
if isinstance(struct, list):
|
473 |
+
return [name_map(x) for x in struct]
|
474 |
+
elif isinstance(struct, tuple):
|
475 |
+
return tuple([name_map(x) for x in struct])
|
476 |
+
elif isinstance(struct, set):
|
477 |
+
return set([name_map(x) for x in struct])
|
478 |
+
elif isinstance(struct, dict):
|
479 |
+
return {name_map(x): name_map(y) for x, y in struct.items()}
|
480 |
+
else:
|
481 |
+
return getattr(struct, 'name', '')
|
482 |
+
|
483 |
+
|
484 |
+
class Angle(Node):
|
485 |
+
"""Node of type Angle."""
|
486 |
+
|
487 |
+
def new_val(self) -> Measure:
|
488 |
+
return Measure()
|
489 |
+
|
490 |
+
def set_directions(self, d1: Direction, d2: Direction) -> None:
|
491 |
+
self._d = d1, d2
|
492 |
+
|
493 |
+
@property
|
494 |
+
def directions(self) -> tuple[Direction, Direction]:
|
495 |
+
d1, d2 = self._d
|
496 |
+
if d1 is None or d2 is None:
|
497 |
+
return d1, d2
|
498 |
+
return d1.rep(), d2.rep()
|
499 |
+
|
500 |
+
|
501 |
+
class Measure(Node):
|
502 |
+
pass
|
503 |
+
|
504 |
+
|
505 |
+
class Length(Node):
|
506 |
+
pass
|
507 |
+
|
508 |
+
|
509 |
+
class Ratio(Node):
|
510 |
+
"""Node of type Ratio."""
|
511 |
+
|
512 |
+
def new_val(self) -> Value:
|
513 |
+
return Value()
|
514 |
+
|
515 |
+
def set_lengths(self, l1: Length, l2: Length) -> None:
|
516 |
+
self._l = l1, l2
|
517 |
+
|
518 |
+
@property
|
519 |
+
def lengths(self) -> tuple[Length, Length]:
|
520 |
+
l1, l2 = self._l
|
521 |
+
if l1 is None or l2 is None:
|
522 |
+
return l1, l2
|
523 |
+
return l1.rep(), l2.rep()
|
524 |
+
|
525 |
+
|
526 |
+
class Value(Node):
|
527 |
+
pass
|
528 |
+
|
529 |
+
|
530 |
+
def all_angles(
|
531 |
+
d1: Direction, d2: Direction, level: int = None
|
532 |
+
) -> tuple[Angle, list[Direction], list[Direction]]:
|
533 |
+
level = level or float('inf')
|
534 |
+
d1s = d1.equivs_upto(level)
|
535 |
+
d2s = d2.equivs_upto(level)
|
536 |
+
|
537 |
+
for ang in d1.rep().neighbors(Angle):
|
538 |
+
d1_, d2_ = ang._d
|
539 |
+
if d1_ in d1s and d2_ in d2s:
|
540 |
+
yield ang, d1s, d2s
|
541 |
+
|
542 |
+
|
543 |
+
def all_ratios(
|
544 |
+
d1, d2, level=None
|
545 |
+
) -> tuple[Angle, list[Direction], list[Direction]]:
|
546 |
+
level = level or float('inf')
|
547 |
+
d1s = d1.equivs_upto(level)
|
548 |
+
d2s = d2.equivs_upto(level)
|
549 |
+
|
550 |
+
for ang in d1.rep().neighbors(Ratio):
|
551 |
+
d1_, d2_ = ang._l
|
552 |
+
if d1_ in d1s and d2_ in d2s:
|
553 |
+
yield ang, d1s, d2s
|
554 |
+
|
555 |
+
|
556 |
+
RANKING = {
|
557 |
+
Point: 0,
|
558 |
+
Line: 1,
|
559 |
+
Segment: 2,
|
560 |
+
Circle: 3,
|
561 |
+
Direction: 4,
|
562 |
+
Length: 5,
|
563 |
+
Angle: 6,
|
564 |
+
Ratio: 7,
|
565 |
+
Measure: 8,
|
566 |
+
Value: 9,
|
567 |
+
}
|
568 |
+
|
569 |
+
|
570 |
+
def val_type(x: Node) -> Type[Node]:
|
571 |
+
if isinstance(x, Line):
|
572 |
+
return Direction
|
573 |
+
if isinstance(x, Segment):
|
574 |
+
return Length
|
575 |
+
if isinstance(x, Angle):
|
576 |
+
return Measure
|
577 |
+
if isinstance(x, Ratio):
|
578 |
+
return Value
|
ag4masses/alphageometry/geometry_150M_generate.gin
ADDED
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
NUM_EMBEDDINGS = 1024
|
2 |
+
|
3 |
+
# Number of parameters = 152M
|
4 |
+
NUM_LAYERS = 12
|
5 |
+
EMBED_DIM = 1024
|
6 |
+
NUM_HEADS = 8
|
7 |
+
HEAD_DIM = 128
|
8 |
+
MLP_DIM = 4096
|
9 |
+
|
10 |
+
|
11 |
+
transformer_layer.TransformerLayerGenerate:
|
12 |
+
num_heads = %NUM_HEADS
|
13 |
+
head_size = %HEAD_DIM
|
14 |
+
window_length = 1024
|
15 |
+
use_long_xl_architecture = False
|
16 |
+
max_unrolled_windows = -1 # Always unroll.
|
17 |
+
relative_position_type = "t5" # Can be "fourier", "t5", or None.
|
18 |
+
use_causal_mask = True
|
19 |
+
attn_dropout_rate = %ATTN_DROPOUT_RATE # Attention matrix dropout.
|
20 |
+
memory_num_neighbors = 0
|
21 |
+
dtype = %DTYPE
|
22 |
+
|
23 |
+
decoder_stack.DecoderStackGenerate:
|
24 |
+
num_layers = %NUM_LAYERS
|
25 |
+
embedding_size = %EMBED_DIM
|
26 |
+
embedding_stddev = 1.0
|
27 |
+
layer_factory = @transformer_layer.TransformerLayerGenerate
|
28 |
+
dstack_window_length = 0
|
29 |
+
use_absolute_positions = False
|
30 |
+
use_final_layernorm = True # Final layernorm before token lookup.
|
31 |
+
final_dropout_rate = %DROPOUT_RATE # Dropout before token lookup.
|
32 |
+
final_mlp_factory = None # Final MLP to predict target tokens.
|
33 |
+
recurrent_layer_indices = ()
|
34 |
+
memory_factory = None # e.g. @memory_factory.memory_on_tpu_factory
|
35 |
+
memory_layer_indices = ()
|
36 |
+
dtype = %DTYPE
|
37 |
+
|
38 |
+
|
39 |
+
models.DecoderOnlyLanguageModelGenerate:
|
40 |
+
num_heads = %NUM_HEADS
|
41 |
+
head_size = %HEAD_DIM
|
42 |
+
task_config = @decoder_stack.TransformerTaskConfig()
|
43 |
+
decoder_factory = @decoder_stack.DecoderStackGenerate
|
44 |
+
|
45 |
+
|
46 |
+
training_loop.Trainer:
|
47 |
+
model_definition = @models.DecoderOnlyLanguageModelGenerate
|
ag4masses/alphageometry/geometry_test.py
ADDED
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright 2023 DeepMind Technologies Limited
|
2 |
+
#
|
3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4 |
+
# you may not use this file except in compliance with the License.
|
5 |
+
# You may obtain a copy of the License at
|
6 |
+
#
|
7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8 |
+
#
|
9 |
+
# Unless required by applicable law or agreed to in writing, software
|
10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
+
# See the License for the specific language governing permissions and
|
13 |
+
# limitations under the License.
|
14 |
+
# ==============================================================================
|
15 |
+
|
16 |
+
"""Unit tests for geometry.py."""
|
17 |
+
import unittest
|
18 |
+
|
19 |
+
from absl.testing import absltest
|
20 |
+
import geometry as gm
|
21 |
+
|
22 |
+
|
23 |
+
class GeometryTest(unittest.TestCase):
|
24 |
+
|
25 |
+
def _setup_equality_example(self):
|
26 |
+
# Create 4 nodes a, b, c, d
|
27 |
+
# and their lengths
|
28 |
+
a = gm.Segment('a')
|
29 |
+
la = gm.Length('l(a)')
|
30 |
+
a.connect_to(la)
|
31 |
+
la.connect_to(a)
|
32 |
+
|
33 |
+
b = gm.Segment('b')
|
34 |
+
lb = gm.Length('l(b)')
|
35 |
+
b.connect_to(lb)
|
36 |
+
lb.connect_to(b)
|
37 |
+
|
38 |
+
c = gm.Segment('c')
|
39 |
+
lc = gm.Length('l(c)')
|
40 |
+
c.connect_to(lc)
|
41 |
+
lc.connect_to(c)
|
42 |
+
|
43 |
+
d = gm.Segment('d')
|
44 |
+
ld = gm.Length('l(d)')
|
45 |
+
d.connect_to(ld)
|
46 |
+
ld.connect_to(d)
|
47 |
+
|
48 |
+
# Now let a=b, b=c, a=c, c=d
|
49 |
+
la.merge([lb], 'fact1')
|
50 |
+
lb.merge([lc], 'fact2')
|
51 |
+
la.merge([lc], 'fact3')
|
52 |
+
lc.merge([ld], 'fact4')
|
53 |
+
return a, b, c, d, la, lb, lc, ld
|
54 |
+
|
55 |
+
def test_merged_node_representative(self):
|
56 |
+
_, _, _, _, la, lb, lc, ld = self._setup_equality_example()
|
57 |
+
|
58 |
+
# all nodes are now represented by la.
|
59 |
+
self.assertEqual(la.rep(), la)
|
60 |
+
self.assertEqual(lb.rep(), la)
|
61 |
+
self.assertEqual(lc.rep(), la)
|
62 |
+
self.assertEqual(ld.rep(), la)
|
63 |
+
|
64 |
+
def test_merged_node_equivalence(self):
|
65 |
+
_, _, _, _, la, lb, lc, ld = self._setup_equality_example()
|
66 |
+
# all la, lb, lc, ld are equivalent
|
67 |
+
self.assertCountEqual(la.equivs(), [la, lb, lc, ld])
|
68 |
+
self.assertCountEqual(lb.equivs(), [la, lb, lc, ld])
|
69 |
+
self.assertCountEqual(lc.equivs(), [la, lb, lc, ld])
|
70 |
+
self.assertCountEqual(ld.equivs(), [la, lb, lc, ld])
|
71 |
+
|
72 |
+
def test_bfs_for_equality_transitivity(self):
|
73 |
+
a, _, _, d, _, _, _, _ = self._setup_equality_example()
|
74 |
+
|
75 |
+
# check that a==d because fact3 & fact4, not fact1 & fact2
|
76 |
+
self.assertCountEqual(gm.why_equal(a, d), ['fact3', 'fact4'])
|
77 |
+
|
78 |
+
|
79 |
+
if __name__ == '__main__':
|
80 |
+
absltest.main()
|
ag4masses/alphageometry/graph.py
ADDED
@@ -0,0 +1,3057 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright 2023 DeepMind Technologies Limited
|
2 |
+
#
|
3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4 |
+
# you may not use this file except in compliance with the License.
|
5 |
+
# You may obtain a copy of the License at
|
6 |
+
#
|
7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8 |
+
#
|
9 |
+
# Unless required by applicable law or agreed to in writing, software
|
10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
+
# See the License for the specific language governing permissions and
|
13 |
+
# limitations under the License.
|
14 |
+
# ==============================================================================
|
15 |
+
|
16 |
+
"""Implements the graph representation of the proof state."""
|
17 |
+
|
18 |
+
# pylint: disable=g-multiple-import
|
19 |
+
from __future__ import annotations
|
20 |
+
|
21 |
+
from collections import defaultdict # pylint: disable=g-importing-member
|
22 |
+
from typing import Callable, Generator, Optional, Type, Union
|
23 |
+
|
24 |
+
from absl import logging
|
25 |
+
import ar
|
26 |
+
import geometry as gm
|
27 |
+
from geometry import Angle, Direction, Length, Ratio
|
28 |
+
from geometry import Circle, Line, Point, Segment
|
29 |
+
from geometry import Measure, Value
|
30 |
+
import graph_utils as utils
|
31 |
+
import numericals as nm
|
32 |
+
import problem
|
33 |
+
from problem import Dependency, EmptyDependency
|
34 |
+
|
35 |
+
|
36 |
+
np = nm.np
|
37 |
+
|
38 |
+
|
39 |
+
FREE = [
|
40 |
+
'free',
|
41 |
+
'segment',
|
42 |
+
'r_triangle',
|
43 |
+
'risos',
|
44 |
+
'triangle',
|
45 |
+
'triangle12',
|
46 |
+
'ieq_triangle',
|
47 |
+
'eq_quadrangle',
|
48 |
+
'eq_trapezoid',
|
49 |
+
'eqdia_quadrangle',
|
50 |
+
'quadrangle',
|
51 |
+
'r_trapezoid',
|
52 |
+
'rectangle',
|
53 |
+
'isquare',
|
54 |
+
'trapezoid',
|
55 |
+
'pentagon',
|
56 |
+
'iso_triangle',
|
57 |
+
]
|
58 |
+
|
59 |
+
INTERSECT = [
|
60 |
+
'angle_bisector',
|
61 |
+
'angle_mirror',
|
62 |
+
'eqdistance',
|
63 |
+
'lc_tangent',
|
64 |
+
'on_aline',
|
65 |
+
'on_bline',
|
66 |
+
'on_circle',
|
67 |
+
'on_line',
|
68 |
+
'on_pline',
|
69 |
+
'on_tline',
|
70 |
+
'on_dia',
|
71 |
+
's_angle',
|
72 |
+
'on_opline',
|
73 |
+
'eqangle3',
|
74 |
+
]
|
75 |
+
|
76 |
+
|
77 |
+
# pylint: disable=protected-access
|
78 |
+
# pylint: disable=unused-argument
|
79 |
+
|
80 |
+
|
81 |
+
class DepCheckFailError(Exception):
|
82 |
+
pass
|
83 |
+
|
84 |
+
|
85 |
+
class PointTooCloseError(Exception):
|
86 |
+
pass
|
87 |
+
|
88 |
+
|
89 |
+
class PointTooFarError(Exception):
|
90 |
+
pass
|
91 |
+
|
92 |
+
|
93 |
+
class Graph:
|
94 |
+
"""Graph data structure representing proof state."""
|
95 |
+
|
96 |
+
def __init__(self):
|
97 |
+
self.type2nodes = {
|
98 |
+
Point: [],
|
99 |
+
Line: [],
|
100 |
+
Segment: [],
|
101 |
+
Circle: [],
|
102 |
+
Direction: [],
|
103 |
+
Length: [],
|
104 |
+
Angle: [],
|
105 |
+
Ratio: [],
|
106 |
+
Measure: [],
|
107 |
+
Value: [],
|
108 |
+
}
|
109 |
+
self._name2point = {}
|
110 |
+
self._name2node = {}
|
111 |
+
|
112 |
+
self.rconst = {} # contains all constant ratios
|
113 |
+
self.aconst = {} # contains all constant angles.
|
114 |
+
|
115 |
+
self.halfpi, _ = self.get_or_create_const_ang(1, 2)
|
116 |
+
self.vhalfpi = self.halfpi.val
|
117 |
+
|
118 |
+
self.atable = ar.AngleTable()
|
119 |
+
self.dtable = ar.DistanceTable()
|
120 |
+
self.rtable = ar.RatioTable()
|
121 |
+
|
122 |
+
# to quick access deps.
|
123 |
+
self.cache = {}
|
124 |
+
|
125 |
+
self._pair2line = {}
|
126 |
+
self._triplet2circle = {}
|
127 |
+
|
128 |
+
def copy(self) -> Graph:
|
129 |
+
"""Make a copy of self."""
|
130 |
+
p, definitions = self.build_def
|
131 |
+
|
132 |
+
p = p.copy()
|
133 |
+
for clause in p.clauses:
|
134 |
+
clause.nums = []
|
135 |
+
for pname in clause.points:
|
136 |
+
clause.nums.append(self._name2node[pname].num)
|
137 |
+
|
138 |
+
g, _ = Graph.build_problem(p, definitions, verbose=False, init_copy=False)
|
139 |
+
|
140 |
+
g.build_clauses = list(getattr(self, 'build_clauses', []))
|
141 |
+
return g
|
142 |
+
|
143 |
+
def _create_const_ang(self, n: int, d: int) -> None:
|
144 |
+
n, d = ar.simplify(n, d)
|
145 |
+
ang = self.aconst[(n, d)] = self.new_node(Angle, f'{n}pi/{d}')
|
146 |
+
ang.set_directions(None, None)
|
147 |
+
self.connect_val(ang, deps=None)
|
148 |
+
|
149 |
+
def _create_const_rat(self, n: int, d: int) -> None:
|
150 |
+
n, d = ar.simplify(n, d)
|
151 |
+
rat = self.rconst[(n, d)] = self.new_node(Ratio, f'{n}/{d}')
|
152 |
+
rat.set_lengths(None, None)
|
153 |
+
self.connect_val(rat, deps=None)
|
154 |
+
|
155 |
+
def get_or_create_const_ang(self, n: int, d: int) -> None:
|
156 |
+
n, d = ar.simplify(n, d)
|
157 |
+
if (n, d) not in self.aconst:
|
158 |
+
self._create_const_ang(n, d)
|
159 |
+
ang1 = self.aconst[(n, d)]
|
160 |
+
|
161 |
+
n, d = ar.simplify(d - n, d)
|
162 |
+
if (n, d) not in self.aconst:
|
163 |
+
self._create_const_ang(n, d)
|
164 |
+
ang2 = self.aconst[(n, d)]
|
165 |
+
return ang1, ang2
|
166 |
+
|
167 |
+
def get_or_create_const_rat(self, n: int, d: int) -> None:
|
168 |
+
n, d = ar.simplify(n, d)
|
169 |
+
if (n, d) not in self.rconst:
|
170 |
+
self._create_const_rat(n, d)
|
171 |
+
rat1 = self.rconst[(n, d)]
|
172 |
+
|
173 |
+
if (d, n) not in self.rconst:
|
174 |
+
self._create_const_rat(d, n) # pylint: disable=arguments-out-of-order
|
175 |
+
rat2 = self.rconst[(d, n)]
|
176 |
+
return rat1, rat2
|
177 |
+
|
178 |
+
def add_algebra(self, dep: Dependency, level: int) -> None:
|
179 |
+
"""Add new algebraic predicates."""
|
180 |
+
_ = level
|
181 |
+
if dep.name not in [
|
182 |
+
'para',
|
183 |
+
'perp',
|
184 |
+
'eqangle',
|
185 |
+
'eqratio',
|
186 |
+
'aconst',
|
187 |
+
'rconst',
|
188 |
+
'cong',
|
189 |
+
]:
|
190 |
+
return
|
191 |
+
|
192 |
+
name, args = dep.name, dep.args
|
193 |
+
|
194 |
+
if name == 'para':
|
195 |
+
ab, cd = dep.algebra
|
196 |
+
self.atable.add_para(ab, cd, dep)
|
197 |
+
|
198 |
+
if name == 'perp':
|
199 |
+
ab, cd = dep.algebra
|
200 |
+
self.atable.add_const_angle(ab, cd, 90, dep)
|
201 |
+
|
202 |
+
if name == 'eqangle':
|
203 |
+
ab, cd, mn, pq = dep.algebra
|
204 |
+
if (ab, cd) == (pq, mn):
|
205 |
+
self.atable.add_const_angle(ab, cd, 90, dep)
|
206 |
+
else:
|
207 |
+
self.atable.add_eqangle(ab, cd, mn, pq, dep)
|
208 |
+
|
209 |
+
if name == 'eqratio':
|
210 |
+
ab, cd, mn, pq = dep.algebra
|
211 |
+
if (ab, cd) == (pq, mn):
|
212 |
+
self.rtable.add_eq(ab, cd, dep)
|
213 |
+
else:
|
214 |
+
self.rtable.add_eqratio(ab, cd, mn, pq, dep)
|
215 |
+
|
216 |
+
if name == 'aconst':
|
217 |
+
bx, ab, y = dep.algebra
|
218 |
+
self.atable.add_const_angle(bx, ab, y, dep)
|
219 |
+
|
220 |
+
if name == 'rconst':
|
221 |
+
l1, l2, m, n = dep.algebra
|
222 |
+
self.rtable.add_const_ratio(l1, l2, m, n, dep)
|
223 |
+
|
224 |
+
if name == 'cong':
|
225 |
+
a, b, c, d = args
|
226 |
+
ab, _ = self.get_line_thru_pair_why(a, b)
|
227 |
+
cd, _ = self.get_line_thru_pair_why(c, d)
|
228 |
+
self.dtable.add_cong(ab, cd, a, b, c, d, dep)
|
229 |
+
|
230 |
+
ab, cd = dep.algebra
|
231 |
+
self.rtable.add_eq(ab, cd, dep)
|
232 |
+
|
233 |
+
def add_eqrat_const(
|
234 |
+
self, args: list[Point], deps: EmptyDependency
|
235 |
+
) -> list[Dependency]:
|
236 |
+
"""Add new algebraic predicates of type eqratio-constant."""
|
237 |
+
a, b, c, d, num, den = args
|
238 |
+
nd, dn = self.get_or_create_const_rat(num, den)
|
239 |
+
|
240 |
+
if num == den:
|
241 |
+
return self.add_cong([a, b, c, d], deps)
|
242 |
+
|
243 |
+
ab = self._get_or_create_segment(a, b, deps=None)
|
244 |
+
cd = self._get_or_create_segment(c, d, deps=None)
|
245 |
+
|
246 |
+
self.connect_val(ab, deps=None)
|
247 |
+
self.connect_val(cd, deps=None)
|
248 |
+
|
249 |
+
if ab.val == cd.val:
|
250 |
+
raise ValueError(f'{ab.name} and {cd.name} cannot be equal')
|
251 |
+
|
252 |
+
args = [a, b, c, d, nd]
|
253 |
+
i = 0
|
254 |
+
for x, y, xy in [(a, b, ab), (c, d, cd)]:
|
255 |
+
i += 1
|
256 |
+
x_, y_ = list(xy._val._obj.points)
|
257 |
+
if {x, y} == {x_, y_}:
|
258 |
+
continue
|
259 |
+
if deps:
|
260 |
+
deps = deps.extend(self, 'rconst', list(args), 'cong', [x, y, x_, y_])
|
261 |
+
args[2 * i - 2] = x_
|
262 |
+
args[2 * i - 1] = y_
|
263 |
+
|
264 |
+
ab_cd, cd_ab, why = self._get_or_create_ratio(ab, cd, deps=None)
|
265 |
+
if why:
|
266 |
+
dep0 = deps.populate('rconst', [a, b, c, d, nd])
|
267 |
+
deps = EmptyDependency(level=deps.level, rule_name=None)
|
268 |
+
deps.why = [dep0] + why
|
269 |
+
|
270 |
+
lab, lcd = ab_cd._l
|
271 |
+
a, b = list(lab._obj.points)
|
272 |
+
c, d = list(lcd._obj.points)
|
273 |
+
|
274 |
+
add = []
|
275 |
+
if not self.is_equal(ab_cd, nd):
|
276 |
+
args = [a, b, c, d, nd]
|
277 |
+
dep1 = deps.populate('rconst', args)
|
278 |
+
dep1.algebra = ab._val, cd._val, num, den
|
279 |
+
self.make_equal(nd, ab_cd, deps=dep1)
|
280 |
+
self.cache_dep('rconst', [a, b, c, d, nd], dep1)
|
281 |
+
add += [dep1]
|
282 |
+
|
283 |
+
if not self.is_equal(cd_ab, dn):
|
284 |
+
args = [c, d, a, b, dn]
|
285 |
+
dep2 = deps.populate('rconst', args)
|
286 |
+
dep2.algebra = cd._val, ab._val, den, num
|
287 |
+
self.make_equal(dn, cd_ab, deps=dep2)
|
288 |
+
self.cache_dep('rconst', [c, d, a, b, dn], dep2)
|
289 |
+
add += [dep2]
|
290 |
+
|
291 |
+
return add
|
292 |
+
|
293 |
+
def do_algebra(self, name: str, args: list[Point]) -> list[Dependency]:
|
294 |
+
"""Derive (but not add) new algebraic predicates."""
|
295 |
+
if name == 'para':
|
296 |
+
a, b, dep = args
|
297 |
+
if gm.is_equiv(a, b):
|
298 |
+
return []
|
299 |
+
(x, y), (m, n) = a._obj.points, b._obj.points
|
300 |
+
return self.add_para([x, y, m, n], dep)
|
301 |
+
|
302 |
+
if name == 'aconst':
|
303 |
+
a, b, n, d, dep = args
|
304 |
+
ab, ba, why = self.get_or_create_angle_d(a, b, deps=None)
|
305 |
+
nd, dn = self.get_or_create_const_ang(n, d)
|
306 |
+
|
307 |
+
(x, y), (m, n) = a._obj.points, b._obj.points
|
308 |
+
|
309 |
+
if why:
|
310 |
+
dep0 = dep.populate('aconst', [x, y, m, n, nd])
|
311 |
+
dep = EmptyDependency(level=dep.level, rule_name=None)
|
312 |
+
dep.why = [dep0] + why
|
313 |
+
|
314 |
+
a, b = ab._d
|
315 |
+
(x, y), (m, n) = a._obj.points, b._obj.points
|
316 |
+
|
317 |
+
added = []
|
318 |
+
if not self.is_equal(ab, nd):
|
319 |
+
if nd == self.halfpi:
|
320 |
+
added += self.add_perp([x, y, m, n], dep)
|
321 |
+
# else:
|
322 |
+
name = 'aconst'
|
323 |
+
args = [x, y, m, n, nd]
|
324 |
+
dep1 = dep.populate(name, args)
|
325 |
+
self.cache_dep(name, args, dep1)
|
326 |
+
self.make_equal(nd, ab, deps=dep1)
|
327 |
+
added += [dep1]
|
328 |
+
|
329 |
+
if not self.is_equal(ba, dn):
|
330 |
+
if dn == self.halfpi:
|
331 |
+
added += self.add_perp([m, n, x, y], dep)
|
332 |
+
name = 'aconst'
|
333 |
+
args = [m, n, x, y, dn]
|
334 |
+
dep2 = dep.populate(name, args)
|
335 |
+
self.cache_dep(name, args, dep2)
|
336 |
+
self.make_equal(dn, ba, deps=dep2)
|
337 |
+
added += [dep2]
|
338 |
+
return added
|
339 |
+
|
340 |
+
if name == 'rconst':
|
341 |
+
a, b, c, d, num, den, dep = args
|
342 |
+
return self.add_eqrat_const([a, b, c, d, num, den], dep)
|
343 |
+
|
344 |
+
if name == 'eqangle':
|
345 |
+
d1, d2, d3, d4, dep = args
|
346 |
+
a, b = d1._obj.points
|
347 |
+
c, d = d2._obj.points
|
348 |
+
e, f = d3._obj.points
|
349 |
+
g, h = d4._obj.points
|
350 |
+
|
351 |
+
return self.add_eqangle([a, b, c, d, e, f, g, h], dep)
|
352 |
+
|
353 |
+
if name == 'eqratio':
|
354 |
+
d1, d2, d3, d4, dep = args
|
355 |
+
a, b = d1._obj.points
|
356 |
+
c, d = d2._obj.points
|
357 |
+
e, f = d3._obj.points
|
358 |
+
g, h = d4._obj.points
|
359 |
+
|
360 |
+
return self.add_eqratio([a, b, c, d, e, f, g, h], dep)
|
361 |
+
|
362 |
+
if name in ['cong', 'cong2']:
|
363 |
+
a, b, c, d, dep = args
|
364 |
+
if not (a != b and c != d and (a != c or b != d)):
|
365 |
+
return []
|
366 |
+
return self.add_cong([a, b, c, d], dep)
|
367 |
+
|
368 |
+
return []
|
369 |
+
|
370 |
+
def derive_algebra(
|
371 |
+
self, level: int, verbose: bool = False
|
372 |
+
) -> tuple[
|
373 |
+
dict[str, list[tuple[Point, ...]]], dict[str, [tuple[Point, ...]]]
|
374 |
+
]:
|
375 |
+
"""Derive new algebraic predicates."""
|
376 |
+
derives = {}
|
377 |
+
ang_derives = self.derive_angle_algebra(level, verbose=verbose)
|
378 |
+
dist_derives = self.derive_distance_algebra(level, verbose=verbose)
|
379 |
+
rat_derives = self.derive_ratio_algebra(level, verbose=verbose)
|
380 |
+
|
381 |
+
derives.update(ang_derives)
|
382 |
+
derives.update(dist_derives)
|
383 |
+
derives.update(rat_derives)
|
384 |
+
|
385 |
+
# Separate eqangle and eqratio derivations
|
386 |
+
# As they are too numerous => slow down DD+AR.
|
387 |
+
# & reserve them only for last effort.
|
388 |
+
eqs = {'eqangle': derives.pop('eqangle'), 'eqratio': derives.pop('eqratio')}
|
389 |
+
return derives, eqs
|
390 |
+
|
391 |
+
def derive_ratio_algebra(
|
392 |
+
self, level: int, verbose: bool = False
|
393 |
+
) -> dict[str, list[tuple[Point, ...]]]:
|
394 |
+
"""Derive new eqratio predicates."""
|
395 |
+
added = {'cong2': [], 'eqratio': []}
|
396 |
+
|
397 |
+
for x in self.rtable.get_all_eqs_and_why():
|
398 |
+
x, why = x[:-1], x[-1]
|
399 |
+
dep = EmptyDependency(level=level, rule_name='a01')
|
400 |
+
dep.why = why
|
401 |
+
|
402 |
+
if len(x) == 2:
|
403 |
+
a, b = x
|
404 |
+
if gm.is_equiv(a, b):
|
405 |
+
continue
|
406 |
+
|
407 |
+
(m, n), (p, q) = a._obj.points, b._obj.points
|
408 |
+
added['cong2'].append((m, n, p, q, dep))
|
409 |
+
|
410 |
+
if len(x) == 4:
|
411 |
+
a, b, c, d = x
|
412 |
+
added['eqratio'].append((a, b, c, d, dep))
|
413 |
+
|
414 |
+
return added
|
415 |
+
|
416 |
+
def derive_angle_algebra(
|
417 |
+
self, level: int, verbose: bool = False
|
418 |
+
) -> dict[str, list[tuple[Point, ...]]]:
|
419 |
+
"""Derive new eqangles predicates."""
|
420 |
+
added = {'eqangle': [], 'aconst': [], 'para': []}
|
421 |
+
|
422 |
+
for x in self.atable.get_all_eqs_and_why():
|
423 |
+
x, why = x[:-1], x[-1]
|
424 |
+
dep = EmptyDependency(level=level, rule_name='a02')
|
425 |
+
dep.why = why
|
426 |
+
|
427 |
+
if len(x) == 2:
|
428 |
+
a, b = x
|
429 |
+
if gm.is_equiv(a, b):
|
430 |
+
continue
|
431 |
+
|
432 |
+
(e, f), (p, q) = a._obj.points, b._obj.points
|
433 |
+
if not nm.check('para', [e, f, p, q]):
|
434 |
+
continue
|
435 |
+
|
436 |
+
added['para'].append((a, b, dep))
|
437 |
+
|
438 |
+
if len(x) == 3:
|
439 |
+
a, b, (n, d) = x
|
440 |
+
|
441 |
+
(e, f), (p, q) = a._obj.points, b._obj.points
|
442 |
+
if not nm.check('aconst', [e, f, p, q, n, d]):
|
443 |
+
continue
|
444 |
+
|
445 |
+
added['aconst'].append((a, b, n, d, dep))
|
446 |
+
|
447 |
+
if len(x) == 4:
|
448 |
+
a, b, c, d = x
|
449 |
+
added['eqangle'].append((a, b, c, d, dep))
|
450 |
+
|
451 |
+
return added
|
452 |
+
|
453 |
+
def derive_distance_algebra(
|
454 |
+
self, level: int, verbose: bool = False
|
455 |
+
) -> dict[str, list[tuple[Point, ...]]]:
|
456 |
+
"""Derive new cong predicates."""
|
457 |
+
added = {'inci': [], 'cong': [], 'rconst': []}
|
458 |
+
for x in self.dtable.get_all_eqs_and_why():
|
459 |
+
x, why = x[:-1], x[-1]
|
460 |
+
dep = EmptyDependency(level=level, rule_name='a00')
|
461 |
+
dep.why = why
|
462 |
+
|
463 |
+
if len(x) == 2:
|
464 |
+
a, b = x
|
465 |
+
if a == b:
|
466 |
+
continue
|
467 |
+
|
468 |
+
dep.name = f'inci {a.name} {b.name}'
|
469 |
+
added['inci'].append((x, dep))
|
470 |
+
|
471 |
+
if len(x) == 4:
|
472 |
+
a, b, c, d = x
|
473 |
+
if not (a != b and c != d and (a != c or b != d)):
|
474 |
+
continue
|
475 |
+
added['cong'].append((a, b, c, d, dep))
|
476 |
+
|
477 |
+
if len(x) == 6:
|
478 |
+
a, b, c, d, num, den = x
|
479 |
+
if not (a != b and c != d and (a != c or b != d)):
|
480 |
+
continue
|
481 |
+
added['rconst'].append((a, b, c, d, num, den, dep))
|
482 |
+
|
483 |
+
return added
|
484 |
+
|
485 |
+
@classmethod
|
486 |
+
def build_problem(
|
487 |
+
cls,
|
488 |
+
pr: problem.Problem,
|
489 |
+
definitions: dict[str, problem.Definition],
|
490 |
+
verbose: bool = True,
|
491 |
+
init_copy: bool = True,
|
492 |
+
) -> tuple[Graph, list[Dependency]]:
|
493 |
+
"""Build a problem into a gr.Graph object."""
|
494 |
+
check = False
|
495 |
+
g = None
|
496 |
+
added = None
|
497 |
+
if verbose:
|
498 |
+
logging.info(pr.url)
|
499 |
+
logging.info(pr.txt())
|
500 |
+
while not check:
|
501 |
+
try:
|
502 |
+
g = Graph()
|
503 |
+
added = []
|
504 |
+
plevel = 0
|
505 |
+
for clause in pr.clauses:
|
506 |
+
adds, plevel = g.add_clause(
|
507 |
+
clause, plevel, definitions, verbose=verbose
|
508 |
+
)
|
509 |
+
added += adds
|
510 |
+
g.plevel = plevel
|
511 |
+
|
512 |
+
except (nm.InvalidLineIntersectError, nm.InvalidQuadSolveError):
|
513 |
+
continue
|
514 |
+
except DepCheckFailError:
|
515 |
+
continue
|
516 |
+
except (PointTooCloseError, PointTooFarError):
|
517 |
+
continue
|
518 |
+
|
519 |
+
if not pr.goal:
|
520 |
+
break
|
521 |
+
|
522 |
+
args = list(map(lambda x: g.get(x, lambda: int(x)), pr.goal.args))
|
523 |
+
check = nm.check(pr.goal.name, args)
|
524 |
+
|
525 |
+
g.url = pr.url
|
526 |
+
g.build_def = (pr, definitions)
|
527 |
+
for add in added:
|
528 |
+
g.add_algebra(add, level=0)
|
529 |
+
|
530 |
+
return g, added
|
531 |
+
|
532 |
+
def all_points(self) -> list[Point]:
|
533 |
+
"""Return all nodes of type Point."""
|
534 |
+
return list(self.type2nodes[Point])
|
535 |
+
|
536 |
+
def all_nodes(self) -> list[gm.Node]:
|
537 |
+
"""Return all nodes."""
|
538 |
+
return list(self._name2node.values())
|
539 |
+
|
540 |
+
def add_points(self, pnames: list[str]) -> list[Point]:
|
541 |
+
"""Add new points with given names in list pnames."""
|
542 |
+
result = [self.new_node(Point, name) for name in pnames]
|
543 |
+
self._name2point.update(zip(pnames, result))
|
544 |
+
return result
|
545 |
+
|
546 |
+
def names2nodes(self, pnames: list[str]) -> list[gm.Node]:
|
547 |
+
return [self._name2node[name] for name in pnames]
|
548 |
+
|
549 |
+
def names2points(
|
550 |
+
self, pnames: list[str], create_new_point: bool = False
|
551 |
+
) -> list[Point]:
|
552 |
+
"""Return Point objects given names."""
|
553 |
+
result = []
|
554 |
+
for name in pnames:
|
555 |
+
if name not in self._name2node and not create_new_point:
|
556 |
+
raise ValueError(f'Cannot find point {name} in graph')
|
557 |
+
elif name in self._name2node:
|
558 |
+
obj = self._name2node[name]
|
559 |
+
else:
|
560 |
+
obj = self.new_node(Point, name)
|
561 |
+
result.append(obj)
|
562 |
+
|
563 |
+
return result
|
564 |
+
|
565 |
+
def names2points_or_int(self, pnames: list[str]) -> list[Point]:
|
566 |
+
"""Return Point objects given names."""
|
567 |
+
result = []
|
568 |
+
for name in pnames:
|
569 |
+
if name.isdigit():
|
570 |
+
result += [int(name)]
|
571 |
+
elif 'pi/' in name:
|
572 |
+
n, d = name.split('pi/')
|
573 |
+
ang, _ = self.get_or_create_const_ang(int(n), int(d))
|
574 |
+
result += [ang]
|
575 |
+
elif '/' in name:
|
576 |
+
n, d = name.split('/')
|
577 |
+
rat, _ = self.get_or_create_const_rat(int(n), int(d))
|
578 |
+
result += [rat]
|
579 |
+
else:
|
580 |
+
result += [self._name2point[name]]
|
581 |
+
|
582 |
+
return result
|
583 |
+
|
584 |
+
def get(self, pointname: str, default_fn: Callable[str, Point]) -> Point:
|
585 |
+
if pointname in self._name2point:
|
586 |
+
return self._name2point[pointname]
|
587 |
+
if pointname in self._name2node:
|
588 |
+
return self._name2node[pointname]
|
589 |
+
return default_fn()
|
590 |
+
|
591 |
+
def new_node(self, oftype: Type[gm.Node], name: str = '') -> gm.Node:
|
592 |
+
node = oftype(name, self)
|
593 |
+
|
594 |
+
self.type2nodes[oftype].append(node)
|
595 |
+
self._name2node[name] = node
|
596 |
+
|
597 |
+
if isinstance(node, Point):
|
598 |
+
self._name2point[name] = node
|
599 |
+
|
600 |
+
return node
|
601 |
+
|
602 |
+
def merge(self, nodes: list[gm.Node], deps: Dependency) -> gm.Node:
|
603 |
+
"""Merge all nodes."""
|
604 |
+
if len(nodes) < 2:
|
605 |
+
return
|
606 |
+
|
607 |
+
node0, *nodes1 = nodes
|
608 |
+
all_nodes = self.type2nodes[type(node0)]
|
609 |
+
|
610 |
+
# find node0 that exists in all_nodes to be the rep
|
611 |
+
# and merge all other nodes into node0
|
612 |
+
for node in nodes:
|
613 |
+
if node in all_nodes:
|
614 |
+
node0 = node
|
615 |
+
nodes1 = [n for n in nodes if n != node0]
|
616 |
+
break
|
617 |
+
return self.merge_into(node0, nodes1, deps)
|
618 |
+
|
619 |
+
def merge_into(
|
620 |
+
self, node0: gm.Node, nodes1: list[gm.Node], deps: Dependency
|
621 |
+
) -> gm.Node:
|
622 |
+
"""Merge nodes1 into a single node0."""
|
623 |
+
node0.merge(nodes1, deps)
|
624 |
+
for n in nodes1:
|
625 |
+
if n.rep() != n:
|
626 |
+
self.remove([n])
|
627 |
+
|
628 |
+
nodes = [node0] + nodes1
|
629 |
+
if any([node._val for node in nodes]):
|
630 |
+
for node in nodes:
|
631 |
+
self.connect_val(node, deps=None)
|
632 |
+
|
633 |
+
vals1 = [n._val for n in nodes1]
|
634 |
+
node0._val.merge(vals1, deps)
|
635 |
+
|
636 |
+
for v in vals1:
|
637 |
+
if v.rep() != v:
|
638 |
+
self.remove([v])
|
639 |
+
|
640 |
+
return node0
|
641 |
+
|
642 |
+
def remove(self, nodes: list[gm.Node]) -> None:
|
643 |
+
"""Remove nodes out of self because they are merged."""
|
644 |
+
if not nodes:
|
645 |
+
return
|
646 |
+
|
647 |
+
for node in nodes:
|
648 |
+
all_nodes = self.type2nodes[type(nodes[0])]
|
649 |
+
|
650 |
+
if node in all_nodes:
|
651 |
+
all_nodes.remove(node)
|
652 |
+
|
653 |
+
if node.name in self._name2node.values():
|
654 |
+
self._name2node.pop(node.name)
|
655 |
+
|
656 |
+
def connect(self, a: gm.Node, b: gm.Node, deps: Dependency) -> None:
|
657 |
+
a.connect_to(b, deps)
|
658 |
+
b.connect_to(a, deps)
|
659 |
+
|
660 |
+
def connect_val(self, node: gm.Node, deps: Dependency) -> gm.Node:
|
661 |
+
"""Connect a node into its value (equality) node."""
|
662 |
+
if node._val:
|
663 |
+
return node._val
|
664 |
+
name = None
|
665 |
+
if isinstance(node, Line):
|
666 |
+
name = 'd(' + node.name + ')'
|
667 |
+
if isinstance(node, Angle):
|
668 |
+
name = 'm(' + node.name + ')'
|
669 |
+
if isinstance(node, Segment):
|
670 |
+
name = 'l(' + node.name + ')'
|
671 |
+
if isinstance(node, Ratio):
|
672 |
+
name = 'r(' + node.name + ')'
|
673 |
+
v = self.new_node(gm.val_type(node), name)
|
674 |
+
self.connect(node, v, deps=deps)
|
675 |
+
return v
|
676 |
+
|
677 |
+
def is_equal(self, x: gm.Node, y: gm.Node, level: int = None) -> bool:
|
678 |
+
return gm.is_equal(x, y, level)
|
679 |
+
|
680 |
+
def add_piece(
|
681 |
+
self, name: str, args: list[Point], deps: EmptyDependency
|
682 |
+
) -> list[Dependency]:
|
683 |
+
"""Add a new predicate."""
|
684 |
+
if name in ['coll', 'collx']:
|
685 |
+
return self.add_coll(args, deps)
|
686 |
+
elif name == 'para':
|
687 |
+
return self.add_para(args, deps)
|
688 |
+
elif name == 'perp':
|
689 |
+
return self.add_perp(args, deps)
|
690 |
+
elif name == 'midp':
|
691 |
+
return self.add_midp(args, deps)
|
692 |
+
elif name == 'cong':
|
693 |
+
return self.add_cong(args, deps)
|
694 |
+
elif name == 'circle':
|
695 |
+
return self.add_circle(args, deps)
|
696 |
+
elif name == 'cyclic':
|
697 |
+
return self.add_cyclic(args, deps)
|
698 |
+
elif name in ['eqangle', 'eqangle6']:
|
699 |
+
return self.add_eqangle(args, deps)
|
700 |
+
elif name in ['eqratio', 'eqratio6']:
|
701 |
+
return self.add_eqratio(args, deps)
|
702 |
+
# numerical!
|
703 |
+
elif name == 's_angle':
|
704 |
+
return self.add_s_angle(args, deps)
|
705 |
+
elif name == 'aconst':
|
706 |
+
a, b, c, d, ang = args
|
707 |
+
|
708 |
+
if isinstance(ang, str):
|
709 |
+
name = ang
|
710 |
+
else:
|
711 |
+
name = ang.name
|
712 |
+
|
713 |
+
num, den = name.split('pi/')
|
714 |
+
num, den = int(num), int(den)
|
715 |
+
return self.add_aconst([a, b, c, d, num, den], deps)
|
716 |
+
elif name == 's_angle':
|
717 |
+
b, x, a, b, ang = ( # pylint: disable=redeclared-assigned-name,unused-variable
|
718 |
+
args
|
719 |
+
)
|
720 |
+
|
721 |
+
if isinstance(ang, str):
|
722 |
+
name = ang
|
723 |
+
else:
|
724 |
+
name = ang.name
|
725 |
+
|
726 |
+
n, d = name.split('pi/')
|
727 |
+
ang = int(n) * 180 / int(d)
|
728 |
+
return self.add_s_angle([a, b, x, ang], deps)
|
729 |
+
elif name == 'rconst':
|
730 |
+
a, b, c, d, rat = args
|
731 |
+
|
732 |
+
if isinstance(rat, str):
|
733 |
+
name = rat
|
734 |
+
else:
|
735 |
+
name = rat.name
|
736 |
+
|
737 |
+
num, den = name.split('/')
|
738 |
+
num, den = int(num), int(den)
|
739 |
+
return self.add_eqrat_const([a, b, c, d, num, den], deps)
|
740 |
+
|
741 |
+
# composite pieces:
|
742 |
+
elif name == 'cong2':
|
743 |
+
return self.add_cong2(args, deps)
|
744 |
+
elif name == 'eqratio3':
|
745 |
+
return self.add_eqratio3(args, deps)
|
746 |
+
elif name == 'eqratio4':
|
747 |
+
return self.add_eqratio4(args, deps)
|
748 |
+
elif name == 'simtri':
|
749 |
+
return self.add_simtri(args, deps)
|
750 |
+
elif name == 'contri':
|
751 |
+
return self.add_contri(args, deps)
|
752 |
+
elif name == 'simtri2':
|
753 |
+
return self.add_simtri2(args, deps)
|
754 |
+
elif name == 'contri2':
|
755 |
+
return self.add_contri2(args, deps)
|
756 |
+
elif name == 'simtri*':
|
757 |
+
return self.add_simtri_check(args, deps)
|
758 |
+
elif name == 'contri*':
|
759 |
+
return self.add_contri_check(args, deps)
|
760 |
+
elif name in ['acompute', 'rcompute']:
|
761 |
+
dep = deps.populate(name, args)
|
762 |
+
self.cache_dep(name, args, dep)
|
763 |
+
return [dep]
|
764 |
+
elif name in ['fixl', 'fixc', 'fixb', 'fixt', 'fixp']:
|
765 |
+
dep = deps.populate(name, args)
|
766 |
+
self.cache_dep(name, args, dep)
|
767 |
+
return [dep]
|
768 |
+
elif name in ['ind']:
|
769 |
+
return []
|
770 |
+
raise ValueError(f'Not recognize {name}')
|
771 |
+
|
772 |
+
def check(self, name: str, args: list[Point]) -> bool:
|
773 |
+
"""Symbolically check if a predicate is True."""
|
774 |
+
if name == 'ncoll':
|
775 |
+
return self.check_ncoll(args)
|
776 |
+
if name == 'npara':
|
777 |
+
return self.check_npara(args)
|
778 |
+
if name == 'nperp':
|
779 |
+
return self.check_nperp(args)
|
780 |
+
if name == 'midp':
|
781 |
+
return self.check_midp(args)
|
782 |
+
if name == 'cong':
|
783 |
+
return self.check_cong(args)
|
784 |
+
if name == 'perp':
|
785 |
+
return self.check_perp(args)
|
786 |
+
if name == 'para':
|
787 |
+
return self.check_para(args)
|
788 |
+
if name == 'coll':
|
789 |
+
return self.check_coll(args)
|
790 |
+
if name == 'cyclic':
|
791 |
+
return self.check_cyclic(args)
|
792 |
+
if name == 'circle':
|
793 |
+
return self.check_circle(args)
|
794 |
+
if name == 'aconst':
|
795 |
+
return self.check_aconst(args)
|
796 |
+
if name == 'rconst':
|
797 |
+
return self.check_rconst(args)
|
798 |
+
if name == 'acompute':
|
799 |
+
return self.check_acompute(args)
|
800 |
+
if name == 'rcompute':
|
801 |
+
return self.check_rcompute(args)
|
802 |
+
if name in ['eqangle', 'eqangle6']:
|
803 |
+
if len(args) == 5:
|
804 |
+
return self.check_aconst(args)
|
805 |
+
return self.check_eqangle(args)
|
806 |
+
if name in ['eqratio', 'eqratio6']:
|
807 |
+
if len(args) == 5:
|
808 |
+
return self.check_rconst(args)
|
809 |
+
return self.check_eqratio(args)
|
810 |
+
if name in ['simtri', 'simtri2', 'simtri*']:
|
811 |
+
return self.check_simtri(args)
|
812 |
+
if name in ['contri', 'contri2', 'contri*']:
|
813 |
+
return self.check_contri(args)
|
814 |
+
if name == 'sameside':
|
815 |
+
return self.check_sameside(args)
|
816 |
+
if name in 'diff':
|
817 |
+
a, b = args
|
818 |
+
return not a.num.close(b.num)
|
819 |
+
if name in ['fixl', 'fixc', 'fixb', 'fixt', 'fixp']:
|
820 |
+
return self.in_cache(name, args)
|
821 |
+
if name in ['ind']:
|
822 |
+
return True
|
823 |
+
raise ValueError(f'Not recognize {name}')
|
824 |
+
|
825 |
+
def get_lines_thru_all(self, *points: list[gm.Point]) -> list[Line]:
|
826 |
+
line2count = defaultdict(lambda: 0)
|
827 |
+
points = set(points)
|
828 |
+
for p in points:
|
829 |
+
for l in p.neighbors(Line):
|
830 |
+
line2count[l] += 1
|
831 |
+
return [l for l, count in line2count.items() if count == len(points)]
|
832 |
+
|
833 |
+
def _get_line(self, a: Point, b: Point) -> Optional[Line]:
|
834 |
+
linesa = a.neighbors(Line)
|
835 |
+
for l in b.neighbors(Line):
|
836 |
+
if l in linesa:
|
837 |
+
return l
|
838 |
+
return None
|
839 |
+
|
840 |
+
def _get_line_all(self, a: Point, b: Point) -> Generator[Line, None, None]:
|
841 |
+
linesa = a.neighbors(Line, do_rep=False)
|
842 |
+
linesb = b.neighbors(Line, do_rep=False)
|
843 |
+
for l in linesb:
|
844 |
+
if l in linesa:
|
845 |
+
yield l
|
846 |
+
|
847 |
+
def _get_lines(self, *points: list[Point]) -> list[Line]:
|
848 |
+
"""Return all lines that connect to >= 2 points."""
|
849 |
+
line2count = defaultdict(lambda: 0)
|
850 |
+
for p in points:
|
851 |
+
for l in p.neighbors(Line):
|
852 |
+
line2count[l] += 1
|
853 |
+
return [l for l, count in line2count.items() if count >= 2]
|
854 |
+
|
855 |
+
def get_circle_thru_triplet(self, p1: Point, p2: Point, p3: Point) -> Circle:
|
856 |
+
p1, p2, p3 = sorted([p1, p2, p3], key=lambda x: x.name)
|
857 |
+
if (p1, p2, p3) in self._triplet2circle:
|
858 |
+
return self._triplet2circle[(p1, p2, p3)]
|
859 |
+
return self.get_new_circle_thru_triplet(p1, p2, p3)
|
860 |
+
|
861 |
+
def get_new_circle_thru_triplet(
|
862 |
+
self, p1: Point, p2: Point, p3: Point
|
863 |
+
) -> Circle:
|
864 |
+
"""Get a new Circle that goes thru three given Points."""
|
865 |
+
p1, p2, p3 = sorted([p1, p2, p3], key=lambda x: x.name)
|
866 |
+
name = p1.name.lower() + p2.name.lower() + p3.name.lower()
|
867 |
+
circle = self.new_node(Circle, f'({name})')
|
868 |
+
circle.num = nm.Circle(p1=p1.num, p2=p2.num, p3=p3.num)
|
869 |
+
circle.points = p1, p2, p3
|
870 |
+
|
871 |
+
self.connect(p1, circle, deps=None)
|
872 |
+
self.connect(p2, circle, deps=None)
|
873 |
+
self.connect(p3, circle, deps=None)
|
874 |
+
self._triplet2circle[(p1, p2, p3)] = circle
|
875 |
+
return circle
|
876 |
+
|
877 |
+
def get_line_thru_pair(self, p1: Point, p2: Point) -> Line:
|
878 |
+
if (p1, p2) in self._pair2line:
|
879 |
+
return self._pair2line[(p1, p2)]
|
880 |
+
if (p2, p1) in self._pair2line:
|
881 |
+
return self._pair2line[(p2, p1)]
|
882 |
+
return self.get_new_line_thru_pair(p1, p2)
|
883 |
+
|
884 |
+
def get_new_line_thru_pair(self, p1: Point, p2: Point) -> Line:
|
885 |
+
if p1.name.lower() > p2.name.lower():
|
886 |
+
p1, p2 = p2, p1
|
887 |
+
name = p1.name.lower() + p2.name.lower()
|
888 |
+
line = self.new_node(Line, name)
|
889 |
+
line.num = nm.Line(p1.num, p2.num)
|
890 |
+
line.points = p1, p2
|
891 |
+
|
892 |
+
self.connect(p1, line, deps=None)
|
893 |
+
self.connect(p2, line, deps=None)
|
894 |
+
self._pair2line[(p1, p2)] = line
|
895 |
+
return line
|
896 |
+
|
897 |
+
def get_line_thru_pair_why(
|
898 |
+
self, p1: Point, p2: Point
|
899 |
+
) -> tuple[Line, list[Dependency]]:
|
900 |
+
"""Get one line thru two given points and the corresponding dependency list."""
|
901 |
+
if p1.name.lower() > p2.name.lower():
|
902 |
+
p1, p2 = p2, p1
|
903 |
+
if (p1, p2) in self._pair2line:
|
904 |
+
return self._pair2line[(p1, p2)].rep_and_why()
|
905 |
+
|
906 |
+
l, why = gm.line_of_and_why([p1, p2])
|
907 |
+
if l is None:
|
908 |
+
l = self.get_new_line_thru_pair(p1, p2)
|
909 |
+
why = []
|
910 |
+
return l, why
|
911 |
+
|
912 |
+
def coll_dep(self, points: list[Point], p: Point) -> list[Dependency]:
|
913 |
+
"""Return the dep(.why) explaining why p is coll with points."""
|
914 |
+
for p1, p2 in utils.comb2(points):
|
915 |
+
if self.check_coll([p1, p2, p]):
|
916 |
+
dep = Dependency('coll', [p1, p2, p], None, None)
|
917 |
+
return dep.why_me_or_cache(self, None)
|
918 |
+
|
919 |
+
def add_coll(
|
920 |
+
self, points: list[Point], deps: EmptyDependency
|
921 |
+
) -> list[Dependency]:
|
922 |
+
"""Add a predicate that `points` are collinear."""
|
923 |
+
points = list(set(points))
|
924 |
+
og_points = list(points)
|
925 |
+
|
926 |
+
all_lines = []
|
927 |
+
for p1, p2 in utils.comb2(points):
|
928 |
+
all_lines.append(self.get_line_thru_pair(p1, p2))
|
929 |
+
points = sum([l.neighbors(Point) for l in all_lines], [])
|
930 |
+
points = list(set(points))
|
931 |
+
|
932 |
+
existed = set()
|
933 |
+
new = set()
|
934 |
+
for p1, p2 in utils.comb2(points):
|
935 |
+
if p1.name > p2.name:
|
936 |
+
p1, p2 = p2, p1
|
937 |
+
if (p1, p2) in self._pair2line:
|
938 |
+
line = self._pair2line[(p1, p2)]
|
939 |
+
existed.add(line)
|
940 |
+
else:
|
941 |
+
line = self.get_new_line_thru_pair(p1, p2)
|
942 |
+
new.add(line)
|
943 |
+
|
944 |
+
existed = sorted(existed, key=lambda l: l.name)
|
945 |
+
new = sorted(new, key=lambda l: l.name)
|
946 |
+
|
947 |
+
existed, new = list(existed), list(new)
|
948 |
+
if not existed:
|
949 |
+
line0, *lines = new
|
950 |
+
else:
|
951 |
+
line0, lines = existed[0], existed[1:] + new
|
952 |
+
|
953 |
+
add = []
|
954 |
+
line0, why0 = line0.rep_and_why()
|
955 |
+
a, b = line0.points
|
956 |
+
for line in lines:
|
957 |
+
c, d = line.points
|
958 |
+
args = list({a, b, c, d})
|
959 |
+
if len(args) < 3:
|
960 |
+
continue
|
961 |
+
|
962 |
+
whys = []
|
963 |
+
for x in args:
|
964 |
+
if x not in og_points:
|
965 |
+
whys.append(self.coll_dep(og_points, x))
|
966 |
+
|
967 |
+
abcd_deps = deps
|
968 |
+
if whys + why0:
|
969 |
+
dep0 = deps.populate('coll', og_points)
|
970 |
+
abcd_deps = EmptyDependency(level=deps.level, rule_name=None)
|
971 |
+
abcd_deps.why = [dep0] + whys
|
972 |
+
|
973 |
+
is_coll = self.check_coll(args)
|
974 |
+
dep = abcd_deps.populate('coll', args)
|
975 |
+
self.cache_dep('coll', args, dep)
|
976 |
+
self.merge_into(line0, [line], dep)
|
977 |
+
|
978 |
+
if not is_coll:
|
979 |
+
add += [dep]
|
980 |
+
|
981 |
+
return add
|
982 |
+
|
983 |
+
def check_coll(self, points: list[Point]) -> bool:
|
984 |
+
points = list(set(points))
|
985 |
+
if len(points) < 3:
|
986 |
+
return True
|
987 |
+
line2count = defaultdict(lambda: 0)
|
988 |
+
for p in points:
|
989 |
+
for l in p.neighbors(Line):
|
990 |
+
line2count[l] += 1
|
991 |
+
return any([count == len(points) for _, count in line2count.items()])
|
992 |
+
|
993 |
+
def why_coll(self, args: tuple[Line, list[Point]]) -> list[Dependency]:
|
994 |
+
line, points = args
|
995 |
+
return line.why_coll(points)
|
996 |
+
|
997 |
+
def check_ncoll(self, points: list[Point]) -> bool:
|
998 |
+
if self.check_coll(points):
|
999 |
+
return False
|
1000 |
+
return not nm.check_coll([p.num for p in points])
|
1001 |
+
|
1002 |
+
def check_sameside(self, points: list[Point]) -> bool:
|
1003 |
+
return nm.check_sameside([p.num for p in points])
|
1004 |
+
|
1005 |
+
def make_equal(self, x: gm.Node, y: gm.Node, deps: Dependency) -> None:
|
1006 |
+
"""Make that two nodes x and y are equal, i.e. merge their value node."""
|
1007 |
+
if x.val is None:
|
1008 |
+
x, y = y, x
|
1009 |
+
|
1010 |
+
self.connect_val(x, deps=None)
|
1011 |
+
self.connect_val(y, deps=None)
|
1012 |
+
vx = x._val
|
1013 |
+
vy = y._val
|
1014 |
+
|
1015 |
+
if vx == vy:
|
1016 |
+
return
|
1017 |
+
|
1018 |
+
merges = [vx, vy]
|
1019 |
+
|
1020 |
+
if (
|
1021 |
+
isinstance(x, Angle)
|
1022 |
+
and x not in self.aconst.values()
|
1023 |
+
and y not in self.aconst.values()
|
1024 |
+
and x.directions == y.directions[::-1]
|
1025 |
+
and x.directions[0] != x.directions[1]
|
1026 |
+
):
|
1027 |
+
merges = [self.vhalfpi, vx, vy]
|
1028 |
+
|
1029 |
+
self.merge(merges, deps)
|
1030 |
+
|
1031 |
+
def merge_vals(self, vx: gm.Node, vy: gm.Node, deps: Dependency) -> None:
|
1032 |
+
if vx == vy:
|
1033 |
+
return
|
1034 |
+
merges = [vx, vy]
|
1035 |
+
self.merge(merges, deps)
|
1036 |
+
|
1037 |
+
def why_equal(self, x: gm.Node, y: gm.Node, level: int) -> list[Dependency]:
|
1038 |
+
return gm.why_equal(x, y, level)
|
1039 |
+
|
1040 |
+
def _why_coll4(
|
1041 |
+
self,
|
1042 |
+
a: Point,
|
1043 |
+
b: Point,
|
1044 |
+
ab: Line,
|
1045 |
+
c: Point,
|
1046 |
+
d: Point,
|
1047 |
+
cd: Line,
|
1048 |
+
level: int,
|
1049 |
+
) -> list[Dependency]:
|
1050 |
+
return self._why_coll2(a, b, ab, level) + self._why_coll2(c, d, cd, level)
|
1051 |
+
|
1052 |
+
def _why_coll8(
|
1053 |
+
self,
|
1054 |
+
a: Point,
|
1055 |
+
b: Point,
|
1056 |
+
ab: Line,
|
1057 |
+
c: Point,
|
1058 |
+
d: Point,
|
1059 |
+
cd: Line,
|
1060 |
+
m: Point,
|
1061 |
+
n: Point,
|
1062 |
+
mn: Line,
|
1063 |
+
p: Point,
|
1064 |
+
q: Point,
|
1065 |
+
pq: Line,
|
1066 |
+
level: int,
|
1067 |
+
) -> list[Dependency]:
|
1068 |
+
"""Dependency list of why 8 points are collinear."""
|
1069 |
+
why8 = self._why_coll4(a, b, ab, c, d, cd, level)
|
1070 |
+
why8 += self._why_coll4(m, n, mn, p, q, pq, level)
|
1071 |
+
return why8
|
1072 |
+
|
1073 |
+
def add_para(
|
1074 |
+
self, points: list[Point], deps: EmptyDependency
|
1075 |
+
) -> list[Dependency]:
|
1076 |
+
"""Add a new predicate that 4 points (2 lines) are parallel."""
|
1077 |
+
a, b, c, d = points
|
1078 |
+
ab, why1 = self.get_line_thru_pair_why(a, b)
|
1079 |
+
cd, why2 = self.get_line_thru_pair_why(c, d)
|
1080 |
+
|
1081 |
+
is_equal = self.is_equal(ab, cd)
|
1082 |
+
|
1083 |
+
(a, b), (c, d) = ab.points, cd.points
|
1084 |
+
|
1085 |
+
dep0 = deps.populate('para', points)
|
1086 |
+
deps = EmptyDependency(level=deps.level, rule_name=None)
|
1087 |
+
|
1088 |
+
deps = deps.populate('para', [a, b, c, d])
|
1089 |
+
deps.why = [dep0] + why1 + why2
|
1090 |
+
|
1091 |
+
self.make_equal(ab, cd, deps)
|
1092 |
+
deps.algebra = ab._val, cd._val
|
1093 |
+
|
1094 |
+
self.cache_dep('para', [a, b, c, d], deps)
|
1095 |
+
if not is_equal:
|
1096 |
+
return [deps]
|
1097 |
+
return []
|
1098 |
+
|
1099 |
+
def why_para(self, args: list[Point]) -> list[Dependency]:
|
1100 |
+
ab, cd, lvl = args
|
1101 |
+
return self.why_equal(ab, cd, lvl)
|
1102 |
+
|
1103 |
+
def check_para_or_coll(self, points: list[Point]) -> bool:
|
1104 |
+
return self.check_para(points) or self.check_coll(points)
|
1105 |
+
|
1106 |
+
def check_para(self, points: list[Point]) -> bool:
|
1107 |
+
a, b, c, d = points
|
1108 |
+
if (a == b) or (c == d):
|
1109 |
+
return False
|
1110 |
+
ab = self._get_line(a, b)
|
1111 |
+
cd = self._get_line(c, d)
|
1112 |
+
if not ab or not cd:
|
1113 |
+
return False
|
1114 |
+
|
1115 |
+
return self.is_equal(ab, cd)
|
1116 |
+
|
1117 |
+
def check_npara(self, points: list[Point]) -> bool:
|
1118 |
+
if self.check_para(points):
|
1119 |
+
return False
|
1120 |
+
return not nm.check_para([p.num for p in points])
|
1121 |
+
|
1122 |
+
def _get_angle(
|
1123 |
+
self, d1: Direction, d2: Direction
|
1124 |
+
) -> tuple[Angle, Optional[Angle]]:
|
1125 |
+
for a in self.type2nodes[Angle]:
|
1126 |
+
if a.directions == (d1, d2):
|
1127 |
+
return a, a.opposite
|
1128 |
+
return None, None
|
1129 |
+
|
1130 |
+
def get_first_angle(
|
1131 |
+
self, l1: Line, l2: Line
|
1132 |
+
) -> tuple[Angle, list[Dependency]]:
|
1133 |
+
"""Get a first angle between line l1 and line l2."""
|
1134 |
+
d1, d2 = l1._val, l2._val
|
1135 |
+
|
1136 |
+
d1s = d1.all_reps()
|
1137 |
+
d2s = d2.all_reps()
|
1138 |
+
|
1139 |
+
found = d1.first_angle(d2s)
|
1140 |
+
if found is None:
|
1141 |
+
found = d2.first_angle(d1s)
|
1142 |
+
if found is None:
|
1143 |
+
return None, []
|
1144 |
+
ang, x2, x1 = found
|
1145 |
+
found = ang.opposite, x1, x2
|
1146 |
+
|
1147 |
+
ang, x1, x2 = found
|
1148 |
+
return ang, d1.deps_upto(x1) + d2.deps_upto(x2)
|
1149 |
+
|
1150 |
+
def _get_or_create_angle(
|
1151 |
+
self, l1: Line, l2: Line, deps: Dependency
|
1152 |
+
) -> tuple[Angle, Angle, list[Dependency]]:
|
1153 |
+
return self.get_or_create_angle_d(l1._val, l2._val, deps)
|
1154 |
+
|
1155 |
+
def get_or_create_angle_d(
|
1156 |
+
self, d1: Direction, d2: Direction, deps: Dependency
|
1157 |
+
) -> tuple[Angle, Angle, list[Dependency]]:
|
1158 |
+
"""Get or create an angle between two Direction d1 and d2."""
|
1159 |
+
for a in self.type2nodes[Angle]:
|
1160 |
+
if a.directions == (d1.rep(), d2.rep()): # directions = _d.rep()
|
1161 |
+
d1_, d2_ = a._d
|
1162 |
+
why1 = d1.why_equal([d1_], None) + d1_.why_rep()
|
1163 |
+
why2 = d2.why_equal([d2_], None) + d2_.why_rep()
|
1164 |
+
return a, a.opposite, why1 + why2
|
1165 |
+
|
1166 |
+
d1, why1 = d1.rep_and_why()
|
1167 |
+
d2, why2 = d2.rep_and_why()
|
1168 |
+
a12 = self.new_node(Angle, f'{d1.name}-{d2.name}')
|
1169 |
+
a21 = self.new_node(Angle, f'{d2.name}-{d1.name}')
|
1170 |
+
self.connect(d1, a12, deps)
|
1171 |
+
self.connect(d2, a21, deps)
|
1172 |
+
self.connect(a12, a21, deps)
|
1173 |
+
a12.set_directions(d1, d2)
|
1174 |
+
a21.set_directions(d2, d1)
|
1175 |
+
a12.opposite = a21
|
1176 |
+
a21.opposite = a12
|
1177 |
+
return a12, a21, why1 + why2
|
1178 |
+
|
1179 |
+
def _add_para_or_coll(
|
1180 |
+
self,
|
1181 |
+
a: Point,
|
1182 |
+
b: Point,
|
1183 |
+
c: Point,
|
1184 |
+
d: Point,
|
1185 |
+
x: Point,
|
1186 |
+
y: Point,
|
1187 |
+
m: Point,
|
1188 |
+
n: Point,
|
1189 |
+
deps: EmptyDependency,
|
1190 |
+
) -> list[Dependency]:
|
1191 |
+
"""Add a new parallel or collinear predicate."""
|
1192 |
+
extends = [('perp', [x, y, m, n])]
|
1193 |
+
if {a, b} == {x, y}:
|
1194 |
+
pass
|
1195 |
+
elif self.check_para([a, b, x, y]):
|
1196 |
+
extends.append(('para', [a, b, x, y]))
|
1197 |
+
elif self.check_coll([a, b, x, y]):
|
1198 |
+
extends.append(('coll', set(list([a, b, x, y]))))
|
1199 |
+
else:
|
1200 |
+
return None
|
1201 |
+
|
1202 |
+
if m in [c, d] or n in [c, d] or c in [m, n] or d in [m, n]:
|
1203 |
+
pass
|
1204 |
+
elif self.check_coll([c, d, m]):
|
1205 |
+
extends.append(('coll', [c, d, m]))
|
1206 |
+
elif self.check_coll([c, d, n]):
|
1207 |
+
extends.append(('coll', [c, d, n]))
|
1208 |
+
elif self.check_coll([c, m, n]):
|
1209 |
+
extends.append(('coll', [c, m, n]))
|
1210 |
+
elif self.check_coll([d, m, n]):
|
1211 |
+
extends.append(('coll', [d, m, n]))
|
1212 |
+
else:
|
1213 |
+
deps = deps.extend_many(self, 'perp', [a, b, c, d], extends)
|
1214 |
+
return self.add_para([c, d, m, n], deps)
|
1215 |
+
|
1216 |
+
deps = deps.extend_many(self, 'perp', [a, b, c, d], extends)
|
1217 |
+
return self.add_coll(list(set([c, d, m, n])), deps)
|
1218 |
+
|
1219 |
+
def maybe_make_para_from_perp(
|
1220 |
+
self, points: list[Point], deps: EmptyDependency
|
1221 |
+
) -> Optional[list[Dependency]]:
|
1222 |
+
"""Maybe add a new parallel predicate from perp predicate."""
|
1223 |
+
a, b, c, d = points
|
1224 |
+
halfpi = self.aconst[(1, 2)]
|
1225 |
+
for ang in halfpi.val.neighbors(Angle):
|
1226 |
+
if ang == halfpi:
|
1227 |
+
continue
|
1228 |
+
d1, d2 = ang.directions
|
1229 |
+
x, y = d1._obj.points
|
1230 |
+
m, n = d2._obj.points
|
1231 |
+
|
1232 |
+
for args in [
|
1233 |
+
(a, b, c, d, x, y, m, n),
|
1234 |
+
(a, b, c, d, m, n, x, y),
|
1235 |
+
(c, d, a, b, x, y, m, n),
|
1236 |
+
(c, d, a, b, m, n, x, y),
|
1237 |
+
]:
|
1238 |
+
args = args + (deps,)
|
1239 |
+
add = self._add_para_or_coll(*args)
|
1240 |
+
if add:
|
1241 |
+
return add
|
1242 |
+
|
1243 |
+
return None
|
1244 |
+
|
1245 |
+
def add_perp(
|
1246 |
+
self, points: list[Point], deps: EmptyDependency
|
1247 |
+
) -> list[Dependency]:
|
1248 |
+
"""Add a new perpendicular predicate from 4 points (2 lines)."""
|
1249 |
+
add = self.maybe_make_para_from_perp(points, deps)
|
1250 |
+
if add is not None:
|
1251 |
+
return add
|
1252 |
+
|
1253 |
+
a, b, c, d = points
|
1254 |
+
ab, why1 = self.get_line_thru_pair_why(a, b)
|
1255 |
+
cd, why2 = self.get_line_thru_pair_why(c, d)
|
1256 |
+
|
1257 |
+
(a, b), (c, d) = ab.points, cd.points
|
1258 |
+
|
1259 |
+
if why1 + why2:
|
1260 |
+
dep0 = deps.populate('perp', points)
|
1261 |
+
deps = EmptyDependency(level=deps.level, rule_name=None)
|
1262 |
+
deps.why = [dep0] + why1 + why2
|
1263 |
+
|
1264 |
+
self.connect_val(ab, deps=None)
|
1265 |
+
self.connect_val(cd, deps=None)
|
1266 |
+
|
1267 |
+
if ab.val == cd.val:
|
1268 |
+
raise ValueError(f'{ab.name} and {cd.name} Cannot be perp.')
|
1269 |
+
|
1270 |
+
args = [a, b, c, d]
|
1271 |
+
i = 0
|
1272 |
+
for x, y, xy in [(a, b, ab), (c, d, cd)]:
|
1273 |
+
i += 1
|
1274 |
+
x_, y_ = xy._val._obj.points
|
1275 |
+
if {x, y} == {x_, y_}:
|
1276 |
+
continue
|
1277 |
+
if deps:
|
1278 |
+
deps = deps.extend(self, 'perp', list(args), 'para', [x, y, x_, y_])
|
1279 |
+
args[2 * i - 2] = x_
|
1280 |
+
args[2 * i - 1] = y_
|
1281 |
+
|
1282 |
+
a12, a21, why = self._get_or_create_angle(ab, cd, deps=None)
|
1283 |
+
|
1284 |
+
if why:
|
1285 |
+
dep0 = deps.populate('perp', [a, b, c, d])
|
1286 |
+
deps = EmptyDependency(level=deps.level, rule_name=None)
|
1287 |
+
deps.why = [dep0] + why
|
1288 |
+
|
1289 |
+
dab, dcd = a12._d
|
1290 |
+
a, b = dab._obj.points
|
1291 |
+
c, d = dcd._obj.points
|
1292 |
+
|
1293 |
+
is_equal = self.is_equal(a12, a21)
|
1294 |
+
deps = deps.populate('perp', [a, b, c, d])
|
1295 |
+
deps.algebra = [dab, dcd]
|
1296 |
+
self.make_equal(a12, a21, deps=deps)
|
1297 |
+
|
1298 |
+
self.cache_dep('perp', [a, b, c, d], deps)
|
1299 |
+
self.cache_dep('eqangle', [a, b, c, d, c, d, a, b], deps)
|
1300 |
+
|
1301 |
+
if not is_equal:
|
1302 |
+
return [deps]
|
1303 |
+
return []
|
1304 |
+
|
1305 |
+
def why_perp(
|
1306 |
+
self, args: list[Union[Point, list[Dependency]]]
|
1307 |
+
) -> list[Dependency]:
|
1308 |
+
a, b, deps = args
|
1309 |
+
return deps + self.why_equal(a, b, None)
|
1310 |
+
|
1311 |
+
def check_perpl(self, ab: Line, cd: Line) -> bool:
|
1312 |
+
if ab.val is None or cd.val is None:
|
1313 |
+
return False
|
1314 |
+
if ab.val == cd.val:
|
1315 |
+
return False
|
1316 |
+
a12, a21 = self._get_angle(ab.val, cd.val)
|
1317 |
+
if a12 is None or a21 is None:
|
1318 |
+
return False
|
1319 |
+
return self.is_equal(a12, a21)
|
1320 |
+
|
1321 |
+
def check_perp(self, points: list[Point]) -> bool:
|
1322 |
+
a, b, c, d = points
|
1323 |
+
ab = self._get_line(a, b)
|
1324 |
+
cd = self._get_line(c, d)
|
1325 |
+
if not ab or not cd:
|
1326 |
+
return False
|
1327 |
+
return self.check_perpl(ab, cd)
|
1328 |
+
|
1329 |
+
def check_nperp(self, points: list[Point]) -> bool:
|
1330 |
+
if self.check_perp(points):
|
1331 |
+
return False
|
1332 |
+
return not nm.check_perp([p.num for p in points])
|
1333 |
+
|
1334 |
+
def _get_segment(self, p1: Point, p2: Point) -> Optional[Segment]:
|
1335 |
+
for s in self.type2nodes[Segment]:
|
1336 |
+
if s.points == {p1, p2}:
|
1337 |
+
return s
|
1338 |
+
return None
|
1339 |
+
|
1340 |
+
def _get_or_create_segment(
|
1341 |
+
self, p1: Point, p2: Point, deps: Dependency
|
1342 |
+
) -> Segment:
|
1343 |
+
"""Get or create a Segment object between two Points p1 and p2."""
|
1344 |
+
if p1 == p2:
|
1345 |
+
raise ValueError(f'Creating same 0-length segment {p1.name}')
|
1346 |
+
|
1347 |
+
for s in self.type2nodes[Segment]:
|
1348 |
+
if s.points == {p1, p2}:
|
1349 |
+
return s
|
1350 |
+
|
1351 |
+
if p1.name > p2.name:
|
1352 |
+
p1, p2 = p2, p1
|
1353 |
+
s = self.new_node(Segment, name=f'{p1.name.upper()}{p2.name.upper()}')
|
1354 |
+
self.connect(p1, s, deps=deps)
|
1355 |
+
self.connect(p2, s, deps=deps)
|
1356 |
+
s.points = {p1, p2}
|
1357 |
+
return s
|
1358 |
+
|
1359 |
+
def add_cong(
|
1360 |
+
self, points: list[Point], deps: EmptyDependency
|
1361 |
+
) -> list[Dependency]:
|
1362 |
+
"""Add that two segments (4 points) are congruent."""
|
1363 |
+
a, b, c, d = points
|
1364 |
+
ab = self._get_or_create_segment(a, b, deps=None)
|
1365 |
+
cd = self._get_or_create_segment(c, d, deps=None)
|
1366 |
+
|
1367 |
+
is_equal = self.is_equal(ab, cd)
|
1368 |
+
|
1369 |
+
dep = deps.populate('cong', [a, b, c, d])
|
1370 |
+
self.make_equal(ab, cd, deps=dep)
|
1371 |
+
dep.algebra = ab._val, cd._val
|
1372 |
+
|
1373 |
+
self.cache_dep('cong', [a, b, c, d], dep)
|
1374 |
+
|
1375 |
+
result = []
|
1376 |
+
|
1377 |
+
if not is_equal:
|
1378 |
+
result += [dep]
|
1379 |
+
|
1380 |
+
if a not in [c, d] and b not in [c, d]:
|
1381 |
+
return result
|
1382 |
+
|
1383 |
+
if b in [c, d]:
|
1384 |
+
a, b = b, a
|
1385 |
+
if a == d:
|
1386 |
+
c, d = d, c # pylint: disable=unused-variable
|
1387 |
+
|
1388 |
+
result += self._maybe_add_cyclic_from_cong(a, b, d, dep)
|
1389 |
+
return result
|
1390 |
+
|
1391 |
+
def _maybe_add_cyclic_from_cong(
|
1392 |
+
self, a: Point, b: Point, c: Point, cong_ab_ac: Dependency
|
1393 |
+
) -> list[Dependency]:
|
1394 |
+
"""Maybe add a new cyclic predicate from given congruent segments."""
|
1395 |
+
ab = self._get_or_create_segment(a, b, deps=None)
|
1396 |
+
|
1397 |
+
# all eq segs with one end being a.
|
1398 |
+
segs = [s for s in ab.val.neighbors(Segment) if a in s.points]
|
1399 |
+
|
1400 |
+
# all points on circle (a, b)
|
1401 |
+
points = []
|
1402 |
+
for s in segs:
|
1403 |
+
x, y = list(s.points)
|
1404 |
+
points.append(x if y == a else y)
|
1405 |
+
|
1406 |
+
# for sure both b and c are in points
|
1407 |
+
points = [p for p in points if p not in [b, c]]
|
1408 |
+
|
1409 |
+
if len(points) < 2:
|
1410 |
+
return []
|
1411 |
+
|
1412 |
+
x, y = points[:2]
|
1413 |
+
|
1414 |
+
if self.check_cyclic([b, c, x, y]):
|
1415 |
+
return []
|
1416 |
+
|
1417 |
+
ax = self._get_or_create_segment(a, x, deps=None)
|
1418 |
+
ay = self._get_or_create_segment(a, y, deps=None)
|
1419 |
+
why = ab._val.why_equal([ax._val, ay._val], level=None)
|
1420 |
+
why += [cong_ab_ac]
|
1421 |
+
|
1422 |
+
deps = EmptyDependency(cong_ab_ac.level, '')
|
1423 |
+
deps.why = why
|
1424 |
+
|
1425 |
+
return self.add_cyclic([b, c, x, y], deps)
|
1426 |
+
|
1427 |
+
def check_cong(self, points: list[Point]) -> bool:
|
1428 |
+
a, b, c, d = points
|
1429 |
+
if {a, b} == {c, d}:
|
1430 |
+
return True
|
1431 |
+
|
1432 |
+
ab = self._get_segment(a, b)
|
1433 |
+
cd = self._get_segment(c, d)
|
1434 |
+
if ab is None or cd is None:
|
1435 |
+
return False
|
1436 |
+
return self.is_equal(ab, cd)
|
1437 |
+
|
1438 |
+
def why_cong(self, args: tuple[Segment, Segment]) -> list[Dependency]:
|
1439 |
+
ab, cd = args
|
1440 |
+
return self.why_equal(ab, cd, None)
|
1441 |
+
|
1442 |
+
def add_midp(
|
1443 |
+
self, points: list[Point], deps: EmptyDependency
|
1444 |
+
) -> list[Dependency]:
|
1445 |
+
m, a, b = points
|
1446 |
+
add = self.add_coll(points, deps=deps)
|
1447 |
+
add += self.add_cong([m, a, m, b], deps)
|
1448 |
+
return add
|
1449 |
+
|
1450 |
+
def why_midp(
|
1451 |
+
self, args: tuple[Line, list[Point], Segment, Segment]
|
1452 |
+
) -> list[Dependency]:
|
1453 |
+
line, points, ma, mb = args
|
1454 |
+
return self.why_coll([line, points]) + self.why_cong([ma, mb])
|
1455 |
+
|
1456 |
+
def check_midp(self, points: list[Point]) -> bool:
|
1457 |
+
if not self.check_coll(points):
|
1458 |
+
return False
|
1459 |
+
m, a, b = points
|
1460 |
+
return self.check_cong([m, a, m, b])
|
1461 |
+
|
1462 |
+
def add_circle(
|
1463 |
+
self, points: list[Point], deps: EmptyDependency
|
1464 |
+
) -> list[Dependency]:
|
1465 |
+
o, a, b, c = points
|
1466 |
+
add = self.add_cong([o, a, o, b], deps=deps)
|
1467 |
+
add += self.add_cong([o, a, o, c], deps=deps)
|
1468 |
+
return add
|
1469 |
+
|
1470 |
+
def why_circle(
|
1471 |
+
self, args: tuple[Segment, Segment, Segment]
|
1472 |
+
) -> list[Dependency]:
|
1473 |
+
oa, ob, oc = args
|
1474 |
+
return self.why_equal(oa, ob, None) and self.why_equal(oa, oc, None)
|
1475 |
+
|
1476 |
+
def check_circle(self, points: list[Point]) -> bool:
|
1477 |
+
o, a, b, c = points
|
1478 |
+
return self.check_cong([o, a, o, b]) and self.check_cong([o, a, o, c])
|
1479 |
+
|
1480 |
+
def get_circles_thru_all(self, *points: list[Point]) -> list[Circle]:
|
1481 |
+
circle2count = defaultdict(lambda: 0)
|
1482 |
+
points = set(points)
|
1483 |
+
for p in points:
|
1484 |
+
for c in p.neighbors(Circle):
|
1485 |
+
circle2count[c] += 1
|
1486 |
+
return [c for c, count in circle2count.items() if count == len(points)]
|
1487 |
+
|
1488 |
+
def _get_circles(self, *points: list[Point]) -> list[Circle]:
|
1489 |
+
circle2count = defaultdict(lambda: 0)
|
1490 |
+
for p in points:
|
1491 |
+
for c in p.neighbors(Circle):
|
1492 |
+
circle2count[c] += 1
|
1493 |
+
return [c for c, count in circle2count.items() if count >= 3]
|
1494 |
+
|
1495 |
+
def cyclic_dep(self, points: list[Point], p: Point) -> list[Dependency]:
|
1496 |
+
for p1, p2, p3 in utils.comb3(points):
|
1497 |
+
if self.check_cyclic([p1, p2, p3, p]):
|
1498 |
+
dep = Dependency('cyclic', [p1, p2, p3, p], None, None)
|
1499 |
+
return dep.why_me_or_cache(self, None)
|
1500 |
+
|
1501 |
+
def add_cyclic(
|
1502 |
+
self, points: list[Point], deps: EmptyDependency
|
1503 |
+
) -> list[Dependency]:
|
1504 |
+
"""Add a new cyclic predicate that 4 points are concyclic."""
|
1505 |
+
points = list(set(points))
|
1506 |
+
og_points = list(points)
|
1507 |
+
|
1508 |
+
all_circles = []
|
1509 |
+
for p1, p2, p3 in utils.comb3(points):
|
1510 |
+
all_circles.append(self.get_circle_thru_triplet(p1, p2, p3))
|
1511 |
+
points = sum([c.neighbors(Point) for c in all_circles], [])
|
1512 |
+
points = list(set(points))
|
1513 |
+
|
1514 |
+
existed = set()
|
1515 |
+
new = set()
|
1516 |
+
for p1, p2, p3 in utils.comb3(points):
|
1517 |
+
p1, p2, p3 = sorted([p1, p2, p3], key=lambda x: x.name)
|
1518 |
+
|
1519 |
+
if (p1, p2, p3) in self._triplet2circle:
|
1520 |
+
circle = self._triplet2circle[(p1, p2, p3)]
|
1521 |
+
existed.add(circle)
|
1522 |
+
else:
|
1523 |
+
circle = self.get_new_circle_thru_triplet(p1, p2, p3)
|
1524 |
+
new.add(circle)
|
1525 |
+
|
1526 |
+
existed = sorted(existed, key=lambda l: l.name)
|
1527 |
+
new = sorted(new, key=lambda l: l.name)
|
1528 |
+
|
1529 |
+
existed, new = list(existed), list(new)
|
1530 |
+
if not existed:
|
1531 |
+
circle0, *circles = new
|
1532 |
+
else:
|
1533 |
+
circle0, circles = existed[0], existed[1:] + new
|
1534 |
+
|
1535 |
+
add = []
|
1536 |
+
circle0, why0 = circle0.rep_and_why()
|
1537 |
+
a, b, c = circle0.points
|
1538 |
+
for circle in circles:
|
1539 |
+
d, e, f = circle.points
|
1540 |
+
args = list({a, b, c, d, e, f})
|
1541 |
+
if len(args) < 4:
|
1542 |
+
continue
|
1543 |
+
whys = []
|
1544 |
+
for x in [a, b, c, d, e, f]:
|
1545 |
+
if x not in og_points:
|
1546 |
+
whys.append(self.cyclic_dep(og_points, x))
|
1547 |
+
abcdef_deps = deps
|
1548 |
+
if whys + why0:
|
1549 |
+
dep0 = deps.populate('cyclic', og_points)
|
1550 |
+
abcdef_deps = EmptyDependency(level=deps.level, rule_name=None)
|
1551 |
+
abcdef_deps.why = [dep0] + whys
|
1552 |
+
|
1553 |
+
is_cyclic = self.check_cyclic(args)
|
1554 |
+
|
1555 |
+
dep = abcdef_deps.populate('cyclic', args)
|
1556 |
+
self.cache_dep('cyclic', args, dep)
|
1557 |
+
self.merge_into(circle0, [circle], dep)
|
1558 |
+
if not is_cyclic:
|
1559 |
+
add += [dep]
|
1560 |
+
|
1561 |
+
return add
|
1562 |
+
|
1563 |
+
def check_cyclic(self, points: list[Point]) -> bool:
|
1564 |
+
points = list(set(points))
|
1565 |
+
if len(points) < 4:
|
1566 |
+
return True
|
1567 |
+
circle2count = defaultdict(lambda: 0)
|
1568 |
+
for p in points:
|
1569 |
+
for c in p.neighbors(Circle):
|
1570 |
+
circle2count[c] += 1
|
1571 |
+
return any([count == len(points) for _, count in circle2count.items()])
|
1572 |
+
|
1573 |
+
def make_equal_pairs(
|
1574 |
+
self,
|
1575 |
+
a: Point,
|
1576 |
+
b: Point,
|
1577 |
+
c: Point,
|
1578 |
+
d: Point,
|
1579 |
+
m: Point,
|
1580 |
+
n: Point,
|
1581 |
+
p: Point,
|
1582 |
+
q: Point,
|
1583 |
+
ab: Line,
|
1584 |
+
cd: Line,
|
1585 |
+
mn: Line,
|
1586 |
+
pq: Line,
|
1587 |
+
deps: EmptyDependency,
|
1588 |
+
) -> list[Dependency]:
|
1589 |
+
"""Add ab/cd = mn/pq in case either two of (ab,cd,mn,pq) are equal."""
|
1590 |
+
depname = 'eqratio' if isinstance(ab, Segment) else 'eqangle'
|
1591 |
+
eqname = 'cong' if isinstance(ab, Segment) else 'para'
|
1592 |
+
|
1593 |
+
is_equal = self.is_equal(mn, pq)
|
1594 |
+
|
1595 |
+
if ab != cd:
|
1596 |
+
dep0 = deps.populate(depname, [a, b, c, d, m, n, p, q])
|
1597 |
+
deps = EmptyDependency(level=deps.level, rule_name=None)
|
1598 |
+
|
1599 |
+
dep = Dependency(eqname, [a, b, c, d], None, deps.level)
|
1600 |
+
deps.why = [dep0, dep.why_me_or_cache(self, None)]
|
1601 |
+
|
1602 |
+
elif eqname == 'para': # ab == cd.
|
1603 |
+
colls = [a, b, c, d]
|
1604 |
+
if len(set(colls)) > 2:
|
1605 |
+
dep0 = deps.populate(depname, [a, b, c, d, m, n, p, q])
|
1606 |
+
deps = EmptyDependency(level=deps.level, rule_name=None)
|
1607 |
+
|
1608 |
+
dep = Dependency('collx', colls, None, deps.level)
|
1609 |
+
deps.why = [dep0, dep.why_me_or_cache(self, None)]
|
1610 |
+
|
1611 |
+
deps = deps.populate(eqname, [m, n, p, q])
|
1612 |
+
self.make_equal(mn, pq, deps=deps)
|
1613 |
+
|
1614 |
+
deps.algebra = mn._val, pq._val
|
1615 |
+
self.cache_dep(eqname, [m, n, p, q], deps)
|
1616 |
+
|
1617 |
+
if is_equal:
|
1618 |
+
return []
|
1619 |
+
return [deps]
|
1620 |
+
|
1621 |
+
def maybe_make_equal_pairs(
|
1622 |
+
self,
|
1623 |
+
a: Point,
|
1624 |
+
b: Point,
|
1625 |
+
c: Point,
|
1626 |
+
d: Point,
|
1627 |
+
m: Point,
|
1628 |
+
n: Point,
|
1629 |
+
p: Point,
|
1630 |
+
q: Point,
|
1631 |
+
ab: Line,
|
1632 |
+
cd: Line,
|
1633 |
+
mn: Line,
|
1634 |
+
pq: Line,
|
1635 |
+
deps: EmptyDependency,
|
1636 |
+
) -> Optional[list[Dependency]]:
|
1637 |
+
"""Add ab/cd = mn/pq in case maybe either two of (ab,cd,mn,pq) are equal."""
|
1638 |
+
level = deps.level
|
1639 |
+
if self.is_equal(ab, cd, level):
|
1640 |
+
return self.make_equal_pairs(a, b, c, d, m, n, p, q, ab, cd, mn, pq, deps)
|
1641 |
+
elif self.is_equal(mn, pq, level):
|
1642 |
+
return self.make_equal_pairs( # pylint: disable=arguments-out-of-order
|
1643 |
+
m,
|
1644 |
+
n,
|
1645 |
+
p,
|
1646 |
+
q,
|
1647 |
+
a,
|
1648 |
+
b,
|
1649 |
+
c,
|
1650 |
+
d,
|
1651 |
+
mn,
|
1652 |
+
pq,
|
1653 |
+
ab,
|
1654 |
+
cd,
|
1655 |
+
deps,
|
1656 |
+
)
|
1657 |
+
elif self.is_equal(ab, mn, level):
|
1658 |
+
return self.make_equal_pairs( # pylint: disable=arguments-out-of-order
|
1659 |
+
a,
|
1660 |
+
b,
|
1661 |
+
m,
|
1662 |
+
n,
|
1663 |
+
c,
|
1664 |
+
d,
|
1665 |
+
p,
|
1666 |
+
q,
|
1667 |
+
ab,
|
1668 |
+
mn,
|
1669 |
+
cd,
|
1670 |
+
pq,
|
1671 |
+
deps,
|
1672 |
+
)
|
1673 |
+
elif self.is_equal(cd, pq, level):
|
1674 |
+
return self.make_equal_pairs( # pylint: disable=arguments-out-of-order
|
1675 |
+
c,
|
1676 |
+
d,
|
1677 |
+
p,
|
1678 |
+
q,
|
1679 |
+
a,
|
1680 |
+
b,
|
1681 |
+
m,
|
1682 |
+
n,
|
1683 |
+
cd,
|
1684 |
+
pq,
|
1685 |
+
ab,
|
1686 |
+
mn,
|
1687 |
+
deps,
|
1688 |
+
)
|
1689 |
+
else:
|
1690 |
+
return None
|
1691 |
+
|
1692 |
+
def _add_eqangle(
|
1693 |
+
self,
|
1694 |
+
a: Point,
|
1695 |
+
b: Point,
|
1696 |
+
c: Point,
|
1697 |
+
d: Point,
|
1698 |
+
m: Point,
|
1699 |
+
n: Point,
|
1700 |
+
p: Point,
|
1701 |
+
q: Point,
|
1702 |
+
ab: Line,
|
1703 |
+
cd: Line,
|
1704 |
+
mn: Line,
|
1705 |
+
pq: Line,
|
1706 |
+
deps: EmptyDependency,
|
1707 |
+
) -> list[Dependency]:
|
1708 |
+
"""Add eqangle core."""
|
1709 |
+
if deps:
|
1710 |
+
deps = deps.copy()
|
1711 |
+
|
1712 |
+
args = [a, b, c, d, m, n, p, q]
|
1713 |
+
i = 0
|
1714 |
+
for x, y, xy in [(a, b, ab), (c, d, cd), (m, n, mn), (p, q, pq)]:
|
1715 |
+
i += 1
|
1716 |
+
x_, y_ = xy._val._obj.points
|
1717 |
+
if {x, y} == {x_, y_}:
|
1718 |
+
continue
|
1719 |
+
if deps:
|
1720 |
+
deps = deps.extend(self, 'eqangle', list(args), 'para', [x, y, x_, y_])
|
1721 |
+
|
1722 |
+
args[2 * i - 2] = x_
|
1723 |
+
args[2 * i - 1] = y_
|
1724 |
+
|
1725 |
+
add = []
|
1726 |
+
ab_cd, cd_ab, why1 = self._get_or_create_angle(ab, cd, deps=None)
|
1727 |
+
mn_pq, pq_mn, why2 = self._get_or_create_angle(mn, pq, deps=None)
|
1728 |
+
|
1729 |
+
why = why1 + why2
|
1730 |
+
if why:
|
1731 |
+
dep0 = deps.populate('eqangle', args)
|
1732 |
+
deps = EmptyDependency(level=deps.level, rule_name=None)
|
1733 |
+
deps.why = [dep0] + why
|
1734 |
+
|
1735 |
+
dab, dcd = ab_cd._d
|
1736 |
+
dmn, dpq = mn_pq._d
|
1737 |
+
|
1738 |
+
a, b = dab._obj.points
|
1739 |
+
c, d = dcd._obj.points
|
1740 |
+
m, n = dmn._obj.points
|
1741 |
+
p, q = dpq._obj.points
|
1742 |
+
|
1743 |
+
is_eq1 = self.is_equal(ab_cd, mn_pq)
|
1744 |
+
deps1 = None
|
1745 |
+
if deps:
|
1746 |
+
deps1 = deps.populate('eqangle', [a, b, c, d, m, n, p, q])
|
1747 |
+
deps1.algebra = [dab, dcd, dmn, dpq]
|
1748 |
+
if not is_eq1:
|
1749 |
+
add += [deps1]
|
1750 |
+
self.cache_dep('eqangle', [a, b, c, d, m, n, p, q], deps1)
|
1751 |
+
self.make_equal(ab_cd, mn_pq, deps=deps1)
|
1752 |
+
|
1753 |
+
is_eq2 = self.is_equal(cd_ab, pq_mn)
|
1754 |
+
deps2 = None
|
1755 |
+
if deps:
|
1756 |
+
deps2 = deps.populate('eqangle', [c, d, a, b, p, q, m, n])
|
1757 |
+
deps2.algebra = [dcd, dab, dpq, dmn]
|
1758 |
+
if not is_eq2:
|
1759 |
+
add += [deps2]
|
1760 |
+
self.cache_dep('eqangle', [c, d, a, b, p, q, m, n], deps2)
|
1761 |
+
self.make_equal(cd_ab, pq_mn, deps=deps2)
|
1762 |
+
|
1763 |
+
return add
|
1764 |
+
|
1765 |
+
def add_eqangle(
|
1766 |
+
self, points: list[Point], deps: EmptyDependency
|
1767 |
+
) -> list[Dependency]:
|
1768 |
+
"""Add eqangle made by 8 points in `points`."""
|
1769 |
+
if deps:
|
1770 |
+
deps = deps.copy()
|
1771 |
+
a, b, c, d, m, n, p, q = points
|
1772 |
+
ab, why1 = self.get_line_thru_pair_why(a, b)
|
1773 |
+
cd, why2 = self.get_line_thru_pair_why(c, d)
|
1774 |
+
mn, why3 = self.get_line_thru_pair_why(m, n)
|
1775 |
+
pq, why4 = self.get_line_thru_pair_why(p, q)
|
1776 |
+
|
1777 |
+
a, b = ab.points
|
1778 |
+
c, d = cd.points
|
1779 |
+
m, n = mn.points
|
1780 |
+
p, q = pq.points
|
1781 |
+
|
1782 |
+
if deps and why1 + why2 + why3 + why4:
|
1783 |
+
dep0 = deps.populate('eqangle', points)
|
1784 |
+
deps = EmptyDependency(level=deps.level, rule_name=None)
|
1785 |
+
deps.why = [dep0] + why1 + why2 + why3 + why4
|
1786 |
+
|
1787 |
+
add = self.maybe_make_equal_pairs(
|
1788 |
+
a, b, c, d, m, n, p, q, ab, cd, mn, pq, deps
|
1789 |
+
)
|
1790 |
+
|
1791 |
+
if add is not None:
|
1792 |
+
return add
|
1793 |
+
|
1794 |
+
self.connect_val(ab, deps=None)
|
1795 |
+
self.connect_val(cd, deps=None)
|
1796 |
+
self.connect_val(mn, deps=None)
|
1797 |
+
self.connect_val(pq, deps=None)
|
1798 |
+
|
1799 |
+
add = []
|
1800 |
+
if (
|
1801 |
+
ab.val != cd.val
|
1802 |
+
and mn.val != pq.val
|
1803 |
+
and (ab.val != mn.val or cd.val != pq.val)
|
1804 |
+
):
|
1805 |
+
add += self._add_eqangle(a, b, c, d, m, n, p, q, ab, cd, mn, pq, deps)
|
1806 |
+
|
1807 |
+
if (
|
1808 |
+
ab.val != mn.val
|
1809 |
+
and cd.val != pq.val
|
1810 |
+
and (ab.val != cd.val or mn.val != pq.val)
|
1811 |
+
):
|
1812 |
+
add += self._add_eqangle( # pylint: disable=arguments-out-of-order
|
1813 |
+
a,
|
1814 |
+
b,
|
1815 |
+
m,
|
1816 |
+
n,
|
1817 |
+
c,
|
1818 |
+
d,
|
1819 |
+
p,
|
1820 |
+
q,
|
1821 |
+
ab,
|
1822 |
+
mn,
|
1823 |
+
cd,
|
1824 |
+
pq,
|
1825 |
+
deps,
|
1826 |
+
)
|
1827 |
+
|
1828 |
+
return add
|
1829 |
+
|
1830 |
+
def add_aconst(
|
1831 |
+
self, points: list[Point], deps: EmptyDependency
|
1832 |
+
) -> list[Dependency]:
|
1833 |
+
"""Add that an angle is equal to some constant."""
|
1834 |
+
a, b, c, d, num, den = points
|
1835 |
+
nd, dn = self.get_or_create_const_ang(num, den)
|
1836 |
+
|
1837 |
+
if nd == self.halfpi:
|
1838 |
+
return self.add_perp([a, b, c, d], deps)
|
1839 |
+
|
1840 |
+
ab, why1 = self.get_line_thru_pair_why(a, b)
|
1841 |
+
cd, why2 = self.get_line_thru_pair_why(c, d)
|
1842 |
+
|
1843 |
+
(a, b), (c, d) = ab.points, cd.points
|
1844 |
+
if why1 + why2:
|
1845 |
+
args = points[:-2] + [nd]
|
1846 |
+
dep0 = deps.populate('aconst', args)
|
1847 |
+
deps = EmptyDependency(level=deps.level, rule_name=None)
|
1848 |
+
deps.why = [dep0] + why1 + why2
|
1849 |
+
|
1850 |
+
self.connect_val(ab, deps=None)
|
1851 |
+
self.connect_val(cd, deps=None)
|
1852 |
+
|
1853 |
+
if ab.val == cd.val:
|
1854 |
+
raise ValueError(f'{ab.name} - {cd.name} cannot be {nd.name}')
|
1855 |
+
|
1856 |
+
args = [a, b, c, d, nd]
|
1857 |
+
i = 0
|
1858 |
+
for x, y, xy in [(a, b, ab), (c, d, cd)]:
|
1859 |
+
i += 1
|
1860 |
+
x_, y_ = xy._val._obj.points
|
1861 |
+
if {x, y} == {x_, y_}:
|
1862 |
+
continue
|
1863 |
+
if deps:
|
1864 |
+
deps = deps.extend(self, 'aconst', list(args), 'para', [x, y, x_, y_])
|
1865 |
+
args[2 * i - 2] = x_
|
1866 |
+
args[2 * i - 1] = y_
|
1867 |
+
|
1868 |
+
ab_cd, cd_ab, why = self._get_or_create_angle(ab, cd, deps=None)
|
1869 |
+
if why:
|
1870 |
+
dep0 = deps.populate('aconst', [a, b, c, d, nd])
|
1871 |
+
deps = EmptyDependency(level=deps.level, rule_name=None)
|
1872 |
+
deps.why = [dep0] + why
|
1873 |
+
|
1874 |
+
dab, dcd = ab_cd._d
|
1875 |
+
a, b = dab._obj.points
|
1876 |
+
c, d = dcd._obj.points
|
1877 |
+
|
1878 |
+
ang = int(num) * 180 / int(den)
|
1879 |
+
add = []
|
1880 |
+
if not self.is_equal(ab_cd, nd):
|
1881 |
+
deps1 = deps.populate('aconst', [a, b, c, d, nd])
|
1882 |
+
deps1.algebra = dab, dcd, ang % 180
|
1883 |
+
self.make_equal(ab_cd, nd, deps=deps1)
|
1884 |
+
self.cache_dep('aconst', [a, b, c, d, nd], deps1)
|
1885 |
+
add += [deps1]
|
1886 |
+
|
1887 |
+
if not self.is_equal(cd_ab, dn):
|
1888 |
+
deps2 = deps.populate('aconst', [c, d, a, b, dn])
|
1889 |
+
deps2.algebra = dcd, dab, 180 - ang % 180
|
1890 |
+
self.make_equal(cd_ab, dn, deps=deps2)
|
1891 |
+
self.cache_dep('aconst', [c, d, a, b, dn], deps2)
|
1892 |
+
add += [deps2]
|
1893 |
+
return add
|
1894 |
+
|
1895 |
+
def add_s_angle(
|
1896 |
+
self, points: list[Point], deps: EmptyDependency
|
1897 |
+
) -> list[Dependency]:
|
1898 |
+
"""Add that an angle abx is equal to constant y."""
|
1899 |
+
a, b, x, y = points
|
1900 |
+
|
1901 |
+
n, d = ar.simplify(y % 180, 180)
|
1902 |
+
nd, dn = self.get_or_create_const_ang(n, d)
|
1903 |
+
|
1904 |
+
if nd == self.halfpi:
|
1905 |
+
return self.add_perp([a, b, b, x], deps)
|
1906 |
+
|
1907 |
+
ab, why1 = self.get_line_thru_pair_why(a, b)
|
1908 |
+
bx, why2 = self.get_line_thru_pair_why(b, x)
|
1909 |
+
|
1910 |
+
self.connect_val(ab, deps=None)
|
1911 |
+
self.connect_val(bx, deps=None)
|
1912 |
+
add = []
|
1913 |
+
|
1914 |
+
if ab.val == bx.val:
|
1915 |
+
return add
|
1916 |
+
|
1917 |
+
deps.why += why1 + why2
|
1918 |
+
|
1919 |
+
for p, q, pq in [(a, b, ab), (b, x, bx)]:
|
1920 |
+
p_, q_ = pq.val._obj.points
|
1921 |
+
if {p, q} == {p_, q_}:
|
1922 |
+
continue
|
1923 |
+
dep = Dependency('para', [p, q, p_, q_], None, deps.level)
|
1924 |
+
deps.why += [dep.why_me_or_cache(self, None)]
|
1925 |
+
|
1926 |
+
xba, abx, why = self._get_or_create_angle(bx, ab, deps=None)
|
1927 |
+
if why:
|
1928 |
+
dep0 = deps.populate('aconst', [b, x, a, b, nd])
|
1929 |
+
deps = EmptyDependency(level=deps.level, rule_name=None)
|
1930 |
+
deps.why = [dep0] + why
|
1931 |
+
|
1932 |
+
dab, dbx = abx._d
|
1933 |
+
a, b = dab._obj.points
|
1934 |
+
c, x = dbx._obj.points
|
1935 |
+
|
1936 |
+
if not self.is_equal(xba, nd):
|
1937 |
+
deps1 = deps.populate('aconst', [c, x, a, b, nd])
|
1938 |
+
deps1.algebra = dbx, dab, y % 180
|
1939 |
+
|
1940 |
+
self.make_equal(xba, nd, deps=deps1)
|
1941 |
+
self.cache_dep('aconst', [c, x, a, b, nd], deps1)
|
1942 |
+
add += [deps1]
|
1943 |
+
|
1944 |
+
if not self.is_equal(abx, dn):
|
1945 |
+
deps2 = deps.populate('aconst', [a, b, c, x, dn])
|
1946 |
+
deps2.algebra = dab, dbx, 180 - (y % 180)
|
1947 |
+
|
1948 |
+
self.make_equal(abx, dn, deps=deps2)
|
1949 |
+
self.cache_dep('s_angle', [a, b, c, x, dn], deps2)
|
1950 |
+
add += [deps2]
|
1951 |
+
return add
|
1952 |
+
|
1953 |
+
def check_aconst(self, points: list[Point], verbose: bool = False) -> bool:
|
1954 |
+
"""Check if the angle is equal to a certain constant."""
|
1955 |
+
a, b, c, d, nd = points
|
1956 |
+
_ = verbose
|
1957 |
+
if isinstance(nd, str):
|
1958 |
+
name = nd
|
1959 |
+
else:
|
1960 |
+
name = nd.name
|
1961 |
+
num, den = name.split('pi/')
|
1962 |
+
ang, _ = self.get_or_create_const_ang(int(num), int(den))
|
1963 |
+
|
1964 |
+
ab = self._get_line(a, b)
|
1965 |
+
cd = self._get_line(c, d)
|
1966 |
+
if not ab or not cd:
|
1967 |
+
return False
|
1968 |
+
|
1969 |
+
if not (ab.val and cd.val):
|
1970 |
+
return False
|
1971 |
+
|
1972 |
+
for ang1, _, _ in gm.all_angles(ab._val, cd._val):
|
1973 |
+
if self.is_equal(ang1, ang):
|
1974 |
+
return True
|
1975 |
+
return False
|
1976 |
+
|
1977 |
+
def check_acompute(self, points: list[Point]) -> bool:
|
1978 |
+
"""Check if an angle has a constant value."""
|
1979 |
+
a, b, c, d = points
|
1980 |
+
ab = self._get_line(a, b)
|
1981 |
+
cd = self._get_line(c, d)
|
1982 |
+
if not ab or not cd:
|
1983 |
+
return False
|
1984 |
+
|
1985 |
+
if not (ab.val and cd.val):
|
1986 |
+
return False
|
1987 |
+
|
1988 |
+
for ang0 in self.aconst.values():
|
1989 |
+
for ang in ang0.val.neighbors(Angle):
|
1990 |
+
d1, d2 = ang.directions
|
1991 |
+
if ab.val == d1 and cd.val == d2:
|
1992 |
+
return True
|
1993 |
+
return False
|
1994 |
+
|
1995 |
+
def check_eqangle(self, points: list[Point]) -> bool:
|
1996 |
+
"""Check if two angles are equal."""
|
1997 |
+
a, b, c, d, m, n, p, q = points
|
1998 |
+
|
1999 |
+
if {a, b} == {c, d} and {m, n} == {p, q}:
|
2000 |
+
return True
|
2001 |
+
if {a, b} == {m, n} and {c, d} == {p, q}:
|
2002 |
+
return True
|
2003 |
+
|
2004 |
+
if (a == b) or (c == d) or (m == n) or (p == q):
|
2005 |
+
return False
|
2006 |
+
ab = self._get_line(a, b)
|
2007 |
+
cd = self._get_line(c, d)
|
2008 |
+
mn = self._get_line(m, n)
|
2009 |
+
pq = self._get_line(p, q)
|
2010 |
+
|
2011 |
+
if {a, b} == {c, d} and mn and pq and self.is_equal(mn, pq):
|
2012 |
+
return True
|
2013 |
+
if {a, b} == {m, n} and cd and pq and self.is_equal(cd, pq):
|
2014 |
+
return True
|
2015 |
+
if {p, q} == {m, n} and ab and cd and self.is_equal(ab, cd):
|
2016 |
+
return True
|
2017 |
+
if {p, q} == {c, d} and ab and mn and self.is_equal(ab, mn):
|
2018 |
+
return True
|
2019 |
+
|
2020 |
+
if not ab or not cd or not mn or not pq:
|
2021 |
+
return False
|
2022 |
+
|
2023 |
+
if self.is_equal(ab, cd) and self.is_equal(mn, pq):
|
2024 |
+
return True
|
2025 |
+
if self.is_equal(ab, mn) and self.is_equal(cd, pq):
|
2026 |
+
return True
|
2027 |
+
|
2028 |
+
if not (ab.val and cd.val and mn.val and pq.val):
|
2029 |
+
return False
|
2030 |
+
|
2031 |
+
if (ab.val, cd.val) == (mn.val, pq.val) or (ab.val, mn.val) == (
|
2032 |
+
cd.val,
|
2033 |
+
pq.val,
|
2034 |
+
):
|
2035 |
+
return True
|
2036 |
+
|
2037 |
+
for ang1, _, _ in gm.all_angles(ab._val, cd._val):
|
2038 |
+
for ang2, _, _ in gm.all_angles(mn._val, pq._val):
|
2039 |
+
if self.is_equal(ang1, ang2):
|
2040 |
+
return True
|
2041 |
+
|
2042 |
+
if self.check_perp([a, b, m, n]) and self.check_perp([c, d, p, q]):
|
2043 |
+
return True
|
2044 |
+
if self.check_perp([a, b, p, q]) and self.check_perp([c, d, m, n]):
|
2045 |
+
return True
|
2046 |
+
|
2047 |
+
return False
|
2048 |
+
|
2049 |
+
def _get_ratio(self, l1: Length, l2: Length) -> tuple[Ratio, Ratio]:
|
2050 |
+
for r in self.type2nodes[Ratio]:
|
2051 |
+
if r.lengths == (l1, l2):
|
2052 |
+
return r, r.opposite
|
2053 |
+
return None, None
|
2054 |
+
|
2055 |
+
def _get_or_create_ratio(
|
2056 |
+
self, s1: Segment, s2: Segment, deps: Dependency
|
2057 |
+
) -> tuple[Ratio, Ratio, list[Dependency]]:
|
2058 |
+
return self._get_or_create_ratio_l(s1._val, s2._val, deps)
|
2059 |
+
|
2060 |
+
def _get_or_create_ratio_l(
|
2061 |
+
self, l1: Length, l2: Length, deps: Dependency
|
2062 |
+
) -> tuple[Ratio, Ratio, list[Dependency]]:
|
2063 |
+
"""Get or create a new Ratio from two Lenghts l1 and l2."""
|
2064 |
+
for r in self.type2nodes[Ratio]:
|
2065 |
+
if r.lengths == (l1.rep(), l2.rep()):
|
2066 |
+
l1_, l2_ = r._l
|
2067 |
+
why1 = l1.why_equal([l1_], None) + l1_.why_rep()
|
2068 |
+
why2 = l2.why_equal([l2_], None) + l2_.why_rep()
|
2069 |
+
return r, r.opposite, why1 + why2
|
2070 |
+
|
2071 |
+
l1, why1 = l1.rep_and_why()
|
2072 |
+
l2, why2 = l2.rep_and_why()
|
2073 |
+
r12 = self.new_node(Ratio, f'{l1.name}/{l2.name}')
|
2074 |
+
r21 = self.new_node(Ratio, f'{l2.name}/{l1.name}')
|
2075 |
+
self.connect(l1, r12, deps)
|
2076 |
+
self.connect(l2, r21, deps)
|
2077 |
+
self.connect(r12, r21, deps)
|
2078 |
+
r12.set_lengths(l1, l2)
|
2079 |
+
r21.set_lengths(l2, l1)
|
2080 |
+
r12.opposite = r21
|
2081 |
+
r21.opposite = r12
|
2082 |
+
return r12, r21, why1 + why2
|
2083 |
+
|
2084 |
+
def add_cong2(
|
2085 |
+
self, points: list[Point], deps: EmptyDependency
|
2086 |
+
) -> list[Dependency]:
|
2087 |
+
m, n, a, b = points
|
2088 |
+
add = []
|
2089 |
+
add += self.add_cong([m, a, n, a], deps)
|
2090 |
+
add += self.add_cong([m, b, n, b], deps)
|
2091 |
+
return add
|
2092 |
+
|
2093 |
+
def add_eqratio3(
|
2094 |
+
self, points: list[Point], deps: EmptyDependency
|
2095 |
+
) -> list[Dependency]:
|
2096 |
+
"""Add three eqratios through a list of 6 points (due to parallel lines)."""
|
2097 |
+
a, b, c, d, m, n = points
|
2098 |
+
# a -- b
|
2099 |
+
# m -- n
|
2100 |
+
# c -- d
|
2101 |
+
add = []
|
2102 |
+
add += self.add_eqratio([m, a, m, c, n, b, n, d], deps)
|
2103 |
+
add += self.add_eqratio([a, m, a, c, b, n, b, d], deps)
|
2104 |
+
add += self.add_eqratio([c, m, c, a, d, n, d, b], deps)
|
2105 |
+
if m == n:
|
2106 |
+
add += self.add_eqratio([m, a, m, c, a, b, c, d], deps)
|
2107 |
+
return add
|
2108 |
+
|
2109 |
+
def add_eqratio4(
|
2110 |
+
self, points: list[Point], deps: EmptyDependency
|
2111 |
+
) -> list[Dependency]:
|
2112 |
+
o, a, b, c, d = points
|
2113 |
+
# o
|
2114 |
+
# a b
|
2115 |
+
# c d
|
2116 |
+
add = self.add_eqratio3([a, b, c, d, o, o], deps)
|
2117 |
+
add += self.add_eqratio([o, a, o, c, a, b, c, d], deps)
|
2118 |
+
return add
|
2119 |
+
|
2120 |
+
def _add_eqratio(
|
2121 |
+
self,
|
2122 |
+
a: Point,
|
2123 |
+
b: Point,
|
2124 |
+
c: Point,
|
2125 |
+
d: Point,
|
2126 |
+
m: Point,
|
2127 |
+
n: Point,
|
2128 |
+
p: Point,
|
2129 |
+
q: Point,
|
2130 |
+
ab: Segment,
|
2131 |
+
cd: Segment,
|
2132 |
+
mn: Segment,
|
2133 |
+
pq: Segment,
|
2134 |
+
deps: EmptyDependency,
|
2135 |
+
) -> list[Dependency]:
|
2136 |
+
"""Add a new eqratio from 8 points (core)."""
|
2137 |
+
if deps:
|
2138 |
+
deps = deps.copy()
|
2139 |
+
|
2140 |
+
args = [a, b, c, d, m, n, p, q]
|
2141 |
+
i = 0
|
2142 |
+
for x, y, xy in [(a, b, ab), (c, d, cd), (m, n, mn), (p, q, pq)]:
|
2143 |
+
if {x, y} == set(xy.points):
|
2144 |
+
continue
|
2145 |
+
x_, y_ = list(xy.points)
|
2146 |
+
if deps:
|
2147 |
+
deps = deps.extend(self, 'eqratio', list(args), 'cong', [x, y, x_, y_])
|
2148 |
+
args[2 * i - 2] = x_
|
2149 |
+
args[2 * i - 1] = y_
|
2150 |
+
|
2151 |
+
add = []
|
2152 |
+
ab_cd, cd_ab, why1 = self._get_or_create_ratio(ab, cd, deps=None)
|
2153 |
+
mn_pq, pq_mn, why2 = self._get_or_create_ratio(mn, pq, deps=None)
|
2154 |
+
|
2155 |
+
why = why1 + why2
|
2156 |
+
if why:
|
2157 |
+
dep0 = deps.populate('eqratio', args)
|
2158 |
+
deps = EmptyDependency(level=deps.level, rule_name=None)
|
2159 |
+
deps.why = [dep0] + why
|
2160 |
+
|
2161 |
+
lab, lcd = ab_cd._l
|
2162 |
+
lmn, lpq = mn_pq._l
|
2163 |
+
|
2164 |
+
a, b = lab._obj.points
|
2165 |
+
c, d = lcd._obj.points
|
2166 |
+
m, n = lmn._obj.points
|
2167 |
+
p, q = lpq._obj.points
|
2168 |
+
|
2169 |
+
is_eq1 = self.is_equal(ab_cd, mn_pq)
|
2170 |
+
deps1 = None
|
2171 |
+
if deps:
|
2172 |
+
deps1 = deps.populate('eqratio', [a, b, c, d, m, n, p, q])
|
2173 |
+
deps1.algebra = [ab._val, cd._val, mn._val, pq._val]
|
2174 |
+
if not is_eq1:
|
2175 |
+
add += [deps1]
|
2176 |
+
self.cache_dep('eqratio', [a, b, c, d, m, n, p, q], deps1)
|
2177 |
+
self.make_equal(ab_cd, mn_pq, deps=deps1)
|
2178 |
+
|
2179 |
+
is_eq2 = self.is_equal(cd_ab, pq_mn)
|
2180 |
+
deps2 = None
|
2181 |
+
if deps:
|
2182 |
+
deps2 = deps.populate('eqratio', [c, d, a, b, p, q, m, n])
|
2183 |
+
deps2.algebra = [cd._val, ab._val, pq._val, mn._val]
|
2184 |
+
if not is_eq2:
|
2185 |
+
add += [deps2]
|
2186 |
+
self.cache_dep('eqratio', [c, d, a, b, p, q, m, n], deps2)
|
2187 |
+
self.make_equal(cd_ab, pq_mn, deps=deps2)
|
2188 |
+
return add
|
2189 |
+
|
2190 |
+
def add_eqratio(
|
2191 |
+
self, points: list[Point], deps: EmptyDependency
|
2192 |
+
) -> list[Dependency]:
|
2193 |
+
"""Add a new eqratio from 8 points."""
|
2194 |
+
if deps:
|
2195 |
+
deps = deps.copy()
|
2196 |
+
a, b, c, d, m, n, p, q = points
|
2197 |
+
ab = self._get_or_create_segment(a, b, deps=None)
|
2198 |
+
cd = self._get_or_create_segment(c, d, deps=None)
|
2199 |
+
mn = self._get_or_create_segment(m, n, deps=None)
|
2200 |
+
pq = self._get_or_create_segment(p, q, deps=None)
|
2201 |
+
|
2202 |
+
add = self.maybe_make_equal_pairs(
|
2203 |
+
a, b, c, d, m, n, p, q, ab, cd, mn, pq, deps
|
2204 |
+
)
|
2205 |
+
|
2206 |
+
if add is not None:
|
2207 |
+
return add
|
2208 |
+
|
2209 |
+
self.connect_val(ab, deps=None)
|
2210 |
+
self.connect_val(cd, deps=None)
|
2211 |
+
self.connect_val(mn, deps=None)
|
2212 |
+
self.connect_val(pq, deps=None)
|
2213 |
+
|
2214 |
+
add = []
|
2215 |
+
if (
|
2216 |
+
ab.val != cd.val
|
2217 |
+
and mn.val != pq.val
|
2218 |
+
and (ab.val != mn.val or cd.val != pq.val)
|
2219 |
+
):
|
2220 |
+
add += self._add_eqratio(a, b, c, d, m, n, p, q, ab, cd, mn, pq, deps)
|
2221 |
+
|
2222 |
+
if (
|
2223 |
+
ab.val != mn.val
|
2224 |
+
and cd.val != pq.val
|
2225 |
+
and (ab.val != cd.val or mn.val != pq.val)
|
2226 |
+
):
|
2227 |
+
add += self._add_eqratio( # pylint: disable=arguments-out-of-order
|
2228 |
+
a,
|
2229 |
+
b,
|
2230 |
+
m,
|
2231 |
+
n,
|
2232 |
+
c,
|
2233 |
+
d,
|
2234 |
+
p,
|
2235 |
+
q,
|
2236 |
+
ab,
|
2237 |
+
mn,
|
2238 |
+
cd,
|
2239 |
+
pq,
|
2240 |
+
deps,
|
2241 |
+
)
|
2242 |
+
return add
|
2243 |
+
|
2244 |
+
def check_rconst(self, points: list[Point], verbose: bool = False) -> bool:
|
2245 |
+
"""Check whether a ratio is equal to some given constant."""
|
2246 |
+
_ = verbose
|
2247 |
+
a, b, c, d, nd = points
|
2248 |
+
if isinstance(nd, str):
|
2249 |
+
name = nd
|
2250 |
+
else:
|
2251 |
+
name = nd.name
|
2252 |
+
num, den = name.split('/')
|
2253 |
+
rat, _ = self.get_or_create_const_rat(int(num), int(den))
|
2254 |
+
|
2255 |
+
ab = self._get_segment(a, b)
|
2256 |
+
cd = self._get_segment(c, d)
|
2257 |
+
|
2258 |
+
if not ab or not cd:
|
2259 |
+
return False
|
2260 |
+
|
2261 |
+
if not (ab.val and cd.val):
|
2262 |
+
return False
|
2263 |
+
|
2264 |
+
for rat1, _, _ in gm.all_ratios(ab._val, cd._val):
|
2265 |
+
if self.is_equal(rat1, rat):
|
2266 |
+
return True
|
2267 |
+
return False
|
2268 |
+
|
2269 |
+
def check_rcompute(self, points: list[Point]) -> bool:
|
2270 |
+
"""Check whether a ratio is equal to some constant."""
|
2271 |
+
a, b, c, d = points
|
2272 |
+
ab = self._get_segment(a, b)
|
2273 |
+
cd = self._get_segment(c, d)
|
2274 |
+
|
2275 |
+
if not ab or not cd:
|
2276 |
+
return False
|
2277 |
+
|
2278 |
+
if not (ab.val and cd.val):
|
2279 |
+
return False
|
2280 |
+
|
2281 |
+
for rat0 in self.rconst.values():
|
2282 |
+
for rat in rat0.val.neighbors(Ratio):
|
2283 |
+
l1, l2 = rat.lengths
|
2284 |
+
if ab.val == l1 and cd.val == l2:
|
2285 |
+
return True
|
2286 |
+
return False
|
2287 |
+
|
2288 |
+
def check_eqratio(self, points: list[Point]) -> bool:
|
2289 |
+
"""Check if 8 points make an eqratio predicate."""
|
2290 |
+
a, b, c, d, m, n, p, q = points
|
2291 |
+
|
2292 |
+
if {a, b} == {c, d} and {m, n} == {p, q}:
|
2293 |
+
return True
|
2294 |
+
if {a, b} == {m, n} and {c, d} == {p, q}:
|
2295 |
+
return True
|
2296 |
+
|
2297 |
+
ab = self._get_segment(a, b)
|
2298 |
+
cd = self._get_segment(c, d)
|
2299 |
+
mn = self._get_segment(m, n)
|
2300 |
+
pq = self._get_segment(p, q)
|
2301 |
+
|
2302 |
+
if {a, b} == {c, d} and mn and pq and self.is_equal(mn, pq):
|
2303 |
+
return True
|
2304 |
+
if {a, b} == {m, n} and cd and pq and self.is_equal(cd, pq):
|
2305 |
+
return True
|
2306 |
+
if {p, q} == {m, n} and ab and cd and self.is_equal(ab, cd):
|
2307 |
+
return True
|
2308 |
+
if {p, q} == {c, d} and ab and mn and self.is_equal(ab, mn):
|
2309 |
+
return True
|
2310 |
+
|
2311 |
+
if not ab or not cd or not mn or not pq:
|
2312 |
+
return False
|
2313 |
+
|
2314 |
+
if self.is_equal(ab, cd) and self.is_equal(mn, pq):
|
2315 |
+
return True
|
2316 |
+
if self.is_equal(ab, mn) and self.is_equal(cd, pq):
|
2317 |
+
return True
|
2318 |
+
|
2319 |
+
if not (ab.val and cd.val and mn.val and pq.val):
|
2320 |
+
return False
|
2321 |
+
|
2322 |
+
if (ab.val, cd.val) == (mn.val, pq.val) or (ab.val, mn.val) == (
|
2323 |
+
cd.val,
|
2324 |
+
pq.val,
|
2325 |
+
):
|
2326 |
+
return True
|
2327 |
+
|
2328 |
+
for rat1, _, _ in gm.all_ratios(ab._val, cd._val):
|
2329 |
+
for rat2, _, _ in gm.all_ratios(mn._val, pq._val):
|
2330 |
+
if self.is_equal(rat1, rat2):
|
2331 |
+
return True
|
2332 |
+
return False
|
2333 |
+
|
2334 |
+
def add_simtri_check(
|
2335 |
+
self, points: list[Point], deps: EmptyDependency
|
2336 |
+
) -> list[Dependency]:
|
2337 |
+
if nm.same_clock(*[p.num for p in points]):
|
2338 |
+
return self.add_simtri(points, deps)
|
2339 |
+
return self.add_simtri2(points, deps)
|
2340 |
+
|
2341 |
+
def add_contri_check(
|
2342 |
+
self, points: list[Point], deps: EmptyDependency
|
2343 |
+
) -> list[Dependency]:
|
2344 |
+
if nm.same_clock(*[p.num for p in points]):
|
2345 |
+
return self.add_contri(points, deps)
|
2346 |
+
return self.add_contri2(points, deps)
|
2347 |
+
|
2348 |
+
def enum_sides(
|
2349 |
+
self, points: list[Point]
|
2350 |
+
) -> Generator[list[Point], None, None]:
|
2351 |
+
a, b, c, x, y, z = points
|
2352 |
+
yield [a, b, x, y]
|
2353 |
+
yield [b, c, y, z]
|
2354 |
+
yield [c, a, z, x]
|
2355 |
+
|
2356 |
+
def enum_triangle(
|
2357 |
+
self, points: list[Point]
|
2358 |
+
) -> Generator[list[Point], None, None]:
|
2359 |
+
a, b, c, x, y, z = points
|
2360 |
+
yield [a, b, a, c, x, y, x, z]
|
2361 |
+
yield [b, a, b, c, y, x, y, z]
|
2362 |
+
yield [c, a, c, b, z, x, z, y]
|
2363 |
+
|
2364 |
+
def enum_triangle2(
|
2365 |
+
self, points: list[Point]
|
2366 |
+
) -> Generator[list[Point], None, None]:
|
2367 |
+
a, b, c, x, y, z = points
|
2368 |
+
yield [a, b, a, c, x, z, x, y]
|
2369 |
+
yield [b, a, b, c, y, z, y, x]
|
2370 |
+
yield [c, a, c, b, z, y, z, x]
|
2371 |
+
|
2372 |
+
def add_simtri(
|
2373 |
+
self, points: list[Point], deps: EmptyDependency
|
2374 |
+
) -> list[Dependency]:
|
2375 |
+
"""Add two similar triangles."""
|
2376 |
+
add = []
|
2377 |
+
hashs = [d.hashed() for d in deps.why]
|
2378 |
+
|
2379 |
+
for args in self.enum_triangle(points):
|
2380 |
+
if problem.hashed('eqangle6', args) in hashs:
|
2381 |
+
continue
|
2382 |
+
add += self.add_eqangle(args, deps=deps)
|
2383 |
+
|
2384 |
+
for args in self.enum_triangle(points):
|
2385 |
+
if problem.hashed('eqratio6', args) in hashs:
|
2386 |
+
continue
|
2387 |
+
add += self.add_eqratio(args, deps=deps)
|
2388 |
+
|
2389 |
+
return add
|
2390 |
+
|
2391 |
+
def check_simtri(self, points: list[Point]) -> bool:
|
2392 |
+
a, b, c, x, y, z = points
|
2393 |
+
return self.check_eqangle([a, b, a, c, x, y, x, z]) and self.check_eqangle(
|
2394 |
+
[b, a, b, c, y, x, y, z]
|
2395 |
+
)
|
2396 |
+
|
2397 |
+
def add_simtri2(
|
2398 |
+
self, points: list[Point], deps: EmptyDependency
|
2399 |
+
) -> list[Dependency]:
|
2400 |
+
"""Add two similar reflected triangles."""
|
2401 |
+
add = []
|
2402 |
+
hashs = [d.hashed() for d in deps.why]
|
2403 |
+
for args in self.enum_triangle2(points):
|
2404 |
+
if problem.hashed('eqangle6', args) in hashs:
|
2405 |
+
continue
|
2406 |
+
add += self.add_eqangle(args, deps=deps)
|
2407 |
+
|
2408 |
+
for args in self.enum_triangle(points):
|
2409 |
+
if problem.hashed('eqratio6', args) in hashs:
|
2410 |
+
continue
|
2411 |
+
add += self.add_eqratio(args, deps=deps)
|
2412 |
+
|
2413 |
+
return add
|
2414 |
+
|
2415 |
+
def add_contri(
|
2416 |
+
self, points: list[Point], deps: EmptyDependency
|
2417 |
+
) -> list[Dependency]:
|
2418 |
+
"""Add two congruent triangles."""
|
2419 |
+
add = []
|
2420 |
+
hashs = [d.hashed() for d in deps.why]
|
2421 |
+
for args in self.enum_triangle(points):
|
2422 |
+
if problem.hashed('eqangle6', args) in hashs:
|
2423 |
+
continue
|
2424 |
+
add += self.add_eqangle(args, deps=deps)
|
2425 |
+
|
2426 |
+
for args in self.enum_sides(points):
|
2427 |
+
if problem.hashed('cong', args) in hashs:
|
2428 |
+
continue
|
2429 |
+
add += self.add_cong(args, deps=deps)
|
2430 |
+
return add
|
2431 |
+
|
2432 |
+
def check_contri(self, points: list[Point]) -> bool:
|
2433 |
+
a, b, c, x, y, z = points
|
2434 |
+
return (
|
2435 |
+
self.check_cong([a, b, x, y])
|
2436 |
+
and self.check_cong([b, c, y, z])
|
2437 |
+
and self.check_cong([c, a, z, x])
|
2438 |
+
)
|
2439 |
+
|
2440 |
+
def add_contri2(
|
2441 |
+
self, points: list[Point], deps: EmptyDependency
|
2442 |
+
) -> list[Dependency]:
|
2443 |
+
"""Add two congruent reflected triangles."""
|
2444 |
+
add = []
|
2445 |
+
hashs = [d.hashed() for d in deps.why]
|
2446 |
+
for args in self.enum_triangle2(points):
|
2447 |
+
if problem.hashed('eqangle6', args) in hashs:
|
2448 |
+
continue
|
2449 |
+
add += self.add_eqangle(args, deps=deps)
|
2450 |
+
|
2451 |
+
for args in self.enum_sides(points):
|
2452 |
+
if problem.hashed('cong', args) in hashs:
|
2453 |
+
continue
|
2454 |
+
add += self.add_cong(args, deps=deps)
|
2455 |
+
|
2456 |
+
return add
|
2457 |
+
|
2458 |
+
def in_cache(self, name: str, args: list[Point]) -> bool:
|
2459 |
+
return problem.hashed(name, args) in self.cache
|
2460 |
+
|
2461 |
+
def cache_dep(
|
2462 |
+
self, name: str, args: list[Point], premises: list[Dependency]
|
2463 |
+
) -> None:
|
2464 |
+
hashed = problem.hashed(name, args)
|
2465 |
+
if hashed in self.cache:
|
2466 |
+
return
|
2467 |
+
self.cache[hashed] = premises
|
2468 |
+
|
2469 |
+
def all_same_line(
|
2470 |
+
self, a: Point, b: Point
|
2471 |
+
) -> Generator[tuple[Point, Point], None, None]:
|
2472 |
+
ab = self._get_line(a, b)
|
2473 |
+
if ab is None:
|
2474 |
+
return
|
2475 |
+
for p1, p2 in utils.comb2(ab.neighbors(Point)):
|
2476 |
+
if {p1, p2} != {a, b}:
|
2477 |
+
yield p1, p2
|
2478 |
+
|
2479 |
+
def all_same_angle(
|
2480 |
+
self, a: Point, b: Point, c: Point, d: Point
|
2481 |
+
) -> Generator[tuple[Point, Point, Point, Point], None, None]:
|
2482 |
+
for x, y in self.all_same_line(a, b):
|
2483 |
+
for m, n in self.all_same_line(c, d):
|
2484 |
+
yield x, y, m, n
|
2485 |
+
|
2486 |
+
def additionally_draw(self, name: str, args: list[Point]) -> None:
|
2487 |
+
"""Draw some extra line/circles for illustration purpose."""
|
2488 |
+
|
2489 |
+
if name in ['circle']:
|
2490 |
+
center, point = args[:2]
|
2491 |
+
circle = self.new_node(Circle, f'({center.name},{point.name})')
|
2492 |
+
circle.num = nm.Circle(center.num, p1=point.num)
|
2493 |
+
circle.points = center, point
|
2494 |
+
|
2495 |
+
if name in ['on_circle', 'tangent']:
|
2496 |
+
center, point = args[-2:]
|
2497 |
+
circle = self.new_node(Circle, f'({center.name},{point.name})')
|
2498 |
+
circle.num = nm.Circle(center.num, p1=point.num)
|
2499 |
+
circle.points = center, point
|
2500 |
+
|
2501 |
+
if name in ['incenter', 'excenter', 'incenter2', 'excenter2']:
|
2502 |
+
d, a, b, c = [x for x in args[-4:]]
|
2503 |
+
a, b, c = sorted([a, b, c], key=lambda x: x.name.lower())
|
2504 |
+
circle = self.new_node(Circle, f'({d.name},h.{a.name}{b.name})')
|
2505 |
+
p = d.num.foot(nm.Line(a.num, b.num))
|
2506 |
+
circle.num = nm.Circle(d.num, p1=p)
|
2507 |
+
circle.points = d, a, b, c
|
2508 |
+
|
2509 |
+
if name in ['cc_tangent']:
|
2510 |
+
o, a, w, b = args[-4:]
|
2511 |
+
c1 = self.new_node(Circle, f'({o.name},{a.name})')
|
2512 |
+
c1.num = nm.Circle(o.num, p1=a.num)
|
2513 |
+
c1.points = o, a
|
2514 |
+
|
2515 |
+
c2 = self.new_node(Circle, f'({w.name},{b.name})')
|
2516 |
+
c2.num = nm.Circle(w.num, p1=b.num)
|
2517 |
+
c2.points = w, b
|
2518 |
+
|
2519 |
+
if name in ['ninepoints']:
|
2520 |
+
a, b, c = args[-3:]
|
2521 |
+
a, b, c = sorted([a, b, c], key=lambda x: x.name.lower())
|
2522 |
+
circle = self.new_node(Circle, f'(,m.{a.name}{b.name}{c.name})')
|
2523 |
+
p1 = (b.num + c.num) * 0.5
|
2524 |
+
p2 = (c.num + a.num) * 0.5
|
2525 |
+
p3 = (a.num + b.num) * 0.5
|
2526 |
+
circle.num = nm.Circle(p1=p1, p2=p2, p3=p3)
|
2527 |
+
circle.points = (None, None, a, b, c)
|
2528 |
+
|
2529 |
+
if name in ['2l1c']:
|
2530 |
+
a, b, c, o = args[:4]
|
2531 |
+
a, b, c = sorted([a, b, c], key=lambda x: x.name.lower())
|
2532 |
+
circle = self.new_node(Circle, f'({o.name},{a.name}{b.name}{c.name})')
|
2533 |
+
circle.num = nm.Circle(p1=a.num, p2=b.num, p3=c.num)
|
2534 |
+
circle.points = (a, b, c)
|
2535 |
+
|
2536 |
+
def add_clause(
|
2537 |
+
self,
|
2538 |
+
clause: problem.Clause,
|
2539 |
+
plevel: int,
|
2540 |
+
definitions: dict[str, problem.Definition],
|
2541 |
+
verbose: int = False,
|
2542 |
+
) -> tuple[list[Dependency], int]:
|
2543 |
+
"""Add a new clause of construction, e.g. a new excenter."""
|
2544 |
+
existing_points = self.all_points()
|
2545 |
+
new_points = [Point(name) for name in clause.points]
|
2546 |
+
|
2547 |
+
new_points_dep_points = set()
|
2548 |
+
new_points_dep = []
|
2549 |
+
|
2550 |
+
# Step 1: check for all deps.
|
2551 |
+
for c in clause.constructions:
|
2552 |
+
cdef = definitions[c.name]
|
2553 |
+
|
2554 |
+
if len(cdef.construction.args) != len(c.args):
|
2555 |
+
if len(cdef.construction.args) - len(c.args) == len(clause.points):
|
2556 |
+
c.args = clause.points + c.args
|
2557 |
+
else:
|
2558 |
+
correct_form = ' '.join(cdef.points + ['=', c.name] + cdef.args)
|
2559 |
+
raise ValueError('Argument mismatch. ' + correct_form)
|
2560 |
+
|
2561 |
+
mapping = dict(zip(cdef.construction.args, c.args))
|
2562 |
+
c_name = 'midp' if c.name == 'midpoint' else c.name
|
2563 |
+
deps = EmptyDependency(level=0, rule_name=problem.CONSTRUCTION_RULE)
|
2564 |
+
deps.construction = Dependency(c_name, c.args, rule_name=None, level=0)
|
2565 |
+
|
2566 |
+
for d in cdef.deps.constructions:
|
2567 |
+
args = self.names2points([mapping[a] for a in d.args])
|
2568 |
+
new_points_dep_points.update(args)
|
2569 |
+
if not self.check(d.name, args):
|
2570 |
+
raise DepCheckFailError(
|
2571 |
+
d.name + ' ' + ' '.join([x.name for x in args])
|
2572 |
+
)
|
2573 |
+
deps.why += [
|
2574 |
+
Dependency(
|
2575 |
+
d.name, args, rule_name=problem.CONSTRUCTION_RULE, level=0
|
2576 |
+
)
|
2577 |
+
]
|
2578 |
+
|
2579 |
+
new_points_dep += [deps]
|
2580 |
+
|
2581 |
+
# Step 2: draw.
|
2582 |
+
def range_fn() -> (
|
2583 |
+
list[Union[nm.Point, nm.Line, nm.Circle, nm.HalfLine, nm.HoleCircle]]
|
2584 |
+
):
|
2585 |
+
to_be_intersected = []
|
2586 |
+
for c in clause.constructions:
|
2587 |
+
cdef = definitions[c.name]
|
2588 |
+
mapping = dict(zip(cdef.construction.args, c.args))
|
2589 |
+
for n in cdef.numerics:
|
2590 |
+
args = [mapping[a] for a in n.args]
|
2591 |
+
args = list(map(lambda x: self.get(x, lambda: int(x)), args))
|
2592 |
+
to_be_intersected += nm.sketch(n.name, args)
|
2593 |
+
|
2594 |
+
return to_be_intersected
|
2595 |
+
|
2596 |
+
is_total_free = (
|
2597 |
+
len(clause.constructions) == 1 and clause.constructions[0].name in FREE
|
2598 |
+
)
|
2599 |
+
is_semi_free = (
|
2600 |
+
len(clause.constructions) == 1
|
2601 |
+
and clause.constructions[0].name in INTERSECT
|
2602 |
+
)
|
2603 |
+
|
2604 |
+
existing_points = [p.num for p in existing_points]
|
2605 |
+
|
2606 |
+
def draw_fn() -> list[nm.Point]:
|
2607 |
+
to_be_intersected = range_fn()
|
2608 |
+
return nm.reduce(to_be_intersected, existing_points)
|
2609 |
+
|
2610 |
+
rely_on = set()
|
2611 |
+
for c in clause.constructions:
|
2612 |
+
cdef = definitions[c.name]
|
2613 |
+
mapping = dict(zip(cdef.construction.args, c.args))
|
2614 |
+
for n in cdef.numerics:
|
2615 |
+
args = [mapping[a] for a in n.args]
|
2616 |
+
args = list(map(lambda x: self.get(x, lambda: int(x)), args))
|
2617 |
+
rely_on.update([a for a in args if isinstance(a, Point)])
|
2618 |
+
|
2619 |
+
for p in rely_on:
|
2620 |
+
p.change.update(new_points)
|
2621 |
+
|
2622 |
+
nums = draw_fn()
|
2623 |
+
for p, num, num0 in zip(new_points, nums, clause.nums):
|
2624 |
+
p.co_change = new_points
|
2625 |
+
if isinstance(num0, nm.Point):
|
2626 |
+
num = num0
|
2627 |
+
elif isinstance(num0, (tuple, list)):
|
2628 |
+
x, y = num0
|
2629 |
+
num = nm.Point(x, y)
|
2630 |
+
|
2631 |
+
p.num = num
|
2632 |
+
|
2633 |
+
# check two things.
|
2634 |
+
if nm.check_too_close(nums, existing_points):
|
2635 |
+
raise PointTooCloseError()
|
2636 |
+
if nm.check_too_far(nums, existing_points):
|
2637 |
+
raise PointTooFarError()
|
2638 |
+
|
2639 |
+
# Commit: now that all conditions are passed.
|
2640 |
+
# add these points to current graph.
|
2641 |
+
for p in new_points:
|
2642 |
+
self._name2point[p.name] = p
|
2643 |
+
self._name2node[p.name] = p
|
2644 |
+
self.type2nodes[Point].append(p)
|
2645 |
+
|
2646 |
+
for p in new_points:
|
2647 |
+
p.why = sum([d.why for d in new_points_dep], []) # to generate txt logs.
|
2648 |
+
p.group = new_points
|
2649 |
+
p.dep_points = new_points_dep_points
|
2650 |
+
p.dep_points.update(new_points)
|
2651 |
+
p.plevel = plevel
|
2652 |
+
|
2653 |
+
# movement dependency:
|
2654 |
+
rely_dict_0 = defaultdict(lambda: [])
|
2655 |
+
|
2656 |
+
for c in clause.constructions:
|
2657 |
+
cdef = definitions[c.name]
|
2658 |
+
mapping = dict(zip(cdef.construction.args, c.args))
|
2659 |
+
for p, ps in cdef.rely.items():
|
2660 |
+
p = mapping[p]
|
2661 |
+
ps = [mapping[x] for x in ps]
|
2662 |
+
rely_dict_0[p].append(ps)
|
2663 |
+
|
2664 |
+
rely_dict = {}
|
2665 |
+
for p, pss in rely_dict_0.items():
|
2666 |
+
ps = sum(pss, [])
|
2667 |
+
if len(pss) > 1:
|
2668 |
+
ps = [x for x in ps if x != p]
|
2669 |
+
|
2670 |
+
p = self._name2point[p]
|
2671 |
+
ps = self.names2nodes(ps)
|
2672 |
+
rely_dict[p] = ps
|
2673 |
+
|
2674 |
+
for p in new_points:
|
2675 |
+
p.rely_on = set(rely_dict.get(p, []))
|
2676 |
+
for x in p.rely_on:
|
2677 |
+
if not hasattr(x, 'base_rely_on'):
|
2678 |
+
x.base_rely_on = set()
|
2679 |
+
p.base_rely_on = set.union(*[x.base_rely_on for x in p.rely_on] + [set()])
|
2680 |
+
if is_total_free or is_semi_free:
|
2681 |
+
p.rely_on.add(p)
|
2682 |
+
p.base_rely_on.add(p)
|
2683 |
+
|
2684 |
+
plevel_done = set()
|
2685 |
+
added = []
|
2686 |
+
basics = []
|
2687 |
+
# Step 3: build the basics.
|
2688 |
+
for c, deps in zip(clause.constructions, new_points_dep):
|
2689 |
+
cdef = definitions[c.name]
|
2690 |
+
mapping = dict(zip(cdef.construction.args, c.args))
|
2691 |
+
|
2692 |
+
# not necessary for proofing, but for visualization.
|
2693 |
+
c_args = list(map(lambda x: self.get(x, lambda: int(x)), c.args))
|
2694 |
+
self.additionally_draw(c.name, c_args)
|
2695 |
+
|
2696 |
+
for points, bs in cdef.basics:
|
2697 |
+
if points:
|
2698 |
+
points = self.names2nodes([mapping[p] for p in points])
|
2699 |
+
points = [p for p in points if p not in plevel_done]
|
2700 |
+
for p in points:
|
2701 |
+
p.plevel = plevel
|
2702 |
+
plevel_done.update(points)
|
2703 |
+
plevel += 1
|
2704 |
+
else:
|
2705 |
+
continue
|
2706 |
+
|
2707 |
+
for b in bs:
|
2708 |
+
if b.name != 'rconst':
|
2709 |
+
args = [mapping[a] for a in b.args]
|
2710 |
+
else:
|
2711 |
+
num, den = map(int, b.args[-2:])
|
2712 |
+
rat, _ = self.get_or_create_const_rat(num, den)
|
2713 |
+
args = [mapping[a] for a in b.args[:-2]] + [rat.name]
|
2714 |
+
|
2715 |
+
args = list(map(lambda x: self.get(x, lambda: int(x)), args))
|
2716 |
+
|
2717 |
+
adds = self.add_piece(name=b.name, args=args, deps=deps)
|
2718 |
+
basics.append((b.name, args, deps))
|
2719 |
+
if adds:
|
2720 |
+
added += adds
|
2721 |
+
for add in adds:
|
2722 |
+
self.cache_dep(add.name, add.args, add)
|
2723 |
+
|
2724 |
+
assert len(plevel_done) == len(new_points)
|
2725 |
+
for p in new_points:
|
2726 |
+
p.basics = basics
|
2727 |
+
|
2728 |
+
return added, plevel
|
2729 |
+
|
2730 |
+
def all_eqangle_same_lines(self) -> Generator[tuple[Point, ...], None, None]:
|
2731 |
+
for l1, l2 in utils.perm2(self.type2nodes[Line]):
|
2732 |
+
for a, b, c, d, e, f, g, h in utils.all_8points(l1, l2, l1, l2):
|
2733 |
+
if (a, b, c, d) != (e, f, g, h):
|
2734 |
+
yield a, b, c, d, e, f, g, h
|
2735 |
+
|
2736 |
+
def all_eqangles_distinct_linepairss(
|
2737 |
+
self,
|
2738 |
+
) -> Generator[tuple[Line, ...], None, None]:
|
2739 |
+
"""No eqangles betcause para-para, or para-corresponding, or same."""
|
2740 |
+
|
2741 |
+
for measure in self.type2nodes[Measure]:
|
2742 |
+
angs = measure.neighbors(Angle)
|
2743 |
+
line_pairss = []
|
2744 |
+
for ang in angs:
|
2745 |
+
d1, d2 = ang.directions
|
2746 |
+
if d1 is None or d2 is None:
|
2747 |
+
continue
|
2748 |
+
l1s = d1.neighbors(Line)
|
2749 |
+
l2s = d2.neighbors(Line)
|
2750 |
+
# Any pair in this is para-para.
|
2751 |
+
para_para = list(utils.cross(l1s, l2s))
|
2752 |
+
line_pairss.append(para_para)
|
2753 |
+
|
2754 |
+
for pairs1, pairs2 in utils.comb2(line_pairss):
|
2755 |
+
for pair1, pair2 in utils.cross(pairs1, pairs2):
|
2756 |
+
(l1, l2), (l3, l4) = pair1, pair2
|
2757 |
+
yield l1, l2, l3, l4
|
2758 |
+
|
2759 |
+
def all_eqangles_8points(self) -> Generator[tuple[Point, ...], None, None]:
|
2760 |
+
"""List all sets of 8 points that make two equal angles."""
|
2761 |
+
# Case 1: (l1-l2) = (l3-l4), including because l1//l3, l2//l4 (para-para)
|
2762 |
+
angss = []
|
2763 |
+
for measure in self.type2nodes[Measure]:
|
2764 |
+
angs = measure.neighbors(Angle)
|
2765 |
+
angss.append(angs)
|
2766 |
+
|
2767 |
+
# include the angs that do not have any measure.
|
2768 |
+
angss.extend([[ang] for ang in self.type2nodes[Angle] if ang.val is None])
|
2769 |
+
|
2770 |
+
line_pairss = []
|
2771 |
+
for angs in angss:
|
2772 |
+
line_pairs = set()
|
2773 |
+
for ang in angs:
|
2774 |
+
d1, d2 = ang.directions
|
2775 |
+
if d1 is None or d2 is None:
|
2776 |
+
continue
|
2777 |
+
l1s = d1.neighbors(Line)
|
2778 |
+
l2s = d2.neighbors(Line)
|
2779 |
+
line_pairs.update(set(utils.cross(l1s, l2s)))
|
2780 |
+
line_pairss.append(line_pairs)
|
2781 |
+
|
2782 |
+
# include (d1, d2) in which d1 does not have any angles.
|
2783 |
+
noang_ds = [d for d in self.type2nodes[Direction] if not d.neighbors(Angle)]
|
2784 |
+
|
2785 |
+
for d1 in noang_ds:
|
2786 |
+
for d2 in self.type2nodes[Direction]:
|
2787 |
+
if d1 == d2:
|
2788 |
+
continue
|
2789 |
+
l1s = d1.neighbors(Line)
|
2790 |
+
l2s = d2.neighbors(Line)
|
2791 |
+
if len(l1s) < 2 and len(l2s) < 2:
|
2792 |
+
continue
|
2793 |
+
line_pairss.append(set(utils.cross(l1s, l2s)))
|
2794 |
+
line_pairss.append(set(utils.cross(l2s, l1s)))
|
2795 |
+
|
2796 |
+
# Case 2: d1 // d2 => (d1-d3) = (d2-d3)
|
2797 |
+
# include lines that does not have any direction.
|
2798 |
+
nodir_ls = [l for l in self.type2nodes[Line] if l.val is None]
|
2799 |
+
|
2800 |
+
for line in nodir_ls:
|
2801 |
+
for d in self.type2nodes[Direction]:
|
2802 |
+
l1s = d.neighbors(Line)
|
2803 |
+
if len(l1s) < 2:
|
2804 |
+
continue
|
2805 |
+
l2s = [line]
|
2806 |
+
line_pairss.append(set(utils.cross(l1s, l2s)))
|
2807 |
+
line_pairss.append(set(utils.cross(l2s, l1s)))
|
2808 |
+
|
2809 |
+
record = set()
|
2810 |
+
for line_pairs in line_pairss:
|
2811 |
+
for pair1, pair2 in utils.perm2(list(line_pairs)):
|
2812 |
+
(l1, l2), (l3, l4) = pair1, pair2
|
2813 |
+
if l1 == l2 or l3 == l4:
|
2814 |
+
continue
|
2815 |
+
if (l1, l2) == (l3, l4):
|
2816 |
+
continue
|
2817 |
+
if (l1, l2, l3, l4) in record:
|
2818 |
+
continue
|
2819 |
+
record.add((l1, l2, l3, l4))
|
2820 |
+
for a, b, c, d, e, f, g, h in utils.all_8points(l1, l2, l3, l4):
|
2821 |
+
yield (a, b, c, d, e, f, g, h)
|
2822 |
+
|
2823 |
+
for a, b, c, d, e, f, g, h in self.all_eqangle_same_lines():
|
2824 |
+
yield a, b, c, d, e, f, g, h
|
2825 |
+
|
2826 |
+
def all_eqangles_6points(self) -> Generator[tuple[Point, ...], None, None]:
|
2827 |
+
"""List all sets of 6 points that make two equal angles."""
|
2828 |
+
record = set()
|
2829 |
+
for a, b, c, d, e, f, g, h in self.all_eqangles_8points():
|
2830 |
+
if (
|
2831 |
+
a not in (c, d)
|
2832 |
+
and b not in (c, d)
|
2833 |
+
or e not in (g, h)
|
2834 |
+
and f not in (g, h)
|
2835 |
+
):
|
2836 |
+
continue
|
2837 |
+
|
2838 |
+
if b in (c, d):
|
2839 |
+
a, b = b, a # now a in c, d
|
2840 |
+
if f in (g, h):
|
2841 |
+
e, f = f, e # now e in g, h
|
2842 |
+
if a == d:
|
2843 |
+
c, d = d, c # now a == c
|
2844 |
+
if e == h:
|
2845 |
+
g, h = h, g # now e == g
|
2846 |
+
if (a, b, c, d, e, f, g, h) in record:
|
2847 |
+
continue
|
2848 |
+
record.add((a, b, c, d, e, f, g, h))
|
2849 |
+
yield a, b, c, d, e, f, g, h # where a==c, e==g
|
2850 |
+
|
2851 |
+
def all_paras(self) -> Generator[tuple[Point, ...], None, None]:
|
2852 |
+
for d in self.type2nodes[Direction]:
|
2853 |
+
for l1, l2 in utils.perm2(d.neighbors(Line)):
|
2854 |
+
for a, b, c, d in utils.all_4points(l1, l2):
|
2855 |
+
yield a, b, c, d
|
2856 |
+
|
2857 |
+
def all_perps(self) -> Generator[tuple[Point, ...], None, None]:
|
2858 |
+
for ang in self.vhalfpi.neighbors(Angle):
|
2859 |
+
d1, d2 = ang.directions
|
2860 |
+
if d1 is None or d2 is None:
|
2861 |
+
continue
|
2862 |
+
if d1 == d2:
|
2863 |
+
continue
|
2864 |
+
for l1, l2 in utils.cross(d1.neighbors(Line), d2.neighbors(Line)):
|
2865 |
+
for a, b, c, d in utils.all_4points(l1, l2):
|
2866 |
+
yield a, b, c, d
|
2867 |
+
|
2868 |
+
def all_congs(self) -> Generator[tuple[Point, ...], None, None]:
|
2869 |
+
for l in self.type2nodes[Length]:
|
2870 |
+
for s1, s2 in utils.perm2(l.neighbors(Segment)):
|
2871 |
+
(a, b), (c, d) = s1.points, s2.points
|
2872 |
+
for x, y in [(a, b), (b, a)]:
|
2873 |
+
for m, n in [(c, d), (d, c)]:
|
2874 |
+
yield x, y, m, n
|
2875 |
+
|
2876 |
+
def all_eqratios_8points(self) -> Generator[tuple[Point, ...], None, None]:
|
2877 |
+
"""List all sets of 8 points that make two equal ratios."""
|
2878 |
+
ratss = []
|
2879 |
+
for value in self.type2nodes[Value]:
|
2880 |
+
rats = value.neighbors(Ratio)
|
2881 |
+
ratss.append(rats)
|
2882 |
+
|
2883 |
+
# include the rats that do not have any val.
|
2884 |
+
ratss.extend([[rat] for rat in self.type2nodes[Ratio] if rat.val is None])
|
2885 |
+
|
2886 |
+
seg_pairss = []
|
2887 |
+
for rats in ratss:
|
2888 |
+
seg_pairs = set()
|
2889 |
+
for rat in rats:
|
2890 |
+
l1, l2 = rat.lengths
|
2891 |
+
if l1 is None or l2 is None:
|
2892 |
+
continue
|
2893 |
+
s1s = l1.neighbors(Segment)
|
2894 |
+
s2s = l2.neighbors(Segment)
|
2895 |
+
seg_pairs.update(utils.cross(s1s, s2s))
|
2896 |
+
seg_pairss.append(seg_pairs)
|
2897 |
+
|
2898 |
+
# include (l1, l2) in which l1 does not have any ratio.
|
2899 |
+
norat_ls = [l for l in self.type2nodes[Length] if not l.neighbors(Ratio)]
|
2900 |
+
|
2901 |
+
for l1 in norat_ls:
|
2902 |
+
for l2 in self.type2nodes[Length]:
|
2903 |
+
if l1 == l2:
|
2904 |
+
continue
|
2905 |
+
s1s = l1.neighbors(Segment)
|
2906 |
+
s2s = l2.neighbors(Segment)
|
2907 |
+
if len(s1s) < 2 and len(s2s) < 2:
|
2908 |
+
continue
|
2909 |
+
seg_pairss.append(set(utils.cross(s1s, s2s)))
|
2910 |
+
seg_pairss.append(set(utils.cross(s2s, s1s)))
|
2911 |
+
|
2912 |
+
# include Seg that does not have any Length.
|
2913 |
+
nolen_ss = [s for s in self.type2nodes[Segment] if s.val is None]
|
2914 |
+
|
2915 |
+
for seg in nolen_ss:
|
2916 |
+
for l in self.type2nodes[Length]:
|
2917 |
+
s1s = l.neighbors(Segment)
|
2918 |
+
if len(s1s) == 1:
|
2919 |
+
continue
|
2920 |
+
s2s = [seg]
|
2921 |
+
seg_pairss.append(set(utils.cross(s1s, s2s)))
|
2922 |
+
seg_pairss.append(set(utils.cross(s2s, s1s)))
|
2923 |
+
|
2924 |
+
record = set()
|
2925 |
+
for seg_pairs in seg_pairss:
|
2926 |
+
for pair1, pair2 in utils.perm2(list(seg_pairs)):
|
2927 |
+
(s1, s2), (s3, s4) = pair1, pair2
|
2928 |
+
if s1 == s2 or s3 == s4:
|
2929 |
+
continue
|
2930 |
+
if (s1, s2) == (s3, s4):
|
2931 |
+
continue
|
2932 |
+
if (s1, s2, s3, s4) in record:
|
2933 |
+
continue
|
2934 |
+
record.add((s1, s2, s3, s4))
|
2935 |
+
a, b = s1.points
|
2936 |
+
c, d = s2.points
|
2937 |
+
e, f = s3.points
|
2938 |
+
g, h = s4.points
|
2939 |
+
|
2940 |
+
for x, y in [(a, b), (b, a)]:
|
2941 |
+
for z, t in [(c, d), (d, c)]:
|
2942 |
+
for m, n in [(e, f), (f, e)]:
|
2943 |
+
for p, q in [(g, h), (h, g)]:
|
2944 |
+
yield (x, y, z, t, m, n, p, q)
|
2945 |
+
|
2946 |
+
segss = []
|
2947 |
+
# finally the list of ratios that is equal to 1.0
|
2948 |
+
for length in self.type2nodes[Length]:
|
2949 |
+
segs = length.neighbors(Segment)
|
2950 |
+
segss.append(segs)
|
2951 |
+
|
2952 |
+
segs_pair = list(utils.perm2(list(segss)))
|
2953 |
+
segs_pair += list(zip(segss, segss))
|
2954 |
+
for segs1, segs2 in segs_pair:
|
2955 |
+
for s1, s2 in utils.perm2(list(segs1)):
|
2956 |
+
for s3, s4 in utils.perm2(list(segs2)):
|
2957 |
+
if (s1, s2) == (s3, s4) or (s1, s3) == (s2, s4):
|
2958 |
+
continue
|
2959 |
+
if (s1, s2, s3, s4) in record:
|
2960 |
+
continue
|
2961 |
+
record.add((s1, s2, s3, s4))
|
2962 |
+
a, b = s1.points
|
2963 |
+
c, d = s2.points
|
2964 |
+
e, f = s3.points
|
2965 |
+
g, h = s4.points
|
2966 |
+
|
2967 |
+
for x, y in [(a, b), (b, a)]:
|
2968 |
+
for z, t in [(c, d), (d, c)]:
|
2969 |
+
for m, n in [(e, f), (f, e)]:
|
2970 |
+
for p, q in [(g, h), (h, g)]:
|
2971 |
+
yield (x, y, z, t, m, n, p, q)
|
2972 |
+
|
2973 |
+
def all_eqratios_6points(self) -> Generator[tuple[Point, ...], None, None]:
|
2974 |
+
"""List all sets of 6 points that make two equal angles."""
|
2975 |
+
record = set()
|
2976 |
+
for a, b, c, d, e, f, g, h in self.all_eqratios_8points():
|
2977 |
+
if (
|
2978 |
+
a not in (c, d)
|
2979 |
+
and b not in (c, d)
|
2980 |
+
or e not in (g, h)
|
2981 |
+
and f not in (g, h)
|
2982 |
+
):
|
2983 |
+
continue
|
2984 |
+
if b in (c, d):
|
2985 |
+
a, b = b, a
|
2986 |
+
if f in (g, h):
|
2987 |
+
e, f = f, e
|
2988 |
+
if a == d:
|
2989 |
+
c, d = d, c
|
2990 |
+
if e == h:
|
2991 |
+
g, h = h, g
|
2992 |
+
if (a, b, c, d, e, f, g, h) in record:
|
2993 |
+
continue
|
2994 |
+
record.add((a, b, c, d, e, f, g, h))
|
2995 |
+
yield a, b, c, d, e, f, g, h # now a==c, e==g
|
2996 |
+
|
2997 |
+
def all_cyclics(self) -> Generator[tuple[Point, ...], None, None]:
|
2998 |
+
for c in self.type2nodes[Circle]:
|
2999 |
+
for x, y, z, t in utils.perm4(c.neighbors(Point)):
|
3000 |
+
yield x, y, z, t
|
3001 |
+
|
3002 |
+
def all_colls(self) -> Generator[tuple[Point, ...], None, None]:
|
3003 |
+
for l in self.type2nodes[Line]:
|
3004 |
+
for x, y, z in utils.perm3(l.neighbors(Point)):
|
3005 |
+
yield x, y, z
|
3006 |
+
|
3007 |
+
def all_midps(self) -> Generator[tuple[Point, ...], None, None]:
|
3008 |
+
for l in self.type2nodes[Line]:
|
3009 |
+
for a, b, c in utils.perm3(l.neighbors(Point)):
|
3010 |
+
if self.check_cong([a, b, a, c]):
|
3011 |
+
yield a, b, c
|
3012 |
+
|
3013 |
+
def all_circles(self) -> Generator[tuple[Point, ...], None, None]:
|
3014 |
+
for l in self.type2nodes[Length]:
|
3015 |
+
p2p = defaultdict(list)
|
3016 |
+
for s in l.neighbors(Segment):
|
3017 |
+
a, b = s.points
|
3018 |
+
p2p[a].append(b)
|
3019 |
+
p2p[b].append(a)
|
3020 |
+
for p, ps in p2p.items():
|
3021 |
+
if len(ps) >= 3:
|
3022 |
+
for a, b, c in utils.perm3(ps):
|
3023 |
+
yield p, a, b, c
|
3024 |
+
|
3025 |
+
def two_points_on_direction(self, d: Direction) -> tuple[Point, Point]:
|
3026 |
+
l = d.neighbors(Line)[0]
|
3027 |
+
p1, p2 = l.neighbors(Point)[:2]
|
3028 |
+
return p1, p2
|
3029 |
+
|
3030 |
+
def two_points_of_length(self, l: Length) -> tuple[Point, Point]:
|
3031 |
+
s = l.neighbors(Segment)[0]
|
3032 |
+
p1, p2 = s.points
|
3033 |
+
return p1, p2
|
3034 |
+
|
3035 |
+
|
3036 |
+
def create_consts_str(g: Graph, s: str) -> Union[Ratio, Angle]:
|
3037 |
+
if 'pi/' in s:
|
3038 |
+
n, d = s.split('pi/')
|
3039 |
+
n, d = int(n), int(d)
|
3040 |
+
p0, _ = g.get_or_create_const_ang(n, d)
|
3041 |
+
else:
|
3042 |
+
n, d = s.split('/')
|
3043 |
+
n, d = int(n), int(d)
|
3044 |
+
p0, _ = g.get_or_create_const_rat(n, d)
|
3045 |
+
return p0
|
3046 |
+
|
3047 |
+
|
3048 |
+
def create_consts(g: Graph, p: gm.Node) -> Union[Ratio, Angle]:
|
3049 |
+
if isinstance(p, Angle):
|
3050 |
+
n, d = p.name.split('pi/')
|
3051 |
+
n, d = int(n), int(d)
|
3052 |
+
p0, _ = g.get_or_create_const_ang(n, d)
|
3053 |
+
if isinstance(p, Ratio):
|
3054 |
+
n, d = p.name.split('/')
|
3055 |
+
n, d = int(n), int(d)
|
3056 |
+
p0, _ = g.get_or_create_const_rat(n, d)
|
3057 |
+
return p0 # pylint: disable=undefined-variable
|
ag4masses/alphageometry/graph_test.py
ADDED
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright 2023 DeepMind Technologies Limited
|
2 |
+
#
|
3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4 |
+
# you may not use this file except in compliance with the License.
|
5 |
+
# You may obtain a copy of the License at
|
6 |
+
#
|
7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8 |
+
#
|
9 |
+
# Unless required by applicable law or agreed to in writing, software
|
10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
+
# See the License for the specific language governing permissions and
|
13 |
+
# limitations under the License.
|
14 |
+
# ==============================================================================
|
15 |
+
|
16 |
+
"""Unit tests for graph.py."""
|
17 |
+
import unittest
|
18 |
+
|
19 |
+
from absl.testing import absltest
|
20 |
+
import graph as gh
|
21 |
+
import numericals as nm
|
22 |
+
import problem as pr
|
23 |
+
|
24 |
+
|
25 |
+
MAX_LEVEL = 1000
|
26 |
+
|
27 |
+
|
28 |
+
class GraphTest(unittest.TestCase):
|
29 |
+
|
30 |
+
@classmethod
|
31 |
+
def setUpClass(cls):
|
32 |
+
super().setUpClass()
|
33 |
+
|
34 |
+
cls.defs = pr.Definition.from_txt_file('defs.txt', to_dict=True)
|
35 |
+
cls.rules = pr.Theorem.from_txt_file('rules.txt', to_dict=True)
|
36 |
+
|
37 |
+
# load a complex setup:
|
38 |
+
txt = 'a b c = triangle a b c; h = orthocenter a b c; h1 = foot a b c; h2 = foot b c a; h3 = foot c a b; g1 g2 g3 g = centroid g1 g2 g3 g a b c; o = circle a b c ? coll h g o' # pylint: disable=line-too-long
|
39 |
+
p = pr.Problem.from_txt(txt, translate=False)
|
40 |
+
cls.g, _ = gh.Graph.build_problem(p, GraphTest.defs)
|
41 |
+
|
42 |
+
def test_build_graph_points(self):
|
43 |
+
g = GraphTest.g
|
44 |
+
|
45 |
+
all_points = g.all_points()
|
46 |
+
all_names = [p.name for p in all_points]
|
47 |
+
self.assertCountEqual(
|
48 |
+
all_names,
|
49 |
+
['a', 'b', 'c', 'g', 'h', 'o', 'g1', 'g2', 'g3', 'h1', 'h2', 'h3'],
|
50 |
+
)
|
51 |
+
|
52 |
+
def test_build_graph_predicates(self):
|
53 |
+
gr = GraphTest.g
|
54 |
+
|
55 |
+
a, b, c, g, h, o, g1, g2, g3, h1, h2, h3 = gr.names2points(
|
56 |
+
['a', 'b', 'c', 'g', 'h', 'o', 'g1', 'g2', 'g3', 'h1', 'h2', 'h3']
|
57 |
+
)
|
58 |
+
|
59 |
+
# Explicit statements:
|
60 |
+
self.assertTrue(gr.check_cong([b, g1, g1, c]))
|
61 |
+
self.assertTrue(gr.check_cong([c, g2, g2, a]))
|
62 |
+
self.assertTrue(gr.check_cong([a, g3, g3, b]))
|
63 |
+
self.assertTrue(gr.check_perp([a, h1, b, c]))
|
64 |
+
self.assertTrue(gr.check_perp([b, h2, c, a]))
|
65 |
+
self.assertTrue(gr.check_perp([c, h3, a, b]))
|
66 |
+
self.assertTrue(gr.check_cong([o, a, o, b]))
|
67 |
+
self.assertTrue(gr.check_cong([o, b, o, c]))
|
68 |
+
self.assertTrue(gr.check_cong([o, a, o, c]))
|
69 |
+
self.assertTrue(gr.check_coll([a, g, g1]))
|
70 |
+
self.assertTrue(gr.check_coll([b, g, g2]))
|
71 |
+
self.assertTrue(gr.check_coll([g1, b, c]))
|
72 |
+
self.assertTrue(gr.check_coll([g2, c, a]))
|
73 |
+
self.assertTrue(gr.check_coll([g3, a, b]))
|
74 |
+
self.assertTrue(gr.check_perp([a, h, b, c]))
|
75 |
+
self.assertTrue(gr.check_perp([b, h, c, a]))
|
76 |
+
|
77 |
+
# These are NOT part of the premises:
|
78 |
+
self.assertFalse(gr.check_perp([c, h, a, b]))
|
79 |
+
self.assertFalse(gr.check_coll([c, g, g3]))
|
80 |
+
|
81 |
+
# These are automatically inferred by the graph datastructure:
|
82 |
+
self.assertTrue(gr.check_eqangle([a, h1, b, c, b, h2, c, a]))
|
83 |
+
self.assertTrue(gr.check_eqangle([a, h1, b, h2, b, c, c, a]))
|
84 |
+
self.assertTrue(gr.check_eqratio([b, g1, g1, c, c, g2, g2, a]))
|
85 |
+
self.assertTrue(gr.check_eqratio([b, g1, g1, c, o, a, o, b]))
|
86 |
+
self.assertTrue(gr.check_para([a, h, a, h1]))
|
87 |
+
self.assertTrue(gr.check_para([b, h, b, h2]))
|
88 |
+
self.assertTrue(gr.check_coll([a, h, h1]))
|
89 |
+
self.assertTrue(gr.check_coll([b, h, h2]))
|
90 |
+
|
91 |
+
def test_enumerate_colls(self):
|
92 |
+
g = GraphTest.g
|
93 |
+
|
94 |
+
for a, b, c in g.all_colls():
|
95 |
+
self.assertTrue(g.check_coll([a, b, c]))
|
96 |
+
self.assertTrue(nm.check_coll([a.num, b.num, c.num]))
|
97 |
+
|
98 |
+
def test_enumerate_paras(self):
|
99 |
+
g = GraphTest.g
|
100 |
+
|
101 |
+
for a, b, c, d in g.all_paras():
|
102 |
+
self.assertTrue(g.check_para([a, b, c, d]))
|
103 |
+
self.assertTrue(nm.check_para([a.num, b.num, c.num, d.num]))
|
104 |
+
|
105 |
+
def test_enumerate_perps(self):
|
106 |
+
g = GraphTest.g
|
107 |
+
|
108 |
+
for a, b, c, d in g.all_perps():
|
109 |
+
self.assertTrue(g.check_perp([a, b, c, d]))
|
110 |
+
self.assertTrue(nm.check_perp([a.num, b.num, c.num, d.num]))
|
111 |
+
|
112 |
+
def test_enumerate_congs(self):
|
113 |
+
g = GraphTest.g
|
114 |
+
|
115 |
+
for a, b, c, d in g.all_congs():
|
116 |
+
self.assertTrue(g.check_cong([a, b, c, d]))
|
117 |
+
self.assertTrue(nm.check_cong([a.num, b.num, c.num, d.num]))
|
118 |
+
|
119 |
+
def test_enumerate_eqangles(self):
|
120 |
+
g = GraphTest.g
|
121 |
+
|
122 |
+
for a, b, c, d, x, y, z, t in g.all_eqangles_8points():
|
123 |
+
self.assertTrue(g.check_eqangle([a, b, c, d, x, y, z, t]))
|
124 |
+
self.assertTrue(
|
125 |
+
nm.check_eqangle(
|
126 |
+
[a.num, b.num, c.num, d.num, x.num, y.num, z.num, t.num]
|
127 |
+
)
|
128 |
+
)
|
129 |
+
|
130 |
+
def test_enumerate_eqratios(self):
|
131 |
+
g = GraphTest.g
|
132 |
+
|
133 |
+
for a, b, c, d, x, y, z, t in g.all_eqratios_8points():
|
134 |
+
self.assertTrue(g.check_eqratio([a, b, c, d, x, y, z, t]))
|
135 |
+
self.assertTrue(
|
136 |
+
nm.check_eqratio(
|
137 |
+
[a.num, b.num, c.num, d.num, x.num, y.num, z.num, t.num]
|
138 |
+
)
|
139 |
+
)
|
140 |
+
|
141 |
+
def test_enumerate_cyclics(self):
|
142 |
+
g = GraphTest.g
|
143 |
+
|
144 |
+
for a, b, c, d, x, y, z, t in g.all_cyclics():
|
145 |
+
self.assertTrue(g.check_cyclic([a, b, c, d, x, y, z, t]))
|
146 |
+
self.assertTrue(nm.check_cyclic([a.num, b.num, c.num, d.num]))
|
147 |
+
|
148 |
+
def test_enumerate_midps(self):
|
149 |
+
g = GraphTest.g
|
150 |
+
|
151 |
+
for a, b, c in g.all_midps():
|
152 |
+
self.assertTrue(g.check_midp([a, b, c]))
|
153 |
+
self.assertTrue(nm.check_midp([a.num, b.num, c.num]))
|
154 |
+
|
155 |
+
def test_enumerate_circles(self):
|
156 |
+
g = GraphTest.g
|
157 |
+
|
158 |
+
for a, b, c, d in g.all_circles():
|
159 |
+
self.assertTrue(g.check_circle([a, b, c, d]))
|
160 |
+
self.assertTrue(nm.check_circle([a.num, b.num, c.num, d.num]))
|
161 |
+
|
162 |
+
|
163 |
+
if __name__ == '__main__':
|
164 |
+
absltest.main()
|
ag4masses/alphageometry/graph_utils.py
ADDED
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright 2023 DeepMind Technologies Limited
|
2 |
+
#
|
3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4 |
+
# you may not use this file except in compliance with the License.
|
5 |
+
# You may obtain a copy of the License at
|
6 |
+
#
|
7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8 |
+
#
|
9 |
+
# Unless required by applicable law or agreed to in writing, software
|
10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
+
# See the License for the specific language governing permissions and
|
13 |
+
# limitations under the License.
|
14 |
+
# ==============================================================================
|
15 |
+
|
16 |
+
"""Utilizations for graph representation.
|
17 |
+
|
18 |
+
Mainly for listing combinations and permutations of elements.
|
19 |
+
"""
|
20 |
+
|
21 |
+
from geometry import Point
|
22 |
+
|
23 |
+
|
24 |
+
def _cross(elems1, elems2):
|
25 |
+
for e1 in elems1:
|
26 |
+
for e2 in elems2:
|
27 |
+
yield e1, e2
|
28 |
+
|
29 |
+
|
30 |
+
def cross(elems1, elems2):
|
31 |
+
return list(_cross(elems1, elems2))
|
32 |
+
|
33 |
+
|
34 |
+
def _comb2(elems):
|
35 |
+
if len(elems) < 2:
|
36 |
+
return
|
37 |
+
for i, e1 in enumerate(elems[:-1]):
|
38 |
+
for e2 in elems[i + 1 :]:
|
39 |
+
yield e1, e2
|
40 |
+
|
41 |
+
|
42 |
+
def comb2(elems):
|
43 |
+
return list(_comb2(elems))
|
44 |
+
|
45 |
+
|
46 |
+
def _comb3(elems):
|
47 |
+
if len(elems) < 3:
|
48 |
+
return
|
49 |
+
for i, e1 in enumerate(elems[:-2]):
|
50 |
+
for j, e2 in enumerate(elems[i + 1 : -1]):
|
51 |
+
for e3 in elems[i + j + 2 :]:
|
52 |
+
yield e1, e2, e3
|
53 |
+
|
54 |
+
|
55 |
+
def comb3(elems):
|
56 |
+
return list(_comb3(elems))
|
57 |
+
|
58 |
+
|
59 |
+
def _comb4(elems):
|
60 |
+
if len(elems) < 4:
|
61 |
+
return
|
62 |
+
for i, e1 in enumerate(elems[:-3]):
|
63 |
+
for j, e2 in enumerate(elems[i + 1 : -2]):
|
64 |
+
for e3, e4 in _comb2(elems[i + j + 2 :]):
|
65 |
+
yield e1, e2, e3, e4
|
66 |
+
|
67 |
+
|
68 |
+
def comb4(elems):
|
69 |
+
return list(_comb4(elems))
|
70 |
+
|
71 |
+
|
72 |
+
def _perm2(elems):
|
73 |
+
for e1, e2 in comb2(elems):
|
74 |
+
yield e1, e2
|
75 |
+
yield e2, e1
|
76 |
+
|
77 |
+
|
78 |
+
def perm2(elems):
|
79 |
+
return list(_perm2(elems))
|
80 |
+
|
81 |
+
|
82 |
+
def _all_4points(l1, l2):
|
83 |
+
p1s = l1.neighbors(Point)
|
84 |
+
p2s = l2.neighbors(Point)
|
85 |
+
for a, b in perm2(p1s):
|
86 |
+
for c, d in perm2(p2s):
|
87 |
+
yield a, b, c, d
|
88 |
+
|
89 |
+
|
90 |
+
def all_4points(l1, l2):
|
91 |
+
return list(_all_4points(l1, l2))
|
92 |
+
|
93 |
+
|
94 |
+
def _all_8points(l1, l2, l3, l4):
|
95 |
+
for a, b, c, d in all_4points(l1, l2):
|
96 |
+
for e, f, g, h in all_4points(l3, l4):
|
97 |
+
yield (a, b, c, d, e, f, g, h)
|
98 |
+
|
99 |
+
|
100 |
+
def all_8points(l1, l2, l3, l4):
|
101 |
+
return list(_all_8points(l1, l2, l3, l4))
|
102 |
+
|
103 |
+
|
104 |
+
def _perm3(elems):
|
105 |
+
for x in elems:
|
106 |
+
for y in elems:
|
107 |
+
if y == x:
|
108 |
+
continue
|
109 |
+
for z in elems:
|
110 |
+
if z not in (x, y):
|
111 |
+
yield x, y, z
|
112 |
+
|
113 |
+
|
114 |
+
def perm3(elems):
|
115 |
+
return list(_perm3(elems))
|
116 |
+
|
117 |
+
|
118 |
+
def _perm4(elems):
|
119 |
+
for x in elems:
|
120 |
+
for y in elems:
|
121 |
+
if y == x:
|
122 |
+
continue
|
123 |
+
for z in elems:
|
124 |
+
if z in (x, y):
|
125 |
+
continue
|
126 |
+
for t in elems:
|
127 |
+
if t not in (x, y, z):
|
128 |
+
yield x, y, z, t
|
129 |
+
|
130 |
+
|
131 |
+
def perm4(elems):
|
132 |
+
return list(_perm4(elems))
|
ag4masses/alphageometry/graph_utils_test.py
ADDED
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright 2023 DeepMind Technologies Limited
|
2 |
+
#
|
3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4 |
+
# you may not use this file except in compliance with the License.
|
5 |
+
# You may obtain a copy of the License at
|
6 |
+
#
|
7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8 |
+
#
|
9 |
+
# Unless required by applicable law or agreed to in writing, software
|
10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
+
# See the License for the specific language governing permissions and
|
13 |
+
# limitations under the License.
|
14 |
+
# ==============================================================================
|
15 |
+
|
16 |
+
"""Unit tests for graph_utils.py."""
|
17 |
+
import unittest
|
18 |
+
|
19 |
+
from absl.testing import absltest
|
20 |
+
import graph_utils as gu
|
21 |
+
|
22 |
+
|
23 |
+
class GraphUtilsTest(unittest.TestCase):
|
24 |
+
|
25 |
+
def test_cross(self):
|
26 |
+
self.assertEqual(gu.cross([], [1]), [])
|
27 |
+
self.assertEqual(gu.cross([1], []), [])
|
28 |
+
self.assertEqual(gu.cross([1], [2]), [(1, 2)])
|
29 |
+
self.assertEqual(gu.cross([1], [2, 3]), [(1, 2), (1, 3)])
|
30 |
+
|
31 |
+
e1 = [1, 2, 3]
|
32 |
+
e2 = [4, 5]
|
33 |
+
target = [(1, 4), (1, 5), (2, 4), (2, 5), (3, 4), (3, 5)]
|
34 |
+
self.assertEqual(gu.cross(e1, e2), target)
|
35 |
+
|
36 |
+
def test_comb2(self):
|
37 |
+
self.assertEqual(gu.comb2([]), [])
|
38 |
+
self.assertEqual(gu.comb2([1]), [])
|
39 |
+
self.assertEqual(gu.comb2([1, 2]), [(1, 2)])
|
40 |
+
self.assertEqual(gu.comb2([1, 2, 3]), [(1, 2), (1, 3), (2, 3)])
|
41 |
+
|
42 |
+
def test_comb3(self):
|
43 |
+
self.assertEqual(gu.comb3([]), [])
|
44 |
+
self.assertEqual(gu.comb3([1]), [])
|
45 |
+
self.assertEqual(gu.comb3([1, 2]), [])
|
46 |
+
self.assertEqual(gu.comb3([1, 2, 3]), [(1, 2, 3)])
|
47 |
+
self.assertEqual(
|
48 |
+
gu.comb3([1, 2, 3, 4]), [(1, 2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4)]
|
49 |
+
)
|
50 |
+
|
51 |
+
def test_comb4(self):
|
52 |
+
self.assertEqual(gu.comb4([]), [])
|
53 |
+
self.assertEqual(gu.comb4([1]), [])
|
54 |
+
self.assertEqual(gu.comb4([1, 2]), [])
|
55 |
+
self.assertEqual(gu.comb4([1, 2, 3]), [])
|
56 |
+
self.assertEqual(gu.comb4([1, 2, 3, 4]), [(1, 2, 3, 4)])
|
57 |
+
self.assertEqual(
|
58 |
+
gu.comb4([1, 2, 3, 4, 5]),
|
59 |
+
[(1, 2, 3, 4), (1, 2, 3, 5), (1, 2, 4, 5), (1, 3, 4, 5), (2, 3, 4, 5)],
|
60 |
+
)
|
61 |
+
|
62 |
+
def test_perm2(self):
|
63 |
+
self.assertEqual(gu.perm2([]), [])
|
64 |
+
self.assertEqual(gu.perm2([1]), [])
|
65 |
+
self.assertEqual(gu.perm2([1, 2]), [(1, 2), (2, 1)])
|
66 |
+
self.assertEqual(
|
67 |
+
gu.perm2([1, 2, 3]), [(1, 2), (2, 1), (1, 3), (3, 1), (2, 3), (3, 2)]
|
68 |
+
)
|
69 |
+
|
70 |
+
def test_perm3(self):
|
71 |
+
self.assertEqual(gu.perm3([]), [])
|
72 |
+
self.assertEqual(gu.perm3([1]), [])
|
73 |
+
self.assertEqual(gu.perm3([1, 2]), [])
|
74 |
+
self.assertEqual(
|
75 |
+
gu.perm3([1, 2, 3]),
|
76 |
+
[(1, 2, 3), (1, 3, 2), (2, 1, 3), (2, 3, 1), (3, 1, 2), (3, 2, 1)],
|
77 |
+
)
|
78 |
+
self.assertEqual(
|
79 |
+
gu.perm3([1, 2, 3, 4]),
|
80 |
+
[
|
81 |
+
(1, 2, 3),
|
82 |
+
(1, 2, 4),
|
83 |
+
(1, 3, 2),
|
84 |
+
(1, 3, 4),
|
85 |
+
(1, 4, 2),
|
86 |
+
(1, 4, 3),
|
87 |
+
(2, 1, 3),
|
88 |
+
(2, 1, 4),
|
89 |
+
(2, 3, 1),
|
90 |
+
(2, 3, 4),
|
91 |
+
(2, 4, 1),
|
92 |
+
(2, 4, 3),
|
93 |
+
(3, 1, 2),
|
94 |
+
(3, 1, 4),
|
95 |
+
(3, 2, 1),
|
96 |
+
(3, 2, 4),
|
97 |
+
(3, 4, 1),
|
98 |
+
(3, 4, 2),
|
99 |
+
(4, 1, 2),
|
100 |
+
(4, 1, 3),
|
101 |
+
(4, 2, 1),
|
102 |
+
(4, 2, 3),
|
103 |
+
(4, 3, 1),
|
104 |
+
(4, 3, 2),
|
105 |
+
],
|
106 |
+
)
|
107 |
+
|
108 |
+
def test_perm4(self):
|
109 |
+
self.assertEqual(gu.perm3([]), [])
|
110 |
+
self.assertEqual(gu.perm3([1]), [])
|
111 |
+
self.assertEqual(gu.perm3([1, 2]), [])
|
112 |
+
self.assertEqual(gu.perm4([1, 2, 3]), [])
|
113 |
+
self.assertEqual(
|
114 |
+
gu.perm4([1, 2, 3, 4]),
|
115 |
+
[
|
116 |
+
(1, 2, 3, 4),
|
117 |
+
(1, 2, 4, 3),
|
118 |
+
(1, 3, 2, 4),
|
119 |
+
(1, 3, 4, 2),
|
120 |
+
(1, 4, 2, 3),
|
121 |
+
(1, 4, 3, 2), # pylint: disable=line-too-long
|
122 |
+
(2, 1, 3, 4),
|
123 |
+
(2, 1, 4, 3),
|
124 |
+
(2, 3, 1, 4),
|
125 |
+
(2, 3, 4, 1),
|
126 |
+
(2, 4, 1, 3),
|
127 |
+
(2, 4, 3, 1), # pylint: disable=line-too-long
|
128 |
+
(3, 1, 2, 4),
|
129 |
+
(3, 1, 4, 2),
|
130 |
+
(3, 2, 1, 4),
|
131 |
+
(3, 2, 4, 1),
|
132 |
+
(3, 4, 1, 2),
|
133 |
+
(3, 4, 2, 1), # pylint: disable=line-too-long
|
134 |
+
(4, 1, 2, 3),
|
135 |
+
(4, 1, 3, 2),
|
136 |
+
(4, 2, 1, 3),
|
137 |
+
(4, 2, 3, 1),
|
138 |
+
(4, 3, 1, 2),
|
139 |
+
(4, 3, 2, 1),
|
140 |
+
], # pylint: disable=line-too-long
|
141 |
+
)
|
142 |
+
|
143 |
+
|
144 |
+
if __name__ == '__main__':
|
145 |
+
absltest.main()
|
ag4masses/alphageometry/imo_ag_30.txt
ADDED
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
translated_imo_2000_p1
|
2 |
+
a b = segment a b; g1 = on_tline g1 a a b; g2 = on_tline g2 b b a; m = on_circle m g1 a, on_circle m g2 b; n = on_circle n g1 a, on_circle n g2 b; c = on_pline c m a b, on_circle c g1 a; d = on_pline d m a b, on_circle d g2 b; e = on_line e a c, on_line e b d; p = on_line p a n, on_line p c d; q = on_line q b n, on_line q c d ? cong e p e q
|
3 |
+
translated_imo_2000_p6
|
4 |
+
a b c = triangle a b c; h = orthocenter h a b c; t1 t2 t3 i = incenter2 t1 t2 t3 i a b c; h1 = foot h1 a b c; h2 = foot h2 b c a; h3 = foot h3 c a b; x1 = reflect x1 h1 t1 t2; x2 = reflect x2 h2 t1 t2; y2 = reflect y2 h2 t2 t3; y3 = reflect y3 h3 t2 t3; z = on_line z x1 x2, on_line z y2 y3 ? cong i z i t1
|
5 |
+
translated_imo_2002_p2a
|
6 |
+
b c = segment b c; o = midpoint o b c; a = on_circle a o b; d = on_circle d o b, on_bline d a b; e = on_bline e o a, on_circle e o b; f = on_bline f o a, on_circle f o b; j = on_pline j o a d, on_line j a c ? eqangle e c e j e j e f
|
7 |
+
translated_imo_2002_p2b
|
8 |
+
b c = segment b c; o = midpoint o b c; a = on_circle a o b; d = on_circle d o b, on_bline d a b; e = on_bline e o a, on_circle e o b; f = on_bline f o a, on_circle f o b; j = on_pline j o a d, on_line j a c ? eqangle c e c j c j c f
|
9 |
+
translated_imo_2003_p4
|
10 |
+
a b c = triangle a b c; o = circle o a b c; b1 = on_circle b1 o a, on_bline b1 c a; d1 = on_circle d1 o a, on_bline d1 c a; x = on_line x b b1, on_line x a c; d = on_line d d1 x, on_circle d o a; p = foot p d b c; q = foot q d c a; r = foot r d a b ? cong p q q r
|
11 |
+
translated_imo_2004_p1
|
12 |
+
a b c = triangle a b c; o = midpoint o b c; m = on_circle m o b, on_line m a b; n = on_circle n o b, on_line n a c; r = angle_bisector r b a c, angle_bisector r m o n; o1 = circle o1 b m r; o2 = circle o2 c n r; p = on_circle p o1 r, on_circle p o2 r ? coll p b c
|
13 |
+
translated_imo_2004_p5
|
14 |
+
a b c = triangle a b c; o = circle o a b c; d = on_circle d o a; p = on_aline p b c a b d, on_aline p d c a d b ? cong a p c p
|
15 |
+
translated_imo_2005_p5
|
16 |
+
a b c = triangle a b c; d = eqdistance d a b c; e = on_line e b c; f = on_line f a d, eqdistance f d e b; p = on_line p a c, on_line p b d; q = on_line q e f, on_line q b d; r = on_line r e f, on_line r a c; o1 = circle o1 a p d; o2 = circle o2 b p c; m = on_circle m o1 p, on_circle m o2 p ? cyclic p q r m
|
17 |
+
translated_imo_2007_p4
|
18 |
+
a b c = triangle a b c; o = circle o a b c; r = on_circle r o a, on_bline r a b; l = midpoint l c a; k = midpoint k c b; p = on_line p o k, on_line p c r; q = on_line q o l, on_line q c r; l1 = foot l1 l c r; k1 = foot k1 k c r ? eqratio k k1 l l1 r q r p
|
19 |
+
translated_imo_2008_p1a
|
20 |
+
a b c = triangle a b c; h = orthocenter h a b c; d = midpoint d b c; e = midpoint e a c; f = midpoint f a b; a1 = on_circle a1 d h, on_line a1 b c; a2 = on_circle a2 d h, on_line a2 b c; b1 = on_circle b1 e h, on_line b1 c a; b2 = on_circle b2 e h, on_line b2 c a; c1 = on_circle c1 f h, on_line c1 a b; c2 = on_circle c2 f h, on_line c2 a b ? cyclic c1 c2 b1 b2
|
21 |
+
translated_imo_2008_p1b
|
22 |
+
a b c = triangle a b c; h = orthocenter h a b c; d = midpoint d b c; e = midpoint e a c; f = midpoint f a b; a1 = on_circle a1 d h, on_line a1 b c; a2 = on_circle a2 d h, on_line a2 b c; b1 = on_circle b1 e h, on_line b1 c a; b2 = on_circle b2 e h, on_line b2 c a; c1 = on_circle c1 f h, on_line c1 a b; c2 = on_circle c2 f h, on_line c2 a b ? cyclic c1 c2 b1 a1
|
23 |
+
translated_imo_2008_p6
|
24 |
+
x@4.96_-0.13 y@-1.0068968328888160_-1.2534881080682770 z@-2.8402847238575120_-4.9117762734006830 = triangle x y z; o = circle o x y z; w@6.9090049230038776_-1.3884003936987552 = on_circle w o x; a = on_tline a z o z, on_tline a x o x; b = on_tline b z o z, on_tline b w o w; c = on_tline c y o y, on_tline c w o w; d = on_tline d x o x, on_tline d y o y; i1 = incenter i1 a b c; i2 = incenter i2 a c d; f1 = foot f1 i1 a c; f2 = foot f2 i2 a c; q t p s = cc_tangent q t p s i1 f1 i2 f2; k = on_line k q t, on_line k p s ? cong o k o x
|
25 |
+
translated_imo_2009_p2
|
26 |
+
m l k = triangle m l k; w = circle w m l k; q = on_tline q m w m; p = mirror p q m; b = mirror b p k; c = mirror c q l; a = on_line a b q, on_line a c p; o = circle o a b c ? cong o p o q
|
27 |
+
translated_imo_2010_p2
|
28 |
+
a b c = triangle a b c; o = circle o a b c; i = incenter i a b c; d = on_line d a i, on_circle d o a; f = on_line f b c; e = on_aline e a c b a f, on_circle e o a; g = midpoint g i f; k = on_line k d g, on_line k e i ? cong o a o k
|
29 |
+
translated_imo_2010_p4
|
30 |
+
s c p = iso_triangle s c p; o = on_tline o c s c; a = on_circle a o c; b = on_circle b o c, on_line b s a; m = on_line m c p, on_circle m o c; l = on_line l b p, on_circle l o c; k = on_line k a p, on_circle k o c ? cong m k m l
|
31 |
+
translated_imo_2011_p6
|
32 |
+
a b c = triangle a b c; o = circle o a b c; p = on_circle p o a; q = on_tline q p o p; pa = reflect pa p b c; pb = reflect pb p c a; pc = reflect pc p a b; qa = reflect qa q b c; qb = reflect qb q c a; qc = reflect qc q a b; a1 = on_line a1 pb qb, on_line a1 pc qc; b1 = on_line b1 pa qa, on_line b1 pc qc; c1 = on_line c1 pa qa, on_line c1 pb qb; o1 = circle o1 a1 b1 c1; x = on_circle x o a, on_circle x o1 a1 ? coll x o o1
|
33 |
+
translated_imo_2012_p1
|
34 |
+
a b c = triangle a b c; m l k j = excenter2 m l k j a b c; f = on_line f m l, on_line f b j; g = on_line g m k, on_line g c j; s = on_line s f a, on_line s b c; t = on_line t g a, on_line t c b ? cong m s m t
|
35 |
+
translated_imo_2012_p5
|
36 |
+
c a b = r_triangle c a b; d = foot d c a b; x = on_line x c d; k = on_line k a x, on_circle k b c; l = on_line l b x, on_circle l a c; m = on_line m a l, on_line m b k ? cong m k m l
|
37 |
+
translated_imo_2013_p4
|
38 |
+
a b c = triangle a b c; h = orthocenter h a b c; m = on_line m h b, on_line m a c; n = on_line n h c, on_line n a b; w = on_line w b c; o1 = circle o1 b n w; o2 = circle o2 c m w; x = on_line x o1 w, on_circle x o1 w; y = on_line y o2 w, on_circle y o2 w ? coll x h y
|
39 |
+
translated_imo_2014_p4
|
40 |
+
a b c = triangle a b c; p = on_line p b c, on_aline p a b b c a; q = on_line q b c, on_aline q a c c b a; m = mirror m a p; n = mirror n a q; x = on_line x b m, on_line x c n; o = circle o a b c ? cong o x o a
|
41 |
+
translated_imo_2015_p3
|
42 |
+
a b c = triangle a b c; h = orthocenter h a b c; f = on_line f h a, on_line f b c; m = midpoint m b c; o = circle o a b c; q = on_dia q a h, on_circle q o a; k = on_dia k h q, on_circle k o a; o1 = circle o1 k q h; o2 = circle o2 f k m ? coll o1 o2 k
|
43 |
+
translated_imo_2015_p4
|
44 |
+
a b c = triangle a b c; o = circle o a b c; d = on_line d b c; e = on_line e b c, on_circle e a d; f = on_circle f o a, on_circle f a d; g = on_circle g o a, on_circle g a d; o1 = circle o1 f b d; o2 = circle o2 g c e; k = on_circle k o1 b, on_line k a b; l = on_circle l o2 c, on_line l a c; x = on_line x f k, on_line x l g ? coll x o a
|
45 |
+
translated_imo_2016_p1
|
46 |
+
a b z = triangle a b z; f = angle_bisector f b a z, on_bline f a b; c = on_tline c b f b, on_line c a f; d = on_line d a z, on_bline d a c; e = angle_mirror e c a d, on_bline e a d; m = midpoint m c f; x = parallelogram e a m x; y = on_line y f x, on_line y e m ? coll y b d
|
47 |
+
translated_imo_2017_p4
|
48 |
+
r s = segment r s; t = mirror t r s; o = on_bline o r s; j = on_circle j o s; o1 = circle o1 j s t; a = on_tline a r o r, on_circle a o1 s; b = on_tline b r o r, on_circle b o1 s; k = on_line k j a, on_circle k o s ? perp k t o1 t
|
49 |
+
translated_imo_2018_p1
|
50 |
+
a b c = triangle a b c; o = circle o a b c; d = on_line d a b; e = on_line e a c, on_circle e a d; f = on_bline f b d, on_circle f o a; g = on_bline g e c, on_circle g o a ? para d e f g
|
51 |
+
translated_imo_2019_p2
|
52 |
+
a b c = triangle; a1 = on_line b c; b1 = on_line a c; p = on_line a a1; q = on_line b b1, on_pline p a b; p1 = on_line p b1, eqangle3 p c a b c; q1 = on_line q a1, eqangle3 c q b c a ? cyclic p q p1 q1
|
53 |
+
translated_imo_2019_p6
|
54 |
+
a b c = triangle a b c; d e f i = incenter2 d e f i a b c; r = on_tline r d e f, on_circle r i d; p = on_line p r a, on_circle p i d; o1 = circle o1 p c e; o2 = circle o2 p b f; q = on_circle q o1 p, on_circle q o2 p; t = on_line t p q, on_line t i d ? perp a t a i
|
55 |
+
translated_imo_2020_p1
|
56 |
+
p a b = triangle p a b; x = angle_bisector p b a; y = angle_bisector p a b; z = on_aline z a p a b x; t = on_aline t p a p a z; d = on_aline d p t p b a, on_line a z; u = on_aline u b p b a y; v = on_aline v p b p b u; c = on_aline c p v p a b, on_line b u; o = angle_bisector a d p, angle_bisector p c b ? cong o a o b
|
57 |
+
translated_imo_2021_p3
|
58 |
+
a b c = triangle; d = angle_bisector b a c; e = on_aline d a d c b, on_line a c; f = on_aline d a d b c, on_line a b; x = on_bline b c, on_line a c; o1 = circle a d c; o2 = circle e x d; y = on_line e f, on_line b c ? coll o1 o2 y
|
59 |
+
translated_imo_2022_p4
|
60 |
+
b c = segment; d = free; e = eqdistance d b c; t = on_bline b d, on_bline c e; a = eqangle2 b t e; p = on_line a b, on_line c d; q = on_line a b, on_line c t; r = on_line a e, on_line c d; s = on_line a e, on_line d t ? cyclic p q r s
|
ag4masses/alphageometry/jgex_ag_231.txt
ADDED
@@ -0,0 +1,462 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
examples/complete2/012/complete_004_6_GDD_FULL_81-109_101.gex
|
2 |
+
a b c = triangle a b c; o = circle o a b c; h = midpoint h c b; d = on_line d o h, on_line d a b; e = on_tline e c c o, on_tline e a a o ? cyclic a o e d
|
3 |
+
examples/complete2/012/complete_002_6_GDD_FULL_41-60_59.gex
|
4 |
+
a b c = triangle a b c; m = midpoint m b a; o = circle o a b c; n = on_line n o m, on_circle n o a ? eqangle c a c n c n c b
|
5 |
+
examples/complete2/012/complete_002_6_GDD_FULL_01-20_04.gex
|
6 |
+
a b c = triangle a b c; o = circle o a b c; d = on_circle d o a; q = midpoint q c b; s = midpoint s a d; j = midpoint j s q; m = mirror m o j; i = on_line i a d, on_line i b c ? perp s m b c
|
7 |
+
examples/complete2/012/complete_004_6_GDD_FULL_81-109_90.gex
|
8 |
+
a b c = triangle a b c; o = circle o a b c; d = on_circle d o a; g = foot g d a b; f = foot f d a c; c1 = on_circle c1 o d, on_line c1 d g; b1 = on_circle b1 o d, on_line b1 d f ? para c1 c b1 b
|
9 |
+
examples/complete2/012/complete_004_6_GDD_FULL_81-109_94.gex
|
10 |
+
a b c = triangle a b c; o = circle o a b c; d = on_circle d o a; p = on_circle p o a; f = foot f p a d; g = foot g p a b; h = foot h p b c; e = foot e p c d; i = on_line i f g, on_line i h e ? cyclic p g i h
|
11 |
+
examples/complete2/012/complete_003_6_GDD_FULL_21-40_37.gex
|
12 |
+
a b c = triangle a b c; h = orthocenter h a b c; o = circle o a b c; c1 = on_circle c1 o c, on_line c1 c h; a1 = on_circle a1 o a, on_line a1 a h ? cong b a1 b c1
|
13 |
+
examples/complete2/012/complete_003_6_GDD_FULL_21-40_22.gex
|
14 |
+
a b c = triangle a b c; o = circle o a b c; p = foot p o a c; q = foot q o a b; m = on_line m o q, on_circle m o a; n = on_line n o p, on_circle n o a; e = on_line e a c, on_line e n m; d = on_line d a b, on_line d n m ? eqangle d a d e e d e a
|
15 |
+
examples/complete2/012/complete_001_6_GDD_FULL_01-20_19.gex
|
16 |
+
a b c = triangle a b c; f = free f; p = circle p a b f; o = circle o a b c; e = on_circle e p a, on_line e a c; d = on_circle d o b, on_line d b f ? para c d e f
|
17 |
+
examples/complete2/012/complete_001_6_GDD_FULL_61-80_74.gex
|
18 |
+
a b c = triangle a b c; g = foot g c a b; o = circle o a b c; d = on_circle d o c, on_line d c g; e = foot e d a c; f = foot f d b c ? cyclic a e f b
|
19 |
+
examples/complete2/013/complete_002_6_GDD_FULL_41-60_49.gex
|
20 |
+
a b c = triangle a b c; p = midpoint p b a; q = midpoint q c b; d = on_tline d b a c; r = midpoint r d c; s = midpoint s a d; o = on_line o p r, on_line o q s ? cong o s o r
|
21 |
+
examples/complete2/013/complete_006_Other_ndgTest_70.gex
|
22 |
+
p a b = triangle p a b; o = midpoint o b a; a1 = on_line a1 p a, on_circle a1 o a; b1 = on_line b1 p b, on_circle b1 o a; o1 = circle o1 p a1 b1 ? perp o a1 a1 o1
|
23 |
+
examples/complete2/013/complete_001_6_GDD_FULL_01-20_16.gex
|
24 |
+
a b o = triangle a b o; m = on_line m a b; p = foot p m a o; q = foot q m b o; d = foot d b a o; c = foot c a b o; t = foot t q a o; k = foot k p b o; s = on_line s q t, on_line s p k ? perp o s p q
|
25 |
+
examples/complete2/013/complete_001_6_GDD_FULL_61-80_67.gex
|
26 |
+
m b c = triangle m b c; i = incenter i m b c; i_b = on_tline i_b c c i, on_line i_b b i; i_c = on_tline i_c b b i, on_line i_c c i; a = midpoint a i_b i_c; o = circumcenter o b i c ? perp a b b o
|
27 |
+
examples/complete2/013/complete_000_2_PWW_A018.gex
|
28 |
+
o a = segment o a; p = on_circle p a o; q = intersection_cc q a o p; r = lc_tangent r p a, on_circle r o p ? cong p q p r
|
29 |
+
examples/complete2/013/complete_004_6_GDD_FULL_81-109_88.gex
|
30 |
+
o x l = triangle o x l; a = foot a l o x; y = free y; b = foot b l o y; p = mirror p l a; q = mirror q l b; q1 = on_line q1 l a, on_line q1 o y; p1 = on_line p1 o x, on_line p1 l b ? cyclic o p q p1
|
31 |
+
examples/complete2/013/complete_003_6_GDD_FULL_21-40_24.gex
|
32 |
+
q r p = triangle q r p; o1 = circle o1 q r p; s = on_circle s o1 q; y = on_line y q s; o = circle o y p q; x = on_circle x o q; i = on_line i r s, on_line i y x ? eqangle i r i x p r p x
|
33 |
+
examples/complete2/013/complete_003_6_GDD_FULL_21-40_32.gex
|
34 |
+
b c r = triangle b c r; o = circle o b c r; s = on_circle s o b; a = on_line a b r, on_line a c s; m = foot m a r s; n = foot n a b c ? eqangle a b a m a n a c
|
35 |
+
examples/complete2/013/complete_002_6_GDD_FULL_41-60_54.gex
|
36 |
+
a b c = r_triangle a b c; d = foot d a b c; o = midpoint o c b; m = foot m b a o; g = on_line g b m, on_circle g o a; f = on_line f c a, on_line f b m; e = on_line e a d, on_line e b m ? cong e a e b
|
37 |
+
examples/complete2/013/complete_005_Other_ndg1_53.gex
|
38 |
+
a o = segment a o; b = on_circle b o a; c = on_line c a b; e = intersection_tt e b b o c c o; d = intersection_lt d c e a a o ? cong o e o d
|
39 |
+
examples/complete2/013/complete_002_6_GDD_FULL_41-60_56.gex
|
40 |
+
m a b = iso_triangle m a b; o = circle o a b m; d = on_line d m o, on_line d a b; e = on_tline e a a o, on_pline e m a o ? cong m e m d
|
41 |
+
examples/complete2/013/complete_002_6_GDD_FULL_41-60_52.gex
|
42 |
+
e c d = r_triangle e c d; o = midpoint o d c; a = on_tline a c c d, on_tline a e e o; f = on_line f c a, on_line f d e ? cong a e a f
|
43 |
+
examples/complete2/014/complete_008_7_Book_LLL_L053-1.gex
|
44 |
+
b c d = triangle b c d; e = foot e b c d; a = free a; f = foot f a c d; g = midpoint g b a ? cong f g g e
|
45 |
+
examples/complete2/014/complete_007_7_Book_LLL_L058-9.gex
|
46 |
+
a b = segment a b; c = on_bline c a b; d = on_tline d b a b; e = intersection_lt e c d a a b ? cong e c c d
|
47 |
+
examples/complete2/007/complete_003_6_GDD_FULL_more_E015-6.gex
|
48 |
+
a b c = triangle a b c; d = midpoint d a c; e = midpoint e b a; f = midpoint f c b; g = on_pline g d a f, on_pline g f a c ? para c e g b
|
49 |
+
examples/complete2/007/complete_003_6_GDD_FULL_more_E022-9.gex
|
50 |
+
a b c = triangle a b c; d = circumcenter d b a c; e = on_line e a c, angle_bisector e c b a; f = intersection_lc f e d b; g = on_tline g f d f ? para f g a c
|
51 |
+
examples/complete2/007/complete_012_7_Book_00EE_02_E028-2.gex
|
52 |
+
a b c = triangle a b c; d e = square a c d e; g f = square c b g f ? perp d b f a
|
53 |
+
examples/complete2/007/complete_012_7_Book_00EE_05_E051-22.gex
|
54 |
+
a b = segment a b; c = midpoint c b a; d = on_tline d b a b; e = on_line e a d, on_circle e c a; f = on_pline f e a b, on_circle f c e; g = foot g d a f ? cong a f f g
|
55 |
+
examples/complete2/007/complete_005_Other_other_E075-25-sss.gex
|
56 |
+
a b c = triangle a b c; d = midpoint d c b; e = midpoint e a c; f = midpoint f b a; g = foot g a b c ? eqangle d f d e g f g e
|
57 |
+
examples/complete2/007/complete_001_6_GDD_FULL_01-20_01.gex
|
58 |
+
a b c = triangle a b c; d = foot d c a b; e = foot e b a c; f = midpoint f c b; g = midpoint g d e ? perp f g d e
|
59 |
+
examples/complete2/007/complete_000_2_PWW_B016x.gex
|
60 |
+
a b c = triangle a b c; d = midpoint d b c; e = midpoint e c a; f = midpoint f b a; g = parallelogram d a e g ? cong c f g b
|
61 |
+
examples/complete2/007/complete_001_6_GDD_FULL_61-80_66.gex
|
62 |
+
a b c = triangle a b c; d = foot d c a b; e = on_tline e a b c, on_line e c d; f = midpoint f a e; g = midpoint g c b ? perp d g d f
|
63 |
+
examples/complete2/007/complete_016_7_Book_00EE_06_E051-30.gex
|
64 |
+
a b = segment a b; c = midpoint c b a; d = on_circle d c a; e = lc_tangent e d c, on_line e a b; f = angle_bisector f d e a, on_line f a d; g = on_line g e f, on_line g b d ? cong d f d g
|
65 |
+
examples/complete2/007/complete_016_7_Book_00EE_06_E051-24.gex
|
66 |
+
a b = segment a b; c = midpoint c b a; d = on_circle d c a; e = on_line e b d; f = circle f d c e; g = on_pline g e a b, on_circle g f c ? cong g e c b
|
67 |
+
examples/complete2/007/complete_013_7_Book_00EE_11_E077-37.gex
|
68 |
+
a b = segment a b; c = midpoint c b a; d = on_circle d c b; e = foot e d a b; f = lc_tangent f d c; g = on_line g d f ? eqangle d f d a d a d e
|
69 |
+
examples/complete2/007/complete_013_7_Book_00EE_07_E059-54-1.gex
|
70 |
+
a b = segment a b; c = midpoint c b a; d = mirror d c b; e = on_circle e c a, on_circle e b c; f = on_tline f b a b, on_line f a e; g = on_line g b f, on_line g d e ? cong e g g f
|
71 |
+
examples/complete2/007/complete_008_ex-gao_ex160_e201f.gex
|
72 |
+
a b c = triangle a b c; d e = square a b d e; f = foot f a b c; g = on_line g a f, eqdistance g a b c ? perp g b c d
|
73 |
+
examples/complete2/000/complete_016_ex-gao_gao_M_M020-52.gex
|
74 |
+
a b = segment a b; c = on_circle c a b; d = on_circle d a b; e = on_circle e a b, on_pline e d b c ? cong d c e b
|
75 |
+
examples/complete2/000/complete_010_Other_gao_L_L190-7.gex
|
76 |
+
a b = segment a b; c = nsquare c b a; d = psquare d a b; e = on_line e b d; f = foot f e b c; g = foot g e d c ? cong e a g f
|
77 |
+
examples/complete2/000/complete_007_7_Book_LLL_L017-11.gex
|
78 |
+
c a = segment c a; b = on_tline b c c a; d = foot d c a b ? eqangle a c a d c b c d
|
79 |
+
examples/complete2/000/complete_016_ex-gao_gao_M_M024-94.gex
|
80 |
+
a b c = triangle a b c; d = on_circle d a b, on_circle d c b; e = on_line e d a, on_circle e a d; f = on_line f d c, on_circle f c d ? coll e b f
|
81 |
+
examples/complete2/000/complete_007_7_Book_LLL_L054-2-1.gex
|
82 |
+
a b = segment a b; c = on_bline c a b; d = on_line d a c; e = eqdistance e b a d, on_line e b c; f = on_line f a b, on_line f d e; g = on_line g b c, on_pline g d a b ? cong d f f e
|
83 |
+
examples/complete2/000/complete_007_7_Book_LLL_L057-1-1.gex
|
84 |
+
a b = segment a b; m = midpoint m a b; c = on_circle c m a; d = angle_mirror d a b c; e = midpoint e a d; f = on_line f b d, on_line f a c ? para c e b d
|
85 |
+
examples/complete2/000/complete_016_ex-gao_gao_M_M021-64.gex
|
86 |
+
a b = segment a b; d = midpoint d b a; c = on_circle c b a, on_circle c a b; e = on_line e a c, on_circle e d a; f = on_circle f d a, on_line f b c ? cong a e e f
|
87 |
+
examples/complete2/000/complete_007_7_Book_LLL_L057-3-2.gex
|
88 |
+
a b = segment a b; c = on_bline c a b; e = midpoint e a c; d = on_circle d a c, on_line d a c; f = midpoint f b d ? cong b e b f
|
89 |
+
examples/complete2/000/complete_004_6_GDD_FULL_81-109_95.gex
|
90 |
+
a b c = triangle a b c; a1 = midpoint a1 c b; f = circle f a b c; s = on_aline s a b c a a1, on_line s b c; p = on_circle p f a, on_line p a a1; q = on_circle q f a, on_line q a s ? para b c p q
|
91 |
+
examples/complete2/000/complete_001_6_GDD_FULL_01-20_02.gex
|
92 |
+
a b c = triangle a b c; a1 = midpoint a1 c b; b1 = midpoint b1 c a; c1 = midpoint c1 b a; o = circle o a b c ? perp o a1 b1 c1
|
93 |
+
examples/complete2/000/complete_004_6_GDD_FULL_81-109_96.gex
|
94 |
+
a b c = triangle a b c; a1 = midpoint a1 c b; n = on_line n a a1; g = foot g n a b; h = foot h n a c; s = on_aline s a b c a a1, on_line s b c ? perp g h a s
|
95 |
+
examples/complete2/000/complete_007_7_Book_LLL_L194-2.gex
|
96 |
+
a b = segment a b; c = on_bline c a b; d = on_bline d a b; e = on_line e c d, on_line e a b ? cong a e e b
|
97 |
+
examples/complete2/000/complete_017_ex-gao_gao_L_L022-1.gex
|
98 |
+
a b = segment a b; c = on_bline c a b; d = on_line d a c; e = on_circle e c d, on_line e b c ? cong a e b d
|
99 |
+
examples/complete2/000/complete_016_ex-gao_gao_M_M09-14.gex
|
100 |
+
c b = segment c b; d = midpoint d c b; a = free a; e = midpoint e b a; f = midpoint f c a; g = on_line g a d, on_line g e f ? cong e g g f
|
101 |
+
examples/complete2/009/complete_014_7_Book_00EE_09_E071-4.gex
|
102 |
+
a b = segment a b; c = midpoint c b a; d = on_circle d c a; e = lc_tangent e d c, on_line e a b; f = foot f a d e ? eqangle a f a d a d a b
|
103 |
+
examples/complete2/009/complete_013_7_Book_00EE_10_E072-13.gex
|
104 |
+
a b c = triangle a b c; d = foot d b a c; e = foot e a b c; f = foot f b d e ? eqangle b a b d b c b f
|
105 |
+
examples/complete2/009/complete_014_7_Book_00EE_09_E071-2.gex
|
106 |
+
a b c = triangle a b c; d = midpoint d c b; e = foot e b a c; f = foot f c a b ? eqangle a b a c e f e d
|
107 |
+
examples/complete2/009/complete_014_7_Book_00EE_09_E071-1.gex
|
108 |
+
a b c = triangle a b c; e = on_line e a b, on_circle e a c; d = angle_bisector d b a c, on_line d b c; f = on_pline f e b c, on_line f a c ? eqangle e d e c e c e f
|
109 |
+
examples/complete2/009/complete_017_ex-gao_ex160_4_e10.gex
|
110 |
+
a b c d = isquare a b c d; e = on_line e b d, on_circle e b c; f = on_tline f e b d, on_line f d c ? cong e d c f
|
111 |
+
examples/complete2/009/complete_003_6_GDD_FULL_more_E022-12.gex
|
112 |
+
a b c = triangle a b c; e = circumcenter e a b c; d = on_line d a b, angle_bisector d a c b; f = on_tline f c c e, on_pline f d a c ? cong c f d b
|
113 |
+
examples/complete2/009/complete_001_6_GDD_FULL_61-80_69.gex
|
114 |
+
d a b = r_triangle d a b; c = midpoint c b a; e = circle e a c d; f = circle f b d c ? perp e d d f
|
115 |
+
examples/complete2/009/complete_012_7_Book_00EE_05_E051-19.gex
|
116 |
+
a b c = triangle a b c; d = circumcenter d a c b; e = on_line e b c; f = on_circle f d a, angle_bisector f a c e ? cong a f f b
|
117 |
+
examples/complete2/009/complete_016_7_Book_00EE_06_E051-32.gex
|
118 |
+
a b c = triangle a b c; d = eq_triangle d a b; e = eq_triangle e a c; f = eq_triangle f c b ? para e d c f
|
119 |
+
examples/complete2/009/complete_013_7_Book_00EE_10_E074-23.gex
|
120 |
+
a b c = triangle a b c; d = foot d a b c; e = circumcenter e b a c; f = angle_bisector f b a c, on_circle f e a ? eqangle e a a f f a a d
|
121 |
+
examples/complete2/009/complete_011_Other_Auxiliary_aux2_trapezoid.gex
|
122 |
+
a b c d = trapezoid a b c d; e = midpoint e d a; f = on_pline f e a b, on_line f b c ? midp f b c
|
123 |
+
examples/complete2/009/complete_016_7_Book_00EE_06_E057-37.gex
|
124 |
+
a b c = triangle a b c; d = eq_triangle d a b; e = eq_triangle e a c; f = parallelogram c e d f ? cong b f f c
|
125 |
+
examples/complete2/008/complete_004_6_GDD_FULL_81-109_100.gex
|
126 |
+
a c = segment a c; b = eq_triangle b c a; e = mirror e c b; d = mirror d b e; f = foot f d a b ? perp a c c f
|
127 |
+
examples/complete2/008/complete_005_Other_ndgs_02.gex
|
128 |
+
b a c = triangle b a c; d = foot d b a c; e = foot e c a b; f = intersection_ll f b d c e ? perp b c a f
|
129 |
+
examples/complete2/008/complete_008_ex-gao_ex160_205.gex
|
130 |
+
c a b = r_triangle c a b; d = midpoint d c a; f = midpoint f c b; e = on_line e a b, on_circle e d c ? perp d e e f
|
131 |
+
examples/complete2/008/complete_015_7_Book_00EE_08_E061-62.gex
|
132 |
+
a b = segment a b; c = on_circle c a b; e = on_circle e a b; d = on_circle d a b, on_circle d b c; f = on_circle f b c, on_line f c e ? cong e d e f
|
133 |
+
examples/complete2/008/complete_015_7_Book_00EE_06_E051-31.gex
|
134 |
+
a b c = triangle a b c; d = parallelogram a b c d; e = eq_triangle e a b; f = eq_triangle f b c ? cong d e d f
|
135 |
+
examples/complete2/008/complete_011_7_Book_00EE_03_E037-22.gex
|
136 |
+
c a b = risos c a b; e = midpoint e b a; d = on_line d a b, on_circle d b c; f = on_line f a c, on_circle f c e ? perp a c f d
|
137 |
+
examples/complete2/008/complete_011_7_Book_00EE_03_E037-21.gex
|
138 |
+
a b = segment a b; c = on_circle c a b; d = lc_tangent d c a, on_line d a b; e = on_line e a b, on_circle e a b; f = on_pline f a c e, on_line f c d ? perp f b a b
|
139 |
+
examples/complete2/008/complete_011_7_Book_00EE_04_E051-5.gex
|
140 |
+
c a = segment c a; b = eq_triangle b c a; d = circumcenter d c a b; e = on_pline e d a c, on_line e a b; f = on_pline f d b c, on_line f a b ? cong a e e f
|
141 |
+
examples/complete2/008/complete_003_6_GDD_FULL_more_E009-1.gex
|
142 |
+
a c = segment a c; b = on_tline b c a c; d = on_dia d b a, on_circle d a c; e = on_line e b c, on_circle e a b; f = on_line f b d, on_circle f a b ? para c d e f
|
143 |
+
examples/complete2/008/complete_011_7_Book_00EE_03_E039-28.gex
|
144 |
+
a b = segment a b; c = on_circle c a b; d = on_circle d a b, on_circle d c b; e = mirror e d c; f = on_circle f a b, on_line f b e ? coll d a f
|
145 |
+
examples/complete2/008/complete_011_7_Book_00EE_03_E040-28-1.gex
|
146 |
+
c a b = iso_triangle c a b; d = on_line d b c; e = circle e a b d; f = on_circle f e a, on_line f a c ? para a b f d
|
147 |
+
examples/complete2/008/complete_018_ex-gao_ex160_4_004.gex
|
148 |
+
b a c = triangle b a c; d = on_line d b c, on_circle d a b; e = on_tline e c a c, on_tline e b a b; f = on_tline f d a d, on_line f c e ? cong e c c f
|
149 |
+
examples/complete2/008/complete_014_7_Book_00EE_07_E059-50.gex
|
150 |
+
a b = segment a b; c = on_circle c a b; d = on_circle d a b; e = circle e c a d; f = on_line f b c, on_circle f e a ? cong d f f b
|
151 |
+
examples/complete2/008/complete_013_7_Book_00EE_07_E057-44.gex
|
152 |
+
c a b = iso_triangle c a b; d = foot d a b c; e = foot e b a c; f = on_line f a d, on_line f b e ? cong f a f b
|
153 |
+
examples/complete2/001/complete_006_7_Book_LLL_L046-16.gex
|
154 |
+
a b = segment a b; c = on_line c a b; d = on_circle d c a, on_circle d a c; e = on_aline e b a d c a, on_aline e c a d a b; f = on_line f c d, on_line f a e; g = on_line g b d, on_line g c e ? cong c f c g
|
155 |
+
examples/complete2/001/complete_016_ex-gao_gao_M_M010-32.gex
|
156 |
+
b c a = triangle b c a; d = on_pline d a b c, on_pline d c a b; e = on_line e b c; f = on_line f a d, on_pline f e a b; g = on_line g a e, on_line g b f; h = on_line h c f, on_line h d e ? para h g d a
|
157 |
+
examples/complete2/001/complete_016_ex-gao_gao_M_M010-26.gex
|
158 |
+
b d = segment b d; e = midpoint e b d; c = free c; a = on_pline a d b c, on_pline a b d c; f = on_line f c d; g = on_line g a b, on_line g e f; h = on_line h e f, on_line h a d; i = on_line i b c, on_line i e f ? cong f h g i
|
159 |
+
examples/complete2/001/complete_016_ex-gao_gao_C_C101.gex
|
160 |
+
a b c = triangle a b c; e = foot e a b c; f = foot f c a b; d = on_bline d a c, on_bline d a b; g = on_line g c f, on_line g a e; h = on_line h c f, on_circle h d c ? cong g f f h
|
161 |
+
examples/complete2/001/complete_016_ex-gao_gao_C_C100.gex
|
162 |
+
a c = segment a c; b = on_tline b c c a; e = on_circle e b c; d = on_circle d a c, on_circle d b c; f = on_line f c e, on_circle f a c; g = on_line g e b, on_circle g b e ? coll d f g
|
163 |
+
examples/complete2/001/complete_016_ex-gao_gao_L_L182-6.gex
|
164 |
+
a b c = triangle a b c; e = midpoint e b c; d = on_line d a b; f = midpoint f d c; g = midpoint g b a; h = midpoint h g f; i = on_line i a b, on_line i e h ? cong a i i d
|
165 |
+
examples/complete2/001/complete_016_ex-gao_gao_C_C111.gex
|
166 |
+
a d c = triangle a d c; b = on_pline b a d c; e = on_line e a d; f = on_line f a c, on_pline f e a b; g = on_line g b d, on_line g e f; h = on_line h b c, on_line h e f ? cong e f g h
|
167 |
+
examples/complete2/001/complete_016_ex-gao_gao_L_L025-5.gex
|
168 |
+
a b = segment a b; c = on_bline c a b; d = on_line d a c; e = on_circle e c d, on_line e b c; f = on_line f b d, on_line f a e ? eqangle a c c f f c c b
|
169 |
+
examples/complete2/001/complete_017_ex-gao_gao_L_L189-2.gex
|
170 |
+
a b = segment a b; c = on_bline c a b; e = midpoint e c a; f = midpoint f b c; d = on_pline d b a c, on_pline d a b c; g = midpoint g d b; h = midpoint h a d ? perp h e e f
|
171 |
+
examples/complete2/001/complete_016_ex-gao_gao_L_L182-5.gex
|
172 |
+
c d a = triangle c d a; b = on_pline b c d a, on_pline b a d c; e = on_line e c d; f = on_line f a b, on_pline f c a e; g = on_line g b e, on_line g c f; h = on_line h d f, on_line h a e ? cong g e f h
|
173 |
+
examples/complete2/001/complete_017_ex-gao_gao_L_L189-1.gex
|
174 |
+
a b = segment a b; c = on_tline c b a b; d = on_tline d c b c, on_tline d a a b; e = midpoint e c d; f = midpoint f b c; g = midpoint g a b; h = midpoint h a d ? cong h g h e
|
175 |
+
examples/complete2/001/complete_016_ex-gao_gao_C_C109.gex
|
176 |
+
b d a = triangle b d a; c = on_pline c d a b; e = on_line e b d, on_line e a c; f = on_line f a d, on_pline f e a b; g = on_line g b c, on_line g e f ? cong f e e g
|
177 |
+
examples/complete2/001/complete_016_ex-gao_gao_L_LL153-1.gex
|
178 |
+
c a d = triangle c a d; e = foot e c a d; b = free b; f = foot f b a d; g = midpoint g c b; h = midpoint h e f ? cong g e g f
|
179 |
+
examples/complete2/001/complete_010_Other_gao_Y_yL182-4.gex
|
180 |
+
c d = segment c d; e = midpoint e c d; a = free a; b = on_pline b c d a, on_pline b a d c; f = midpoint f a b; g = on_line g a c, on_line g b e; h = on_line h d f, on_line h a c ? cong a h h g
|
181 |
+
examples/complete2/006/complete_012_7_Book_00EE_02_E028-3.gex
|
182 |
+
c a b = risos c a b; d = midpoint d b a; e = on_line e b c; f = circle f d b e; g = on_line g a e, on_circle g f b ? perp c g a e
|
183 |
+
examples/complete2/006/complete_003_6_GDD_FULL_more_E022-11.gex
|
184 |
+
a b c = triangle a b c; d = circumcenter d a b c; f = foot f d a b; e = on_tline e c c d, on_tline e b b d; g = on_line g d f, on_line g a c ? para g e a b
|
185 |
+
examples/complete2/006/complete_010_Other_Auxiliary_aux2_e04f.gex
|
186 |
+
a b c d = trapezoid a b c d; e = midpoint e c a; f = midpoint f d b; g = on_line g e f, on_line g a d ? midp g a d
|
187 |
+
examples/complete2/006/complete_004_6_GDD_FULL_81-109_98.gex
|
188 |
+
a b c = triangle a b c; e = on_line e a b; d = circle d a b c; f = on_circle f d a, on_aline f c b a c e; g = on_circle g d c, on_line g c e ? para a b g f
|
189 |
+
examples/complete2/006/complete_001_6_GDD_FULL_61-80_72.gex
|
190 |
+
a b c = triangle a b c; d = circle d a b c; e = on_circle e d a; f = foot f e a c; g = foot g e a b ? simtri e f g e c b
|
191 |
+
examples/complete2/006/complete_013_7_Book_00EE_11_E075-26.gex
|
192 |
+
a b = segment a b; c = mirror c a b; d = mirror d b c; e = midpoint e c b; f = on_circle f e c, on_dia f a e; g = on_line g a f ? eqangle b f a f c f e f
|
193 |
+
examples/complete2/006/complete_015_7_Book_00EE_06_E057-38.gex
|
194 |
+
c a b = r_triangle c a b; d = foot d c a b; e = angle_bisector e c a b, on_line e b c; g = foot g e a b; f = on_line f c d, on_line f a e ? cong c e c f
|
195 |
+
examples/complete2/006/complete_014_7_Book_00EE_07_E059-47.gex
|
196 |
+
a b c d = rectangle a b c d; e = on_line e b d, on_line e a c; f = midpoint f e d; g = midpoint g e a ? cong f c g b
|
197 |
+
examples/complete2/006/complete_014_7_Book_00EE_07_E059-53.gex
|
198 |
+
a b c = triangle a b c; d = circle d c a b; e = circle e c d b; f = on_line f a b, on_circle f e b; g = on_line g a c, on_circle g e b ? cong g b g a
|
199 |
+
examples/complete2/006/complete_003_6_GDD_FULL_more_E023-15.gex
|
200 |
+
a b c d = quadrangle a b c d; e = on_line e a c; g = on_pline g e a b, on_line g b c; f = on_pline f e a d, on_line f c d ? para b d g f
|
201 |
+
examples/complete2/011/complete_002_6_GDD_FULL_01-20_12.gex
|
202 |
+
a b c = triangle a b c; o = circle o a b c; d = on_tline d b a c, on_circle d o a; f = midpoint f b a; e = on_line e a c, on_line e b d ? perp f e c d
|
203 |
+
examples/complete2/011/complete_002_6_GDD_FULL_01-20_05.gex
|
204 |
+
a b c = triangle a b c; h = orthocenter h a b c; o = circumcenter o a b c; c1 = circumcenter c1 a b h; b1 = circumcenter b1 a h c; a1 = circumcenter a1 b h c ? perp a1 o b1 c1
|
205 |
+
examples/complete2/011/complete_003_6_GDD_FULL_21-40_34.gex
|
206 |
+
a b c = triangle a b c; h = orthocenter h a b c; o = circle o h b c; p = on_tline p h c h, on_circle p o b ? para a h b p
|
207 |
+
examples/complete2/011/complete_004_6_GDD_FULL_81-109_99.gex
|
208 |
+
a b c = triangle a b c; m = free m; n = on_aline n a c b a m; q = foot q m a b; p = foot p m a c ? perp a n p q
|
209 |
+
examples/complete2/011/complete_003_6_GDD_FULL_21-40_35.gex
|
210 |
+
a b c = triangle a b c; d = foot d c a b; e = foot e b a c; o = circle o a b c; k = on_circle k o c, on_line k c d; h = on_line h c d, on_line h b e ? cong a k a h
|
211 |
+
examples/complete2/011/complete_003_6_GDD_FULL_21-40_31.gex
|
212 |
+
a b c = triangle a b c; c1 = midpoint c1 b a; b1 = midpoint b1 c a; o = circle o a b c; p = on_line p o c1, on_line p a c; q = on_line q o b1, on_line q a b ? cyclic q b c p
|
213 |
+
examples/complete2/011/complete_002_6_GDD_FULL_41-60_41.gex
|
214 |
+
a b c = triangle a b c; i = incenter i a b c; y = foot y i a c; l = foot l i b c; x = foot x b a i ? coll x y l
|
215 |
+
examples/complete2/011/complete_002_6_GDD_FULL_41-60_43.gex
|
216 |
+
c a b = r_triangle c a b; f e = square a b f e; p = on_line p b e, on_line p a f ? eqangle c a c p c p c b
|
217 |
+
examples/complete2/011/complete_002_6_GDD_FULL_41-60_51.gex
|
218 |
+
a b c = triangle a b c; o = circle o a b c; d = on_tline d b a c, on_circle d o a; e = on_circle e o d, on_line e d o ? para b e a c
|
219 |
+
examples/complete2/011/complete_002_6_GDD_FULL_41-60_44.gex
|
220 |
+
a b c = triangle a b c; o = circle o a b c; d = on_circle d o a; a1 = on_tline a1 a a b, on_line a1 c d; c1 = on_tline c1 c c d, on_line c1 a b ? para d b a1 c1
|
221 |
+
examples/complete2/010/complete_004_6_GDD_FULL_21-40_29.gex
|
222 |
+
a b c = triangle a b c; d = foot d a b c; q = foot q d a b; p = foot p d a c ? cyclic b q p c
|
223 |
+
examples/complete2/010/complete_002_6_GDD_FULL_01-20_10.gex
|
224 |
+
a b c d = quadrangle a b c d; e = on_line e b c, on_line e a d; o1 = circle o1 c d e; o = circle o e b a; p = on_line p c d, on_line p a b; q = on_circle q o1 c, on_circle q o a ? cyclic p d q a
|
225 |
+
examples/complete2/010/complete_013_7_Book_00EE_10_E072-15.gex
|
226 |
+
b c a = triangle b c a; d = lc_tangent d c a, lc_tangent d b a; e = on_circle e a c, on_dia e a d ? eqangle b e b a b a b c
|
227 |
+
examples/complete2/010/complete_011_7_Book_00EE_04_E051-6.gex
|
228 |
+
a b c = triangle a b c; d = eq_triangle d c a; e = eq_triangle e b a ? cong d e c b
|
229 |
+
examples/complete2/010/complete_012_7_Book_00EE_05_E051-20.gex
|
230 |
+
a b = segment a b; d = midpoint d a b; c = on_tline c b a b; e = on_line e a c, on_circle e d b; f = lc_tangent f e d, on_line f b c ? cong f c f b
|
231 |
+
examples/complete2/010/complete_011_7_Book_00EE_03_E037-20.gex
|
232 |
+
a b = segment a b; c = midpoint c a b; d = on_circle d c a; e = lc_tangent e d c, angle_mirror e b a d ? perp a e e d
|
233 |
+
examples/complete2/010/complete_012_7_Book_00EE_11_E076-32.gex
|
234 |
+
c a b = r_triangle c a b; d = midpoint d b c; e = foot e c a d ? eqangle a b b c d e e b
|
235 |
+
examples/complete2/010/complete_000_3_JAR_JAR02-new_fig214.gex
|
236 |
+
a b c = triangle a b c; d = intersection_pp d a b c c a b; e = intersection_ll e a c b d ? cong a e e c
|
237 |
+
examples/complete2/010/complete_003_6_GDD_FULL_more_E021-3.gex
|
238 |
+
a b = segment a b; c = on_circle c a b; e = intersection_lc e a a c; d = on_tline d c a c, on_tline d b a b ? para a d b e
|
239 |
+
examples/complete2/010/complete_013_7_Book_00EE_10_E074-22.gex
|
240 |
+
a b = segment a b; c = on_circle c a b; d = on_line d a c; e = on_line e a b, on_circle e a d; f = on_line f b d, on_line f c e ? eqangle a b a f a f a c
|
241 |
+
examples/complete2/010/complete_001_6_GDD_FULL_01-20_20.gex
|
242 |
+
a b c = triangle a b c; d = foot d a b c; e = foot e b a c; h = on_line h a d, on_line h b e; g = foot g h a b ? eqangle g e g h g h g d
|
243 |
+
examples/complete2/010/complete_002_6_GDD_FULL_41-60_57.gex
|
244 |
+
a b c = triangle a b c; d = foot d a b c; o = midpoint o a d; e = on_line e a b, on_circle e o d; f = on_line f a c, on_circle f o d ? cyclic b c e f
|
245 |
+
examples/complete2/010/complete_010_Other_Auxiliary_ye_aux_ppara.gex
|
246 |
+
a b c d = eq_trapezoid a b c d; e = on_pline e b a d, on_line e c d ? eqangle a d a b b a b c
|
247 |
+
examples/complete2/003/complete_003_6_GDD_FULL_more_E013-3.gex
|
248 |
+
a b = segment a b; d = on_tline d b a b; e = on_circle e a b; f = on_line f d e, on_circle f a b; g = midpoint g e f; c = on_circle c a b, on_dia c d a; h = intersection_lc h g a c ? para e f b h
|
249 |
+
examples/complete2/003/complete_005_Other_ndgs_01.gex
|
250 |
+
b c a = triangle b c a; d = intersection_cc d b a c; e = on_circle e b c; g = intersection_lc g e a d; f = on_circle f b c; h = intersection_lc h f a c ? para g h e f
|
251 |
+
examples/complete2/003/complete_013_7_Book_00EE_10_E072-12.gex
|
252 |
+
b a c = triangle b a c; d = on_circle d a b, on_circle d c b; e = on_tline e d b d, on_circle e a b; f = on_circle f c d, on_line f d e; h = on_circle h a b, on_line h b f; g = on_circle g c b, on_line g b e ? eqangle d h d b d b d g
|
253 |
+
examples/complete2/003/complete_010_Other_Auxiliary_ye_aux_wang3.gex
|
254 |
+
a b c d = isquare a b c d; f = angle_bisector f a d b, on_line f a c; g = foot g c d f; e = on_line e b d, on_line e a c; h = on_line h c g, on_line h a d; i = on_line i b d, on_line i c g; x = midpoint x a h ? cong a x e i
|
255 |
+
examples/complete2/003/complete_003_6_GDD_FULL_more_E022-8.gex
|
256 |
+
b a c = triangle b a c; d = on_circle d a b, on_circle d c b; e = on_circle e c b; f = intersection_lc f e a d; g = intersection_lc g e a b; h = on_tline h e c e ? para h e g f
|
257 |
+
examples/complete2/003/complete_008_ex-gao_ex160_206.gex
|
258 |
+
a b c = triangle a b c; e d = square b a e d; f g = square a c f g; h = on_line h b e, on_line h a d; i = on_pline i e a g, on_pline i g a e ? perp c h h i
|
259 |
+
examples/complete2/003/complete_013_7_Book_00EE_11_E077-38.gex
|
260 |
+
a b c = triangle a b c; d = circumcenter d a b c; e = lc_tangent e b d; f = angle_bisector f e b c, on_circle f d a; g = foot g f b c; h = foot h f b e ? eqangle b a a f f a a c
|
261 |
+
examples/complete2/003/complete_004_6_GDD_FULL_81-109_84.gex
|
262 |
+
a b c = triangle a b c; d = midpoint d b a; e = midpoint e c b; f = midpoint f a c; g = circle g d e f; h = on_tline h e e g, on_line h a b; i = on_line i e h, on_line i a c ? cyclic b h c i
|
263 |
+
examples/complete2/003/complete_003_6_GDD_FULL_more_E022-10.gex
|
264 |
+
a b = segment a b; c = on_circle c a b; e = on_circle e a b; d = on_circle d a b; f = on_line f c e, on_line f b d; g = circumcenter g e f b; h = on_tline h f f g ? para h f c d
|
265 |
+
examples/complete2/003/complete_011_7_Book_00EE_03_E037-25.gex
|
266 |
+
a b = segment a b; c = midpoint c b a; d = on_tline d c a b, on_circle d c a; e = on_circle e c d, on_line e d c; f = on_line f a b; g = on_line g c d, on_circle g c f; h = on_circle h c e, on_line h e f; i = on_line i b g, on_circle i c a ? perp e h b i
|
267 |
+
examples/complete2/003/complete_016_7_Book_00EE_06_E051-25.gex
|
268 |
+
a b c = triangle a b c; d = foot d b a c; e = foot e c a b; g = circumcenter g b c a; h = intersection_lc h g g a; f = on_line f b d, on_line f c e; i = on_line i b c, on_line i f h ? cong f i i h
|
269 |
+
examples/complete2/003/complete_013_7_Book_00EE_10_E074-20.gex
|
270 |
+
a b c = triangle a b c; d = on_line d a b; e = foot e d b c; f = foot f e a b; g = on_line g b c; h = foot h g a b; i = foot i h b c ? eqangle d g d h f i f d
|
271 |
+
examples/complete2/003/complete_017_ex-gao_ex160_4_003.gex
|
272 |
+
a b = segment a b; c = on_circle c a b; e = on_line e b c; d = on_tline d c a c, on_tline d b a b; f = on_tline f e a e, on_line f c d; g = on_line g e f, on_circle g a b; h = on_line h e f, on_line h b d ? cong c f h b
|
273 |
+
examples/complete2/003/complete_015_7_Book_00EE_08_E059-56.gex
|
274 |
+
a b c = triangle a b c; d = foot d b a c; e = foot e a b c; f = on_line f b d, on_line f a e; g = circle g b f a; h = on_line h b c, on_circle h g a; i = on_line i a c, on_circle i g a ? cong f c f i
|
275 |
+
examples/complete2/003/complete_014_7_Book_00EE_07_E059-52.gex
|
276 |
+
a b = segment a b; d = on_circle d a b; c = on_tline c a a b, on_circle c a b; e = on_tline e c a c, on_tline e b a b; f = lc_tangent f d a, on_line f c e; g = on_line g b e, on_line g d f; h = on_line h c e, on_line h b d ? cong h e d g
|
277 |
+
examples/complete2/004/complete_002_6_GDD_FULL_01-20_13.gex
|
278 |
+
a b c = triangle a b c; d = parallelogram a b c d; e = foot e b a c; f = foot f a b d; g = foot g d a c; h = foot h c b d ? para e f g h
|
279 |
+
examples/complete2/004/complete_006_Other_Auxiliary_E092-5.gex
|
280 |
+
a b c = triangle a b c; d = parallelogram a b c d; e = angle_bisector e d a b, on_dia e a d; f = foot f b a e; g = foot g c b f; h = on_line h d e, on_line h c g ? para e g a b
|
281 |
+
examples/complete2/004/complete_004_6_GDD_FULL_81-109_86.gex
|
282 |
+
a b c = triangle a b c; d = midpoint d c a; e = midpoint e b c; f = midpoint f a b; g = angle_bisector g a b c, on_line g d f; h = on_line h b g, on_line h d e ? cong d g d h
|
283 |
+
examples/complete2/004/complete_011_7_Book_00EE_03_E037-26.gex
|
284 |
+
a b c d = isquare a b c d; e = on_line e b c; g = on_line g d c, on_line g a e; f = on_line f b d, on_line f a e; h = circle h g e c ? perp f c c h
|
285 |
+
examples/complete2/004/complete_016_7_Book_00EE_06_E051-27.gex
|
286 |
+
a b = segment a b; c = midpoint c b a; d = on_circle d c a; e = angle_bisector e d c a, on_circle e c a; f = foot f e a b; g = intersection_ll g a d e f; h = intersection_ll h a d b e ? cong a g g e
|
287 |
+
examples/complete2/004/complete_001_6_GDD_FULL_61-80_73.gex
|
288 |
+
a b c = triangle a b c; d = circle d a b c; e = on_circle e d a; f = foot f e a c; g = foot g e a b; h = on_circle h d e, on_line h e g ? para g f h c
|
289 |
+
examples/complete2/004/complete_014_7_Book_00EE_07_E057-42.gex
|
290 |
+
a b c = triangle a b c; d = midpoint d a c; e = midpoint e b a; f = midpoint f c b; g = on_line g a b; h = on_pline h d f g, on_line h a b ? cong h a g e
|
291 |
+
examples/complete2/005/complete_005_Other_ndgs_03.gex
|
292 |
+
b a c = triangle b a c; d = foot d b a c; e = foot e c a b; f = midpoint f c b; g = foot g f d e ? cong g e g d
|
293 |
+
examples/complete2/005/complete_000_rebuilt_example_9point.gex
|
294 |
+
a b c = triangle a b c; d = foot d a b c; e = midpoint e b a; f = midpoint f c b; g = midpoint g a c; o = circumcenter o e f g ? cyclic d g e f
|
295 |
+
examples/complete2/005/complete_013_7_Book_00EE_11_E081-2.gex
|
296 |
+
a b = segment a b; d = on_circle d a b; c = on_circle c a b, on_circle c b d; e = foot e d b c; f = intersection_lc f e a d; g = on_line g d e, on_circle g e f ? cong f c g d
|
297 |
+
examples/complete2/005/complete_002_6_GDD_FULL_41-60_58.gex
|
298 |
+
a b c = triangle a b c; d = circle d a b c; e = on_circle e d a; f = on_line f a b, on_line f c e; g = on_pline g f a e, on_line g b c ? eqangle f g f b c f c b
|
299 |
+
examples/complete2/005/complete_016_7_Book_00EE_06_E051-26.gex
|
300 |
+
a b = segment a b; c = on_circle c a b; e = on_line e b c; d = lc_tangent d b a, lc_tangent d c a; f = on_tline f e a e, on_line f c d; g = on_line g e f, on_line g b d ? cong g e e f
|
301 |
+
examples/complete2/005/complete_001_6_GDD_FULL_61-80_61.gex
|
302 |
+
a b c = triangle a b c; e = midpoint e b a; d = circle d a b c; f = on_line f d e; g = on_line g b c, on_circle g f a ? simtri a d f a c g
|
303 |
+
examples/complete2/005/complete_017_ex-gao_ex160_4_e03a_lratio.gex
|
304 |
+
c a b = iso_triangle c a b; e = midpoint e b c; f = on_line f a b, on_circle f e b; d x = trisegment d x c b; g = on_line g c f, on_line g a d ? cong c g g f
|
305 |
+
examples/complete2/005/complete_008_ex-gao_ex160_e122.gex
|
306 |
+
a b = segment a b; c = on_circle c a b; d = on_circle d a b; e = on_circle e a b, on_pline e d b c; f = on_circle f a b; g = on_circle g a b, on_pline g d c f ? para g b e f
|
307 |
+
examples/complete2/002/complete_007_7_Book_LLL_yL251-1.gex
|
308 |
+
a d b = triangle a d b; c = free c; e = on_line e a d; f = on_line f a b; g = on_line g a c; h = on_line h a d; i = on_line i a c, on_pline i h e g; j = on_line j a b, on_pline j i f g ? para e f h j
|
309 |
+
examples/complete2/002/complete_017_ex-gao_ex160_4_e12.gex
|
310 |
+
a b c d = quadrangle a b c d; e = midpoint e c b; f = midpoint f d c; g = midpoint g a d; h = midpoint h b a; i = on_line i e g, on_line i f h ? midp i h f
|
311 |
+
examples/complete2/002/complete_013_7_Book_00EE_10_E073-18.gex
|
312 |
+
a b c = triangle a b c; d = parallelogram a b c d; e = on_line e b d; f = foot f e a b; g = foot g e b c; h = on_line h e f, on_line h c d; i = on_line i e g, on_line i a d ? para h i g f
|
313 |
+
examples/complete2/002/complete_011_7_Book_00EE_03_E043-3.gex
|
314 |
+
a b = segment a b; c = on_circle c a b; f = on_circle f a b; e = on_line e b c, on_dia e f a; g = intersection_lc g e a f; d = lc_tangent d b a, lc_tangent d c a; h = on_line h c d, on_line h e f; i = on_line i e f, on_line i b d ? cong c h b i
|
315 |
+
examples/complete2/002/complete_012_7_Book_00EE_02_E028-2-1.gex
|
316 |
+
a b c = triangle a b c; e d = square a c e d; f g = square c b f g; h = midpoint h a e; i = midpoint i b g; j = midpoint j a b ? perp h j j i
|
317 |
+
examples/complete2/002/complete_008_ex-gao_ex160_e124.gex
|
318 |
+
b a c = triangle b a c; e = on_circle e a b; d = on_circle d a b, on_circle d c b; h = on_tline h e a e, on_circle h c b; g = on_circle g c d, on_line g d e; f = on_circle f c b, on_line f b e; i = on_circle i c h, on_line i h e ? para g f h i
|
319 |
+
examples/complete2/000/complete_004_6_GDD_FULL_81-109_106.gex
|
320 |
+
q4 q1 q3 q0 q2 = pentagon q4 q1 q3 q0 q2; p0 = on_line p0 q4 q1, on_line p0 q0 q2; p4 = on_line p4 q4 q1, on_line p4 q3 q0; p3 = on_line p3 q3 q0, on_line p3 q4 q2; p2 = on_line p2 q1 q3, on_line p2 q4 q2; p1 = on_line p1 q1 q3, on_line p1 q0 q2; o0 = circle o0 q0 p0 p4; o1 = circle o1 p1 q1 p0; o4 = circle o4 p4 p3 q4; o3 = circle o3 p3 p2 q3; o2 = circle o2 p1 p2 q2; m0 = on_circle m0 o0 q0, on_circle m0 o1 q1; m4 = on_circle m4 o0 q0, on_circle m4 o4 q4; m3 = on_circle m3 o4 q4, on_circle m3 o3 q3; m2 = on_circle m2 o3 q3, on_circle m2 o2 q2; m1 = on_circle m1 o2 q2, on_circle m1 o1 q1 ? cyclic m4 m3 m2 m1
|
321 |
+
examples/complete2/unsolved2/complete_010_Other_Auxiliary_ye_aux_think2.gex
|
322 |
+
c a b = iso_triangle c a b; d = on_line d a c; e = on_line e b c, eqdistance e b d a; f = on_line f a b, on_line f d e; g = on_pline g f a c, on_line g b c ? midp f d e
|
323 |
+
examples/complete2/unsolved2/complete_012_7_Book_00EE_02_E023-21.gex
|
324 |
+
a b = segment a b; c = on_tline c b a b; d = on_circle d a b; f = midpoint f c b; g = on_line g d f, on_circle g a b; h = intersection_lc h c a g; e = on_line e c d, on_circle e a b ? para b c h e
|
325 |
+
examples/complete2/unsolved2/complete_006_7_Book_LLL_yL252-6.gex
|
326 |
+
a c d = triangle a c d; b = on_pline b c d a, on_pline b a d c; e = on_line e a c; g = on_line g a b, on_pline g e a d; f = on_line f a d, on_pline f e c d; h = on_line h e g, on_line h c d; i = on_line i b c, on_line i e f ? para f g h i
|
327 |
+
examples/complete2/unsolved2/complete_015_7_Book_00EE_08_E059-59.gex
|
328 |
+
a b c = triangle a b c; d = angle_bisector d b a c; e = on_pline e c b d, on_pline e b c d; f = on_line f b e, on_line f a c; g = on_line g c e, on_line g a b ? cong b g c f
|
329 |
+
examples/complete2/unsolved2/complete_013_7_Book_00EE_10_E072-16.gex
|
330 |
+
a b c = triangle a b c; d = parallelogram a b c d; e = on_line e c d; f = on_line f a d, eqdistance f c a e; g = on_line g a e, on_line g c f ? eqangle g a g b g b g c
|
331 |
+
examples/complete2/unsolved2/complete_003_6_GDD_FULL_more_E023-19.gex
|
332 |
+
c a b = r_triangle c a b; d = foot d c a b; e = on_line e c d, angle_bisector e b a c; f = on_line f a b, angle_bisector f d c b; g = on_line g b c, on_line g a e ? para e f c b
|
333 |
+
examples/complete2/unsolved2/complete_010_Other_Auxiliary_ye_aux_ll43.gex
|
334 |
+
a b = segment a b; c = on_dia c a b, on_bline c a b; d = midpoint d a c; e = foot e c b d; f = on_line f c e, on_line f a b ? eqangle d c d b d f d a
|
335 |
+
examples/complete2/unsolved2/complete_010_Other_Auxiliary_aux2_22.gex
|
336 |
+
c a b = iso_triangle c a b; d = on_line d a c; e = on_line e b c, eqdistance e b a d; f = on_line f a b, on_line f d e ? cong d f e f
|
337 |
+
examples/complete2/unsolved2/complete_014_7_Book_00EE_09_E066-04.gex
|
338 |
+
a b = segment a b; c = lc_tangent c b a; d = midpoint d b c; e = on_circle e a b; f = on_line f d e, on_circle f a b ? eqangle e c c d d f f c
|
339 |
+
examples/complete2/unsolved2/complete_014_7_Book_00EE_08_E061-66.gex
|
340 |
+
a b = segment a b; c = s_angle b a c 60; d = foot d a b c; e = foot e b a c; g = circumcenter g b c a; f = on_line f a d, on_line f b e ? cong a f a g
|
341 |
+
examples/complete2/unsolved2/complete_011_7_Book_00EE_03_E037-24.gex
|
342 |
+
a b c = triangle a b c; d = circle d b a c; e = lc_tangent e a d, on_line e b c; f = angle_bisector f b e a, on_line f a b; g = on_line g a c, on_line g e f; h = angle_bisector h b a c, on_line h b c ? perp f e a h
|
343 |
+
examples/complete2/unsolved2/complete_014_7_Book_00EE_08_E061-65.gex
|
344 |
+
a b c d = isquare a b c d; e = s_angle c d e 15, s_angle d c e -15; f = reflect f e a c ? contri e a b a b e
|
345 |
+
examples/complete2/unsolved2/complete_012_7_Book_00EE_11_E076-31.gex
|
346 |
+
a b c = triangle a b c; d = angle_bisector d c b a, on_line d a c; e = angle_bisector e a c b, on_line e a b; f = on_line f d e, on_line f b c; g = on_line g a b ? eqangle a g a f a f a c
|
347 |
+
examples/complete2/unsolved2/complete_010_Other_Auxiliary_ye_aux_y1.gex
|
348 |
+
a b c = triangle a b c; d = angle_bisector d a b c, on_dia d b c; e = angle_bisector e b a c, on_dia e a c ? para d e a b
|
349 |
+
examples/complete2/unsolved2/complete_004_6_GDD_FULL_21-40_40.gex
|
350 |
+
a b c = triangle a b c; i = incenter i a b c; e = on_pline e i a b, on_line e a c ? cong e i e a
|
351 |
+
examples/complete2/unsolved2/complete_014_7_Book_00EE_09_E069-8.gex
|
352 |
+
a b c = triangle a b c; d = parallelogram a b c d; e = eqangle2 e d a b ? eqangle d a a e e c c d
|
353 |
+
examples/complete2/unsolved2/complete_011_7_Book_00EE_04_E051-9.gex
|
354 |
+
a b = segment a b; c = s_angle b a c 30; d = mirror d b c; e = foot e d a b ? cong d e a c
|
355 |
+
examples/complete2/unsolved2/complete_003_6_GDD_FULL_21-40_27.gex
|
356 |
+
a b c = triangle a b c; h = orthocenter h a b c; o = circumcenter o a b c; o3 = circumcenter o3 a h b; o1 = circumcenter o1 b h c; o2 = circumcenter o2 c h a ? cong h o1 h o2
|
357 |
+
examples/complete2/unsolved2/complete_017_ex-gao_ex160_4_e08.gex
|
358 |
+
a b c = triangle a b c; d = on_line d b c, angle_bisector d b a c; e = on_pline e d a c, on_line e a b; f = on_pline f e b c, on_line f a c ? cong e a f c
|
359 |
+
examples/complete2/unsolved2/complete_015_7_Book_00EE_06_E051-29.gex
|
360 |
+
a b = segment a b; c = on_circle c a b; d = on_circle d a b; e = on_circle e a b; f = on_line f b e, on_line f c d; g = on_line g d e, on_pline g f b c; h = on_circle h a b, on_dia h g a ? cong f g g h
|
361 |
+
examples/complete2/unsolved2/complete_002_6_GDD_FULL_41-60_42.gex
|
362 |
+
a b c = triangle a b c; d = incenter d a b c; e = foot e a b c; f = foot f b a d; g = foot g c a d; h = midpoint h c b ? cyclic e f g h
|
363 |
+
examples/complete2/unsolved2/complete_014_7_Book_00EE_08_E061-63f.gex
|
364 |
+
a b = segment a b; c = midpoint c b a; d = s_angle b a d 30, on_circle d c a; e = lc_tangent e d c, on_line e a b ? cong d a d e
|
365 |
+
examples/complete2/unsolved2/complete_007_7_Book_LLL_yL198-1.gex
|
366 |
+
c d = segment c d; e = midpoint e c d; b = free b; f = midpoint f b c; a = eqdistance a d c b, on_pline a b d c; g = midpoint g a b; h = midpoint h d a ? cong h e e f
|
367 |
+
examples/complete2/unsolved/complete_015_7_Book_00EE_08_E061-61.gex
|
368 |
+
a b = segment a b; c = on_circle c a b; d = on_circle d a b; e = lc_tangent e c a, lc_tangent e d a; f = lc_tangent f b a, on_line f c e; h = on_pline h c b f, on_line h b d; g = on_line g d e, on_line g b f; i = on_line i b e, on_line i c h ? cong c i i h
|
369 |
+
examples/complete2/unsolved/complete_008_ex-gao_ex160_204.gex
|
370 |
+
a b c = triangle a b c; d = circumcenter d a b c; e = on_circle e d a; f = on_line f a b, on_line f c e; h = on_line h b c, angle_bisector h a f c; g = on_line g a e, on_line g b c; i = on_line i a b, angle_bisector i a g b; j = on_line j f h, on_line j g i; k = on_line k a e, on_line k f h ? perp g i f h
|
371 |
+
examples/complete2/unsolved/complete_005_Other_unsolved_65.gex
|
372 |
+
a b c = triangle a b c; e = on_line e a b; f = on_pline f e b c, on_line f a c; d = circle d a b c; g = circle g a e f ? coll a g d
|
373 |
+
examples/complete2/unsolved/complete_008_ex-gao_ex160_005.gex
|
374 |
+
a b c = triangle a b c; d = on_line d b c, angle_bisector d b a c; e = on_line e a b; f = on_line f a c, eqdistance f c b e; g = midpoint g f e; h = midpoint h c b ? para g h a d
|
375 |
+
examples/complete2/unsolved/complete_006_Other_ndgTest_65.gex
|
376 |
+
a b c = triangle a b c; e = on_line e a b; f = intersection_lp f a c e c b; d = circle d a b c; g = circle g a e f ? coll a g d
|
377 |
+
examples/complete2/unsolved/complete_005_Other_unsolved_E051-7.gex
|
378 |
+
a b c = triangle a b c; d = on_line d a b; e = angle_bisector e c b a, on_line e a c; f = on_pline f e a b, on_line f b c; g = angle_bisector g c b d, on_line g e f ? cong e f f g
|
379 |
+
examples/complete2/unsolved/complete_005_Other_unsolved_E046-10.gex
|
380 |
+
a b c d = isquare a b c d; e = midpoint e b a; f = on_line f a b; g = on_tline g e d e, angle_bisector g c b f ? cong d e e g
|
381 |
+
examples/complete2/unsolved/ex-gao_ex160_103.gex
|
382 |
+
c b y = triangle c b y; a = foot a c b y; x = angle_bisector x c b y; d = foot d a b c; e = on_line e b x, on_line e c a; f = foot f e b c; g = on_line g b x, on_line g a d ? cong f g f e
|
383 |
+
examples/complete2/unsolved/ex-gao_ex160_104.gex
|
384 |
+
c a y = triangle c a y; b = foot b c a y; x = angle_bisector x c a y; e = foot e b a c; d = on_line d a x, on_line d c b; f = on_line f a x, on_line f b e; g = on_line g c b, on_pline g f a c ? cong b d c g
|
385 |
+
examples/complete2/unsolved/complete_005_Other_unsolved_109f.gex
|
386 |
+
a b d = triangle a b d; e = on_line e a d; c = on_line c a b; f = on_line f b e, on_line f c d; g = circle g d e f; h = circle h a c d; i = circle i b c f; j = circle j b a e ? cyclic i j h g
|
387 |
+
examples/complete2/unsolved/complete_005_Other_unsolved_E046-7.gex
|
388 |
+
a b c = triangle a b c; d = midpoint d c a; e = angle_bisector e b a d, on_line e b d; f = on_pline f b c e, on_line f a c ? cong b a c f
|
389 |
+
examples/complete2/unsolved/complete_008_ex-gao_ex160_e121.gex
|
390 |
+
a b = segment a b; d = midpoint d b a; c = on_tline c a a b; e = on_circle e c a; f = on_line f d e, on_circle f c a; g = on_circle g c f, on_line g f b; h = on_line h b e, on_circle h c a ? para a b g h
|
391 |
+
examples/complete2/unsolved/complete_018_ex-gao_ex160_4_010.gex
|
392 |
+
a b c d = isquare a b c d; e = mirror e a b; f = midpoint f b a; g = on_tline g f d f, angle_bisector g c b e; h = foot h g a b ? cong d f g f
|
393 |
+
examples/complete2/unsolved/complete_005_Other_unsolved_82.gex
|
394 |
+
a b c = triangle a b c; o = incenter o a b c; i = foot i c a o; e = on_tline e a a o; j = foot j c a e; l = foot l c b o ? coll i l j
|
395 |
+
examples/complete2/unsolved/complete_014_7_Book_00EE_07_E057-41.gex
|
396 |
+
a b c = triangle a b c; d = foot d c a b; e = free e; f = on_circle f c e; g = on_line g d f, on_circle g c e; h = on_line h d e, on_circle h c e; i = on_line i f h, on_line i a b; j = on_line j e g, on_line j a b ? cong j d d i
|
397 |
+
examples/complete2/unsolved/complete_015_7_Book_00EE_08_E059-55.gex
|
398 |
+
a b = segment a b; c = on_circle c a b; d = lc_tangent d b a, lc_tangent d c a; e = on_circle e a b; f = on_line f d e, on_circle f a b; g = angle_bisector g f b e, on_line g d e ? cong d b d g
|
399 |
+
examples/complete2/unsolved/complete_005_Other_unsolved_E073-17.gex
|
400 |
+
a b c = triangle a b c; o = circumcenter o a b c; p = lc_tangent p a o, on_line p b c; d = on_circle d p a; e = intersection_lc e d o b; f = intersection_lc f d o c ? para e f p d
|
401 |
+
examples/complete2/unsolved/complete_015_7_Book_00EE_06_E056-33.gex
|
402 |
+
a b = segment a b; c = on_tline c a a b, on_circle c a b; e = s_angle b a e 60, on_circle e a b; d = s_angle b a d 30, on_circle d a b; f = on_line f a e, on_line f b c; g = on_line g a d, on_line g b c ? cong c f g b
|
403 |
+
examples/complete2/unsolved/complete_005_Other_unsolved_E074-24.gex
|
404 |
+
a b = segment a b; c = on_tline c a a b; d = foot d a b c; e = midpoint e d a; f = on_line f b e, on_line f a c; g = foot g f b c; h = midpoint h c a; i = on_tline i f a c, on_circle i h a ? cong f i f g
|
405 |
+
examples/complete2/unsolved1/complete_008_7_Book_LLL_L057-3.gex
|
406 |
+
a b = segment a b; c = on_bline c a b; e = midpoint e a c; d = mirror d c a; f = midpoint f b d ? cong e b f b
|
407 |
+
examples/complete2/unsolved1/complete_006_7_Book_LLL_L046-17.gex
|
408 |
+
c a b = risos c a b; d = midpoint d b a; e = on_line e a b; f = foot f e a c; g = foot g e b c ? cong d f d g
|
409 |
+
examples/complete2/unsolved1/complete_008_7_Book_LLL_L057-2.gex
|
410 |
+
c a b = triangle a b c; d = angle_bisector d b a c; e = foot e c a d; f = intersection_lp f a c e a b ? cong f e f c
|
411 |
+
examples/complete2/unsolved1/complete_008_ex-gao_ex160_e102.gex
|
412 |
+
a b c = triangle a b c; d = on_line d a c, angle_bisector d c b a; e = on_line e a b, angle_bisector e b c a; f = foot f a c e; g = foot g a b d ? para g f b c
|
413 |
+
examples/complete2/unsolved1/complete_012_7_Book_00EE_11_E075-27f.gex
|
414 |
+
a b c = triangle a b c; d = foot d c a b; e = on_line e c d; f = on_line f b e, on_line f a c; g = on_line g a e, on_line g b c ? eqangle d f d c d c d g
|
415 |
+
examples/complete2/unsolved1/complete_006_7_Book_LLL_L091-13.gex
|
416 |
+
b c d = triangle b c d; e = midpoint e c d; a = eqdistance a d c b, on_pline a b d c; f = midpoint f b a ? perp a b e f
|
417 |
+
examples/complete2/unsolved1/complete_010_Other_gao_Y_yL157-1.gex
|
418 |
+
a b d = triangle a b d; e = midpoint e d a; c = angle_bisector c a b d, on_dia c a b ? para c e b d
|
419 |
+
examples/complete2/unsolved1/complete_008_7_Book_LLL_L055-5.gex
|
420 |
+
d b a = triangle d b a; c = angle_bisector c d a b, angle_bisector c d b a; e = on_line e b c, on_tline e d b c; f = on_line f a c, on_tline f d a c ? para e f a b
|
421 |
+
examples/complete2/unsolved1/complete_013_7_Book_00EE_11_E075-29.gex
|
422 |
+
a b = segment a b; d = midpoint d b a; f = on_circle f d a; c = intersection_lt c a b f d f; e = midpoint e c a; g = on_tline g b a b, on_circle g e a ? eqangle f c f g g f g c
|
423 |
+
examples/complete2/unsolved1/complete_012_7_Book_00EE_05_E051-14-1.gex
|
424 |
+
a b c d = quadrangle a b c d; e = midpoint e b a; f = foot f a c d; g = foot g b c d ? cong e f e g
|
425 |
+
examples/complete2/unsolved1/complete_007_7_Book_LLL_L057-3-1.gex
|
426 |
+
a b = segment a b; c = on_bline c a b; e = midpoint e a c; f = mirror f b e; d = on_circle d a c, on_line d a c ? cong f b b d
|
427 |
+
examples/complete2/unsolved1/complete_001_6_GDD_FULL_61-80_80.gex
|
428 |
+
a b c = triangle a b c; o = circle o a b c; u = angle_bisector u b a c, on_line u b c; t = on_tline t a a o, on_line t b c ? cong t a t u
|
429 |
+
examples/complete2/unsolved1/complete_008_ex-gao_ex160_e213.gex
|
430 |
+
a b c = triangle a b c; d = midpoint d a b; e = midpoint e c a; f = on_line f d e, angle_bisector f c b a ? perp a f b f
|
431 |
+
examples/complete2/unsolved1/complete_011_7_Book_00EE_04_E051-2.gex
|
432 |
+
a b c = triangle a b c; d = on_line d a b; e = on_pline e d b c, on_line e a c; f = on_line f c d, on_line f b e; g = on_line g a f, on_line g b c ? midp g b c
|
433 |
+
examples/complete2/unsolved1/complete_007_7_Book_LLL_L043-5.gex
|
434 |
+
a b = segment a b; c = on_circle c a b; d = on_circle d a b; e = on_circle e a b; f = intersection_ll f b c d e; g = intersection_ll g c d b e; h = angle_bisector h d f b; i = angle_bisector i c g b; j = intersection_ll j f h g i ? perp g i f h
|
435 |
+
examples/complete2/unsolved1/complete_001_6_GDD_FULL_61-80_71.gex
|
436 |
+
a b c = triangle a b c; o = circle o a b c; e = on_pline e a b c, on_circle e o a; f = foot f e a b; g = foot g e a c ? para f g a o
|
437 |
+
examples/complete2/unsolved1/complete_007_7_Book_LLL_L043-5-1.gex
|
438 |
+
a b = segment a b; c = on_circle c a b; d = on_circle d a b; e = on_circle e a b; f = intersection_ll f b c d e; g = intersection_ll g c d b e; h = angle_bisector h d f b; i = angle_bisector i c g b; j = intersection_ll j f h g i; k = intersection_ll k d e g i; l = intersection_ll l g i b c ? perp g i f h
|
439 |
+
examples/complete2/unsolved1/complete_011_7_Book_00EE_04_E051-8.gex
|
440 |
+
a b c = triangle a b c; d = angle_bisector d a c b, on_line d a b; e = on_pline e d b c, on_line e a c; f = on_pline f e a b, on_line f b c ? cong c e f b
|
441 |
+
examples/complete2/unsolved1/complete_013_7_Book_00EE_10_E072-8.gex
|
442 |
+
a b c = triangle a b c; d = angle_bisector d b a c, on_line d b c; f = on_line f b c, on_bline f a d; e = on_bline e a d, on_line e a d ? eqangle a b a f c f c a
|
443 |
+
examples/complete2/unsolved1/complete_003_6_GDD_FULL_more_E023-14.gex
|
444 |
+
a b c = triangle a b c; d = midpoint d b a; e = angle_bisector e c d a, on_line e a c; f = angle_bisector f c d b, on_line f c b ? para e f a b
|
445 |
+
examples/complete2/unsolved1/complete_010_Other_gao_Y_yL182-1.gex
|
446 |
+
a c d = triangle a c d; b = on_pline b c d a, on_pline b a d c; e = on_line e a c; f = shift f c a e ? para d e f b
|
447 |
+
examples/complete2/unsolved1/complete_008_ex-gao_ex160_e120.gex
|
448 |
+
a b = segment a b; c = midpoint c b a; d = on_circle d c a; e = on_tline e a a b, on_tline e d c d; f = on_tline f b a b, on_line f d e; g = on_line g b e, on_line g a f ? para d g a e
|
449 |
+
new_unsolved/0.gex
|
450 |
+
c d b = triangle c d b; e = midpoint e c d; a = eqdistance a d c b, on_pline a b d c; f = midpoint f b a ? perp a b e f
|
451 |
+
new_unsolved/1.gex
|
452 |
+
a b c d = eq_trapezoid a b c d ? eqangle a d a b b a b c
|
453 |
+
examples/complete2/unsolved1/complete_009_Other_paper_Thebault_t5.gex
|
454 |
+
a b c = triangle a b c; d = circle d a b c; e = on_line e b c; f g h i = 2l1c f g h i a b e d; j k l m = 2l1c j k l m a c e d; n = incenter n a b c ? coll m n i
|
455 |
+
examples/complete2/unsolved/complete_013_7_Book_00EE_10_E072-11.gex
|
456 |
+
a b = segment b a; c = on_line c a b; d = on_circle d a b; e = on_circle e a b; g = on_line g d e, on_circle g c b; f = on_line f d e, on_circle f c b ? eqangle b e b f b g b d
|
457 |
+
examples/complete2/unsolved2/complete_015_7_Book_00EE_06_E051-28.gex
|
458 |
+
b c = segment b c; a = on_tline a b b c; d = on_circle d c b; e g = e5128 e g a b c d ? cong a g g b
|
459 |
+
examples/complete2/unsolved2/complete_010_Other_Auxiliary_ye_aux_think.gex
|
460 |
+
c a b = iso_triangle c a b; d e f = 3peq d e f c a b ? cong d a b e
|
461 |
+
examples/complete2/unsolved/morley.gex
|
462 |
+
a b c = triangle a b c; d e = trisect d e b a c; f g = trisect f g c b a; h i = trisect h i a c b; j = intersection_ll j b f c i; k = intersection_ll k a e c h; l = intersection_ll l a d b g ? cong j l j k
|
ag4masses/alphageometry/lm_inference.py
ADDED
@@ -0,0 +1,189 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright 2023 DeepMind Technologies Limited
|
2 |
+
#
|
3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4 |
+
# you may not use this file except in compliance with the License.
|
5 |
+
# You may obtain a copy of the License at
|
6 |
+
#
|
7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8 |
+
#
|
9 |
+
# Unless required by applicable law or agreed to in writing, software
|
10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
+
# See the License for the specific language governing permissions and
|
13 |
+
# limitations under the License.
|
14 |
+
# ==============================================================================
|
15 |
+
|
16 |
+
"""Wrapper for language modeling inference implemented in Meliad."""
|
17 |
+
from typing import Any, Dict
|
18 |
+
|
19 |
+
import jax
|
20 |
+
import models # pylint: disable=unused-import
|
21 |
+
import t5.data
|
22 |
+
from transformer import inference_utils
|
23 |
+
|
24 |
+
|
25 |
+
np = jax.numpy
|
26 |
+
|
27 |
+
|
28 |
+
Trainer = inference_utils.Trainer
|
29 |
+
|
30 |
+
MetricsOutput = Dict[str, Any] # Metrics output by model.
|
31 |
+
|
32 |
+
|
33 |
+
parse_gin_configuration = inference_utils.parse_gin_configuration
|
34 |
+
|
35 |
+
|
36 |
+
class LanguageModelInference:
|
37 |
+
"""Meliad wrapper for LM inference."""
|
38 |
+
|
39 |
+
def __init__(self, vocab_path: str, load_dir: str, mode='beam_search'):
|
40 |
+
self.vocab = t5.data.SentencePieceVocabulary(vocab_path)
|
41 |
+
|
42 |
+
# This task won't be pulling from a dataset.
|
43 |
+
def null_iter_fn() -> None:
|
44 |
+
return None
|
45 |
+
|
46 |
+
process_summaries_f = inference_utils.models.process_summaries_function(
|
47 |
+
self.vocab
|
48 |
+
)
|
49 |
+
|
50 |
+
trainer = inference_utils.training_loop.Trainer(
|
51 |
+
get_training_dataset_iterator=null_iter_fn,
|
52 |
+
get_test_dataset_iterator=None,
|
53 |
+
pretty_print_input_function=None,
|
54 |
+
process_summaries_function=process_summaries_f,
|
55 |
+
load_dir=load_dir,
|
56 |
+
workdir='', # Don't log or save checkpoints.
|
57 |
+
replicate_mode=False,
|
58 |
+
) # Run on a single device at batch size 1.
|
59 |
+
self.trainer = trainer
|
60 |
+
|
61 |
+
# Create and initialize the model.
|
62 |
+
(tstate, _, imodel, prngs) = trainer.initialize_model()
|
63 |
+
self.imodel = imodel
|
64 |
+
self.batch_size = imodel.task_config.batch_size
|
65 |
+
|
66 |
+
self.n = imodel.num_heads
|
67 |
+
self.h = imodel.head_size
|
68 |
+
|
69 |
+
# Create an inference task.
|
70 |
+
writers = {}
|
71 |
+
self.task = trainer.create_training_task(mode, imodel, prngs, writers) # pylint: disable=too-many-function-args
|
72 |
+
|
73 |
+
# Register any additional actions.
|
74 |
+
# Actions are cleared first for use with colab.
|
75 |
+
inference_utils.training_loop.clear_interstep_callbacks()
|
76 |
+
inference_utils.training_loop.register_interstep_callbacks()
|
77 |
+
self.tstate = tstate
|
78 |
+
|
79 |
+
# some default parameters.
|
80 |
+
eos = [0] * 1024
|
81 |
+
for idx in self.encode_list(['.', ';']):
|
82 |
+
eos[idx] = 1
|
83 |
+
|
84 |
+
self.eos = np.array(eos, dtype=np.bfloat16)
|
85 |
+
self.mask = jax.numpy.ones([1024], dtype=np.bfloat16)
|
86 |
+
|
87 |
+
def decode(self, ids: list[int]) -> str:
|
88 |
+
return self.vocab.decode(ids)
|
89 |
+
|
90 |
+
def decode_list(self, tokens: list[int]) -> list[str]:
|
91 |
+
return [self.decode([tok]) for tok in tokens]
|
92 |
+
|
93 |
+
def encode(self, inputs_str: str) -> list[int]:
|
94 |
+
return self.vocab.encode(inputs_str)
|
95 |
+
|
96 |
+
def encode_list(self, inputs_strs: list[str]) -> list[int]:
|
97 |
+
result = [self.vocab.encode(x) for x in inputs_strs]
|
98 |
+
assert all([len(x) == 1 for x in result]), [
|
99 |
+
self.decode(x) for x in result if len(x) != 1
|
100 |
+
]
|
101 |
+
return [x[0] for x in result]
|
102 |
+
|
103 |
+
def call(
|
104 |
+
self,
|
105 |
+
inputs: np.ndarray,
|
106 |
+
dstate: tuple[dict[str, np.ndarray], ...] = None,
|
107 |
+
eos: np.ndarray = None,
|
108 |
+
mask: np.ndarray = None,
|
109 |
+
) -> MetricsOutput:
|
110 |
+
"""Call the meliad model."""
|
111 |
+
batch_size, length = inputs.shape
|
112 |
+
inputs = jax.numpy.pad(inputs, [(0, 0), (0, 1024 - length)])
|
113 |
+
|
114 |
+
if eos is None:
|
115 |
+
eos = self.eos
|
116 |
+
if mask is None:
|
117 |
+
mask = self.mask
|
118 |
+
|
119 |
+
x = {'targets': inputs, 'length': length, 'eos': eos, 'mask': mask}
|
120 |
+
|
121 |
+
if dstate is not None:
|
122 |
+
x['start_of_sequence'] = jax.numpy.array([False] * batch_size)
|
123 |
+
else:
|
124 |
+
dstate = tuple(
|
125 |
+
[{ # this dummy value will never be used.
|
126 |
+
'current_index': np.array([0] * batch_size, dtype=np.int32),
|
127 |
+
'keys': np.zeros(
|
128 |
+
(batch_size, 2048, self.n, self.h), dtype=np.bfloat16
|
129 |
+
),
|
130 |
+
'values': np.zeros(
|
131 |
+
(batch_size, 2048, self.n, self.h), dtype=np.bfloat16
|
132 |
+
),
|
133 |
+
'recurrent_kvq': None,
|
134 |
+
'relative_position_bias': np.zeros(
|
135 |
+
(batch_size, self.n, 1, 1024), dtype=np.bfloat16
|
136 |
+
),
|
137 |
+
}]
|
138 |
+
* 12
|
139 |
+
)
|
140 |
+
x['start_of_sequence'] = jax.numpy.array([True] * batch_size)
|
141 |
+
|
142 |
+
x['dstate'] = dstate
|
143 |
+
_, metrics_np = self.task.run_step(self.tstate, x, 0)
|
144 |
+
return metrics_np
|
145 |
+
|
146 |
+
def beam_decode(
|
147 |
+
self,
|
148 |
+
inputs: str,
|
149 |
+
eos_tokens: np.ndarray = None,
|
150 |
+
mask_tokens: np.ndarray = None,
|
151 |
+
dstate: dict[str, np.ndarray] = None,
|
152 |
+
) -> MetricsOutput:
|
153 |
+
"""Beam search."""
|
154 |
+
inputs = jax.numpy.array([self.vocab.encode(inputs)] * self.batch_size)
|
155 |
+
|
156 |
+
eos = self.eos
|
157 |
+
if eos_tokens is not None:
|
158 |
+
eos_ids = self.encode_list(eos_tokens)
|
159 |
+
eos = np.array(
|
160 |
+
[1 if idx in eos_ids else 0 for idx in range(1024)], dtype=np.bfloat16
|
161 |
+
).reshape((1, 1, 1024))
|
162 |
+
|
163 |
+
mask = self.mask
|
164 |
+
if mask_tokens is not None:
|
165 |
+
mask_ids = self.encode_list(mask_tokens)
|
166 |
+
mask = np.array(
|
167 |
+
[0 if idx in mask_ids else 1 for idx in range(1024)],
|
168 |
+
dtype=np.bfloat16,
|
169 |
+
).reshape((1, 1, 1024))
|
170 |
+
|
171 |
+
metrics_np = self.call(inputs, dstate=dstate, eos=eos, mask=mask)
|
172 |
+
|
173 |
+
finished_seqs = metrics_np['finished_seqs']
|
174 |
+
finished_scores = metrics_np['finished_scores']
|
175 |
+
|
176 |
+
seqs = []
|
177 |
+
scores = []
|
178 |
+
for seq, score in zip(finished_seqs, finished_scores):
|
179 |
+
seq = self.decode(seq[1:])
|
180 |
+
seqs.append(seq)
|
181 |
+
scores.append(score)
|
182 |
+
|
183 |
+
return {
|
184 |
+
'finished_seqs': finished_seqs,
|
185 |
+
'finished_scores': finished_scores,
|
186 |
+
'seqs_str': seqs,
|
187 |
+
'scores': scores,
|
188 |
+
'dstate': metrics_np['dstate'],
|
189 |
+
}
|
ag4masses/alphageometry/lm_inference_test.py
ADDED
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright 2023 DeepMind Technologies Limited
|
2 |
+
#
|
3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4 |
+
# you may not use this file except in compliance with the License.
|
5 |
+
# You may obtain a copy of the License at
|
6 |
+
#
|
7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8 |
+
#
|
9 |
+
# Unless required by applicable law or agreed to in writing, software
|
10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
+
# See the License for the specific language governing permissions and
|
13 |
+
# limitations under the License.
|
14 |
+
# ==============================================================================
|
15 |
+
|
16 |
+
"""Unit tests for lm_inference.py."""
|
17 |
+
import os
|
18 |
+
import unittest
|
19 |
+
|
20 |
+
from absl import flags
|
21 |
+
from absl.testing import absltest
|
22 |
+
import lm_inference as lm
|
23 |
+
|
24 |
+
|
25 |
+
_DATA_PATH = flags.DEFINE_string('data_path', '', 'path to ckpt and vocab.')
|
26 |
+
_MELIAD_PATH = flags.DEFINE_string(
|
27 |
+
'meliad_path', '', 'path to meliad repository.'
|
28 |
+
) # pylint: disable=line-too-long
|
29 |
+
|
30 |
+
|
31 |
+
class LmInferenceTest(unittest.TestCase):
|
32 |
+
|
33 |
+
@classmethod
|
34 |
+
def setUpClass(cls):
|
35 |
+
super().setUpClass()
|
36 |
+
gin_file = [
|
37 |
+
'base_htrans.gin',
|
38 |
+
'size/medium_150M.gin',
|
39 |
+
'options/positions_t5.gin',
|
40 |
+
'options/lr_cosine_decay.gin',
|
41 |
+
'options/seq_1024_nocache.gin',
|
42 |
+
'geometry_150M_generate.gin',
|
43 |
+
]
|
44 |
+
|
45 |
+
gin_param = [
|
46 |
+
'DecoderOnlyLanguageModelGenerate.output_token_losses=True',
|
47 |
+
'TransformerTaskConfig.batch_size=2',
|
48 |
+
'TransformerTaskConfig.sequence_length=128',
|
49 |
+
'Trainer.restore_state_variables=False',
|
50 |
+
]
|
51 |
+
|
52 |
+
gin_search_paths = [
|
53 |
+
os.path.join(_MELIAD_PATH.value, 'transformer/configs'),
|
54 |
+
os.getcwd(),
|
55 |
+
]
|
56 |
+
|
57 |
+
vocab_path = os.path.join(_DATA_PATH.value, 'geometry.757.model')
|
58 |
+
|
59 |
+
lm.parse_gin_configuration(gin_file, gin_param, gin_paths=gin_search_paths)
|
60 |
+
|
61 |
+
cls.loaded_lm = lm.LanguageModelInference(
|
62 |
+
vocab_path, _DATA_PATH.value, mode='beam_search'
|
63 |
+
)
|
64 |
+
|
65 |
+
def test_lm_decode(self):
|
66 |
+
outputs = LmInferenceTest.loaded_lm.beam_decode(
|
67 |
+
'{S} a : ; b : ; c : ; d : T a b c d 00 T a c b d 01 ? T a d b c'
|
68 |
+
' {F1} x00',
|
69 |
+
eos_tokens=[';'],
|
70 |
+
)
|
71 |
+
self.assertEqual(
|
72 |
+
outputs['seqs_str'],
|
73 |
+
['e : D a b c e 02 D a c b e 03 ;', 'e : C a c e 02 C b d e 03 ;'],
|
74 |
+
)
|
75 |
+
|
76 |
+
def test_lm_score_may_fail_numerically_for_external_meliad(self):
|
77 |
+
outputs = LmInferenceTest.loaded_lm.beam_decode(
|
78 |
+
'{S} a : ; b : ; c : ; d : T a b c d 00 T a c b d 01 ? T a d b c'
|
79 |
+
' {F1} x00',
|
80 |
+
eos_tokens=[';'],
|
81 |
+
)
|
82 |
+
self.assertEqual(
|
83 |
+
outputs['scores'],
|
84 |
+
[-1.18607294559478759765625, -1.10228693485260009765625],
|
85 |
+
)
|
86 |
+
|
87 |
+
|
88 |
+
if __name__ == '__main__':
|
89 |
+
absltest.main()
|
ag4masses/alphageometry/models.py
ADDED
@@ -0,0 +1,178 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright 2023 DeepMind Technologies Limited
|
2 |
+
#
|
3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4 |
+
# you may not use this file except in compliance with the License.
|
5 |
+
# You may obtain a copy of the License at
|
6 |
+
#
|
7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8 |
+
#
|
9 |
+
# Unless required by applicable law or agreed to in writing, software
|
10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
+
# See the License for the specific language governing permissions and
|
13 |
+
# limitations under the License.
|
14 |
+
# ==============================================================================
|
15 |
+
|
16 |
+
"""Transformer language model generate mode."""
|
17 |
+
|
18 |
+
from typing import Any, Tuple
|
19 |
+
import beam_search
|
20 |
+
import decoder_stack
|
21 |
+
import gin
|
22 |
+
import jax
|
23 |
+
import jax.numpy as jnp
|
24 |
+
from transformer import models
|
25 |
+
|
26 |
+
|
27 |
+
@gin.configurable
|
28 |
+
class DecoderOnlyLanguageModelGenerate(models.DecoderOnlyLanguageModel):
|
29 |
+
"""Decoder only language modeling in inference mode."""
|
30 |
+
|
31 |
+
decoder_factory = decoder_stack.DecoderStackGenerate
|
32 |
+
|
33 |
+
num_heads: int = gin.REQUIRED
|
34 |
+
head_size: int = gin.REQUIRED
|
35 |
+
|
36 |
+
def get_fake_input(self) -> dict[str, Any]:
|
37 |
+
fake_input_dict = super().get_fake_input()
|
38 |
+
b = self.task_config.batch_size
|
39 |
+
n = self.num_heads
|
40 |
+
h = self.head_size
|
41 |
+
fake_input_dict.update({
|
42 |
+
'dstate': tuple(
|
43 |
+
[{
|
44 |
+
'current_index': jnp.array([0] * b, dtype=jnp.int32),
|
45 |
+
'keys': jnp.zeros((b, 2048, n, h), dtype=jnp.bfloat16),
|
46 |
+
'values': jnp.zeros((b, 2048, n, h), dtype=jnp.bfloat16),
|
47 |
+
'recurrent_kvq': None,
|
48 |
+
'relative_position_bias': jnp.zeros(
|
49 |
+
(b, n, 1, 1024), dtype=jnp.bfloat16
|
50 |
+
),
|
51 |
+
}]
|
52 |
+
* 12
|
53 |
+
),
|
54 |
+
'eos': jnp.zeros([1024], dtype=jnp.bfloat16),
|
55 |
+
'mask': jnp.ones([1024], dtype=jnp.bfloat16),
|
56 |
+
'length': 1,
|
57 |
+
'temperature': 1.0,
|
58 |
+
})
|
59 |
+
return fake_input_dict
|
60 |
+
|
61 |
+
def __call__(self, inputs: ...) -> tuple[Any, dict[str, Any]]:
|
62 |
+
# Make sure this code is not used on untested cases.
|
63 |
+
if self.mode not in ['init', 'beam_search']:
|
64 |
+
raise ValueError(f'{type(self)} cannot do mode {self.mode}')
|
65 |
+
if self.decoder.supports_generate():
|
66 |
+
raise ValueError(f'{type(self)}.decoder cannot supports_generate()')
|
67 |
+
|
68 |
+
self.decoder(
|
69 |
+
input_tokens=inputs['targets'][:, 0:1],
|
70 |
+
target_tokens=None,
|
71 |
+
start_of_sequence=inputs['start_of_sequence'],
|
72 |
+
)
|
73 |
+
|
74 |
+
b = inputs['targets'].shape[0]
|
75 |
+
no_start_of_seq = jnp.array([False] * b, dtype=jnp.bool_)
|
76 |
+
|
77 |
+
# This fn is used in both beam_search or topk_sampling.
|
78 |
+
def tokens_to_logits_fn(
|
79 |
+
input_token: jnp.ndarray, dstate: tuple[dict[str, jnp.ndarray], ...]
|
80 |
+
) -> tuple[jnp.ndarray, tuple[dict[str, jnp.ndarray], ...]]:
|
81 |
+
(logits, dstate, _) = self.decoder(
|
82 |
+
input_tokens=input_token,
|
83 |
+
target_tokens=None,
|
84 |
+
start_of_sequence=no_start_of_seq,
|
85 |
+
decoder_state=dstate,
|
86 |
+
)
|
87 |
+
return logits[:, -1, :], dstate
|
88 |
+
|
89 |
+
last_token = jax.lax.dynamic_slice_in_dim(
|
90 |
+
inputs['targets'], inputs['length'] - 1, 1, axis=1
|
91 |
+
)
|
92 |
+
|
93 |
+
# last token is used to seed beam_search
|
94 |
+
inputs['targets'] = inputs['targets'][:, 0:-1]
|
95 |
+
dstate = jax.lax.cond(
|
96 |
+
inputs['start_of_sequence'][0],
|
97 |
+
lambda: self.generate(inputs)[0],
|
98 |
+
lambda: inputs['dstate'],
|
99 |
+
)
|
100 |
+
|
101 |
+
# Then we run beam search, init with last_token & dstate.
|
102 |
+
finished_seqs, finished_scores, dstate = beam_search.beam_search_flat(
|
103 |
+
last_token,
|
104 |
+
dstate,
|
105 |
+
tokens_to_logits_fn,
|
106 |
+
max_decode_len=512,
|
107 |
+
eos=inputs['eos'].reshape((1, 1, -1)),
|
108 |
+
mask=inputs['mask'].reshape((1, 1, -1)),
|
109 |
+
)
|
110 |
+
|
111 |
+
return 0.0, {
|
112 |
+
'finished_seqs': finished_seqs,
|
113 |
+
'finished_scores': finished_scores,
|
114 |
+
'dstate': dstate,
|
115 |
+
}
|
116 |
+
|
117 |
+
def generate(
|
118 |
+
self, inputs: ...
|
119 |
+
) -> tuple[tuple[dict[str, jnp.ndarray, ...], ...], jnp.ndarray]:
|
120 |
+
"""Generate an output sequence.
|
121 |
+
|
122 |
+
Args:
|
123 |
+
inputs: the same as argument to _call_.
|
124 |
+
|
125 |
+
Returns:
|
126 |
+
An array of generated tokens of shape (batch_size, sequence_length).
|
127 |
+
"""
|
128 |
+
input_tokens = inputs['targets'] # [b,seq_len]
|
129 |
+
start_of_sequence = inputs['start_of_sequence'] # [b]
|
130 |
+
target_tokens = jnp.pad(input_tokens[:, 1:], [(0, 0), (0, 1)])
|
131 |
+
batch_size = target_tokens.shape[0]
|
132 |
+
|
133 |
+
# Assuming all sequences start at the same time.
|
134 |
+
start0 = inputs['start_of_sequence'][0]
|
135 |
+
dstate = jax.lax.cond(
|
136 |
+
start0,
|
137 |
+
lambda: self.decoder.init_decoder_state_vanilla( # pylint: disable=g-long-lambda
|
138 |
+
1024, start_of_sequence
|
139 |
+
),
|
140 |
+
lambda: inputs['dstate'],
|
141 |
+
)
|
142 |
+
|
143 |
+
first_token = input_tokens[:, 0:1]
|
144 |
+
no_start_of_seq = jnp.array([False] * batch_size, dtype=jnp.bool_)
|
145 |
+
temperature = 1
|
146 |
+
if 'temperature' in inputs:
|
147 |
+
temperature = inputs['temperature']
|
148 |
+
|
149 |
+
num_steps = inputs['length']
|
150 |
+
if self.mode == 'beam_search':
|
151 |
+
num_steps -= 1
|
152 |
+
|
153 |
+
def cond_fn(scan_state) -> jnp.bool_:
|
154 |
+
_, _, i, _ = scan_state
|
155 |
+
return i < num_steps
|
156 |
+
|
157 |
+
def loop_fn(scan_state: Any) -> Tuple[Any, Any, Any, Any]:
|
158 |
+
(dstate, input_token, i, _) = scan_state
|
159 |
+
|
160 |
+
(logits, dstate, _) = self.decoder(
|
161 |
+
input_tokens=input_token,
|
162 |
+
target_tokens=None,
|
163 |
+
start_of_sequence=no_start_of_seq,
|
164 |
+
decoder_state=dstate,
|
165 |
+
)
|
166 |
+
|
167 |
+
logits = logits / temperature
|
168 |
+
output_token = jax.lax.dynamic_slice_in_dim(target_tokens, i, 1, axis=1)
|
169 |
+
|
170 |
+
return (dstate, output_token, i + 1, logits)
|
171 |
+
|
172 |
+
# Scan over the sequence length.
|
173 |
+
dummy_logits = jnp.zeros((batch_size, 1, 1024))
|
174 |
+
initial_scan_state = (dstate, first_token, 0, dummy_logits)
|
175 |
+
dstate, _, _, logits = jax.lax.while_loop(
|
176 |
+
cond_fn, loop_fn, initial_scan_state
|
177 |
+
)
|
178 |
+
return dstate, logits
|
ag4masses/alphageometry/numericals.py
ADDED
@@ -0,0 +1,1923 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright 2023 DeepMind Technologies Limited
|
2 |
+
#
|
3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4 |
+
# you may not use this file except in compliance with the License.
|
5 |
+
# You may obtain a copy of the License at
|
6 |
+
#
|
7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8 |
+
#
|
9 |
+
# Unless required by applicable law or agreed to in writing, software
|
10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
+
# See the License for the specific language governing permissions and
|
13 |
+
# limitations under the License.
|
14 |
+
# ==============================================================================
|
15 |
+
|
16 |
+
"""Numerical representation of geometry."""
|
17 |
+
from __future__ import annotations
|
18 |
+
|
19 |
+
import math
|
20 |
+
from typing import Any, Optional, Union
|
21 |
+
|
22 |
+
import geometry as gm
|
23 |
+
import matplotlib
|
24 |
+
from matplotlib import pyplot as plt
|
25 |
+
import matplotlib.colors as mcolors
|
26 |
+
import numpy as np
|
27 |
+
from numpy.random import uniform as unif # pylint: disable=g-importing-member
|
28 |
+
|
29 |
+
|
30 |
+
matplotlib.use('TkAgg')
|
31 |
+
|
32 |
+
|
33 |
+
ATOM = 1e-12
|
34 |
+
|
35 |
+
|
36 |
+
# Some variables are there for better code reading.
|
37 |
+
# pylint: disable=unused-assignment
|
38 |
+
# pylint: disable=unused-argument
|
39 |
+
# pylint: disable=unused-variable
|
40 |
+
|
41 |
+
# Naming in geometry is a little different
|
42 |
+
# we stick to geometry naming to better read the code.
|
43 |
+
# pylint: disable=invalid-name
|
44 |
+
|
45 |
+
|
46 |
+
class Point:
|
47 |
+
"""Numerical point."""
|
48 |
+
|
49 |
+
def __init__(self, x, y):
|
50 |
+
self.x = x
|
51 |
+
self.y = y
|
52 |
+
|
53 |
+
def __lt__(self, other: Point) -> bool:
|
54 |
+
return (self.x, self.y) < (other.x, other.y)
|
55 |
+
|
56 |
+
def __gt__(self, other: Point) -> bool:
|
57 |
+
return (self.x, self.y) > (other.x, other.y)
|
58 |
+
|
59 |
+
def __add__(self, p: Point) -> Point:
|
60 |
+
return Point(self.x + p.x, self.y + p.y)
|
61 |
+
|
62 |
+
def __sub__(self, p: Point) -> Point:
|
63 |
+
return Point(self.x - p.x, self.y - p.y)
|
64 |
+
|
65 |
+
def __mul__(self, f: float) -> Point:
|
66 |
+
return Point(self.x * f, self.y * f)
|
67 |
+
|
68 |
+
def __rmul__(self, f: float) -> Point:
|
69 |
+
return self * f
|
70 |
+
|
71 |
+
def __truediv__(self, f: float) -> Point:
|
72 |
+
return Point(self.x / f, self.y / f)
|
73 |
+
|
74 |
+
def __floordiv__(self, f: float) -> Point:
|
75 |
+
div = self / f # true div
|
76 |
+
return Point(int(div.x), int(div.y))
|
77 |
+
|
78 |
+
def __str__(self) -> str:
|
79 |
+
return 'P({},{})'.format(self.x, self.y)
|
80 |
+
|
81 |
+
def close(self, point: Point, tol: float = 1e-12) -> bool:
|
82 |
+
return abs(self.x - point.x) < tol and abs(self.y - point.y) < tol
|
83 |
+
|
84 |
+
def midpoint(self, p: Point) -> Point:
|
85 |
+
return Point(0.5 * (self.x + p.x), 0.5 * (self.y + p.y))
|
86 |
+
|
87 |
+
def distance(self, p: Union[Point, Line, Circle]) -> float:
|
88 |
+
if isinstance(p, Line):
|
89 |
+
return p.distance(self)
|
90 |
+
if isinstance(p, Circle):
|
91 |
+
return abs(p.radius - self.distance(p.center))
|
92 |
+
dx = self.x - p.x
|
93 |
+
dy = self.y - p.y
|
94 |
+
return np.sqrt(dx * dx + dy * dy)
|
95 |
+
|
96 |
+
def distance2(self, p: Point) -> float:
|
97 |
+
if isinstance(p, Line):
|
98 |
+
return p.distance(self)
|
99 |
+
dx = self.x - p.x
|
100 |
+
dy = self.y - p.y
|
101 |
+
return dx * dx + dy * dy
|
102 |
+
|
103 |
+
def rotatea(self, ang: float) -> Point:
|
104 |
+
sinb, cosb = np.sin(ang), np.cos(ang)
|
105 |
+
return self.rotate(sinb, cosb)
|
106 |
+
|
107 |
+
def rotate(self, sinb: float, cosb: float) -> Point:
|
108 |
+
x, y = self.x, self.y
|
109 |
+
return Point(x * cosb - y * sinb, x * sinb + y * cosb)
|
110 |
+
|
111 |
+
def flip(self) -> Point:
|
112 |
+
return Point(-self.x, self.y)
|
113 |
+
|
114 |
+
def perpendicular_line(self, line: Line) -> Line:
|
115 |
+
return line.perpendicular_line(self)
|
116 |
+
|
117 |
+
def foot(self, line: Line) -> Point:
|
118 |
+
if isinstance(line, Line):
|
119 |
+
l = line.perpendicular_line(self)
|
120 |
+
return line_line_intersection(l, line)
|
121 |
+
elif isinstance(line, Circle):
|
122 |
+
c, r = line.center, line.radius
|
123 |
+
return c + (self - c) * r / self.distance(c)
|
124 |
+
raise ValueError('Dropping foot to weird type {}'.format(type(line)))
|
125 |
+
|
126 |
+
def parallel_line(self, line: Line) -> Line:
|
127 |
+
return line.parallel_line(self)
|
128 |
+
|
129 |
+
def norm(self) -> float:
|
130 |
+
return np.sqrt(self.x**2 + self.y**2)
|
131 |
+
|
132 |
+
def cos(self, other: Point) -> float:
|
133 |
+
x, y = self.x, self.y
|
134 |
+
a, b = other.x, other.y
|
135 |
+
return (x * a + y * b) / self.norm() / other.norm()
|
136 |
+
|
137 |
+
def dot(self, other: Point) -> float:
|
138 |
+
return self.x * other.x + self.y * other.y
|
139 |
+
|
140 |
+
def sign(self, line: Line) -> int:
|
141 |
+
return line.sign(self)
|
142 |
+
|
143 |
+
def is_same(self, other: Point) -> bool:
|
144 |
+
return self.distance(other) <= ATOM
|
145 |
+
|
146 |
+
|
147 |
+
class Line:
|
148 |
+
"""Numerical line."""
|
149 |
+
|
150 |
+
def __init__(
|
151 |
+
self,
|
152 |
+
p1: Point = None,
|
153 |
+
p2: Point = None,
|
154 |
+
coefficients: tuple[int, int, int] = None,
|
155 |
+
):
|
156 |
+
if p1 is None and p2 is None and coefficients is None:
|
157 |
+
self.coefficients = None, None, None
|
158 |
+
return
|
159 |
+
|
160 |
+
a, b, c = coefficients or (
|
161 |
+
p1.y - p2.y,
|
162 |
+
p2.x - p1.x,
|
163 |
+
p1.x * p2.y - p2.x * p1.y,
|
164 |
+
)
|
165 |
+
|
166 |
+
# Make sure a is always positive (or always negative for that matter)
|
167 |
+
# With a == 0, Assuming a = +epsilon > 0
|
168 |
+
# Then b such that ax + by = 0 with y>0 should be negative.
|
169 |
+
if a < 0.0 or a == 0.0 and b > 0.0:
|
170 |
+
a, b, c = -a, -b, -c
|
171 |
+
|
172 |
+
self.coefficients = a, b, c
|
173 |
+
|
174 |
+
def parallel_line(self, p: Point) -> Line:
|
175 |
+
a, b, _ = self.coefficients
|
176 |
+
return Line(coefficients=(a, b, -a * p.x - b * p.y)) # pylint: disable=invalid-unary-operand-type
|
177 |
+
|
178 |
+
def perpendicular_line(self, p: Point) -> Line:
|
179 |
+
a, b, _ = self.coefficients
|
180 |
+
return Line(p, p + Point(a, b))
|
181 |
+
|
182 |
+
def greater_than(self, other: Line) -> bool:
|
183 |
+
a, b, _ = self.coefficients
|
184 |
+
x, y, _ = other.coefficients
|
185 |
+
# b/a > y/x
|
186 |
+
return b * x > a * y
|
187 |
+
|
188 |
+
def __gt__(self, other: Line) -> bool:
|
189 |
+
return self.greater_than(other)
|
190 |
+
|
191 |
+
def __lt__(self, other: Line) -> bool:
|
192 |
+
return other.greater_than(self)
|
193 |
+
|
194 |
+
def same(self, other: Line) -> bool:
|
195 |
+
a, b, c = self.coefficients
|
196 |
+
x, y, z = other.coefficients
|
197 |
+
return close_enough(a * y, b * x) and close_enough(b * z, c * y)
|
198 |
+
|
199 |
+
def equal(self, other: Line) -> bool:
|
200 |
+
a, b, _ = self.coefficients
|
201 |
+
x, y, _ = other.coefficients
|
202 |
+
# b/a == y/x
|
203 |
+
return b * x == a * y
|
204 |
+
|
205 |
+
def less_than(self, other: Line) -> bool:
|
206 |
+
a, b, _ = self.coefficients
|
207 |
+
x, y, _ = other.coefficients
|
208 |
+
# b/a > y/x
|
209 |
+
return b * x < a * y
|
210 |
+
|
211 |
+
def intersect(self, obj: Union[Line, Circle]) -> tuple[Point, ...]:
|
212 |
+
if isinstance(obj, Line):
|
213 |
+
return line_line_intersection(self, obj)
|
214 |
+
if isinstance(obj, Circle):
|
215 |
+
return line_circle_intersection(self, obj)
|
216 |
+
|
217 |
+
def distance(self, p: Point) -> float:
|
218 |
+
a, b, c = self.coefficients
|
219 |
+
return abs(self(p.x, p.y)) / math.sqrt(a * a + b * b)
|
220 |
+
|
221 |
+
def __call__(self, x: Point, y: Point = None) -> float:
|
222 |
+
if isinstance(x, Point) and y is None:
|
223 |
+
return self(x.x, x.y)
|
224 |
+
a, b, c = self.coefficients
|
225 |
+
return x * a + y * b + c
|
226 |
+
|
227 |
+
def is_parallel(self, other: Line) -> bool:
|
228 |
+
a, b, _ = self.coefficients
|
229 |
+
x, y, _ = other.coefficients
|
230 |
+
return abs(a * y - b * x) < ATOM
|
231 |
+
|
232 |
+
def is_perp(self, other: Line) -> bool:
|
233 |
+
a, b, _ = self.coefficients
|
234 |
+
x, y, _ = other.coefficients
|
235 |
+
return abs(a * x + b * y) < ATOM
|
236 |
+
|
237 |
+
def cross(self, other: Line) -> float:
|
238 |
+
a, b, _ = self.coefficients
|
239 |
+
x, y, _ = other.coefficients
|
240 |
+
return a * y - b * x
|
241 |
+
|
242 |
+
def dot(self, other: Line) -> float:
|
243 |
+
a, b, _ = self.coefficients
|
244 |
+
x, y, _ = other.coefficients
|
245 |
+
return a * x + b * y
|
246 |
+
|
247 |
+
def point_at(self, x: float = None, y: float = None) -> Optional[Point]:
|
248 |
+
"""Get a point on line closest to (x, y)."""
|
249 |
+
a, b, c = self.coefficients
|
250 |
+
# ax + by + c = 0
|
251 |
+
if x is None and y is not None:
|
252 |
+
if a != 0:
|
253 |
+
return Point((-c - b * y) / a, y) # pylint: disable=invalid-unary-operand-type
|
254 |
+
else:
|
255 |
+
return None
|
256 |
+
elif x is not None and y is None:
|
257 |
+
if b != 0:
|
258 |
+
return Point(x, (-c - a * x) / b) # pylint: disable=invalid-unary-operand-type
|
259 |
+
else:
|
260 |
+
return None
|
261 |
+
elif x is not None and y is not None:
|
262 |
+
if a * x + b * y + c == 0.0:
|
263 |
+
return Point(x, y)
|
264 |
+
return None
|
265 |
+
|
266 |
+
def diff_side(self, p1: Point, p2: Point) -> Optional[bool]:
|
267 |
+
d1 = self(p1.x, p1.y)
|
268 |
+
d2 = self(p2.x, p2.y)
|
269 |
+
if d1 == 0 or d2 == 0:
|
270 |
+
return None
|
271 |
+
return d1 * d2 < 0
|
272 |
+
|
273 |
+
def same_side(self, p1: Point, p2: Point) -> Optional[bool]:
|
274 |
+
d1 = self(p1.x, p1.y)
|
275 |
+
d2 = self(p2.x, p2.y)
|
276 |
+
if d1 == 0 or d2 == 0:
|
277 |
+
return None
|
278 |
+
return d1 * d2 > 0
|
279 |
+
|
280 |
+
def sign(self, point: Point) -> int:
|
281 |
+
s = self(point.x, point.y)
|
282 |
+
if s > 0:
|
283 |
+
return 1
|
284 |
+
elif s < 0:
|
285 |
+
return -1
|
286 |
+
return 0
|
287 |
+
|
288 |
+
def is_same(self, other: Line) -> bool:
|
289 |
+
a, b, c = self.coefficients
|
290 |
+
x, y, z = other.coefficients
|
291 |
+
return abs(a * y - b * x) <= ATOM and abs(b * z - c * y) <= ATOM
|
292 |
+
|
293 |
+
def sample_within(self, points: list[Point], n: int = 5) -> list[Point]:
|
294 |
+
"""Sample a point within the boundary of points."""
|
295 |
+
center = sum(points, Point(0.0, 0.0)) * (1.0 / len(points))
|
296 |
+
radius = max([p.distance(center) for p in points])
|
297 |
+
if close_enough(center.distance(self), radius):
|
298 |
+
center = center.foot(self)
|
299 |
+
a, b = line_circle_intersection(self, Circle(center.foot(self), radius))
|
300 |
+
|
301 |
+
result = None
|
302 |
+
best = -1.0
|
303 |
+
for _ in range(n):
|
304 |
+
rand = unif(0.0, 1.0)
|
305 |
+
x = a + (b - a) * rand
|
306 |
+
mind = min([x.distance(p) for p in points])
|
307 |
+
if mind > best:
|
308 |
+
best = mind
|
309 |
+
result = x
|
310 |
+
|
311 |
+
return [result]
|
312 |
+
|
313 |
+
|
314 |
+
class InvalidLineIntersectError(Exception):
|
315 |
+
pass
|
316 |
+
|
317 |
+
|
318 |
+
class HalfLine(Line):
|
319 |
+
"""Numerical ray."""
|
320 |
+
|
321 |
+
def __init__(self, tail: Point, head: Point): # pylint: disable=super-init-not-called
|
322 |
+
self.line = Line(tail, head)
|
323 |
+
self.coefficients = self.line.coefficients
|
324 |
+
self.tail = tail
|
325 |
+
self.head = head
|
326 |
+
|
327 |
+
def intersect(self, obj: Union[Line, HalfLine, Circle, HoleCircle]) -> Point:
|
328 |
+
if isinstance(obj, (HalfLine, Line)):
|
329 |
+
return line_line_intersection(self.line, obj)
|
330 |
+
|
331 |
+
exclude = [self.tail]
|
332 |
+
if isinstance(obj, HoleCircle):
|
333 |
+
exclude += [obj.hole]
|
334 |
+
|
335 |
+
a, b = line_circle_intersection(self.line, obj)
|
336 |
+
if any([a.close(x) for x in exclude]):
|
337 |
+
return b
|
338 |
+
if any([b.close(x) for x in exclude]):
|
339 |
+
return a
|
340 |
+
|
341 |
+
v = self.head - self.tail
|
342 |
+
va = a - self.tail
|
343 |
+
vb = b - self.tail
|
344 |
+
if v.dot(va) > 0:
|
345 |
+
return a
|
346 |
+
if v.dot(vb) > 0:
|
347 |
+
return b
|
348 |
+
raise InvalidLineIntersectError()
|
349 |
+
|
350 |
+
def sample_within(self, points: list[Point], n: int = 5) -> list[Point]:
|
351 |
+
center = sum(points, Point(0.0, 0.0)) * (1.0 / len(points))
|
352 |
+
radius = max([p.distance(center) for p in points])
|
353 |
+
if close_enough(center.distance(self.line), radius):
|
354 |
+
center = center.foot(self)
|
355 |
+
a, b = line_circle_intersection(self, Circle(center.foot(self), radius))
|
356 |
+
|
357 |
+
if (a - self.tail).dot(self.head - self.tail) > 0:
|
358 |
+
a, b = self.tail, a
|
359 |
+
else:
|
360 |
+
a, b = self.tail, b # pylint: disable=self-assigning-variable
|
361 |
+
|
362 |
+
result = None
|
363 |
+
best = -1.0
|
364 |
+
for _ in range(n):
|
365 |
+
x = a + (b - a) * unif(0.0, 1.0)
|
366 |
+
mind = min([x.distance(p) for p in points])
|
367 |
+
if mind > best:
|
368 |
+
best = mind
|
369 |
+
result = x
|
370 |
+
|
371 |
+
return [result]
|
372 |
+
|
373 |
+
|
374 |
+
def _perpendicular_bisector(p1: Point, p2: Point) -> Line:
|
375 |
+
midpoint = (p1 + p2) * 0.5
|
376 |
+
return Line(midpoint, midpoint + Point(p2.y - p1.y, p1.x - p2.x))
|
377 |
+
|
378 |
+
|
379 |
+
def same_sign(
|
380 |
+
a: Point, b: Point, c: Point, d: Point, e: Point, f: Point
|
381 |
+
) -> bool:
|
382 |
+
a, b, c, d, e, f = map(lambda p: p.sym, [a, b, c, d, e, f])
|
383 |
+
ab, cb = a - b, c - b
|
384 |
+
de, fe = d - e, f - e
|
385 |
+
return (ab.x * cb.y - ab.y * cb.x) * (de.x * fe.y - de.y * fe.x) > 0
|
386 |
+
|
387 |
+
|
388 |
+
class Circle:
|
389 |
+
"""Numerical circle."""
|
390 |
+
|
391 |
+
def __init__(
|
392 |
+
self,
|
393 |
+
center: Optional[Point] = None,
|
394 |
+
radius: Optional[float] = None,
|
395 |
+
p1: Optional[Point] = None,
|
396 |
+
p2: Optional[Point] = None,
|
397 |
+
p3: Optional[Point] = None,
|
398 |
+
):
|
399 |
+
if not center:
|
400 |
+
if not (p1 and p2 and p3):
|
401 |
+
self.center = self.radius = self.r2 = None
|
402 |
+
return
|
403 |
+
# raise ValueError('Circle without center need p1 p2 p3')
|
404 |
+
|
405 |
+
l12 = _perpendicular_bisector(p1, p2)
|
406 |
+
l23 = _perpendicular_bisector(p2, p3)
|
407 |
+
center = line_line_intersection(l12, l23)
|
408 |
+
|
409 |
+
self.center = center
|
410 |
+
self.a, self.b = center.x, center.y
|
411 |
+
|
412 |
+
if not radius:
|
413 |
+
if not (p1 or p2 or p3):
|
414 |
+
raise ValueError('Circle needs radius or p1 or p2 or p3')
|
415 |
+
p = p1 or p2 or p3
|
416 |
+
self.r2 = (self.a - p.x) ** 2 + (self.b - p.y) ** 2
|
417 |
+
self.radius = math.sqrt(self.r2)
|
418 |
+
else:
|
419 |
+
self.radius = radius
|
420 |
+
self.r2 = radius * radius
|
421 |
+
|
422 |
+
def intersect(self, obj: Union[Line, Circle]) -> tuple[Point, ...]:
|
423 |
+
if isinstance(obj, Line):
|
424 |
+
return obj.intersect(self)
|
425 |
+
if isinstance(obj, Circle):
|
426 |
+
return circle_circle_intersection(self, obj)
|
427 |
+
|
428 |
+
def sample_within(self, points: list[Point], n: int = 5) -> list[Point]:
|
429 |
+
"""Sample a point within the boundary of points."""
|
430 |
+
result = None
|
431 |
+
best = -1.0
|
432 |
+
for _ in range(n):
|
433 |
+
ang = unif(0.0, 2.0) * np.pi
|
434 |
+
x = self.center + Point(np.cos(ang), np.sin(ang)) * self.radius
|
435 |
+
mind = min([x.distance(p) for p in points])
|
436 |
+
if mind > best:
|
437 |
+
best = mind
|
438 |
+
result = x
|
439 |
+
|
440 |
+
return [result]
|
441 |
+
|
442 |
+
|
443 |
+
class HoleCircle(Circle):
|
444 |
+
"""Numerical circle with a missing point."""
|
445 |
+
|
446 |
+
def __init__(self, center: Point, radius: float, hole: Point):
|
447 |
+
super().__init__(center, radius)
|
448 |
+
self.hole = hole
|
449 |
+
|
450 |
+
def intersect(self, obj: Union[Line, HalfLine, Circle, HoleCircle]) -> Point:
|
451 |
+
if isinstance(obj, Line):
|
452 |
+
a, b = line_circle_intersection(obj, self)
|
453 |
+
if a.close(self.hole):
|
454 |
+
return b
|
455 |
+
return a
|
456 |
+
if isinstance(obj, HalfLine):
|
457 |
+
return obj.intersect(self)
|
458 |
+
if isinstance(obj, Circle):
|
459 |
+
a, b = circle_circle_intersection(obj, self)
|
460 |
+
if a.close(self.hole):
|
461 |
+
return b
|
462 |
+
return a
|
463 |
+
if isinstance(obj, HoleCircle):
|
464 |
+
a, b = circle_circle_intersection(obj, self)
|
465 |
+
if a.close(self.hole) or a.close(obj.hole):
|
466 |
+
return b
|
467 |
+
return a
|
468 |
+
|
469 |
+
|
470 |
+
def solve_quad(a: float, b: float, c: float) -> tuple[float, float]:
|
471 |
+
"""Solve a x^2 + bx + c = 0."""
|
472 |
+
a = 2 * a
|
473 |
+
d = b * b - 2 * a * c
|
474 |
+
if d < 0:
|
475 |
+
return None # the caller should expect this result.
|
476 |
+
|
477 |
+
y = math.sqrt(d)
|
478 |
+
return (-b - y) / a, (-b + y) / a
|
479 |
+
|
480 |
+
|
481 |
+
def circle_circle_intersection(c1: Circle, c2: Circle) -> tuple[Point, Point]:
|
482 |
+
"""Returns a pair of Points as intersections of c1 and c2."""
|
483 |
+
# circle 1: (x0, y0), radius r0
|
484 |
+
# circle 2: (x1, y1), radius r1
|
485 |
+
x0, y0, r0 = c1.a, c1.b, c1.radius
|
486 |
+
x1, y1, r1 = c2.a, c2.b, c2.radius
|
487 |
+
|
488 |
+
d = math.sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2)
|
489 |
+
if d == 0:
|
490 |
+
raise InvalidQuadSolveError()
|
491 |
+
|
492 |
+
a = (r0**2 - r1**2 + d**2) / (2 * d)
|
493 |
+
h = r0**2 - a**2
|
494 |
+
if h < 0:
|
495 |
+
raise InvalidQuadSolveError()
|
496 |
+
h = np.sqrt(h)
|
497 |
+
x2 = x0 + a * (x1 - x0) / d
|
498 |
+
y2 = y0 + a * (y1 - y0) / d
|
499 |
+
x3 = x2 + h * (y1 - y0) / d
|
500 |
+
y3 = y2 - h * (x1 - x0) / d
|
501 |
+
x4 = x2 - h * (y1 - y0) / d
|
502 |
+
y4 = y2 + h * (x1 - x0) / d
|
503 |
+
|
504 |
+
return Point(x3, y3), Point(x4, y4)
|
505 |
+
|
506 |
+
|
507 |
+
class InvalidQuadSolveError(Exception):
|
508 |
+
pass
|
509 |
+
|
510 |
+
|
511 |
+
def line_circle_intersection(line: Line, circle: Circle) -> tuple[Point, Point]:
|
512 |
+
"""Returns a pair of points as intersections of line and circle."""
|
513 |
+
a, b, c = line.coefficients
|
514 |
+
r = float(circle.radius)
|
515 |
+
center = circle.center
|
516 |
+
p, q = center.x, center.y
|
517 |
+
|
518 |
+
if b == 0:
|
519 |
+
x = -c / a
|
520 |
+
x_p = x - p
|
521 |
+
x_p2 = x_p * x_p
|
522 |
+
y = solve_quad(1, -2 * q, q * q + x_p2 - r * r)
|
523 |
+
if y is None:
|
524 |
+
raise InvalidQuadSolveError()
|
525 |
+
y1, y2 = y
|
526 |
+
return (Point(x, y1), Point(x, y2))
|
527 |
+
|
528 |
+
if a == 0:
|
529 |
+
y = -c / b
|
530 |
+
y_q = y - q
|
531 |
+
y_q2 = y_q * y_q
|
532 |
+
x = solve_quad(1, -2 * p, p * p + y_q2 - r * r)
|
533 |
+
if x is None:
|
534 |
+
raise InvalidQuadSolveError()
|
535 |
+
x1, x2 = x
|
536 |
+
return (Point(x1, y), Point(x2, y))
|
537 |
+
|
538 |
+
c_ap = c + a * p
|
539 |
+
a2 = a * a
|
540 |
+
y = solve_quad(
|
541 |
+
a2 + b * b, 2 * (b * c_ap - a2 * q), c_ap * c_ap + a2 * (q * q - r * r)
|
542 |
+
)
|
543 |
+
if y is None:
|
544 |
+
raise InvalidQuadSolveError()
|
545 |
+
y1, y2 = y
|
546 |
+
|
547 |
+
return Point(-(b * y1 + c) / a, y1), Point(-(b * y2 + c) / a, y2)
|
548 |
+
|
549 |
+
|
550 |
+
def _check_between(a: Point, b: Point, c: Point) -> bool:
|
551 |
+
"""Whether a is between b & c."""
|
552 |
+
return (a - b).dot(c - b) > 0 and (a - c).dot(b - c) > 0
|
553 |
+
|
554 |
+
|
555 |
+
def circle_segment_intersect(
|
556 |
+
circle: Circle, p1: Point, p2: Point
|
557 |
+
) -> list[Point]:
|
558 |
+
l = Line(p1, p2)
|
559 |
+
px, py = line_circle_intersection(l, circle)
|
560 |
+
|
561 |
+
result = []
|
562 |
+
if _check_between(px, p1, p2):
|
563 |
+
result.append(px)
|
564 |
+
if _check_between(py, p1, p2):
|
565 |
+
result.append(py)
|
566 |
+
return result
|
567 |
+
|
568 |
+
|
569 |
+
def line_segment_intersection(l: Line, A: Point, B: Point) -> Point: # pylint: disable=invalid-name
|
570 |
+
a, b, c = l.coefficients
|
571 |
+
x1, y1, x2, y2 = A.x, A.y, B.x, B.y
|
572 |
+
dx, dy = x2 - x1, y2 - y1
|
573 |
+
alpha = (-c - a * x1 - b * y1) / (a * dx + b * dy)
|
574 |
+
return Point(x1 + alpha * dx, y1 + alpha * dy)
|
575 |
+
|
576 |
+
|
577 |
+
def line_line_intersection(l1: Line, l2: Line) -> Point:
|
578 |
+
a1, b1, c1 = l1.coefficients
|
579 |
+
a2, b2, c2 = l2.coefficients
|
580 |
+
# a1x + b1y + c1 = 0
|
581 |
+
# a2x + b2y + c2 = 0
|
582 |
+
d = a1 * b2 - a2 * b1
|
583 |
+
if d == 0:
|
584 |
+
raise InvalidLineIntersectError
|
585 |
+
return Point((c2 * b1 - c1 * b2) / d, (c1 * a2 - c2 * a1) / d)
|
586 |
+
|
587 |
+
|
588 |
+
def check_too_close(
|
589 |
+
newpoints: list[Point], points: list[Point], tol: int = 0.1
|
590 |
+
) -> bool:
|
591 |
+
if not points:
|
592 |
+
return False
|
593 |
+
avg = sum(points, Point(0.0, 0.0)) * 1.0 / len(points)
|
594 |
+
mindist = min([p.distance(avg) for p in points])
|
595 |
+
for p0 in newpoints:
|
596 |
+
for p1 in points:
|
597 |
+
if p0.distance(p1) < tol * mindist:
|
598 |
+
return True
|
599 |
+
return False
|
600 |
+
|
601 |
+
|
602 |
+
def check_too_far(
|
603 |
+
newpoints: list[Point], points: list[Point], tol: int = 4
|
604 |
+
) -> bool:
|
605 |
+
if len(points) < 2:
|
606 |
+
return False
|
607 |
+
avg = sum(points, Point(0.0, 0.0)) * 1.0 / len(points)
|
608 |
+
maxdist = max([p.distance(avg) for p in points])
|
609 |
+
for p in newpoints:
|
610 |
+
if p.distance(avg) > maxdist * tol:
|
611 |
+
return True
|
612 |
+
return False
|
613 |
+
|
614 |
+
|
615 |
+
def check_aconst(args: list[Point]) -> bool:
|
616 |
+
a, b, c, d, num, den = args
|
617 |
+
d = d + a - c
|
618 |
+
ang = ang_between(a, b, d)
|
619 |
+
if ang < 0:
|
620 |
+
ang += np.pi
|
621 |
+
return close_enough(ang, num * np.pi / den)
|
622 |
+
|
623 |
+
|
624 |
+
def check(name: str, args: list[Union[gm.Point, Point]]) -> bool:
|
625 |
+
"""Numerical check."""
|
626 |
+
if name == 'eqangle6':
|
627 |
+
name = 'eqangle'
|
628 |
+
elif name == 'eqratio6':
|
629 |
+
name = 'eqratio'
|
630 |
+
elif name in ['simtri2', 'simtri*']:
|
631 |
+
name = 'simtri'
|
632 |
+
elif name in ['contri2', 'contri*']:
|
633 |
+
name = 'contri'
|
634 |
+
elif name == 'para':
|
635 |
+
name = 'para_or_coll'
|
636 |
+
elif name == 'on_line':
|
637 |
+
name = 'coll'
|
638 |
+
elif name in ['rcompute', 'acompute']:
|
639 |
+
return True
|
640 |
+
elif name in ['fixl', 'fixc', 'fixb', 'fixt', 'fixp']:
|
641 |
+
return True
|
642 |
+
|
643 |
+
fn_name = 'check_' + name
|
644 |
+
if fn_name not in globals():
|
645 |
+
return None
|
646 |
+
|
647 |
+
fun = globals()['check_' + name]
|
648 |
+
args = [p.num if isinstance(p, gm.Point) else p for p in args]
|
649 |
+
return fun(args)
|
650 |
+
|
651 |
+
|
652 |
+
def check_circle(points: list[Point]) -> bool:
|
653 |
+
if len(points) != 4:
|
654 |
+
return False
|
655 |
+
o, a, b, c = points
|
656 |
+
oa, ob, oc = o.distance(a), o.distance(b), o.distance(c)
|
657 |
+
return close_enough(oa, ob) and close_enough(ob, oc)
|
658 |
+
|
659 |
+
|
660 |
+
def check_coll(points: list[Point]) -> bool:
|
661 |
+
a, b = points[:2]
|
662 |
+
l = Line(a, b)
|
663 |
+
for p in points[2:]:
|
664 |
+
if abs(l(p.x, p.y)) > ATOM:
|
665 |
+
return False
|
666 |
+
return True
|
667 |
+
|
668 |
+
|
669 |
+
def check_ncoll(points: list[Point]) -> bool:
|
670 |
+
return not check_coll(points)
|
671 |
+
|
672 |
+
|
673 |
+
def check_sameside(points: list[Point]) -> bool:
|
674 |
+
b, a, c, y, x, z = points
|
675 |
+
# whether b is to the same side of a & c as y is to x & z
|
676 |
+
ba = b - a
|
677 |
+
bc = b - c
|
678 |
+
yx = y - x
|
679 |
+
yz = y - z
|
680 |
+
return ba.dot(bc) * yx.dot(yz) > 0
|
681 |
+
|
682 |
+
|
683 |
+
def check_para_or_coll(points: list[Point]) -> bool:
|
684 |
+
return check_para(points) or check_coll(points)
|
685 |
+
|
686 |
+
|
687 |
+
def check_para(points: list[Point]) -> bool:
|
688 |
+
a, b, c, d = points
|
689 |
+
ab = Line(a, b)
|
690 |
+
cd = Line(c, d)
|
691 |
+
if ab.same(cd):
|
692 |
+
return False
|
693 |
+
return ab.is_parallel(cd)
|
694 |
+
|
695 |
+
|
696 |
+
def check_perp(points: list[Point]) -> bool:
|
697 |
+
a, b, c, d = points
|
698 |
+
ab = Line(a, b)
|
699 |
+
cd = Line(c, d)
|
700 |
+
return ab.is_perp(cd)
|
701 |
+
|
702 |
+
|
703 |
+
def check_cyclic(points: list[Point]) -> bool:
|
704 |
+
points = list(set(points))
|
705 |
+
(a, b, c, *ps) = points
|
706 |
+
circle = Circle(p1=a, p2=b, p3=c)
|
707 |
+
for d in ps:
|
708 |
+
if not close_enough(d.distance(circle.center), circle.radius):
|
709 |
+
return False
|
710 |
+
return True
|
711 |
+
|
712 |
+
|
713 |
+
def bring_together(
|
714 |
+
a: Point, b: Point, c: Point, d: Point
|
715 |
+
) -> tuple[Point, Point, Point, Point]:
|
716 |
+
ab = Line(a, b)
|
717 |
+
cd = Line(c, d)
|
718 |
+
x = line_line_intersection(ab, cd)
|
719 |
+
unit = Circle(center=x, radius=1.0)
|
720 |
+
y, _ = line_circle_intersection(ab, unit)
|
721 |
+
z, _ = line_circle_intersection(cd, unit)
|
722 |
+
return x, y, x, z
|
723 |
+
|
724 |
+
|
725 |
+
def same_clock(
|
726 |
+
a: Point, b: Point, c: Point, d: Point, e: Point, f: Point
|
727 |
+
) -> bool:
|
728 |
+
ba = b - a
|
729 |
+
cb = c - b
|
730 |
+
ed = e - d
|
731 |
+
fe = f - e
|
732 |
+
return (ba.x * cb.y - ba.y * cb.x) * (ed.x * fe.y - ed.y * fe.x) > 0
|
733 |
+
|
734 |
+
|
735 |
+
def check_const_angle(points: list[Point]) -> bool:
|
736 |
+
"""Check if the angle is equal to the given constant."""
|
737 |
+
a, b, c, d, m, n = points
|
738 |
+
a, b, c, d = bring_together(a, b, c, d)
|
739 |
+
ba = b - a
|
740 |
+
dc = d - c
|
741 |
+
|
742 |
+
a3 = np.arctan2(ba.y, ba.x)
|
743 |
+
a4 = np.arctan2(dc.y, dc.x)
|
744 |
+
y = a3 - a4
|
745 |
+
|
746 |
+
return close_enough(m / n % 1, y / np.pi % 1)
|
747 |
+
|
748 |
+
|
749 |
+
def check_eqangle(points: list[Point]) -> bool:
|
750 |
+
"""Check if 8 points make 2 equal angles."""
|
751 |
+
a, b, c, d, e, f, g, h = points
|
752 |
+
|
753 |
+
ab = Line(a, b)
|
754 |
+
cd = Line(c, d)
|
755 |
+
ef = Line(e, f)
|
756 |
+
gh = Line(g, h)
|
757 |
+
|
758 |
+
if ab.is_parallel(cd):
|
759 |
+
return ef.is_parallel(gh)
|
760 |
+
if ef.is_parallel(gh):
|
761 |
+
return ab.is_parallel(cd)
|
762 |
+
|
763 |
+
a, b, c, d = bring_together(a, b, c, d)
|
764 |
+
e, f, g, h = bring_together(e, f, g, h)
|
765 |
+
|
766 |
+
ba = b - a
|
767 |
+
dc = d - c
|
768 |
+
fe = f - e
|
769 |
+
hg = h - g
|
770 |
+
|
771 |
+
sameclock = (ba.x * dc.y - ba.y * dc.x) * (fe.x * hg.y - fe.y * hg.x) > 0
|
772 |
+
if not sameclock:
|
773 |
+
ba = ba * -1.0
|
774 |
+
|
775 |
+
a1 = np.arctan2(fe.y, fe.x)
|
776 |
+
a2 = np.arctan2(hg.y, hg.x)
|
777 |
+
x = a1 - a2
|
778 |
+
|
779 |
+
a3 = np.arctan2(ba.y, ba.x)
|
780 |
+
a4 = np.arctan2(dc.y, dc.x)
|
781 |
+
y = a3 - a4
|
782 |
+
|
783 |
+
xy = (x - y) % (2 * np.pi)
|
784 |
+
return close_enough(xy, 0, tol=1e-11) or close_enough(
|
785 |
+
xy, 2 * np.pi, tol=1e-11
|
786 |
+
)
|
787 |
+
|
788 |
+
|
789 |
+
def check_eqratio(points: list[Point]) -> bool:
|
790 |
+
a, b, c, d, e, f, g, h = points
|
791 |
+
ab = a.distance(b)
|
792 |
+
cd = c.distance(d)
|
793 |
+
ef = e.distance(f)
|
794 |
+
gh = g.distance(h)
|
795 |
+
return close_enough(ab * gh, cd * ef)
|
796 |
+
|
797 |
+
|
798 |
+
def check_cong(points: list[Point]) -> bool:
|
799 |
+
a, b, c, d = points
|
800 |
+
return close_enough(a.distance(b), c.distance(d))
|
801 |
+
|
802 |
+
|
803 |
+
def check_midp(points: list[Point]) -> bool:
|
804 |
+
a, b, c = points
|
805 |
+
return check_coll(points) and close_enough(a.distance(b), a.distance(c))
|
806 |
+
|
807 |
+
|
808 |
+
def check_simtri(points: list[Point]) -> bool:
|
809 |
+
"""Check if 6 points make a pair of similar triangles."""
|
810 |
+
a, b, c, x, y, z = points
|
811 |
+
ab = a.distance(b)
|
812 |
+
bc = b.distance(c)
|
813 |
+
ca = c.distance(a)
|
814 |
+
xy = x.distance(y)
|
815 |
+
yz = y.distance(z)
|
816 |
+
zx = z.distance(x)
|
817 |
+
tol = 1e-9
|
818 |
+
return close_enough(ab * yz, bc * xy, tol) and close_enough(
|
819 |
+
bc * zx, ca * yz, tol
|
820 |
+
)
|
821 |
+
|
822 |
+
|
823 |
+
def check_contri(points: list[Point]) -> bool:
|
824 |
+
a, b, c, x, y, z = points
|
825 |
+
ab = a.distance(b)
|
826 |
+
bc = b.distance(c)
|
827 |
+
ca = c.distance(a)
|
828 |
+
xy = x.distance(y)
|
829 |
+
yz = y.distance(z)
|
830 |
+
zx = z.distance(x)
|
831 |
+
tol = 1e-9
|
832 |
+
return (
|
833 |
+
close_enough(ab, xy, tol)
|
834 |
+
and close_enough(bc, yz, tol)
|
835 |
+
and close_enough(ca, zx, tol)
|
836 |
+
)
|
837 |
+
|
838 |
+
|
839 |
+
def check_ratio(points: list[Point]) -> bool:
|
840 |
+
a, b, c, d, m, n = points
|
841 |
+
ab = a.distance(b)
|
842 |
+
cd = c.distance(d)
|
843 |
+
return close_enough(ab * n, cd * m)
|
844 |
+
|
845 |
+
|
846 |
+
def draw_angle(
|
847 |
+
ax: matplotlib.axes.Axes,
|
848 |
+
head: Point,
|
849 |
+
p1: Point,
|
850 |
+
p2: Point,
|
851 |
+
color: Any = 'red',
|
852 |
+
alpha: float = 0.5,
|
853 |
+
frac: float = 1.0,
|
854 |
+
) -> None:
|
855 |
+
"""Draw an angle on plt ax."""
|
856 |
+
d1 = p1 - head
|
857 |
+
d2 = p2 - head
|
858 |
+
|
859 |
+
a1 = np.arctan2(float(d1.y), float(d1.x))
|
860 |
+
a2 = np.arctan2(float(d2.y), float(d2.x))
|
861 |
+
a1, a2 = a1 * 180 / np.pi, a2 * 180 / np.pi
|
862 |
+
a1, a2 = a1 % 360, a2 % 360
|
863 |
+
|
864 |
+
if a1 > a2:
|
865 |
+
a1, a2 = a2, a1
|
866 |
+
|
867 |
+
if a2 - a1 > 180:
|
868 |
+
a1, a2 = a2, a1
|
869 |
+
|
870 |
+
b1, b2 = a1, a2
|
871 |
+
if b1 > b2:
|
872 |
+
b2 += 360
|
873 |
+
d = b2 - b1
|
874 |
+
# if d >= 90:
|
875 |
+
# return
|
876 |
+
|
877 |
+
scale = min(2.0, 90 / d)
|
878 |
+
scale = max(scale, 0.4)
|
879 |
+
fov = matplotlib.patches.Wedge(
|
880 |
+
(float(head.x), float(head.y)),
|
881 |
+
unif(0.075, 0.125) * scale * frac,
|
882 |
+
a1,
|
883 |
+
a2,
|
884 |
+
color=color,
|
885 |
+
alpha=alpha,
|
886 |
+
)
|
887 |
+
ax.add_artist(fov)
|
888 |
+
|
889 |
+
|
890 |
+
def naming_position(
|
891 |
+
ax: matplotlib.axes.Axes, p: Point, lines: list[Line], circles: list[Circle]
|
892 |
+
) -> tuple[float, float]:
|
893 |
+
"""Figure out a good naming position on the drawing."""
|
894 |
+
_ = ax
|
895 |
+
r = 0.08
|
896 |
+
c = Circle(center=p, radius=r)
|
897 |
+
avoid = []
|
898 |
+
for p1, p2 in lines:
|
899 |
+
try:
|
900 |
+
avoid.extend(circle_segment_intersect(c, p1, p2))
|
901 |
+
except InvalidQuadSolveError:
|
902 |
+
continue
|
903 |
+
for x in circles:
|
904 |
+
try:
|
905 |
+
avoid.extend(circle_circle_intersection(c, x))
|
906 |
+
except InvalidQuadSolveError:
|
907 |
+
continue
|
908 |
+
|
909 |
+
if not avoid:
|
910 |
+
return [p.x + 0.01, p.y + 0.01]
|
911 |
+
|
912 |
+
angs = sorted([ang_of(p, a) for a in avoid])
|
913 |
+
angs += [angs[0] + 2 * np.pi]
|
914 |
+
angs = [(angs[i + 1] - a, a) for i, a in enumerate(angs[:-1])]
|
915 |
+
|
916 |
+
d, a = max(angs)
|
917 |
+
ang = a + d / 2
|
918 |
+
|
919 |
+
name_pos = p + Point(np.cos(ang), np.sin(ang)) * r
|
920 |
+
|
921 |
+
x, y = (name_pos.x - r / 1.5, name_pos.y - r / 1.5)
|
922 |
+
return x, y
|
923 |
+
|
924 |
+
|
925 |
+
def draw_point(
|
926 |
+
ax: matplotlib.axes.Axes,
|
927 |
+
p: Point,
|
928 |
+
name: str,
|
929 |
+
lines: list[Line],
|
930 |
+
circles: list[Circle],
|
931 |
+
color: Any = 'white',
|
932 |
+
size: float = 15,
|
933 |
+
) -> None:
|
934 |
+
"""draw a point."""
|
935 |
+
ax.scatter(p.x, p.y, color=color, s=size)
|
936 |
+
|
937 |
+
if color == 'white':
|
938 |
+
color = 'lightgreen'
|
939 |
+
else:
|
940 |
+
color = 'grey'
|
941 |
+
|
942 |
+
name = name.upper()
|
943 |
+
if len(name) > 1:
|
944 |
+
name = name[0] + '_' + name[1:]
|
945 |
+
|
946 |
+
ax.annotate(
|
947 |
+
name, naming_position(ax, p, lines, circles), color=color, fontsize=15
|
948 |
+
)
|
949 |
+
|
950 |
+
|
951 |
+
def _draw_line(
|
952 |
+
ax: matplotlib.axes.Axes,
|
953 |
+
p1: Point,
|
954 |
+
p2: Point,
|
955 |
+
color: Any = 'white',
|
956 |
+
lw: float = 1.2,
|
957 |
+
alpha: float = 0.8,
|
958 |
+
) -> None:
|
959 |
+
"""Draw a line in matplotlib."""
|
960 |
+
ls = '-'
|
961 |
+
if color == '--':
|
962 |
+
color = 'black'
|
963 |
+
ls = '--'
|
964 |
+
|
965 |
+
lx, ly = (p1.x, p2.x), (p1.y, p2.y)
|
966 |
+
ax.plot(lx, ly, color=color, lw=lw, alpha=alpha, ls=ls)
|
967 |
+
|
968 |
+
|
969 |
+
def draw_line(
|
970 |
+
ax: matplotlib.axes.Axes, line: Line, color: Any = 'white'
|
971 |
+
) -> tuple[Point, Point]:
|
972 |
+
"""Draw a line."""
|
973 |
+
points = line.neighbors(gm.Point)
|
974 |
+
if len(points) <= 1:
|
975 |
+
return
|
976 |
+
|
977 |
+
points = [p.num for p in points]
|
978 |
+
p1, p2 = points[:2]
|
979 |
+
|
980 |
+
pmin, pmax = (p1, 0.0), (p2, (p2 - p1).dot(p2 - p1))
|
981 |
+
|
982 |
+
for p in points[2:]:
|
983 |
+
v = (p - p1).dot(p2 - p1)
|
984 |
+
if v < pmin[1]:
|
985 |
+
pmin = p, v
|
986 |
+
if v > pmax[1]:
|
987 |
+
pmax = p, v
|
988 |
+
|
989 |
+
p1, p2 = pmin[0], pmax[0]
|
990 |
+
_draw_line(ax, p1, p2, color=color)
|
991 |
+
return p1, p2
|
992 |
+
|
993 |
+
|
994 |
+
def _draw_circle(
|
995 |
+
ax: matplotlib.axes.Axes, c: Circle, color: Any = 'cyan', lw: float = 1.2
|
996 |
+
) -> None:
|
997 |
+
ls = '-'
|
998 |
+
if color == '--':
|
999 |
+
color = 'black'
|
1000 |
+
ls = '--'
|
1001 |
+
|
1002 |
+
ax.add_patch(
|
1003 |
+
plt.Circle(
|
1004 |
+
(c.center.x, c.center.y),
|
1005 |
+
c.radius,
|
1006 |
+
color=color,
|
1007 |
+
alpha=0.8,
|
1008 |
+
fill=False,
|
1009 |
+
lw=lw,
|
1010 |
+
ls=ls,
|
1011 |
+
)
|
1012 |
+
)
|
1013 |
+
|
1014 |
+
|
1015 |
+
def draw_circle(
|
1016 |
+
ax: matplotlib.axes.Axes, circle: Circle, color: Any = 'cyan'
|
1017 |
+
) -> Circle:
|
1018 |
+
"""Draw a circle."""
|
1019 |
+
if circle.num is not None:
|
1020 |
+
circle = circle.num
|
1021 |
+
else:
|
1022 |
+
points = circle.neighbors(gm.Point)
|
1023 |
+
if len(points) <= 2:
|
1024 |
+
return
|
1025 |
+
points = [p.num for p in points]
|
1026 |
+
p1, p2, p3 = points[:3]
|
1027 |
+
circle = Circle(p1=p1, p2=p2, p3=p3)
|
1028 |
+
|
1029 |
+
_draw_circle(ax, circle, color)
|
1030 |
+
return circle
|
1031 |
+
|
1032 |
+
|
1033 |
+
def mark_segment(
|
1034 |
+
ax: matplotlib.axes.Axes, p1: Point, p2: Point, color: Any, alpha: float
|
1035 |
+
) -> None:
|
1036 |
+
_ = alpha
|
1037 |
+
x, y = (p1.x + p2.x) / 2, (p1.y + p2.y) / 2
|
1038 |
+
ax.scatter(x, y, color=color, alpha=1.0, marker='o', s=50)
|
1039 |
+
|
1040 |
+
|
1041 |
+
def highlight_angle(
|
1042 |
+
ax: matplotlib.axes.Axes,
|
1043 |
+
a: Point,
|
1044 |
+
b: Point,
|
1045 |
+
c: Point,
|
1046 |
+
d: Point,
|
1047 |
+
color: Any,
|
1048 |
+
alpha: float,
|
1049 |
+
) -> None:
|
1050 |
+
"""Highlight an angle between ab and cd with (color, alpha)."""
|
1051 |
+
try:
|
1052 |
+
a, b, c, d = bring_together(a, b, c, d)
|
1053 |
+
except: # pylint: disable=bare-except
|
1054 |
+
return
|
1055 |
+
draw_angle(ax, a, b, d, color=color, alpha=alpha, frac=1.0)
|
1056 |
+
|
1057 |
+
|
1058 |
+
def highlight(
|
1059 |
+
ax: matplotlib.axes.Axes,
|
1060 |
+
name: str,
|
1061 |
+
args: list[gm.Point],
|
1062 |
+
lcolor: Any,
|
1063 |
+
color1: Any,
|
1064 |
+
color2: Any,
|
1065 |
+
) -> None:
|
1066 |
+
"""Draw highlights."""
|
1067 |
+
args = list(map(lambda x: x.num if isinstance(x, gm.Point) else x, args))
|
1068 |
+
|
1069 |
+
if name == 'cyclic':
|
1070 |
+
a, b, c, d = args
|
1071 |
+
_draw_circle(ax, Circle(p1=a, p2=b, p3=c), color=color1, lw=2.0)
|
1072 |
+
if name == 'coll':
|
1073 |
+
a, b, c = args
|
1074 |
+
a, b = max(a, b, c), min(a, b, c)
|
1075 |
+
_draw_line(ax, a, b, color=color1, lw=2.0)
|
1076 |
+
if name == 'para':
|
1077 |
+
a, b, c, d = args
|
1078 |
+
_draw_line(ax, a, b, color=color1, lw=2.0)
|
1079 |
+
_draw_line(ax, c, d, color=color2, lw=2.0)
|
1080 |
+
if name == 'eqangle':
|
1081 |
+
a, b, c, d, e, f, g, h = args
|
1082 |
+
|
1083 |
+
x = line_line_intersection(Line(a, b), Line(c, d))
|
1084 |
+
if b.distance(x) > a.distance(x):
|
1085 |
+
a, b = b, a
|
1086 |
+
if d.distance(x) > c.distance(x):
|
1087 |
+
c, d = d, c
|
1088 |
+
a, b, d = x, a, c
|
1089 |
+
|
1090 |
+
y = line_line_intersection(Line(e, f), Line(g, h))
|
1091 |
+
if f.distance(y) > e.distance(y):
|
1092 |
+
e, f = f, e
|
1093 |
+
if h.distance(y) > g.distance(y):
|
1094 |
+
g, h = h, g
|
1095 |
+
e, f, h = y, e, g
|
1096 |
+
|
1097 |
+
_draw_line(ax, a, b, color=lcolor, lw=2.0)
|
1098 |
+
_draw_line(ax, a, d, color=lcolor, lw=2.0)
|
1099 |
+
_draw_line(ax, e, f, color=lcolor, lw=2.0)
|
1100 |
+
_draw_line(ax, e, h, color=lcolor, lw=2.0)
|
1101 |
+
if color1 == '--':
|
1102 |
+
color1 = 'red'
|
1103 |
+
draw_angle(ax, a, b, d, color=color1, alpha=0.5)
|
1104 |
+
if color2 == '--':
|
1105 |
+
color2 = 'red'
|
1106 |
+
draw_angle(ax, e, f, h, color=color2, alpha=0.5)
|
1107 |
+
if name == 'perp':
|
1108 |
+
a, b, c, d = args
|
1109 |
+
_draw_line(ax, a, b, color=color1, lw=2.0)
|
1110 |
+
_draw_line(ax, c, d, color=color1, lw=2.0)
|
1111 |
+
if name == 'ratio':
|
1112 |
+
a, b, c, d, m, n = args
|
1113 |
+
_draw_line(ax, a, b, color=color1, lw=2.0)
|
1114 |
+
_draw_line(ax, c, d, color=color2, lw=2.0)
|
1115 |
+
if name == 'cong':
|
1116 |
+
a, b, c, d = args
|
1117 |
+
_draw_line(ax, a, b, color=color1, lw=2.0)
|
1118 |
+
_draw_line(ax, c, d, color=color2, lw=2.0)
|
1119 |
+
if name == 'midp':
|
1120 |
+
m, a, b = args
|
1121 |
+
_draw_line(ax, a, m, color=color1, lw=2.0, alpha=0.5)
|
1122 |
+
_draw_line(ax, b, m, color=color2, lw=2.0, alpha=0.5)
|
1123 |
+
if name == 'eqratio':
|
1124 |
+
a, b, c, d, m, n, p, q = args
|
1125 |
+
_draw_line(ax, a, b, color=color1, lw=2.0, alpha=0.5)
|
1126 |
+
_draw_line(ax, c, d, color=color2, lw=2.0, alpha=0.5)
|
1127 |
+
_draw_line(ax, m, n, color=color1, lw=2.0, alpha=0.5)
|
1128 |
+
_draw_line(ax, p, q, color=color2, lw=2.0, alpha=0.5)
|
1129 |
+
|
1130 |
+
|
1131 |
+
HCOLORS = None
|
1132 |
+
|
1133 |
+
|
1134 |
+
def _draw(
|
1135 |
+
ax: matplotlib.axes.Axes,
|
1136 |
+
points: list[gm.Point],
|
1137 |
+
lines: list[gm.Line],
|
1138 |
+
circles: list[gm.Circle],
|
1139 |
+
goal: Any,
|
1140 |
+
equals: list[tuple[Any, Any]],
|
1141 |
+
highlights: list[tuple[str, list[gm.Point]]],
|
1142 |
+
):
|
1143 |
+
"""Draw everything."""
|
1144 |
+
colors = ['red', 'green', 'blue', 'orange', 'magenta', 'purple']
|
1145 |
+
pcolor = 'black'
|
1146 |
+
lcolor = 'black'
|
1147 |
+
ccolor = 'grey'
|
1148 |
+
if get_theme() == 'dark':
|
1149 |
+
pcolor, lcolor, ccolor = 'white', 'white', 'cyan'
|
1150 |
+
elif get_theme() == 'light':
|
1151 |
+
pcolor, lcolor, ccolor = 'black', 'black', 'blue'
|
1152 |
+
elif get_theme() == 'grey':
|
1153 |
+
pcolor, lcolor, ccolor = 'black', 'black', 'grey'
|
1154 |
+
colors = ['grey']
|
1155 |
+
|
1156 |
+
line_boundaries = []
|
1157 |
+
for l in lines:
|
1158 |
+
p1, p2 = draw_line(ax, l, color=lcolor)
|
1159 |
+
line_boundaries.append((p1, p2))
|
1160 |
+
circles = [draw_circle(ax, c, color=ccolor) for c in circles]
|
1161 |
+
|
1162 |
+
for p in points:
|
1163 |
+
draw_point(ax, p.num, p.name, line_boundaries, circles, color=pcolor)
|
1164 |
+
|
1165 |
+
if equals:
|
1166 |
+
for i, segs in enumerate(equals['segments']):
|
1167 |
+
color = colors[i % len(colors)]
|
1168 |
+
for a, b in segs:
|
1169 |
+
mark_segment(ax, a, b, color, 0.5)
|
1170 |
+
|
1171 |
+
for i, angs in enumerate(equals['angles']):
|
1172 |
+
color = colors[i % len(colors)]
|
1173 |
+
for a, b, c, d in angs:
|
1174 |
+
highlight_angle(ax, a, b, c, d, color, 0.5)
|
1175 |
+
|
1176 |
+
if highlights:
|
1177 |
+
global HCOLORS
|
1178 |
+
if HCOLORS is None:
|
1179 |
+
HCOLORS = [k for k in mcolors.TABLEAU_COLORS.keys() if 'red' not in k]
|
1180 |
+
|
1181 |
+
for i, (name, args) in enumerate(highlights):
|
1182 |
+
color_i = HCOLORS[i % len(HCOLORS)]
|
1183 |
+
highlight(ax, name, args, 'black', color_i, color_i)
|
1184 |
+
|
1185 |
+
if goal:
|
1186 |
+
name, args = goal
|
1187 |
+
lcolor = color1 = color2 = 'red'
|
1188 |
+
highlight(ax, name, args, lcolor, color1, color2)
|
1189 |
+
|
1190 |
+
|
1191 |
+
THEME = 'dark'
|
1192 |
+
|
1193 |
+
|
1194 |
+
def set_theme(theme) -> None:
|
1195 |
+
global THEME
|
1196 |
+
THEME = theme
|
1197 |
+
|
1198 |
+
|
1199 |
+
def get_theme() -> str:
|
1200 |
+
return THEME
|
1201 |
+
|
1202 |
+
|
1203 |
+
def draw(
|
1204 |
+
points: list[gm.Point],
|
1205 |
+
lines: list[gm.Line],
|
1206 |
+
circles: list[gm.Circle],
|
1207 |
+
segments: list[gm.Segment],
|
1208 |
+
goal: Any = None,
|
1209 |
+
highlights: list[tuple[str, list[gm.Point]]] = None,
|
1210 |
+
equals: list[tuple[Any, Any]] = None,
|
1211 |
+
block: bool = True,
|
1212 |
+
save_to: str = None,
|
1213 |
+
theme: str = 'light',
|
1214 |
+
) -> None:
|
1215 |
+
"""Draw everything on the same canvas."""
|
1216 |
+
plt.close()
|
1217 |
+
imsize = 512 / 100
|
1218 |
+
fig, ax = plt.subplots(figsize=(imsize, imsize), dpi=100)
|
1219 |
+
|
1220 |
+
set_theme(theme)
|
1221 |
+
|
1222 |
+
if get_theme() == 'dark':
|
1223 |
+
ax.set_facecolor((0.0, 0.0, 0.0))
|
1224 |
+
else:
|
1225 |
+
ax.set_facecolor((1.0, 1.0, 1.0))
|
1226 |
+
|
1227 |
+
_draw(ax, points, lines, circles, goal, equals, highlights)
|
1228 |
+
|
1229 |
+
plt.axis('equal')
|
1230 |
+
fig.subplots_adjust(left=0, right=1, top=1, bottom=0, wspace=0, hspace=0)
|
1231 |
+
if points:
|
1232 |
+
xmin = min([p.num.x for p in points])
|
1233 |
+
xmax = max([p.num.x for p in points])
|
1234 |
+
ymin = min([p.num.y for p in points])
|
1235 |
+
ymax = max([p.num.y for p in points])
|
1236 |
+
plt.margins((xmax - xmin) * 0.1, (ymax - ymin) * 0.1)
|
1237 |
+
if save_to:
|
1238 |
+
plt.savefig(save_to)
|
1239 |
+
# plt.show(block=block)
|
1240 |
+
|
1241 |
+
|
1242 |
+
|
1243 |
+
def close_enough(a: float, b: float, tol: float = 1e-12) -> bool:
|
1244 |
+
return abs(a - b) < tol
|
1245 |
+
|
1246 |
+
|
1247 |
+
def assert_close_enough(a: float, b: float, tol: float = 1e-12) -> None:
|
1248 |
+
assert close_enough(a, b, tol), f'|{a}-{b}| = {abs(a-b)} >= {tol}'
|
1249 |
+
|
1250 |
+
|
1251 |
+
def ang_of(tail: Point, head: Point) -> float:
|
1252 |
+
vector = head - tail
|
1253 |
+
arctan = np.arctan2(vector.y, vector.x) % (2 * np.pi)
|
1254 |
+
return arctan
|
1255 |
+
|
1256 |
+
|
1257 |
+
def ang_between(tail: Point, head1: Point, head2: Point) -> float:
|
1258 |
+
ang1 = ang_of(tail, head1)
|
1259 |
+
ang2 = ang_of(tail, head2)
|
1260 |
+
diff = ang1 - ang2
|
1261 |
+
# return diff % (2*np.pi)
|
1262 |
+
if diff > np.pi:
|
1263 |
+
return diff - 2 * np.pi
|
1264 |
+
if diff < -np.pi:
|
1265 |
+
return 2 * np.pi + diff
|
1266 |
+
return diff
|
1267 |
+
|
1268 |
+
|
1269 |
+
def head_from(tail: Point, ang: float, length: float = 1) -> Point:
|
1270 |
+
vector = Point(np.cos(ang) * length, np.sin(ang) * length)
|
1271 |
+
return tail + vector
|
1272 |
+
|
1273 |
+
|
1274 |
+
def random_points(n: int = 3) -> list[Point]:
|
1275 |
+
return [Point(unif(-1, 1), unif(-1, 1)) for _ in range(n)]
|
1276 |
+
|
1277 |
+
|
1278 |
+
def random_rfss(*points: list[Point]) -> list[Point]:
|
1279 |
+
"""Random rotate-flip-scale-shift a point cloud."""
|
1280 |
+
# center point cloud.
|
1281 |
+
average = sum(points, Point(0.0, 0.0)) * (1.0 / len(points))
|
1282 |
+
points = [p - average for p in points]
|
1283 |
+
|
1284 |
+
# rotate
|
1285 |
+
ang = unif(0.0, 2 * np.pi)
|
1286 |
+
sin, cos = np.sin(ang), np.cos(ang)
|
1287 |
+
# scale and shift
|
1288 |
+
scale = unif(0.5, 2.0)
|
1289 |
+
shift = Point(unif(-1, 1), unif(-1, 1))
|
1290 |
+
points = [p.rotate(sin, cos) * scale + shift for p in points]
|
1291 |
+
|
1292 |
+
# randomly flip
|
1293 |
+
if np.random.rand() < 0.5:
|
1294 |
+
points = [p.flip() for p in points]
|
1295 |
+
|
1296 |
+
return points
|
1297 |
+
|
1298 |
+
|
1299 |
+
def reduce(
|
1300 |
+
objs: list[Union[Point, Line, Circle, HalfLine, HoleCircle]],
|
1301 |
+
existing_points: list[Point],
|
1302 |
+
) -> list[Point]:
|
1303 |
+
"""Reduce intersecting objects into one point of intersections."""
|
1304 |
+
if all(isinstance(o, Point) for o in objs):
|
1305 |
+
return objs
|
1306 |
+
|
1307 |
+
elif len(objs) == 1:
|
1308 |
+
return objs[0].sample_within(existing_points)
|
1309 |
+
|
1310 |
+
elif len(objs) == 2:
|
1311 |
+
a, b = objs
|
1312 |
+
result = a.intersect(b)
|
1313 |
+
if isinstance(result, Point):
|
1314 |
+
return [result]
|
1315 |
+
a, b = result
|
1316 |
+
a_close = any([a.close(x) for x in existing_points])
|
1317 |
+
if a_close:
|
1318 |
+
return [b]
|
1319 |
+
b_close = any([b.close(x) for x in existing_points])
|
1320 |
+
if b_close:
|
1321 |
+
return [a]
|
1322 |
+
return [np.random.choice([a, b])]
|
1323 |
+
|
1324 |
+
else:
|
1325 |
+
raise ValueError(f'Cannot reduce {objs}')
|
1326 |
+
|
1327 |
+
|
1328 |
+
def sketch(
|
1329 |
+
name: str, args: list[Union[Point, gm.Point]]
|
1330 |
+
) -> list[Union[Point, Line, Circle, HalfLine, HoleCircle]]:
|
1331 |
+
fun = globals()['sketch_' + name]
|
1332 |
+
args = [p.num if isinstance(p, gm.Point) else p for p in args]
|
1333 |
+
out = fun(args)
|
1334 |
+
|
1335 |
+
# out can be one or multiple {Point/Line/HalfLine}
|
1336 |
+
if isinstance(out, (tuple, list)):
|
1337 |
+
return list(out)
|
1338 |
+
return [out]
|
1339 |
+
|
1340 |
+
|
1341 |
+
def sketch_on_opline(args: tuple[gm.Point, ...]) -> HalfLine:
|
1342 |
+
a, b = args
|
1343 |
+
return HalfLine(a, a + a - b)
|
1344 |
+
|
1345 |
+
|
1346 |
+
def sketch_on_hline(args: tuple[gm.Point, ...]) -> HalfLine:
|
1347 |
+
a, b = args
|
1348 |
+
return HalfLine(a, b)
|
1349 |
+
|
1350 |
+
|
1351 |
+
def sketch_ieq_triangle(args: tuple[gm.Point, ...]) -> tuple[Point, ...]:
|
1352 |
+
a = Point(0.0, 0.0)
|
1353 |
+
b = Point(1.0, 0.0)
|
1354 |
+
|
1355 |
+
c, _ = Circle(a, p1=b).intersect(Circle(b, p1=a))
|
1356 |
+
return a, b, c
|
1357 |
+
|
1358 |
+
|
1359 |
+
def sketch_incenter2(args: tuple[gm.Point, ...]) -> tuple[Point, ...]:
|
1360 |
+
a, b, c = args
|
1361 |
+
l1 = sketch_bisect([b, a, c])
|
1362 |
+
l2 = sketch_bisect([a, b, c])
|
1363 |
+
i = line_line_intersection(l1, l2)
|
1364 |
+
x = i.foot(Line(b, c))
|
1365 |
+
y = i.foot(Line(c, a))
|
1366 |
+
z = i.foot(Line(a, b))
|
1367 |
+
return x, y, z, i
|
1368 |
+
|
1369 |
+
|
1370 |
+
def sketch_excenter2(args: tuple[gm.Point, ...]) -> tuple[Point, ...]:
|
1371 |
+
a, b, c = args
|
1372 |
+
l1 = sketch_bisect([b, a, c])
|
1373 |
+
l2 = sketch_exbisect([a, b, c])
|
1374 |
+
i = line_line_intersection(l1, l2)
|
1375 |
+
x = i.foot(Line(b, c))
|
1376 |
+
y = i.foot(Line(c, a))
|
1377 |
+
z = i.foot(Line(a, b))
|
1378 |
+
return x, y, z, i
|
1379 |
+
|
1380 |
+
|
1381 |
+
def sketch_centroid(args: tuple[gm.Point, ...]) -> tuple[Point, ...]:
|
1382 |
+
a, b, c = args
|
1383 |
+
x = (b + c) * 0.5
|
1384 |
+
y = (c + a) * 0.5
|
1385 |
+
z = (a + b) * 0.5
|
1386 |
+
i = line_line_intersection(Line(a, x), Line(b, y))
|
1387 |
+
return x, y, z, i
|
1388 |
+
|
1389 |
+
|
1390 |
+
def sketch_ninepoints(args: tuple[gm.Point, ...]) -> tuple[Point, ...]:
|
1391 |
+
a, b, c = args
|
1392 |
+
x = (b + c) * 0.5
|
1393 |
+
y = (c + a) * 0.5
|
1394 |
+
z = (a + b) * 0.5
|
1395 |
+
c = Circle(p1=x, p2=y, p3=z)
|
1396 |
+
return x, y, z, c.center
|
1397 |
+
|
1398 |
+
|
1399 |
+
def sketch_2l1c(args: tuple[gm.Point, ...]) -> tuple[Point, ...]:
|
1400 |
+
"""Sketch a circle touching two lines and another circle."""
|
1401 |
+
a, b, c, p = args
|
1402 |
+
bc, ac = Line(b, c), Line(a, c)
|
1403 |
+
circle = Circle(p, p1=a)
|
1404 |
+
|
1405 |
+
d, d_ = line_circle_intersection(p.perpendicular_line(bc), circle)
|
1406 |
+
if bc.diff_side(d_, a):
|
1407 |
+
d = d_
|
1408 |
+
|
1409 |
+
e, e_ = line_circle_intersection(p.perpendicular_line(ac), circle)
|
1410 |
+
if ac.diff_side(e_, b):
|
1411 |
+
e = e_
|
1412 |
+
|
1413 |
+
df = d.perpendicular_line(Line(p, d))
|
1414 |
+
ef = e.perpendicular_line(Line(p, e))
|
1415 |
+
f = line_line_intersection(df, ef)
|
1416 |
+
|
1417 |
+
g, g_ = line_circle_intersection(Line(c, f), circle)
|
1418 |
+
if bc.same_side(g_, a):
|
1419 |
+
g = g_
|
1420 |
+
|
1421 |
+
b_ = c + (b - c) / b.distance(c)
|
1422 |
+
a_ = c + (a - c) / a.distance(c)
|
1423 |
+
m = (a_ + b_) * 0.5
|
1424 |
+
x = line_line_intersection(Line(c, m), Line(p, g))
|
1425 |
+
return x.foot(ac), x.foot(bc), g, x
|
1426 |
+
|
1427 |
+
|
1428 |
+
def sketch_3peq(args: tuple[gm.Point, ...]) -> tuple[Point, ...]:
|
1429 |
+
a, b, c = args
|
1430 |
+
ab, bc, ca = Line(a, b), Line(b, c), Line(c, a)
|
1431 |
+
|
1432 |
+
z = b + (c - b) * np.random.uniform(-0.5, 1.5)
|
1433 |
+
|
1434 |
+
z_ = z * 2 - c
|
1435 |
+
l = z_.parallel_line(ca)
|
1436 |
+
x = line_line_intersection(l, ab)
|
1437 |
+
y = z * 2 - x
|
1438 |
+
return x, y, z
|
1439 |
+
|
1440 |
+
|
1441 |
+
def try_to_sketch_intersect(
|
1442 |
+
name1: str,
|
1443 |
+
args1: list[Union[gm.Point, Point]],
|
1444 |
+
name2: str,
|
1445 |
+
args2: list[Union[gm.Point, Point]],
|
1446 |
+
existing_points: list[Point],
|
1447 |
+
) -> Optional[Point]:
|
1448 |
+
"""Try to sketch an intersection between two objects."""
|
1449 |
+
obj1 = sketch(name1, args1)[0]
|
1450 |
+
obj2 = sketch(name2, args2)[0]
|
1451 |
+
|
1452 |
+
if isinstance(obj1, Line) and isinstance(obj2, Line):
|
1453 |
+
fn = line_line_intersection
|
1454 |
+
elif isinstance(obj1, Circle) and isinstance(obj2, Circle):
|
1455 |
+
fn = circle_circle_intersection
|
1456 |
+
else:
|
1457 |
+
fn = line_circle_intersection
|
1458 |
+
if isinstance(obj2, Line) and isinstance(obj1, Circle):
|
1459 |
+
obj1, obj2 = obj2, obj1
|
1460 |
+
|
1461 |
+
try:
|
1462 |
+
x = fn(obj1, obj2)
|
1463 |
+
except: # pylint: disable=bare-except
|
1464 |
+
return None
|
1465 |
+
|
1466 |
+
if isinstance(x, Point):
|
1467 |
+
return x
|
1468 |
+
|
1469 |
+
x1, x2 = x
|
1470 |
+
|
1471 |
+
close1 = check_too_close([x1], existing_points)
|
1472 |
+
far1 = check_too_far([x1], existing_points)
|
1473 |
+
if not close1 and not far1:
|
1474 |
+
return x1
|
1475 |
+
close2 = check_too_close([x2], existing_points)
|
1476 |
+
far2 = check_too_far([x2], existing_points)
|
1477 |
+
if not close2 and not far2:
|
1478 |
+
return x2
|
1479 |
+
|
1480 |
+
return None
|
1481 |
+
|
1482 |
+
|
1483 |
+
def sketch_acircle(args: tuple[gm.Point, ...]) -> Circle:
|
1484 |
+
a, b, c, d, f = args
|
1485 |
+
de = sketch_aline([c, a, b, f, d])
|
1486 |
+
fe = sketch_aline([a, c, b, d, f])
|
1487 |
+
e = line_line_intersection(de, fe)
|
1488 |
+
return Circle(p1=d, p2=e, p3=f)
|
1489 |
+
|
1490 |
+
|
1491 |
+
def sketch_aline(args: tuple[gm.Point, ...]) -> HalfLine:
|
1492 |
+
"""Sketch the construction aline."""
|
1493 |
+
A, B, C, D, E = args
|
1494 |
+
ab = A - B
|
1495 |
+
cb = C - B
|
1496 |
+
de = D - E
|
1497 |
+
|
1498 |
+
dab = A.distance(B)
|
1499 |
+
ang_ab = np.arctan2(ab.y / dab, ab.x / dab)
|
1500 |
+
|
1501 |
+
dcb = C.distance(B)
|
1502 |
+
ang_bc = np.arctan2(cb.y / dcb, cb.x / dcb)
|
1503 |
+
|
1504 |
+
dde = D.distance(E)
|
1505 |
+
ang_de = np.arctan2(de.y / dde, de.x / dde)
|
1506 |
+
|
1507 |
+
ang_ex = ang_de + ang_bc - ang_ab
|
1508 |
+
X = E + Point(np.cos(ang_ex), np.sin(ang_ex))
|
1509 |
+
return HalfLine(E, X)
|
1510 |
+
|
1511 |
+
|
1512 |
+
def sketch_amirror(args: tuple[gm.Point, ...]) -> HalfLine:
|
1513 |
+
"""Sketch the angle mirror."""
|
1514 |
+
A, B, C = args # pylint: disable=invalid-name
|
1515 |
+
ab = A - B
|
1516 |
+
cb = C - B
|
1517 |
+
|
1518 |
+
dab = A.distance(B)
|
1519 |
+
ang_ab = np.arctan2(ab.y / dab, ab.x / dab)
|
1520 |
+
dcb = C.distance(B)
|
1521 |
+
ang_bc = np.arctan2(cb.y / dcb, cb.x / dcb)
|
1522 |
+
|
1523 |
+
ang_bx = 2 * ang_bc - ang_ab
|
1524 |
+
X = B + Point(np.cos(ang_bx), np.sin(ang_bx)) # pylint: disable=invalid-name
|
1525 |
+
return HalfLine(B, X)
|
1526 |
+
|
1527 |
+
|
1528 |
+
def sketch_bisect(args: tuple[gm.Point, ...]) -> Line:
|
1529 |
+
a, b, c = args
|
1530 |
+
ab = a.distance(b)
|
1531 |
+
bc = b.distance(c)
|
1532 |
+
x = b + (c - b) * (ab / bc)
|
1533 |
+
m = (a + x) * 0.5
|
1534 |
+
return Line(b, m)
|
1535 |
+
|
1536 |
+
|
1537 |
+
def sketch_exbisect(args: tuple[gm.Point, ...]) -> Line:
|
1538 |
+
a, b, c = args
|
1539 |
+
return sketch_bisect(args).perpendicular_line(b)
|
1540 |
+
|
1541 |
+
|
1542 |
+
def sketch_bline(args: tuple[gm.Point, ...]) -> Line:
|
1543 |
+
a, b = args
|
1544 |
+
m = (a + b) * 0.5
|
1545 |
+
return m.perpendicular_line(Line(a, b))
|
1546 |
+
|
1547 |
+
|
1548 |
+
def sketch_dia(args: tuple[gm.Point, ...]) -> Circle:
|
1549 |
+
a, b = args
|
1550 |
+
return Circle((a + b) * 0.5, p1=a)
|
1551 |
+
|
1552 |
+
|
1553 |
+
def sketch_tangent(args: tuple[gm.Point, ...]) -> tuple[Point, Point]:
|
1554 |
+
a, o, b = args
|
1555 |
+
dia = sketch_dia([a, o])
|
1556 |
+
return circle_circle_intersection(Circle(o, p1=b), dia)
|
1557 |
+
|
1558 |
+
|
1559 |
+
def sketch_circle(args: tuple[gm.Point, ...]) -> Circle:
|
1560 |
+
a, b, c = args
|
1561 |
+
return Circle(center=a, radius=b.distance(c))
|
1562 |
+
|
1563 |
+
|
1564 |
+
def sketch_cc_tangent(args: tuple[gm.Point, ...]) -> tuple[Point, ...]:
|
1565 |
+
"""Sketch tangents to two circles."""
|
1566 |
+
o, a, w, b = args
|
1567 |
+
ra, rb = o.distance(a), w.distance(b)
|
1568 |
+
|
1569 |
+
ow = Line(o, w)
|
1570 |
+
if close_enough(ra, rb):
|
1571 |
+
oo = ow.perpendicular_line(o)
|
1572 |
+
oa = Circle(o, ra)
|
1573 |
+
x, z = line_circle_intersection(oo, oa)
|
1574 |
+
y = x + w - o
|
1575 |
+
t = z + w - o
|
1576 |
+
return x, y, z, t
|
1577 |
+
|
1578 |
+
swap = rb > ra
|
1579 |
+
if swap:
|
1580 |
+
o, a, w, b = w, b, o, a
|
1581 |
+
ra, rb = rb, ra
|
1582 |
+
|
1583 |
+
oa = Circle(o, ra)
|
1584 |
+
q = o + (w - o) * ra / (ra - rb)
|
1585 |
+
|
1586 |
+
x, z = circle_circle_intersection(sketch_dia([o, q]), oa)
|
1587 |
+
y = w.foot(Line(x, q))
|
1588 |
+
t = w.foot(Line(z, q))
|
1589 |
+
|
1590 |
+
if swap:
|
1591 |
+
x, y, z, t = y, x, t, z
|
1592 |
+
|
1593 |
+
return x, y, z, t
|
1594 |
+
|
1595 |
+
|
1596 |
+
def sketch_hcircle(args: tuple[gm.Point, ...]) -> HoleCircle:
|
1597 |
+
a, b = args
|
1598 |
+
return HoleCircle(center=a, radius=a.distance(b), hole=b)
|
1599 |
+
|
1600 |
+
|
1601 |
+
def sketch_e5128(args: tuple[gm.Point, ...]) -> tuple[Point, Point]:
|
1602 |
+
a, b, c, d = args
|
1603 |
+
ad = Line(a, d)
|
1604 |
+
|
1605 |
+
g = (a + b) * 0.5
|
1606 |
+
de = Line(d, g)
|
1607 |
+
|
1608 |
+
e, f = line_circle_intersection(de, Circle(c, p1=b))
|
1609 |
+
|
1610 |
+
if e.distance(d) < f.distance(d):
|
1611 |
+
e = f
|
1612 |
+
return e, g
|
1613 |
+
|
1614 |
+
|
1615 |
+
def sketch_eq_quadrangle(args: tuple[gm.Point, ...]) -> tuple[Point, ...]:
|
1616 |
+
"""Sketch quadrangle with two equal opposite sides."""
|
1617 |
+
a = Point(0.0, 0.0)
|
1618 |
+
b = Point(1.0, 0.0)
|
1619 |
+
|
1620 |
+
length = np.random.uniform(0.5, 2.0)
|
1621 |
+
ang = np.random.uniform(np.pi / 3, np.pi * 2 / 3)
|
1622 |
+
d = head_from(a, ang, length)
|
1623 |
+
|
1624 |
+
ang = ang_of(b, d)
|
1625 |
+
ang = np.random.uniform(ang / 10, ang / 9)
|
1626 |
+
c = head_from(b, ang, length)
|
1627 |
+
a, b, c, d = random_rfss(a, b, c, d)
|
1628 |
+
return a, b, c, d
|
1629 |
+
|
1630 |
+
|
1631 |
+
def sketch_eq_trapezoid(args: tuple[gm.Point, ...]) -> tuple[Point, ...]:
|
1632 |
+
a = Point(0.0, 0.0)
|
1633 |
+
b = Point(1.0, 0.0)
|
1634 |
+
l = unif(0.5, 2.0)
|
1635 |
+
|
1636 |
+
height = unif(0.5, 2.0)
|
1637 |
+
c = Point(0.5 + l / 2.0, height)
|
1638 |
+
d = Point(0.5 - l / 2.0, height)
|
1639 |
+
|
1640 |
+
a, b, c, d = random_rfss(a, b, c, d)
|
1641 |
+
return a, b, c, d
|
1642 |
+
|
1643 |
+
|
1644 |
+
def sketch_eqangle2(args: tuple[gm.Point, ...]) -> Point:
|
1645 |
+
"""Sketch the def eqangle2."""
|
1646 |
+
a, b, c = args
|
1647 |
+
|
1648 |
+
d = c * 2 - b
|
1649 |
+
|
1650 |
+
ba = b.distance(a)
|
1651 |
+
bc = b.distance(c)
|
1652 |
+
l = ba * ba / bc
|
1653 |
+
|
1654 |
+
if unif(0.0, 1.0) < 0.5:
|
1655 |
+
be = min(l, bc)
|
1656 |
+
be = unif(be * 0.1, be * 0.9)
|
1657 |
+
else:
|
1658 |
+
be = max(l, bc)
|
1659 |
+
be = unif(be * 1.1, be * 1.5)
|
1660 |
+
|
1661 |
+
e = b + (c - b) * (be / bc)
|
1662 |
+
y = b + (a - b) * (be / l)
|
1663 |
+
return line_line_intersection(Line(c, y), Line(a, e))
|
1664 |
+
|
1665 |
+
|
1666 |
+
def sketch_eqangle3(args: tuple[gm.Point, ...]) -> Circle:
|
1667 |
+
a, b, d, e, f = args
|
1668 |
+
de = d.distance(e)
|
1669 |
+
ef = e.distance(f)
|
1670 |
+
ab = b.distance(a)
|
1671 |
+
ang_ax = ang_of(a, b) + ang_between(e, d, f)
|
1672 |
+
x = head_from(a, ang_ax, length=de / ef * ab)
|
1673 |
+
return Circle(p1=a, p2=b, p3=x)
|
1674 |
+
|
1675 |
+
|
1676 |
+
def sketch_eqdia_quadrangle(args: tuple[gm.Point, ...]) -> tuple[Point, ...]:
|
1677 |
+
"""Sketch quadrangle with two equal diagonals."""
|
1678 |
+
m = unif(0.3, 0.7)
|
1679 |
+
n = unif(0.3, 0.7)
|
1680 |
+
a = Point(-m, 0.0)
|
1681 |
+
c = Point(1 - m, 0.0)
|
1682 |
+
b = Point(0.0, -n)
|
1683 |
+
d = Point(0.0, 1 - n)
|
1684 |
+
|
1685 |
+
ang = unif(-0.25 * np.pi, 0.25 * np.pi)
|
1686 |
+
sin, cos = np.sin(ang), np.cos(ang)
|
1687 |
+
b = b.rotate(sin, cos)
|
1688 |
+
d = d.rotate(sin, cos)
|
1689 |
+
a, b, c, d = random_rfss(a, b, c, d)
|
1690 |
+
return a, b, c, d
|
1691 |
+
|
1692 |
+
|
1693 |
+
def sketch_free(args: tuple[gm.Point, ...]) -> Point:
|
1694 |
+
return random_points(1)[0]
|
1695 |
+
|
1696 |
+
|
1697 |
+
def sketch_isos(args: tuple[gm.Point, ...]) -> tuple[Point, ...]:
|
1698 |
+
base = unif(0.5, 1.5)
|
1699 |
+
height = unif(0.5, 1.5)
|
1700 |
+
|
1701 |
+
b = Point(-base / 2, 0.0)
|
1702 |
+
c = Point(base / 2, 0.0)
|
1703 |
+
a = Point(0.0, height)
|
1704 |
+
a, b, c = random_rfss(a, b, c)
|
1705 |
+
return a, b, c
|
1706 |
+
|
1707 |
+
|
1708 |
+
def sketch_line(args: tuple[gm.Point, ...]) -> Line:
|
1709 |
+
a, b = args
|
1710 |
+
return Line(a, b)
|
1711 |
+
|
1712 |
+
|
1713 |
+
def sketch_cyclic(args: tuple[gm.Point, ...]) -> Circle:
|
1714 |
+
a, b, c = args
|
1715 |
+
return Circle(p1=a, p2=b, p3=c)
|
1716 |
+
|
1717 |
+
|
1718 |
+
def sketch_hline(args: tuple[gm.Point, ...]) -> HalfLine:
|
1719 |
+
a, b = args
|
1720 |
+
return HalfLine(a, b)
|
1721 |
+
|
1722 |
+
|
1723 |
+
def sketch_midp(args: tuple[gm.Point, ...]) -> Point:
|
1724 |
+
a, b = args
|
1725 |
+
return (a + b) * 0.5
|
1726 |
+
|
1727 |
+
|
1728 |
+
def sketch_pentagon(args: tuple[gm.Point, ...]) -> tuple[Point, ...]:
|
1729 |
+
points = [Point(1.0, 0.0)]
|
1730 |
+
ang = 0.0
|
1731 |
+
|
1732 |
+
for i in range(4):
|
1733 |
+
ang += (2 * np.pi - ang) / (5 - i) * unif(0.5, 1.5)
|
1734 |
+
point = Point(np.cos(ang), np.sin(ang))
|
1735 |
+
points.append(point)
|
1736 |
+
|
1737 |
+
a, b, c, d, e = points # pylint: disable=unbalanced-tuple-unpacking
|
1738 |
+
a, b, c, d, e = random_rfss(a, b, c, d, e)
|
1739 |
+
return a, b, c, d, e
|
1740 |
+
|
1741 |
+
|
1742 |
+
def sketch_pline(args: tuple[gm.Point, ...]) -> Line:
|
1743 |
+
a, b, c = args
|
1744 |
+
return a.parallel_line(Line(b, c))
|
1745 |
+
|
1746 |
+
|
1747 |
+
def sketch_pmirror(args: tuple[gm.Point, ...]) -> Point:
|
1748 |
+
a, b = args
|
1749 |
+
return b * 2 - a
|
1750 |
+
|
1751 |
+
|
1752 |
+
def sketch_quadrangle(args: tuple[gm.Point, ...]) -> tuple[Point, ...]:
|
1753 |
+
"""Sketch a random quadrangle."""
|
1754 |
+
m = unif(0.3, 0.7)
|
1755 |
+
n = unif(0.3, 0.7)
|
1756 |
+
|
1757 |
+
a = Point(-m, 0.0)
|
1758 |
+
c = Point(1 - m, 0.0)
|
1759 |
+
b = Point(0.0, -unif(0.25, 0.75))
|
1760 |
+
d = Point(0.0, unif(0.25, 0.75))
|
1761 |
+
|
1762 |
+
ang = unif(-0.25 * np.pi, 0.25 * np.pi)
|
1763 |
+
sin, cos = np.sin(ang), np.cos(ang)
|
1764 |
+
b = b.rotate(sin, cos)
|
1765 |
+
d = d.rotate(sin, cos)
|
1766 |
+
a, b, c, d = random_rfss(a, b, c, d)
|
1767 |
+
return a, b, c, d
|
1768 |
+
|
1769 |
+
|
1770 |
+
def sketch_r_trapezoid(args: tuple[gm.Point, ...]) -> tuple[Point, ...]:
|
1771 |
+
a = Point(0.0, 1.0)
|
1772 |
+
d = Point(0.0, 0.0)
|
1773 |
+
b = Point(unif(0.5, 1.5), 1.0)
|
1774 |
+
c = Point(unif(0.5, 1.5), 0.0)
|
1775 |
+
a, b, c, d = random_rfss(a, b, c, d)
|
1776 |
+
return a, b, c, d
|
1777 |
+
|
1778 |
+
|
1779 |
+
def sketch_r_triangle(args: tuple[gm.Point, ...]) -> tuple[Point, ...]:
|
1780 |
+
a = Point(0.0, 0.0)
|
1781 |
+
b = Point(0.0, unif(0.5, 2.0))
|
1782 |
+
c = Point(unif(0.5, 2.0), 0.0)
|
1783 |
+
a, b, c = random_rfss(a, b, c)
|
1784 |
+
return a, b, c
|
1785 |
+
|
1786 |
+
|
1787 |
+
def sketch_rectangle(args: tuple[gm.Point, ...]) -> tuple[Point, ...]:
|
1788 |
+
a = Point(0.0, 0.0)
|
1789 |
+
b = Point(0.0, 1.0)
|
1790 |
+
l = unif(0.5, 2.0)
|
1791 |
+
c = Point(l, 1.0)
|
1792 |
+
d = Point(l, 0.0)
|
1793 |
+
a, b, c, d = random_rfss(a, b, c, d)
|
1794 |
+
return a, b, c, d
|
1795 |
+
|
1796 |
+
|
1797 |
+
def sketch_reflect(args: tuple[gm.Point, ...]) -> Point:
|
1798 |
+
a, b, c = args
|
1799 |
+
m = a.foot(Line(b, c))
|
1800 |
+
return m * 2 - a
|
1801 |
+
|
1802 |
+
|
1803 |
+
def sketch_risos(args: tuple[gm.Point, ...]) -> tuple[Point, ...]:
|
1804 |
+
a = Point(0.0, 0.0)
|
1805 |
+
b = Point(0.0, 1.0)
|
1806 |
+
c = Point(1.0, 0.0)
|
1807 |
+
a, b, c = random_rfss(a, b, c)
|
1808 |
+
return a, b, c
|
1809 |
+
|
1810 |
+
|
1811 |
+
def sketch_rotaten90(args: tuple[gm.Point, ...]) -> Point:
|
1812 |
+
a, b = args
|
1813 |
+
ang = -np.pi / 2
|
1814 |
+
return a + (b - a).rotate(np.sin(ang), np.cos(ang))
|
1815 |
+
|
1816 |
+
|
1817 |
+
def sketch_rotatep90(args: tuple[gm.Point, ...]) -> Point:
|
1818 |
+
a, b = args
|
1819 |
+
ang = np.pi / 2
|
1820 |
+
return a + (b - a).rotate(np.sin(ang), np.cos(ang))
|
1821 |
+
|
1822 |
+
|
1823 |
+
def sketch_s_angle(args: tuple[gm.Point, ...]) -> HalfLine:
|
1824 |
+
a, b, y = args
|
1825 |
+
ang = y / 180 * np.pi
|
1826 |
+
x = b + (a - b).rotatea(ang)
|
1827 |
+
return HalfLine(b, x)
|
1828 |
+
|
1829 |
+
|
1830 |
+
def sketch_segment(args: tuple[gm.Point, ...]) -> tuple[Point, Point]:
|
1831 |
+
a, b = random_points(2)
|
1832 |
+
return a, b
|
1833 |
+
|
1834 |
+
|
1835 |
+
def sketch_shift(args: tuple[gm.Point, ...]) -> Point:
|
1836 |
+
a, b, c = args
|
1837 |
+
return c + (b - a)
|
1838 |
+
|
1839 |
+
|
1840 |
+
def sketch_square(args: tuple[gm.Point, ...]) -> tuple[Point, Point]:
|
1841 |
+
a, b = args
|
1842 |
+
c = b + (a - b).rotatea(-np.pi / 2)
|
1843 |
+
d = a + (b - a).rotatea(np.pi / 2)
|
1844 |
+
return c, d
|
1845 |
+
|
1846 |
+
|
1847 |
+
def sketch_isquare(args: tuple[gm.Point, ...]) -> tuple[Point, ...]:
|
1848 |
+
a = Point(0.0, 0.0)
|
1849 |
+
b = Point(1.0, 0.0)
|
1850 |
+
c = Point(1.0, 1.0)
|
1851 |
+
d = Point(0.0, 1.0)
|
1852 |
+
a, b, c, d = random_rfss(a, b, c, d)
|
1853 |
+
return a, b, c, d
|
1854 |
+
|
1855 |
+
|
1856 |
+
def sketch_tline(args: tuple[gm.Point, ...]) -> Line:
|
1857 |
+
a, b, c = args
|
1858 |
+
return a.perpendicular_line(Line(b, c))
|
1859 |
+
|
1860 |
+
|
1861 |
+
def sketch_trapezoid(args: tuple[gm.Point, ...]) -> tuple[Point, ...]:
|
1862 |
+
d = Point(0.0, 0.0)
|
1863 |
+
c = Point(1.0, 0.0)
|
1864 |
+
|
1865 |
+
base = unif(0.5, 2.0)
|
1866 |
+
height = unif(0.5, 2.0)
|
1867 |
+
a = Point(unif(0.2, 0.5), height)
|
1868 |
+
b = Point(a.x + base, height)
|
1869 |
+
a, b, c, d = random_rfss(a, b, c, d)
|
1870 |
+
return a, b, c, d
|
1871 |
+
|
1872 |
+
|
1873 |
+
def sketch_triangle(args: tuple[gm.Point, ...]) -> tuple[Point, ...]:
|
1874 |
+
a = Point(0.0, 0.0)
|
1875 |
+
b = Point(1.0, 0.0)
|
1876 |
+
ac = unif(0.5, 2.0)
|
1877 |
+
ang = unif(0.2, 0.8) * np.pi
|
1878 |
+
c = head_from(a, ang, ac)
|
1879 |
+
return a, b, c
|
1880 |
+
|
1881 |
+
|
1882 |
+
def sketch_triangle12(args: tuple[gm.Point, ...]) -> tuple[Point, ...]:
|
1883 |
+
b = Point(0.0, 0.0)
|
1884 |
+
c = Point(unif(1.5, 2.5), 0.0)
|
1885 |
+
a, _ = circle_circle_intersection(Circle(b, 1.0), Circle(c, 2.0))
|
1886 |
+
a, b, c = random_rfss(a, b, c)
|
1887 |
+
return a, b, c
|
1888 |
+
|
1889 |
+
|
1890 |
+
def sketch_trisect(args: tuple[gm.Point, ...]) -> tuple[Point, Point]:
|
1891 |
+
"""Sketch two trisectors of an angle."""
|
1892 |
+
a, b, c = args
|
1893 |
+
ang1 = ang_of(b, a)
|
1894 |
+
ang2 = ang_of(b, c)
|
1895 |
+
|
1896 |
+
swap = 0
|
1897 |
+
if ang1 > ang2:
|
1898 |
+
ang1, ang2 = ang2, ang1
|
1899 |
+
swap += 1
|
1900 |
+
|
1901 |
+
if ang2 - ang1 > np.pi:
|
1902 |
+
ang1, ang2 = ang2, ang1 + 2 * np.pi
|
1903 |
+
swap += 1
|
1904 |
+
|
1905 |
+
angx = ang1 + (ang2 - ang1) / 3
|
1906 |
+
angy = ang2 - (ang2 - ang1) / 3
|
1907 |
+
|
1908 |
+
x = b + Point(np.cos(angx), np.sin(angx))
|
1909 |
+
y = b + Point(np.cos(angy), np.sin(angy))
|
1910 |
+
|
1911 |
+
ac = Line(a, c)
|
1912 |
+
x = line_line_intersection(Line(b, x), ac)
|
1913 |
+
y = line_line_intersection(Line(b, y), ac)
|
1914 |
+
|
1915 |
+
if swap == 1:
|
1916 |
+
return y, x
|
1917 |
+
return x, y
|
1918 |
+
|
1919 |
+
|
1920 |
+
def sketch_trisegment(args: tuple[gm.Point, ...]) -> tuple[Point, Point]:
|
1921 |
+
a, b = args
|
1922 |
+
x, y = a + (b - a) * (1.0 / 3), a + (b - a) * (2.0 / 3)
|
1923 |
+
return x, y
|
ag4masses/alphageometry/numericals_test.py
ADDED
@@ -0,0 +1,313 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright 2023 DeepMind Technologies Limited
|
2 |
+
#
|
3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4 |
+
# you may not use this file except in compliance with the License.
|
5 |
+
# You may obtain a copy of the License at
|
6 |
+
#
|
7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8 |
+
#
|
9 |
+
# Unless required by applicable law or agreed to in writing, software
|
10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
+
# See the License for the specific language governing permissions and
|
13 |
+
# limitations under the License.
|
14 |
+
# ==============================================================================
|
15 |
+
|
16 |
+
"""Unit testing for the geometry numericals code."""
|
17 |
+
|
18 |
+
import unittest
|
19 |
+
|
20 |
+
from absl.testing import absltest
|
21 |
+
import numericals as nm
|
22 |
+
|
23 |
+
np = nm.np
|
24 |
+
|
25 |
+
unif = nm.unif
|
26 |
+
Point = nm.Point
|
27 |
+
Line = nm.Line
|
28 |
+
Circle = nm.Circle
|
29 |
+
HalfLine = nm.HalfLine
|
30 |
+
|
31 |
+
line_circle_intersection = nm.line_circle_intersection
|
32 |
+
line_line_intersection = nm.line_line_intersection
|
33 |
+
|
34 |
+
check_coll = nm.check_coll
|
35 |
+
check_eqangle = nm.check_eqangle
|
36 |
+
|
37 |
+
random_points = nm.random_points
|
38 |
+
ang_between = nm.ang_between
|
39 |
+
head_from = nm.head_from
|
40 |
+
|
41 |
+
|
42 |
+
class NumericalTest(unittest.TestCase):
|
43 |
+
|
44 |
+
def test_sketch_ieq_triangle(self):
|
45 |
+
a, b, c = nm.sketch_ieq_triangle([])
|
46 |
+
self.assertAlmostEqual(a.distance(b), b.distance(c))
|
47 |
+
self.assertAlmostEqual(c.distance(a), b.distance(c))
|
48 |
+
|
49 |
+
def test_sketch_2l1c(self):
|
50 |
+
p = nm.Point(0.0, 0.0)
|
51 |
+
pi = np.pi
|
52 |
+
anga = unif(-0.4 * pi, 0.4 * pi)
|
53 |
+
a = Point(np.cos(anga), np.sin(anga))
|
54 |
+
angb = unif(0.6 * pi, 1.4 * pi)
|
55 |
+
b = Point(np.cos(angb), np.sin(angb))
|
56 |
+
|
57 |
+
angc = unif(anga + 0.05 * pi, angb - 0.05 * pi)
|
58 |
+
c = Point(np.cos(angc), np.sin(angc)) * unif(0.2, 0.8)
|
59 |
+
|
60 |
+
x, y, z, i = nm.sketch_2l1c([a, b, c, p])
|
61 |
+
self.assertTrue(check_coll([x, c, a]))
|
62 |
+
self.assertTrue(check_coll([y, c, b]))
|
63 |
+
self.assertAlmostEqual(z.distance(p), 1.0)
|
64 |
+
self.assertTrue(check_coll([p, i, z]))
|
65 |
+
self.assertTrue(Line(i, x).is_perp(Line(c, a)))
|
66 |
+
self.assertTrue(Line(i, y).is_perp(Line(c, b)))
|
67 |
+
self.assertAlmostEqual(i.distance(x), i.distance(y))
|
68 |
+
self.assertAlmostEqual(i.distance(x), i.distance(z))
|
69 |
+
|
70 |
+
def test_sketch_3peq(self):
|
71 |
+
a, b, c = random_points(3)
|
72 |
+
x, y, z = nm.sketch_3peq([a, b, c])
|
73 |
+
|
74 |
+
self.assertTrue(check_coll([a, b, x]))
|
75 |
+
self.assertTrue(check_coll([a, c, y]))
|
76 |
+
self.assertTrue(check_coll([b, c, z]))
|
77 |
+
self.assertTrue(check_coll([x, y, z]))
|
78 |
+
self.assertAlmostEqual(z.distance(x), z.distance(y))
|
79 |
+
|
80 |
+
def test_sketch_aline(self):
|
81 |
+
a, b, c, d, e = random_points(5)
|
82 |
+
ex = nm.sketch_aline([a, b, c, d, e])
|
83 |
+
self.assertIsInstance(ex, HalfLine)
|
84 |
+
self.assertEqual(ex.tail, e)
|
85 |
+
x = ex.head
|
86 |
+
self.assertAlmostEqual(ang_between(b, a, c), ang_between(e, d, x))
|
87 |
+
|
88 |
+
def test_sketch_amirror(self):
|
89 |
+
a, b, c = random_points(3)
|
90 |
+
bx = nm.sketch_amirror([a, b, c])
|
91 |
+
self.assertIsInstance(bx, HalfLine)
|
92 |
+
assert bx.tail == b
|
93 |
+
x = bx.head
|
94 |
+
|
95 |
+
ang1 = ang_between(b, a, c)
|
96 |
+
ang2 = ang_between(b, c, x)
|
97 |
+
self.assertAlmostEqual(ang1, ang2)
|
98 |
+
|
99 |
+
def test_sketch_bisect(self):
|
100 |
+
a, b, c = random_points(3)
|
101 |
+
line = nm.sketch_bisect([a, b, c])
|
102 |
+
self.assertAlmostEqual(b.distance(line), 0.0)
|
103 |
+
|
104 |
+
l = a.perpendicular_line(line)
|
105 |
+
x = line_line_intersection(l, Line(b, c))
|
106 |
+
self.assertAlmostEqual(a.distance(line), x.distance(line))
|
107 |
+
|
108 |
+
d, _ = line_circle_intersection(line, Circle(b, radius=1))
|
109 |
+
ang1 = ang_between(b, a, d)
|
110 |
+
ang2 = ang_between(b, d, c)
|
111 |
+
self.assertAlmostEqual(ang1, ang2)
|
112 |
+
|
113 |
+
def test_sketch_bline(self):
|
114 |
+
a, b = random_points(2)
|
115 |
+
l = nm.sketch_bline([a, b])
|
116 |
+
self.assertTrue(Line(a, b).is_perp(l))
|
117 |
+
self.assertAlmostEqual(a.distance(l), b.distance(l))
|
118 |
+
|
119 |
+
def test_sketch_cc_tangent(self):
|
120 |
+
o = Point(0.0, 0.0)
|
121 |
+
w = Point(1.0, 0.0)
|
122 |
+
|
123 |
+
ra = unif(0.0, 0.6)
|
124 |
+
rb = unif(0.4, 1.0)
|
125 |
+
|
126 |
+
a = unif(0.0, np.pi)
|
127 |
+
b = unif(0.0, np.pi)
|
128 |
+
|
129 |
+
a = o + ra * Point(np.cos(a), np.sin(a))
|
130 |
+
b = w + rb * Point(np.sin(b), np.cos(b))
|
131 |
+
|
132 |
+
x, y, z, t = nm.sketch_cc_tangent([o, a, w, b])
|
133 |
+
xy = Line(x, y)
|
134 |
+
zt = Line(z, t)
|
135 |
+
self.assertAlmostEqual(o.distance(xy), o.distance(a))
|
136 |
+
self.assertAlmostEqual(o.distance(zt), o.distance(a))
|
137 |
+
self.assertAlmostEqual(w.distance(xy), w.distance(b))
|
138 |
+
self.assertAlmostEqual(w.distance(zt), w.distance(b))
|
139 |
+
|
140 |
+
def test_sketch_circle(self):
|
141 |
+
a, b, c = random_points(3)
|
142 |
+
circle = nm.sketch_circle([a, b, c])
|
143 |
+
self.assertAlmostEqual(circle.center.distance(a), 0.0)
|
144 |
+
self.assertAlmostEqual(circle.radius, b.distance(c))
|
145 |
+
|
146 |
+
def test_sketch_e5128(self):
|
147 |
+
b = Point(0.0, 0.0)
|
148 |
+
c = Point(0.0, 1.0)
|
149 |
+
ang = unif(-np.pi / 2, 3 * np.pi / 2)
|
150 |
+
d = head_from(c, ang, 1.0)
|
151 |
+
a = Point(unif(0.5, 2.0), 0.0)
|
152 |
+
|
153 |
+
e, g = nm.sketch_e5128([a, b, c, d])
|
154 |
+
ang1 = ang_between(a, b, d)
|
155 |
+
ang2 = ang_between(e, a, g)
|
156 |
+
self.assertAlmostEqual(ang1, ang2)
|
157 |
+
|
158 |
+
def test_sketch_eq_quadrangle(self):
|
159 |
+
a, b, c, d = nm.sketch_eq_quadrangle([])
|
160 |
+
self.assertAlmostEqual(a.distance(d), c.distance(b))
|
161 |
+
ac = Line(a, c)
|
162 |
+
assert ac.diff_side(b, d), (ac(b), ac(d))
|
163 |
+
bd = Line(b, d)
|
164 |
+
assert bd.diff_side(a, c), (bd(a), bd(c))
|
165 |
+
|
166 |
+
def test_sketch_eq_trapezoid(self):
|
167 |
+
a, b, c, d = nm.sketch_eq_trapezoid([])
|
168 |
+
assert Line(a, b).is_parallel(Line(c, d))
|
169 |
+
self.assertAlmostEqual(a.distance(d), b.distance(c))
|
170 |
+
|
171 |
+
def test_sketch_eqangle3(self):
|
172 |
+
points = random_points(5)
|
173 |
+
x = nm.sketch_eqangle3(points).sample_within(points)[0]
|
174 |
+
a, b, d, e, f = points
|
175 |
+
self.assertTrue(check_eqangle([x, a, x, b, d, e, d, f]))
|
176 |
+
|
177 |
+
def test_sketch_eqangle2(self):
|
178 |
+
a, b, c = random_points(3)
|
179 |
+
x = nm.sketch_eqangle2([a, b, c])
|
180 |
+
ang1 = ang_between(a, b, x)
|
181 |
+
ang2 = ang_between(c, x, b)
|
182 |
+
self.assertAlmostEqual(ang1, ang2)
|
183 |
+
|
184 |
+
def test_sketch_edia_quadrangle(self):
|
185 |
+
a, b, c, d = nm.sketch_eqdia_quadrangle([])
|
186 |
+
assert Line(a, c).diff_side(b, d)
|
187 |
+
assert Line(b, d).diff_side(a, c)
|
188 |
+
self.assertAlmostEqual(a.distance(c), b.distance(d))
|
189 |
+
|
190 |
+
def test_sketch_isos(self):
|
191 |
+
a, b, c = nm.sketch_isos([])
|
192 |
+
self.assertAlmostEqual(a.distance(b), a.distance(c))
|
193 |
+
self.assertAlmostEqual(ang_between(b, a, c), ang_between(c, b, a))
|
194 |
+
|
195 |
+
def test_sketch_quadrange(self):
|
196 |
+
a, b, c, d = nm.sketch_quadrangle([])
|
197 |
+
self.assertTrue(Line(a, c).diff_side(b, d))
|
198 |
+
self.assertTrue(Line(b, d).diff_side(a, c))
|
199 |
+
|
200 |
+
def test_sketch_r_trapezoid(self):
|
201 |
+
a, b, c, d = nm.sketch_r_trapezoid([])
|
202 |
+
self.assertTrue(Line(a, b).is_perp(Line(a, d)))
|
203 |
+
self.assertTrue(Line(a, b).is_parallel(Line(c, d)))
|
204 |
+
self.assertTrue(Line(a, c).diff_side(b, d))
|
205 |
+
self.assertTrue(Line(b, d).diff_side(a, c))
|
206 |
+
|
207 |
+
def test_sketch_r_triangle(self):
|
208 |
+
a, b, c = nm.sketch_r_triangle([])
|
209 |
+
self.assertTrue(Line(a, b).is_perp(Line(a, c)))
|
210 |
+
|
211 |
+
def test_sketch_rectangle(self):
|
212 |
+
a, b, c, d = nm.sketch_rectangle([])
|
213 |
+
self.assertTrue(Line(a, b).is_perp(Line(b, c)))
|
214 |
+
self.assertTrue(Line(b, c).is_perp(Line(c, d)))
|
215 |
+
self.assertTrue(Line(c, d).is_perp(Line(d, a)))
|
216 |
+
|
217 |
+
def test_sketch_reflect(self):
|
218 |
+
a, b, c = random_points(3)
|
219 |
+
x = nm.sketch_reflect([a, b, c])
|
220 |
+
self.assertTrue(Line(a, x).is_perp(Line(b, c)))
|
221 |
+
self.assertAlmostEqual(x.distance(Line(b, c)), a.distance(Line(b, c)))
|
222 |
+
|
223 |
+
def test_sketch_risos(self):
|
224 |
+
a, b, c = nm.sketch_risos([])
|
225 |
+
self.assertAlmostEqual(a.distance(b), a.distance(c))
|
226 |
+
self.assertTrue(Line(a, b).is_perp(Line(a, c)))
|
227 |
+
|
228 |
+
def test_sketch_rotaten90(self):
|
229 |
+
a, b = random_points(2)
|
230 |
+
x = nm.sketch_rotaten90([a, b])
|
231 |
+
self.assertAlmostEqual(a.distance(x), a.distance(b))
|
232 |
+
self.assertTrue(Line(a, x).is_perp(Line(a, b)))
|
233 |
+
d = Point(0.0, 0.0)
|
234 |
+
e = Point(0.0, 1.0)
|
235 |
+
f = Point(1.0, 0.0)
|
236 |
+
self.assertAlmostEqual(ang_between(d, e, f), ang_between(a, b, x))
|
237 |
+
|
238 |
+
def test_sketch_rotatep90(self):
|
239 |
+
a, b = random_points(2)
|
240 |
+
x = nm.sketch_rotatep90([a, b])
|
241 |
+
self.assertAlmostEqual(a.distance(x), a.distance(b))
|
242 |
+
self.assertTrue(Line(a, x).is_perp(Line(a, b)))
|
243 |
+
d = Point(0.0, 0.0)
|
244 |
+
e = Point(0.0, 1.0)
|
245 |
+
f = Point(1.0, 0.0)
|
246 |
+
self.assertAlmostEqual(ang_between(d, f, e), ang_between(a, b, x))
|
247 |
+
|
248 |
+
def test_sketch_s_angle(self):
|
249 |
+
a, b = random_points(2)
|
250 |
+
y = unif(0.0, np.pi)
|
251 |
+
bx = nm.sketch_s_angle([a, b, y / np.pi * 180])
|
252 |
+
self.assertIsInstance(bx, HalfLine)
|
253 |
+
self.assertEqual(bx.tail, b)
|
254 |
+
x = bx.head
|
255 |
+
|
256 |
+
d = Point(1.0, 0.0)
|
257 |
+
e = Point(0.0, 0.0)
|
258 |
+
f = Point(np.cos(y), np.sin(y))
|
259 |
+
self.assertAlmostEqual(ang_between(e, d, f), ang_between(b, a, x))
|
260 |
+
|
261 |
+
def test_sketch_shift(self):
|
262 |
+
a, b, c = random_points(3)
|
263 |
+
x = nm.sketch_shift([a, b, c])
|
264 |
+
self.assertTrue((b - a).close(x - c))
|
265 |
+
|
266 |
+
def test_sketch_square(self):
|
267 |
+
a, b = random_points(2)
|
268 |
+
c, d = nm.sketch_square([a, b])
|
269 |
+
self.assertTrue(Line(a, b).is_perp(Line(b, c)))
|
270 |
+
self.assertTrue(Line(b, c).is_perp(Line(c, d)))
|
271 |
+
self.assertTrue(Line(c, d).is_perp(Line(d, a)))
|
272 |
+
self.assertAlmostEqual(a.distance(b), b.distance(c))
|
273 |
+
|
274 |
+
def test_sketch_isquare(self):
|
275 |
+
a, b, c, d = nm.sketch_isquare([])
|
276 |
+
self.assertTrue(Line(a, b).is_perp(Line(b, c)))
|
277 |
+
self.assertTrue(Line(b, c).is_perp(Line(c, d)))
|
278 |
+
self.assertTrue(Line(c, d).is_perp(Line(d, a)))
|
279 |
+
self.assertAlmostEqual(a.distance(b), b.distance(c))
|
280 |
+
|
281 |
+
def test_sketch_trapezoid(self):
|
282 |
+
a, b, c, d = nm.sketch_trapezoid([])
|
283 |
+
self.assertTrue(Line(a, b).is_parallel(Line(c, d)))
|
284 |
+
self.assertTrue(Line(a, c).diff_side(b, d))
|
285 |
+
self.assertTrue(Line(b, d).diff_side(a, c))
|
286 |
+
|
287 |
+
def test_sketch_triangle(self):
|
288 |
+
a, b, c = nm.sketch_triangle([])
|
289 |
+
self.assertFalse(check_coll([a, b, c]))
|
290 |
+
|
291 |
+
def test_sketch_triangle12(self):
|
292 |
+
a, b, c = nm.sketch_triangle12([])
|
293 |
+
self.assertAlmostEqual(a.distance(b) * 2, a.distance(c))
|
294 |
+
|
295 |
+
def test_sketch_trisect(self):
|
296 |
+
a, b, c = random_points(3)
|
297 |
+
x, y = nm.sketch_trisect([a, b, c])
|
298 |
+
self.assertAlmostEqual(ang_between(b, a, x), ang_between(b, x, y))
|
299 |
+
self.assertAlmostEqual(ang_between(b, x, y), ang_between(b, y, c))
|
300 |
+
self.assertAlmostEqual(ang_between(b, a, x) * 3, ang_between(b, a, c))
|
301 |
+
|
302 |
+
def test_sketch_trisegment(self):
|
303 |
+
a, b = random_points(2)
|
304 |
+
x, y = nm.sketch_trisegment([a, b])
|
305 |
+
self.assertAlmostEqual(
|
306 |
+
a.distance(x) + x.distance(y) + y.distance(b), a.distance(b)
|
307 |
+
)
|
308 |
+
self.assertAlmostEqual(a.distance(x), x.distance(y))
|
309 |
+
self.assertAlmostEqual(x.distance(y), y.distance(b))
|
310 |
+
|
311 |
+
|
312 |
+
if __name__ == '__main__':
|
313 |
+
absltest.main()
|
ag4masses/alphageometry/pretty.py
ADDED
@@ -0,0 +1,216 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright 2023 DeepMind Technologies Limited
|
2 |
+
#
|
3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4 |
+
# you may not use this file except in compliance with the License.
|
5 |
+
# You may obtain a copy of the License at
|
6 |
+
#
|
7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8 |
+
#
|
9 |
+
# Unless required by applicable law or agreed to in writing, software
|
10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
+
# See the License for the specific language governing permissions and
|
13 |
+
# limitations under the License.
|
14 |
+
# ==============================================================================
|
15 |
+
|
16 |
+
"""Utilities for string manipulation in the DSL."""
|
17 |
+
|
18 |
+
MAP_SYMBOL = {
|
19 |
+
'T': 'perp',
|
20 |
+
'P': 'para',
|
21 |
+
'D': 'cong',
|
22 |
+
'S': 'simtri',
|
23 |
+
'I': 'circle',
|
24 |
+
'M': 'midp',
|
25 |
+
'O': 'cyclic',
|
26 |
+
'C': 'coll',
|
27 |
+
'^': 'eqangle',
|
28 |
+
'/': 'eqratio',
|
29 |
+
'%': 'eqratio',
|
30 |
+
'=': 'contri',
|
31 |
+
'X': 'collx',
|
32 |
+
'A': 'acompute',
|
33 |
+
'R': 'rcompute',
|
34 |
+
'Q': 'fixc',
|
35 |
+
'E': 'fixl',
|
36 |
+
'V': 'fixb',
|
37 |
+
'H': 'fixt',
|
38 |
+
'Z': 'fixp',
|
39 |
+
'Y': 'ind',
|
40 |
+
}
|
41 |
+
|
42 |
+
|
43 |
+
def map_symbol(c: str) -> str:
|
44 |
+
return MAP_SYMBOL[c]
|
45 |
+
|
46 |
+
|
47 |
+
def map_symbol_inv(c: str) -> str:
|
48 |
+
return {v: k for k, v in MAP_SYMBOL.items()}[c]
|
49 |
+
|
50 |
+
|
51 |
+
def _gcd(x: int, y: int) -> int:
|
52 |
+
while y:
|
53 |
+
x, y = y, x % y
|
54 |
+
return x
|
55 |
+
|
56 |
+
|
57 |
+
def simplify(n: int, d: int) -> tuple[int, int]:
|
58 |
+
g = _gcd(n, d)
|
59 |
+
return (n // g, d // g)
|
60 |
+
|
61 |
+
|
62 |
+
def pretty2r(a: str, b: str, c: str, d: str) -> str:
|
63 |
+
if b in (c, d):
|
64 |
+
a, b = b, a
|
65 |
+
|
66 |
+
if a == d:
|
67 |
+
c, d = d, c
|
68 |
+
|
69 |
+
return f'{a} {b} {c} {d}'
|
70 |
+
|
71 |
+
|
72 |
+
def pretty2a(a: str, b: str, c: str, d: str) -> str:
|
73 |
+
if b in (c, d):
|
74 |
+
a, b = b, a
|
75 |
+
|
76 |
+
if a == d:
|
77 |
+
c, d = d, c
|
78 |
+
|
79 |
+
return f'{a} {b} {c} {d}'
|
80 |
+
|
81 |
+
|
82 |
+
def pretty_angle(a: str, b: str, c: str, d: str) -> str:
|
83 |
+
if b in (c, d):
|
84 |
+
a, b = b, a
|
85 |
+
if a == d:
|
86 |
+
c, d = d, c
|
87 |
+
|
88 |
+
if a == c:
|
89 |
+
return f'\u2220{b}{a}{d}'
|
90 |
+
return f'\u2220({a}{b}-{c}{d})'
|
91 |
+
|
92 |
+
|
93 |
+
def pretty_nl(name: str, args: list[str]) -> str:
|
94 |
+
"""Natural lang formatting a predicate."""
|
95 |
+
if name == 'aconst':
|
96 |
+
a, b, c, d, y = args
|
97 |
+
return f'{pretty_angle(a, b, c, d)} = {y}'
|
98 |
+
if name == 'rconst':
|
99 |
+
a, b, c, d, y = args
|
100 |
+
return f'{a}{b}:{c}{d} = {y}'
|
101 |
+
if name == 'acompute':
|
102 |
+
a, b, c, d = args
|
103 |
+
return f'{pretty_angle(a, b, c, d)}'
|
104 |
+
if name in ['coll', 'C']:
|
105 |
+
return '' + ','.join(args) + ' are collinear'
|
106 |
+
if name == 'collx':
|
107 |
+
return '' + ','.join(list(set(args))) + ' are collinear'
|
108 |
+
if name in ['cyclic', 'O']:
|
109 |
+
return '' + ','.join(args) + ' are concyclic'
|
110 |
+
if name in ['midp', 'midpoint', 'M']:
|
111 |
+
x, a, b = args
|
112 |
+
return f'{x} is midpoint of {a}{b}'
|
113 |
+
if name in ['eqangle', 'eqangle6', '^']:
|
114 |
+
a, b, c, d, e, f, g, h = args
|
115 |
+
return f'{pretty_angle(a, b, c, d)} = {pretty_angle(e, f, g, h)}'
|
116 |
+
if name in ['eqratio', 'eqratio6', '/']:
|
117 |
+
return '{}{}:{}{} = {}{}:{}{}'.format(*args)
|
118 |
+
if name == 'eqratio3':
|
119 |
+
a, b, c, d, o, o = args # pylint: disable=redeclared-assigned-name
|
120 |
+
return f'S {o} {a} {b} {o} {c} {d}'
|
121 |
+
if name in ['cong', 'D']:
|
122 |
+
a, b, c, d = args
|
123 |
+
return f'{a}{b} = {c}{d}'
|
124 |
+
if name in ['perp', 'T']:
|
125 |
+
if len(args) == 2: # this is algebraic derivation.
|
126 |
+
ab, cd = args # ab = 'd( ... )'
|
127 |
+
return f'{ab} \u27c2 {cd}'
|
128 |
+
a, b, c, d = args
|
129 |
+
return f'{a}{b} \u27c2 {c}{d}'
|
130 |
+
if name in ['para', 'P']:
|
131 |
+
if len(args) == 2: # this is algebraic derivation.
|
132 |
+
ab, cd = args # ab = 'd( ... )'
|
133 |
+
return f'{ab} \u2225 {cd}'
|
134 |
+
a, b, c, d = args
|
135 |
+
return f'{a}{b} \u2225 {c}{d}'
|
136 |
+
if name in ['simtri2', 'simtri', 'simtri*']:
|
137 |
+
a, b, c, x, y, z = args
|
138 |
+
return f'\u0394{a}{b}{c} is similar to \u0394{x}{y}{z}'
|
139 |
+
if name in ['contri2', 'contri', 'contri*']:
|
140 |
+
a, b, c, x, y, z = args
|
141 |
+
return f'\u0394{a}{b}{c} is congruent to \u0394{x}{y}{z}'
|
142 |
+
if name in ['circle', 'I']:
|
143 |
+
o, a, b, c = args
|
144 |
+
return f'{o} is the circumcenter of \\Delta {a}{b}{c}'
|
145 |
+
if name == 'foot':
|
146 |
+
a, b, c, d = args
|
147 |
+
return f'{a} is the foot of {b} on {c}{d}'
|
148 |
+
|
149 |
+
|
150 |
+
def pretty(txt: str) -> str:
|
151 |
+
"""Pretty formating a predicate string."""
|
152 |
+
if isinstance(txt, str):
|
153 |
+
txt = txt.split(' ')
|
154 |
+
name, *args = txt
|
155 |
+
if name == 'ind':
|
156 |
+
return 'Y ' + ' '.join(args)
|
157 |
+
if name in ['fixc', 'fixl', 'fixb', 'fixt', 'fixp']:
|
158 |
+
return map_symbol_inv(name) + ' ' + ' '.join(args)
|
159 |
+
if name == 'acompute':
|
160 |
+
a, b, c, d = args
|
161 |
+
return 'A ' + ' '.join(args)
|
162 |
+
if name == 'rcompute':
|
163 |
+
a, b, c, d = args
|
164 |
+
return 'R ' + ' '.join(args)
|
165 |
+
if name == 'aconst':
|
166 |
+
a, b, c, d, y = args
|
167 |
+
return f'^ {pretty2a(a, b, c, d)} {y}'
|
168 |
+
if name == 'rconst':
|
169 |
+
a, b, c, d, y = args
|
170 |
+
return f'/ {pretty2r(a, b, c, d)} {y}'
|
171 |
+
if name == 'coll':
|
172 |
+
return 'C ' + ' '.join(args)
|
173 |
+
if name == 'collx':
|
174 |
+
return 'X ' + ' '.join(args)
|
175 |
+
if name == 'cyclic':
|
176 |
+
return 'O ' + ' '.join(args)
|
177 |
+
if name in ['midp', 'midpoint']:
|
178 |
+
x, a, b = args
|
179 |
+
return f'M {x} {a} {b}'
|
180 |
+
if name == 'eqangle':
|
181 |
+
a, b, c, d, e, f, g, h = args
|
182 |
+
return f'^ {pretty2a(a, b, c, d)} {pretty2a(e, f, g, h)}'
|
183 |
+
if name == 'eqratio':
|
184 |
+
a, b, c, d, e, f, g, h = args
|
185 |
+
return f'/ {pretty2r(a, b, c, d)} {pretty2r(e, f, g, h)}'
|
186 |
+
if name == 'eqratio3':
|
187 |
+
a, b, c, d, o, o = args # pylint: disable=redeclared-assigned-name
|
188 |
+
return f'S {o} {a} {b} {o} {c} {d}'
|
189 |
+
if name == 'cong':
|
190 |
+
a, b, c, d = args
|
191 |
+
return f'D {a} {b} {c} {d}'
|
192 |
+
if name == 'perp':
|
193 |
+
if len(args) == 2: # this is algebraic derivation.
|
194 |
+
ab, cd = args # ab = 'd( ... )'
|
195 |
+
return f'T {ab} {cd}'
|
196 |
+
a, b, c, d = args
|
197 |
+
return f'T {a} {b} {c} {d}'
|
198 |
+
if name == 'para':
|
199 |
+
if len(args) == 2: # this is algebraic derivation.
|
200 |
+
ab, cd = args # ab = 'd( ... )'
|
201 |
+
return f'P {ab} {cd}'
|
202 |
+
a, b, c, d = args
|
203 |
+
return f'P {a} {b} {c} {d}'
|
204 |
+
if name in ['simtri2', 'simtri', 'simtri*']:
|
205 |
+
a, b, c, x, y, z = args
|
206 |
+
return f'S {a} {b} {c} {x} {y} {z}'
|
207 |
+
if name in ['contri2', 'contri', 'contri*']:
|
208 |
+
a, b, c, x, y, z = args
|
209 |
+
return f'= {a} {b} {c} {x} {y} {z}'
|
210 |
+
if name == 'circle':
|
211 |
+
o, a, b, c = args
|
212 |
+
return f'I {o} {a} {b} {c}'
|
213 |
+
if name == 'foot':
|
214 |
+
a, b, c, d = args
|
215 |
+
return f'F {a} {b} {c} {d}'
|
216 |
+
return ' '.join(txt)
|
ag4masses/alphageometry/problem.py
ADDED
@@ -0,0 +1,1133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright 2023 DeepMind Technologies Limited
|
2 |
+
#
|
3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4 |
+
# you may not use this file except in compliance with the License.
|
5 |
+
# You may obtain a copy of the License at
|
6 |
+
#
|
7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8 |
+
#
|
9 |
+
# Unless required by applicable law or agreed to in writing, software
|
10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
+
# See the License for the specific language governing permissions and
|
13 |
+
# limitations under the License.
|
14 |
+
# ==============================================================================
|
15 |
+
|
16 |
+
"""Implements objects to represent problems, theorems, proofs, traceback."""
|
17 |
+
|
18 |
+
from __future__ import annotations
|
19 |
+
|
20 |
+
from collections import defaultdict # pylint: disable=g-importing-member
|
21 |
+
from typing import Any
|
22 |
+
|
23 |
+
import geometry as gm
|
24 |
+
import pretty as pt
|
25 |
+
|
26 |
+
|
27 |
+
# pylint: disable=protected-access
|
28 |
+
# pylint: disable=unused-variable
|
29 |
+
# pylint: disable=unused-argument
|
30 |
+
# pylint: disable=unused-assignment
|
31 |
+
|
32 |
+
|
33 |
+
def reshape(l: list[Any], n: int = 1) -> list[list[Any]]:
|
34 |
+
assert len(l) % n == 0
|
35 |
+
columns = [[] for i in range(n)]
|
36 |
+
for i, x in enumerate(l):
|
37 |
+
columns[i % n].append(x)
|
38 |
+
return zip(*columns)
|
39 |
+
|
40 |
+
|
41 |
+
def isint(x: str) -> bool:
|
42 |
+
try:
|
43 |
+
int(x)
|
44 |
+
return True
|
45 |
+
except: # pylint: disable=bare-except
|
46 |
+
return False
|
47 |
+
|
48 |
+
|
49 |
+
class Construction:
|
50 |
+
"""One predicate."""
|
51 |
+
|
52 |
+
@classmethod
|
53 |
+
def from_txt(cls, data: str) -> Construction:
|
54 |
+
data = data.split(' ')
|
55 |
+
return Construction(data[0], data[1:])
|
56 |
+
|
57 |
+
def __init__(self, name: str, args: list[str]):
|
58 |
+
self.name = name
|
59 |
+
self.args = args
|
60 |
+
|
61 |
+
def translate(self, mapping: dict[str, str]) -> Construction:
|
62 |
+
args = [a if isint(a) else mapping[a] for a in self.args]
|
63 |
+
return Construction(self.name, args)
|
64 |
+
|
65 |
+
def txt(self) -> str:
|
66 |
+
return ' '.join([self.name] + list(self.args))
|
67 |
+
|
68 |
+
|
69 |
+
class Clause:
|
70 |
+
"""One construction (>= 1 predicate)."""
|
71 |
+
|
72 |
+
@classmethod
|
73 |
+
def from_txt(cls, data: str) -> Clause:
|
74 |
+
if data == ' =':
|
75 |
+
return Clause([], [])
|
76 |
+
points, constructions = data.split(' = ')
|
77 |
+
return Clause(
|
78 |
+
points.split(' '),
|
79 |
+
[Construction.from_txt(c) for c in constructions.split(', ')],
|
80 |
+
)
|
81 |
+
|
82 |
+
def __init__(self, points: list[str], constructions: list[Construction]):
|
83 |
+
self.points = []
|
84 |
+
self.nums = []
|
85 |
+
|
86 |
+
for p in points:
|
87 |
+
num = None
|
88 |
+
if isinstance(p, str) and '@' in p:
|
89 |
+
p, num = p.split('@')
|
90 |
+
x, y = num.split('_')
|
91 |
+
num = float(x), float(y)
|
92 |
+
self.points.append(p)
|
93 |
+
self.nums.append(num)
|
94 |
+
|
95 |
+
self.constructions = constructions
|
96 |
+
|
97 |
+
def translate(self, mapping: dict[str, str]) -> Clause:
|
98 |
+
points0 = []
|
99 |
+
for p in self.points:
|
100 |
+
pcount = len(mapping) + 1
|
101 |
+
name = chr(96 + pcount)
|
102 |
+
if name > 'z': # pcount = 26 -> name = 'z'
|
103 |
+
name = chr(97 + (pcount - 1) % 26) + str((pcount - 1) // 26)
|
104 |
+
|
105 |
+
p0 = mapping.get(p, name)
|
106 |
+
mapping[p] = p0
|
107 |
+
points0.append(p0)
|
108 |
+
return Clause(points0, [c.translate(mapping) for c in self.constructions])
|
109 |
+
|
110 |
+
def add(self, name: str, args: list[str]) -> None:
|
111 |
+
self.constructions.append(Construction(name, args))
|
112 |
+
|
113 |
+
def txt(self) -> str:
|
114 |
+
return (
|
115 |
+
' '.join(self.points)
|
116 |
+
+ ' = '
|
117 |
+
+ ', '.join(c.txt() for c in self.constructions)
|
118 |
+
)
|
119 |
+
|
120 |
+
|
121 |
+
def _gcd(x: int, y: int) -> int:
|
122 |
+
while y:
|
123 |
+
x, y = y, x % y
|
124 |
+
return x
|
125 |
+
|
126 |
+
|
127 |
+
def simplify(n: int, d: int) -> tuple[int, int]:
|
128 |
+
g = _gcd(n, d)
|
129 |
+
return (n // g, d // g)
|
130 |
+
|
131 |
+
|
132 |
+
def compare_fn(dep: Dependency) -> tuple[Dependency, str]:
|
133 |
+
return (dep, pt.pretty(dep))
|
134 |
+
|
135 |
+
|
136 |
+
def sort_deps(deps: list[Dependency]) -> list[Dependency]:
|
137 |
+
return sorted(deps, key=compare_fn)
|
138 |
+
|
139 |
+
|
140 |
+
class Problem:
|
141 |
+
"""Describe one problem to solve."""
|
142 |
+
|
143 |
+
@classmethod
|
144 |
+
def from_txt_file(
|
145 |
+
cls, fname: str, to_dict: bool = False, translate: bool = True
|
146 |
+
):
|
147 |
+
"""Load a problem from a text file."""
|
148 |
+
with open(fname, 'r') as f:
|
149 |
+
lines = f.read().split('\n')
|
150 |
+
|
151 |
+
lines = [l for l in lines if l]
|
152 |
+
data = [
|
153 |
+
cls.from_txt(url + '\n' + problem, translate)
|
154 |
+
for (url, problem) in reshape(lines, 2)
|
155 |
+
]
|
156 |
+
if to_dict:
|
157 |
+
return cls.to_dict(data)
|
158 |
+
return data
|
159 |
+
|
160 |
+
@classmethod
|
161 |
+
def from_txt(cls, data: str, translate: bool = True) -> Problem:
|
162 |
+
"""Load a problem from a str object."""
|
163 |
+
url = ''
|
164 |
+
if '\n' in data:
|
165 |
+
url, data = data.split('\n')
|
166 |
+
|
167 |
+
if ' ? ' in data:
|
168 |
+
clauses, goal = data.split(' ? ')
|
169 |
+
goal = Construction.from_txt(goal)
|
170 |
+
else:
|
171 |
+
clauses, goal = data, None
|
172 |
+
|
173 |
+
clauses = clauses.split('; ')
|
174 |
+
problem = Problem(
|
175 |
+
url=url, clauses=[Clause.from_txt(c) for c in clauses], goal=goal
|
176 |
+
)
|
177 |
+
if translate:
|
178 |
+
return problem.translate()
|
179 |
+
return problem
|
180 |
+
|
181 |
+
@classmethod
|
182 |
+
def to_dict(cls, data: list[Problem]) -> dict[str, Problem]:
|
183 |
+
return {p.url: p for p in data}
|
184 |
+
|
185 |
+
def __init__(self, url: str, clauses: list[Clause], goal: Construction):
|
186 |
+
self.url = url
|
187 |
+
self.clauses = clauses
|
188 |
+
self.goal = goal
|
189 |
+
|
190 |
+
def copy(self) -> Problem:
|
191 |
+
return Problem(self.url, list(self.clauses), self.goal)
|
192 |
+
|
193 |
+
def translate(self) -> Problem: # to single-char point names
|
194 |
+
"""Translate point names into alphabetical."""
|
195 |
+
mapping = {}
|
196 |
+
clauses = []
|
197 |
+
|
198 |
+
for clause in self.clauses:
|
199 |
+
clauses.append(clause.translate(mapping))
|
200 |
+
|
201 |
+
if self.goal:
|
202 |
+
goal = self.goal.translate(mapping)
|
203 |
+
else:
|
204 |
+
goal = self.goal
|
205 |
+
|
206 |
+
p = Problem(self.url, clauses, goal)
|
207 |
+
p.mapping = mapping
|
208 |
+
return p
|
209 |
+
|
210 |
+
def txt(self) -> str:
|
211 |
+
return (
|
212 |
+
'; '.join([c.txt() for c in self.clauses]) + ' ? ' + self.goal.txt()
|
213 |
+
if self.goal
|
214 |
+
else ''
|
215 |
+
)
|
216 |
+
|
217 |
+
def setup_str_from_problem(self, definitions: list[Definition]) -> str:
|
218 |
+
"""Construct the <theorem_premises> string from Problem object."""
|
219 |
+
ref = 0
|
220 |
+
|
221 |
+
string = []
|
222 |
+
for clause in self.clauses:
|
223 |
+
group = {}
|
224 |
+
p2deps = defaultdict(list)
|
225 |
+
for c in clause.constructions:
|
226 |
+
cdef = definitions[c.name]
|
227 |
+
|
228 |
+
if len(c.args) != len(cdef.construction.args):
|
229 |
+
assert len(c.args) + len(clause.points) == len(cdef.construction.args)
|
230 |
+
c.args = clause.points + c.args
|
231 |
+
|
232 |
+
mapping = dict(zip(cdef.construction.args, c.args))
|
233 |
+
for points, bs in cdef.basics:
|
234 |
+
points = tuple([mapping[x] for x in points])
|
235 |
+
for p in points:
|
236 |
+
group[p] = points
|
237 |
+
|
238 |
+
for b in bs:
|
239 |
+
args = [mapping[a] for a in b.args]
|
240 |
+
name = b.name
|
241 |
+
if b.name in ['s_angle', 'aconst']:
|
242 |
+
x, y, z, v = args
|
243 |
+
name = 'aconst'
|
244 |
+
v = int(v)
|
245 |
+
|
246 |
+
if v < 0:
|
247 |
+
v = -v
|
248 |
+
x, z = z, x
|
249 |
+
|
250 |
+
m, n = simplify(int(v), 180)
|
251 |
+
args = [y, z, y, x, f'{m}pi/{n}']
|
252 |
+
|
253 |
+
p2deps[points].append(hashed_txt(name, args))
|
254 |
+
|
255 |
+
for k, v in p2deps.items():
|
256 |
+
p2deps[k] = sort_deps(v)
|
257 |
+
|
258 |
+
points = clause.points
|
259 |
+
while points:
|
260 |
+
p = points[0]
|
261 |
+
gr = group[p]
|
262 |
+
points = [x for x in points if x not in gr]
|
263 |
+
|
264 |
+
deps_str = []
|
265 |
+
for dep in p2deps[gr]:
|
266 |
+
ref_str = '{:02}'.format(ref)
|
267 |
+
dep_str = pt.pretty(dep)
|
268 |
+
|
269 |
+
if dep[0] == 'aconst':
|
270 |
+
m, n = map(int, dep[-1].split('pi/'))
|
271 |
+
mn = f'{m}. pi / {n}.'
|
272 |
+
dep_str = ' '.join(dep_str.split()[:-1] + [mn])
|
273 |
+
|
274 |
+
deps_str.append(dep_str + ' ' + ref_str)
|
275 |
+
ref += 1
|
276 |
+
|
277 |
+
string.append(' '.join(gr) + ' : ' + ' '.join(deps_str))
|
278 |
+
|
279 |
+
string = '{S} ' + ' ; '.join([s.strip() for s in string])
|
280 |
+
goal = self.goal
|
281 |
+
string += ' ? ' + pt.pretty([goal.name] + goal.args)
|
282 |
+
return string
|
283 |
+
|
284 |
+
|
285 |
+
def parse_rely(s: str) -> dict[str, str]:
|
286 |
+
result = {}
|
287 |
+
if not s:
|
288 |
+
return result
|
289 |
+
s = [x.strip() for x in s.split(',')]
|
290 |
+
for x in s:
|
291 |
+
a, b = x.split(':')
|
292 |
+
a, b = a.strip().split(), b.strip().split()
|
293 |
+
result.update({m: b for m in a})
|
294 |
+
return result
|
295 |
+
|
296 |
+
|
297 |
+
class Definition:
|
298 |
+
"""Definitions of construction statements."""
|
299 |
+
|
300 |
+
@classmethod
|
301 |
+
def from_txt_file(cls, fname: str, to_dict: bool = False) -> Definition:
|
302 |
+
with open(fname, 'r') as f:
|
303 |
+
lines = f.read()
|
304 |
+
return cls.from_string(lines, to_dict)
|
305 |
+
|
306 |
+
@classmethod
|
307 |
+
def from_string(cls, string: str, to_dict: bool = False) -> Definition:
|
308 |
+
lines = string.split('\n')
|
309 |
+
data = [cls.from_txt('\n'.join(group)) for group in reshape(lines, 6)]
|
310 |
+
if to_dict:
|
311 |
+
return cls.to_dict(data)
|
312 |
+
return data
|
313 |
+
|
314 |
+
@classmethod
|
315 |
+
def to_dict(cls, data: list[Definition]) -> dict[str, Definition]:
|
316 |
+
return {d.construction.name: d for d in data}
|
317 |
+
|
318 |
+
@classmethod
|
319 |
+
def from_txt(cls, data: str) -> Definition:
|
320 |
+
"""Load definitions from a str object."""
|
321 |
+
construction, rely, deps, basics, numerics, _ = data.split('\n')
|
322 |
+
basics = [] if not basics else [b.strip() for b in basics.split(';')]
|
323 |
+
|
324 |
+
levels = []
|
325 |
+
for bs in basics:
|
326 |
+
if ':' in bs:
|
327 |
+
points, bs = bs.split(':')
|
328 |
+
points = points.strip().split()
|
329 |
+
else:
|
330 |
+
points = []
|
331 |
+
if bs.strip():
|
332 |
+
bs = [Construction.from_txt(b.strip()) for b in bs.strip().split(',')]
|
333 |
+
else:
|
334 |
+
bs = []
|
335 |
+
levels.append((points, bs))
|
336 |
+
|
337 |
+
numerics = [] if not numerics else numerics.split(', ')
|
338 |
+
|
339 |
+
return Definition(
|
340 |
+
construction=Construction.from_txt(construction),
|
341 |
+
rely=parse_rely(rely),
|
342 |
+
deps=Clause.from_txt(deps),
|
343 |
+
basics=levels,
|
344 |
+
numerics=[Construction.from_txt(c) for c in numerics],
|
345 |
+
)
|
346 |
+
|
347 |
+
def __init__(
|
348 |
+
self,
|
349 |
+
construction: Construction,
|
350 |
+
rely: dict[str, str],
|
351 |
+
deps: Clause,
|
352 |
+
basics: list[tuple[list[str], list[Construction]]],
|
353 |
+
numerics: list[Construction],
|
354 |
+
):
|
355 |
+
self.construction = construction
|
356 |
+
self.rely = rely
|
357 |
+
self.deps = deps
|
358 |
+
self.basics = basics
|
359 |
+
self.numerics = numerics
|
360 |
+
|
361 |
+
args = set()
|
362 |
+
for num in numerics:
|
363 |
+
args.update(num.args)
|
364 |
+
|
365 |
+
self.points = []
|
366 |
+
self.args = []
|
367 |
+
for p in self.construction.args:
|
368 |
+
if p in args:
|
369 |
+
self.args.append(p)
|
370 |
+
else:
|
371 |
+
self.points.append(p)
|
372 |
+
|
373 |
+
|
374 |
+
class Theorem:
|
375 |
+
"""Deduction rule."""
|
376 |
+
|
377 |
+
@classmethod
|
378 |
+
def from_txt_file(cls, fname: str, to_dict: bool = False) -> Theorem:
|
379 |
+
with open(fname, 'r') as f:
|
380 |
+
theorems = f.read()
|
381 |
+
return cls.from_string(theorems, to_dict)
|
382 |
+
|
383 |
+
@classmethod
|
384 |
+
def from_string(cls, string: str, to_dict: bool = False) -> Theorem:
|
385 |
+
"""Load deduction rule from a str object."""
|
386 |
+
theorems = string.split('\n')
|
387 |
+
theorems = [l for l in theorems if l and not l.startswith('#')]
|
388 |
+
theorems = [cls.from_txt(l) for l in theorems]
|
389 |
+
|
390 |
+
for i, th in enumerate(theorems):
|
391 |
+
th.rule_name = 'r{:02}'.format(i)
|
392 |
+
|
393 |
+
if to_dict:
|
394 |
+
result = {}
|
395 |
+
for t in theorems:
|
396 |
+
if t.name in result:
|
397 |
+
t.name += '_'
|
398 |
+
result[t.rule_name] = t
|
399 |
+
|
400 |
+
return result
|
401 |
+
|
402 |
+
return theorems
|
403 |
+
|
404 |
+
@classmethod
|
405 |
+
def from_txt(cls, data: str) -> Theorem:
|
406 |
+
premises, conclusion = data.split(' => ')
|
407 |
+
premises = premises.split(', ')
|
408 |
+
conclusion = conclusion.split(', ')
|
409 |
+
return Theorem(
|
410 |
+
premise=[Construction.from_txt(p) for p in premises],
|
411 |
+
conclusion=[Construction.from_txt(c) for c in conclusion],
|
412 |
+
)
|
413 |
+
|
414 |
+
def __init__(
|
415 |
+
self, premise: list[Construction], conclusion: list[Construction]
|
416 |
+
):
|
417 |
+
if len(conclusion) != 1:
|
418 |
+
raise ValueError('Cannot have more than one conclusion')
|
419 |
+
self.name = '_'.join([p.name for p in premise + conclusion])
|
420 |
+
self.premise = premise
|
421 |
+
self.conclusion = conclusion
|
422 |
+
self.is_arg_reduce = False
|
423 |
+
|
424 |
+
assert len(self.conclusion) == 1
|
425 |
+
con = self.conclusion[0]
|
426 |
+
|
427 |
+
if con.name in [
|
428 |
+
'eqratio3',
|
429 |
+
'midp',
|
430 |
+
'contri',
|
431 |
+
'simtri',
|
432 |
+
'contri2',
|
433 |
+
'simtri2',
|
434 |
+
'simtri*',
|
435 |
+
'contri*',
|
436 |
+
]:
|
437 |
+
return
|
438 |
+
|
439 |
+
prem_args = set(sum([p.args for p in self.premise], []))
|
440 |
+
con_args = set(con.args)
|
441 |
+
if len(prem_args) <= len(con_args):
|
442 |
+
self.is_arg_reduce = True
|
443 |
+
|
444 |
+
def txt(self) -> str:
|
445 |
+
premise_txt = ', '.join([clause.txt() for clause in self.premise])
|
446 |
+
conclusion_txt = ', '.join([clause.txt() for clause in self.conclusion])
|
447 |
+
return f'{premise_txt} => {conclusion_txt}'
|
448 |
+
|
449 |
+
def conclusion_name_args(
|
450 |
+
self, mapping: dict[str, gm.Point]
|
451 |
+
) -> tuple[str, list[gm.Point]]:
|
452 |
+
mapping = {arg: p for arg, p in mapping.items() if isinstance(arg, str)}
|
453 |
+
c = self.conclusion[0]
|
454 |
+
args = [mapping[a] for a in c.args]
|
455 |
+
return c.name, args
|
456 |
+
|
457 |
+
|
458 |
+
def why_eqratio(
|
459 |
+
d1: gm.Direction,
|
460 |
+
d2: gm.Direction,
|
461 |
+
d3: gm.Direction,
|
462 |
+
d4: gm.Direction,
|
463 |
+
level: int,
|
464 |
+
) -> list[Dependency]:
|
465 |
+
"""Why two ratios are equal, returns a Dependency objects."""
|
466 |
+
all12 = list(gm.all_ratios(d1, d2, level))
|
467 |
+
all34 = list(gm.all_ratios(d3, d4, level))
|
468 |
+
|
469 |
+
min_why = None
|
470 |
+
for ang12, d1s, d2s in all12:
|
471 |
+
for ang34, d3s, d4s in all34:
|
472 |
+
why0 = gm.why_equal(ang12, ang34, level)
|
473 |
+
if why0 is None:
|
474 |
+
continue
|
475 |
+
d1_, d2_ = ang12._l
|
476 |
+
d3_, d4_ = ang34._l
|
477 |
+
why1 = gm.bfs_backtrack(d1, [d1_], d1s)
|
478 |
+
why2 = gm.bfs_backtrack(d2, [d2_], d2s)
|
479 |
+
why3 = gm.bfs_backtrack(d3, [d3_], d3s)
|
480 |
+
why4 = gm.bfs_backtrack(d4, [d4_], d4s)
|
481 |
+
why = why0 + why1 + why2 + why3 + why4
|
482 |
+
if min_why is None or len(why) < len(min_why[0]):
|
483 |
+
min_why = why, ang12, ang34, why0, why1, why2, why3, why4
|
484 |
+
|
485 |
+
if min_why is None:
|
486 |
+
return None
|
487 |
+
|
488 |
+
_, ang12, ang34, why0, why1, why2, why3, why4 = min_why
|
489 |
+
d1_, d2_ = ang12._l
|
490 |
+
d3_, d4_ = ang34._l
|
491 |
+
|
492 |
+
if d1 == d1_ and d2 == d2_ and d3 == d3_ and d4 == d4_:
|
493 |
+
return why0
|
494 |
+
|
495 |
+
(a_, b_), (c_, d_) = d1_._obj.points, d2_._obj.points
|
496 |
+
(e_, f_), (g_, h_) = d3_._obj.points, d4_._obj.points
|
497 |
+
deps = []
|
498 |
+
if why0:
|
499 |
+
dep = Dependency('eqratio', [a_, b_, c_, d_, e_, f_, g_, h_], '', level)
|
500 |
+
dep.why = why0
|
501 |
+
deps.append(dep)
|
502 |
+
|
503 |
+
(a, b), (c, d) = d1._obj.points, d2._obj.points
|
504 |
+
(e, f), (g, h) = d3._obj.points, d4._obj.points
|
505 |
+
for why, (x, y), (x_, y_) in zip(
|
506 |
+
[why1, why2, why3, why4],
|
507 |
+
[(a, b), (c, d), (e, f), (g, h)],
|
508 |
+
[(a_, b_), (c_, d_), (e_, f_), (g_, h_)],
|
509 |
+
):
|
510 |
+
if why:
|
511 |
+
dep = Dependency('cong', [x, y, x_, y_], '', level)
|
512 |
+
dep.why = why
|
513 |
+
deps.append(dep)
|
514 |
+
|
515 |
+
return deps
|
516 |
+
|
517 |
+
|
518 |
+
def why_eqangle(
|
519 |
+
d1: gm.Direction,
|
520 |
+
d2: gm.Direction,
|
521 |
+
d3: gm.Direction,
|
522 |
+
d4: gm.Direction,
|
523 |
+
level: int,
|
524 |
+
verbose: bool = False,
|
525 |
+
) -> list[Dependency]:
|
526 |
+
"""Why two angles are equal, returns a Dependency objects."""
|
527 |
+
all12 = list(gm.all_angles(d1, d2, level))
|
528 |
+
all34 = list(gm.all_angles(d3, d4, level))
|
529 |
+
|
530 |
+
min_why = None
|
531 |
+
for ang12, d1s, d2s in all12:
|
532 |
+
for ang34, d3s, d4s in all34:
|
533 |
+
why0 = gm.why_equal(ang12, ang34, level)
|
534 |
+
if why0 is None:
|
535 |
+
continue
|
536 |
+
d1_, d2_ = ang12._d
|
537 |
+
d3_, d4_ = ang34._d
|
538 |
+
why1 = gm.bfs_backtrack(d1, [d1_], d1s)
|
539 |
+
why2 = gm.bfs_backtrack(d2, [d2_], d2s)
|
540 |
+
why3 = gm.bfs_backtrack(d3, [d3_], d3s)
|
541 |
+
why4 = gm.bfs_backtrack(d4, [d4_], d4s)
|
542 |
+
why = why0 + why1 + why2 + why3 + why4
|
543 |
+
if min_why is None or len(why) < len(min_why[0]):
|
544 |
+
min_why = why, ang12, ang34, why0, why1, why2, why3, why4
|
545 |
+
|
546 |
+
if min_why is None:
|
547 |
+
return None
|
548 |
+
|
549 |
+
_, ang12, ang34, why0, why1, why2, why3, why4 = min_why
|
550 |
+
why0 = gm.why_equal(ang12, ang34, level)
|
551 |
+
d1_, d2_ = ang12._d
|
552 |
+
d3_, d4_ = ang34._d
|
553 |
+
|
554 |
+
if d1 == d1_ and d2 == d2_ and d3 == d3_ and d4 == d4_:
|
555 |
+
return (d1_, d2_, d3_, d4_), why0
|
556 |
+
|
557 |
+
(a_, b_), (c_, d_) = d1_._obj.points, d2_._obj.points
|
558 |
+
(e_, f_), (g_, h_) = d3_._obj.points, d4_._obj.points
|
559 |
+
deps = []
|
560 |
+
if why0:
|
561 |
+
dep = Dependency('eqangle', [a_, b_, c_, d_, e_, f_, g_, h_], '', None)
|
562 |
+
dep.why = why0
|
563 |
+
deps.append(dep)
|
564 |
+
|
565 |
+
(a, b), (c, d) = d1._obj.points, d2._obj.points
|
566 |
+
(e, f), (g, h) = d3._obj.points, d4._obj.points
|
567 |
+
for why, d_xy, (x, y), d_xy_, (x_, y_) in zip(
|
568 |
+
[why1, why2, why3, why4],
|
569 |
+
[d1, d2, d3, d4],
|
570 |
+
[(a, b), (c, d), (e, f), (g, h)],
|
571 |
+
[d1_, d2_, d3_, d4_],
|
572 |
+
[(a_, b_), (c_, d_), (e_, f_), (g_, h_)],
|
573 |
+
):
|
574 |
+
xy, xy_ = d_xy._obj, d_xy_._obj
|
575 |
+
if why:
|
576 |
+
if xy == xy_:
|
577 |
+
name = 'collx'
|
578 |
+
else:
|
579 |
+
name = 'para'
|
580 |
+
dep = Dependency(name, [x_, y_, x, y], '', None)
|
581 |
+
dep.why = why
|
582 |
+
deps.append(dep)
|
583 |
+
|
584 |
+
return (d1_, d2_, d3_, d4_), deps
|
585 |
+
|
586 |
+
|
587 |
+
CONSTRUCTION_RULE = 'c0'
|
588 |
+
|
589 |
+
|
590 |
+
class EmptyDependency:
|
591 |
+
"""Empty dependency predicate ready to get filled up."""
|
592 |
+
|
593 |
+
def __init__(self, level: int, rule_name: str):
|
594 |
+
self.level = level
|
595 |
+
self.rule_name = rule_name or ''
|
596 |
+
self.empty = True
|
597 |
+
self.why = []
|
598 |
+
self.trace = None
|
599 |
+
|
600 |
+
def populate(self, name: str, args: list[gm.Point]) -> Dependency:
|
601 |
+
dep = Dependency(name, args, self.rule_name, self.level)
|
602 |
+
dep.trace2 = self.trace
|
603 |
+
dep.why = list(self.why)
|
604 |
+
return dep
|
605 |
+
|
606 |
+
def copy(self) -> EmptyDependency:
|
607 |
+
other = EmptyDependency(self.level, self.rule_name)
|
608 |
+
other.why = list(self.why)
|
609 |
+
return other
|
610 |
+
|
611 |
+
def extend(
|
612 |
+
self,
|
613 |
+
g: Any,
|
614 |
+
name0: str,
|
615 |
+
args0: list[gm.Point],
|
616 |
+
name: str,
|
617 |
+
args: list[gm.Point],
|
618 |
+
) -> EmptyDependency:
|
619 |
+
"""Extend the dependency list by (name, args)."""
|
620 |
+
dep0 = self.populate(name0, args0)
|
621 |
+
deps = EmptyDependency(level=self.level, rule_name=None)
|
622 |
+
dep = Dependency(name, args, None, deps.level)
|
623 |
+
deps.why = [dep0, dep.why_me_or_cache(g, None)]
|
624 |
+
return deps
|
625 |
+
|
626 |
+
def extend_many(
|
627 |
+
self,
|
628 |
+
g: Any,
|
629 |
+
name0: str,
|
630 |
+
args0: list[gm.Point],
|
631 |
+
name_args: list[tuple[str, list[gm.Point]]],
|
632 |
+
) -> EmptyDependency:
|
633 |
+
"""Extend the dependency list by many name_args."""
|
634 |
+
if not name_args:
|
635 |
+
return self
|
636 |
+
dep0 = self.populate(name0, args0)
|
637 |
+
deps = EmptyDependency(level=self.level, rule_name=None)
|
638 |
+
deps.why = [dep0]
|
639 |
+
for name, args in name_args:
|
640 |
+
dep = Dependency(name, args, None, deps.level)
|
641 |
+
deps.why += [dep.why_me_or_cache(g, None)]
|
642 |
+
return deps
|
643 |
+
|
644 |
+
|
645 |
+
def maybe_make_equal_pairs(
|
646 |
+
a: gm.Point,
|
647 |
+
b: gm.Point,
|
648 |
+
c: gm.Point,
|
649 |
+
d: gm.Point,
|
650 |
+
m: gm.Point,
|
651 |
+
n: gm.Point,
|
652 |
+
p: gm.Point,
|
653 |
+
q: gm.Point,
|
654 |
+
ab: gm.Line,
|
655 |
+
mn: gm.Line,
|
656 |
+
g: Any,
|
657 |
+
level: int,
|
658 |
+
) -> list[Dependency]:
|
659 |
+
"""Make a-b:c-d==m-n:p-q in case a-b==m-n or c-d==p-q."""
|
660 |
+
if ab != mn:
|
661 |
+
return
|
662 |
+
why = []
|
663 |
+
eqname = 'para' if isinstance(ab, gm.Line) else 'cong'
|
664 |
+
colls = [a, b, m, n]
|
665 |
+
if len(set(colls)) > 2 and eqname == 'para':
|
666 |
+
dep = Dependency('collx', colls, None, level)
|
667 |
+
dep.why_me(g, level)
|
668 |
+
why += [dep]
|
669 |
+
|
670 |
+
dep = Dependency(eqname, [c, d, p, q], None, level)
|
671 |
+
dep.why_me(g, level)
|
672 |
+
why += [dep]
|
673 |
+
return why
|
674 |
+
|
675 |
+
|
676 |
+
class Dependency(Construction):
|
677 |
+
"""Dependency is a predicate that other predicates depend on."""
|
678 |
+
|
679 |
+
def __init__(
|
680 |
+
self, name: str, args: list[gm.Point], rule_name: str, level: int
|
681 |
+
):
|
682 |
+
super().__init__(name, args)
|
683 |
+
self.rule_name = rule_name or ''
|
684 |
+
self.level = level
|
685 |
+
self.why = []
|
686 |
+
|
687 |
+
self._stat = None
|
688 |
+
self.trace = None
|
689 |
+
|
690 |
+
def _find(self, dep_hashed: tuple[str, ...]) -> Dependency:
|
691 |
+
for w in self.why:
|
692 |
+
f = w._find(dep_hashed)
|
693 |
+
if f:
|
694 |
+
return f
|
695 |
+
if w.hashed() == dep_hashed:
|
696 |
+
return w
|
697 |
+
|
698 |
+
def remove_loop(self) -> Dependency:
|
699 |
+
f = self._find(self.hashed())
|
700 |
+
if f:
|
701 |
+
return f
|
702 |
+
return self
|
703 |
+
|
704 |
+
def copy(self) -> Dependency:
|
705 |
+
dep = Dependency(self.name, self.args, self.rule_name, self.level)
|
706 |
+
dep.trace = self.trace
|
707 |
+
dep.why = list(self.why)
|
708 |
+
return dep
|
709 |
+
|
710 |
+
def why_me_or_cache(self, g: Any, level: int) -> Dependency:
|
711 |
+
if self.hashed() in g.cache:
|
712 |
+
return g.cache[self.hashed()]
|
713 |
+
self.why_me(g, level)
|
714 |
+
return self
|
715 |
+
|
716 |
+
def populate(self, name: str, args: list[gm.Point]) -> Dependency:
|
717 |
+
assert self.rule_name == CONSTRUCTION_RULE, self.rule_name
|
718 |
+
dep = Dependency(self.name, self.args, self.rule_name, self.level)
|
719 |
+
dep.why = list(self.why)
|
720 |
+
return dep
|
721 |
+
|
722 |
+
def why_me(self, g: Any, level: int) -> None:
|
723 |
+
"""Figure out the dependencies predicates of self."""
|
724 |
+
name, args = self.name, self.args
|
725 |
+
|
726 |
+
hashed_me = hashed(name, args)
|
727 |
+
if hashed_me in g.cache:
|
728 |
+
dep = g.cache[hashed_me]
|
729 |
+
self.why = dep.why
|
730 |
+
self.rule_name = dep.rule_name
|
731 |
+
return
|
732 |
+
|
733 |
+
if self.name == 'para':
|
734 |
+
a, b, c, d = self.args
|
735 |
+
if {a, b} == {c, d}:
|
736 |
+
self.why = []
|
737 |
+
return
|
738 |
+
|
739 |
+
ab = g._get_line(a, b)
|
740 |
+
cd = g._get_line(c, d)
|
741 |
+
if ab == cd:
|
742 |
+
if {a, b} == {c, d}:
|
743 |
+
self.why = []
|
744 |
+
self.rule_name = ''
|
745 |
+
return
|
746 |
+
dep = Dependency('coll', list({a, b, c, d}), 't??', None)
|
747 |
+
self.why = [dep.why_me_or_cache(g, level)]
|
748 |
+
return
|
749 |
+
|
750 |
+
for (x, y), xy in zip([(a, b), (c, d)], [ab, cd]):
|
751 |
+
x_, y_ = xy.points
|
752 |
+
if {x, y} == {x_, y_}:
|
753 |
+
continue
|
754 |
+
d = Dependency('collx', [x, y, x_, y_], None, level)
|
755 |
+
self.why += [d.why_me_or_cache(g, level)]
|
756 |
+
|
757 |
+
whypara = g.why_equal(ab, cd, None)
|
758 |
+
self.why += whypara
|
759 |
+
|
760 |
+
elif self.name == 'midp':
|
761 |
+
m, a, b = self.args
|
762 |
+
ma = g._get_segment(m, a)
|
763 |
+
mb = g._get_segment(m, b)
|
764 |
+
dep = Dependency('coll', [m, a, b], None, None).why_me_or_cache(g, None)
|
765 |
+
self.why = [dep] + g.why_equal(ma, mb, level)
|
766 |
+
|
767 |
+
elif self.name == 'perp':
|
768 |
+
a, b, c, d = self.args
|
769 |
+
ab = g._get_line(a, b)
|
770 |
+
cd = g._get_line(c, d)
|
771 |
+
for (x, y), xy in zip([(a, b), (c, d)], [ab, cd]):
|
772 |
+
x_, y_ = xy.points
|
773 |
+
if {x, y} == {x_, y_}:
|
774 |
+
continue
|
775 |
+
d = Dependency('collx', [x, y, x_, y_], None, level)
|
776 |
+
self.why += [d.why_me_or_cache(g, level)]
|
777 |
+
|
778 |
+
_, why = why_eqangle(ab._val, cd._val, cd._val, ab._val, level)
|
779 |
+
a, b = ab.points
|
780 |
+
c, d = cd.points
|
781 |
+
|
782 |
+
if hashed(self.name, [a, b, c, d]) != self.hashed():
|
783 |
+
d = Dependency(self.name, [a, b, c, d], None, level)
|
784 |
+
d.why = why
|
785 |
+
why = [d]
|
786 |
+
|
787 |
+
self.why += why
|
788 |
+
|
789 |
+
elif self.name == 'cong':
|
790 |
+
a, b, c, d = self.args
|
791 |
+
ab = g._get_segment(a, b)
|
792 |
+
cd = g._get_segment(c, d)
|
793 |
+
|
794 |
+
self.why = g.why_equal(ab, cd, level)
|
795 |
+
|
796 |
+
elif self.name == 'coll':
|
797 |
+
_, why = gm.line_of_and_why(self.args, level)
|
798 |
+
self.why = why
|
799 |
+
|
800 |
+
elif self.name == 'collx':
|
801 |
+
if g.check_coll(self.args):
|
802 |
+
args = list(set(self.args))
|
803 |
+
hashed_me = hashed('coll', args)
|
804 |
+
if hashed_me in g.cache:
|
805 |
+
dep = g.cache[hashed_me]
|
806 |
+
self.why = [dep]
|
807 |
+
self.rule_name = ''
|
808 |
+
return
|
809 |
+
_, self.why = gm.line_of_and_why(args, level)
|
810 |
+
else:
|
811 |
+
self.name = 'para'
|
812 |
+
self.why_me(g, level)
|
813 |
+
|
814 |
+
elif self.name == 'cyclic':
|
815 |
+
_, why = gm.circle_of_and_why(self.args, level)
|
816 |
+
self.why = why
|
817 |
+
|
818 |
+
elif self.name == 'circle':
|
819 |
+
o, a, b, c = self.args
|
820 |
+
oa = g._get_segment(o, a)
|
821 |
+
ob = g._get_segment(o, b)
|
822 |
+
oc = g._get_segment(o, c)
|
823 |
+
self.why = g.why_equal(oa, ob, level) + g.why_equal(oa, oc, level)
|
824 |
+
|
825 |
+
elif self.name in ['eqangle', 'eqangle6']:
|
826 |
+
a, b, c, d, m, n, p, q = self.args
|
827 |
+
|
828 |
+
ab, why1 = g.get_line_thru_pair_why(a, b)
|
829 |
+
cd, why2 = g.get_line_thru_pair_why(c, d)
|
830 |
+
mn, why3 = g.get_line_thru_pair_why(m, n)
|
831 |
+
pq, why4 = g.get_line_thru_pair_why(p, q)
|
832 |
+
|
833 |
+
if ab is None or cd is None or mn is None or pq is None:
|
834 |
+
if {a, b} == {m, n}:
|
835 |
+
d = Dependency('para', [c, d, p, q], None, level)
|
836 |
+
self.why = [d.why_me_or_cache(g, level)]
|
837 |
+
if {a, b} == {c, d}:
|
838 |
+
d = Dependency('para', [p, q, m, n], None, level)
|
839 |
+
self.why = [d.why_me_or_cache(g, level)]
|
840 |
+
if {c, d} == {p, q}:
|
841 |
+
d = Dependency('para', [a, b, m, n], None, level)
|
842 |
+
self.why = [d.why_me_or_cache(g, level)]
|
843 |
+
if {p, q} == {m, n}:
|
844 |
+
d = Dependency('para', [a, b, c, d], None, level)
|
845 |
+
self.why = [d.why_me_or_cache(g, level)]
|
846 |
+
return
|
847 |
+
|
848 |
+
for (x, y), xy, whyxy in zip(
|
849 |
+
[(a, b), (c, d), (m, n), (p, q)],
|
850 |
+
[ab, cd, mn, pq],
|
851 |
+
[why1, why2, why3, why4],
|
852 |
+
):
|
853 |
+
x_, y_ = xy.points
|
854 |
+
if {x, y} == {x_, y_}:
|
855 |
+
continue
|
856 |
+
d = Dependency('collx', [x, y, x_, y_], None, level)
|
857 |
+
d.why = whyxy
|
858 |
+
self.why += [d]
|
859 |
+
|
860 |
+
a, b = ab.points
|
861 |
+
c, d = cd.points
|
862 |
+
m, n = mn.points
|
863 |
+
p, q = pq.points
|
864 |
+
diff = hashed(self.name, [a, b, c, d, m, n, p, q]) != self.hashed()
|
865 |
+
|
866 |
+
whyeqangle = None
|
867 |
+
if ab._val and cd._val and mn._val and pq._val:
|
868 |
+
whyeqangle = why_eqangle(ab._val, cd._val, mn._val, pq._val, level)
|
869 |
+
|
870 |
+
if whyeqangle:
|
871 |
+
(dab, dcd, dmn, dpq), whyeqangle = whyeqangle
|
872 |
+
if diff:
|
873 |
+
d = Dependency('eqangle', [a, b, c, d, m, n, p, q], None, level)
|
874 |
+
d.why = whyeqangle
|
875 |
+
whyeqangle = [d]
|
876 |
+
self.why += whyeqangle
|
877 |
+
|
878 |
+
else:
|
879 |
+
if (ab == cd and mn == pq) or (ab == mn and cd == pq):
|
880 |
+
self.why += []
|
881 |
+
elif ab == mn:
|
882 |
+
self.why += maybe_make_equal_pairs(
|
883 |
+
a, b, c, d, m, n, p, q, ab, mn, g, level
|
884 |
+
)
|
885 |
+
elif cd == pq:
|
886 |
+
self.why += maybe_make_equal_pairs(
|
887 |
+
c, d, a, b, p, q, m, n, cd, pq, g, level
|
888 |
+
)
|
889 |
+
elif ab == cd:
|
890 |
+
self.why += maybe_make_equal_pairs(
|
891 |
+
a, b, m, n, c, d, p, q, ab, cd, g, level
|
892 |
+
)
|
893 |
+
elif mn == pq:
|
894 |
+
self.why += maybe_make_equal_pairs(
|
895 |
+
m, n, a, b, p, q, c, d, mn, pq, g, level
|
896 |
+
)
|
897 |
+
elif g.is_equal(ab, mn) or g.is_equal(cd, pq):
|
898 |
+
dep1 = Dependency('para', [a, b, m, n], None, level)
|
899 |
+
dep1.why_me(g, level)
|
900 |
+
dep2 = Dependency('para', [c, d, p, q], None, level)
|
901 |
+
dep2.why_me(g, level)
|
902 |
+
self.why += [dep1, dep2]
|
903 |
+
elif g.is_equal(ab, cd) or g.is_equal(mn, pq):
|
904 |
+
dep1 = Dependency('para', [a, b, c, d], None, level)
|
905 |
+
dep1.why_me(g, level)
|
906 |
+
dep2 = Dependency('para', [m, n, p, q], None, level)
|
907 |
+
dep2.why_me(g, level)
|
908 |
+
self.why += [dep1, dep2]
|
909 |
+
elif ab._val and cd._val and mn._val and pq._val:
|
910 |
+
self.why = why_eqangle(ab._val, cd._val, mn._val, pq._val, level)
|
911 |
+
|
912 |
+
elif self.name in ['eqratio', 'eqratio6']:
|
913 |
+
a, b, c, d, m, n, p, q = self.args
|
914 |
+
ab = g._get_segment(a, b)
|
915 |
+
cd = g._get_segment(c, d)
|
916 |
+
mn = g._get_segment(m, n)
|
917 |
+
pq = g._get_segment(p, q)
|
918 |
+
|
919 |
+
if ab is None or cd is None or mn is None or pq is None:
|
920 |
+
if {a, b} == {m, n}:
|
921 |
+
d = Dependency('cong', [c, d, p, q], None, level)
|
922 |
+
self.why = [d.why_me_or_cache(g, level)]
|
923 |
+
if {a, b} == {c, d}:
|
924 |
+
d = Dependency('cong', [p, q, m, n], None, level)
|
925 |
+
self.why = [d.why_me_or_cache(g, level)]
|
926 |
+
if {c, d} == {p, q}:
|
927 |
+
d = Dependency('cong', [a, b, m, n], None, level)
|
928 |
+
self.why = [d.why_me_or_cache(g, level)]
|
929 |
+
if {p, q} == {m, n}:
|
930 |
+
d = Dependency('cong', [a, b, c, d], None, level)
|
931 |
+
self.why = [d.why_me_or_cache(g, level)]
|
932 |
+
return
|
933 |
+
|
934 |
+
if ab._val and cd._val and mn._val and pq._val:
|
935 |
+
self.why = why_eqratio(ab._val, cd._val, mn._val, pq._val, level)
|
936 |
+
|
937 |
+
if self.why is None:
|
938 |
+
self.why = []
|
939 |
+
if (ab == cd and mn == pq) or (ab == mn and cd == pq):
|
940 |
+
self.why = []
|
941 |
+
elif ab == mn:
|
942 |
+
self.why += maybe_make_equal_pairs(
|
943 |
+
a, b, c, d, m, n, p, q, ab, mn, g, level
|
944 |
+
)
|
945 |
+
elif cd == pq:
|
946 |
+
self.why += maybe_make_equal_pairs(
|
947 |
+
c, d, a, b, p, q, m, n, cd, pq, g, level
|
948 |
+
)
|
949 |
+
elif ab == cd:
|
950 |
+
self.why += maybe_make_equal_pairs(
|
951 |
+
a, b, m, n, c, d, p, q, ab, cd, g, level
|
952 |
+
)
|
953 |
+
elif mn == pq:
|
954 |
+
self.why += maybe_make_equal_pairs(
|
955 |
+
m, n, a, b, p, q, c, d, mn, pq, g, level
|
956 |
+
)
|
957 |
+
elif g.is_equal(ab, mn) or g.is_equal(cd, pq):
|
958 |
+
dep1 = Dependency('cong', [a, b, m, n], None, level)
|
959 |
+
dep1.why_me(g, level)
|
960 |
+
dep2 = Dependency('cong', [c, d, p, q], None, level)
|
961 |
+
dep2.why_me(g, level)
|
962 |
+
self.why += [dep1, dep2]
|
963 |
+
elif g.is_equal(ab, cd) or g.is_equal(mn, pq):
|
964 |
+
dep1 = Dependency('cong', [a, b, c, d], None, level)
|
965 |
+
dep1.why_me(g, level)
|
966 |
+
dep2 = Dependency('cong', [m, n, p, q], None, level)
|
967 |
+
dep2.why_me(g, level)
|
968 |
+
self.why += [dep1, dep2]
|
969 |
+
elif ab._val and cd._val and mn._val and pq._val:
|
970 |
+
self.why = why_eqangle(ab._val, cd._val, mn._val, pq._val, level)
|
971 |
+
|
972 |
+
elif self.name in ['diff', 'npara', 'nperp', 'ncoll', 'sameside']:
|
973 |
+
self.why = []
|
974 |
+
|
975 |
+
elif self.name == 'simtri':
|
976 |
+
a, b, c, x, y, z = self.args
|
977 |
+
dep1 = Dependency('eqangle', [a, b, a, c, x, y, x, z], '', level)
|
978 |
+
dep1.why_me(g, level)
|
979 |
+
dep2 = Dependency('eqangle', [b, a, b, c, y, x, y, z], '', level)
|
980 |
+
dep2.why_me(g, level)
|
981 |
+
self.rule_name = 'r34'
|
982 |
+
self.why = [dep1, dep2]
|
983 |
+
|
984 |
+
elif self.name == 'contri':
|
985 |
+
a, b, c, x, y, z = self.args
|
986 |
+
dep1 = Dependency('cong', [a, b, x, y], '', level)
|
987 |
+
dep1.why_me(g, level)
|
988 |
+
dep2 = Dependency('cong', [b, c, y, z], '', level)
|
989 |
+
dep2.why_me(g, level)
|
990 |
+
dep3 = Dependency('cong', [c, a, z, x], '', level)
|
991 |
+
dep3.why_me(g, level)
|
992 |
+
self.rule_name = 'r32'
|
993 |
+
self.why = [dep1, dep2, dep3]
|
994 |
+
|
995 |
+
elif self.name == 'ind':
|
996 |
+
pass
|
997 |
+
|
998 |
+
elif self.name == 'aconst':
|
999 |
+
a, b, c, d, ang0 = self.args
|
1000 |
+
|
1001 |
+
measure = ang0._val
|
1002 |
+
|
1003 |
+
for ang in measure.neighbors(gm.Angle):
|
1004 |
+
if ang == ang0:
|
1005 |
+
continue
|
1006 |
+
d1, d2 = ang._d
|
1007 |
+
l1, l2 = d1._obj, d2._obj
|
1008 |
+
(a1, b1), (c1, d1) = l1.points, l2.points
|
1009 |
+
|
1010 |
+
if not g.check_para_or_coll([a, b, a1, b1]) or not g.check_para_or_coll(
|
1011 |
+
[c, d, c1, d1]
|
1012 |
+
):
|
1013 |
+
continue
|
1014 |
+
|
1015 |
+
self.why = []
|
1016 |
+
for args in [(a, b, a1, b1), (c, d, c1, d1)]:
|
1017 |
+
if g.check_coll(args):
|
1018 |
+
if len(set(args)) > 2:
|
1019 |
+
dep = Dependency('coll', args, None, None)
|
1020 |
+
self.why.append(dep.why_me_or_cache(g, level))
|
1021 |
+
else:
|
1022 |
+
dep = Dependency('para', args, None, None)
|
1023 |
+
self.why.append(dep.why_me_or_cache(g, level))
|
1024 |
+
|
1025 |
+
self.why += gm.why_equal(ang, ang0)
|
1026 |
+
break
|
1027 |
+
|
1028 |
+
elif self.name == 'rconst':
|
1029 |
+
a, b, c, d, rat0 = self.args
|
1030 |
+
|
1031 |
+
val = rat0._val
|
1032 |
+
|
1033 |
+
for rat in val.neighbors(gm.Ratio):
|
1034 |
+
if rat == rat0:
|
1035 |
+
continue
|
1036 |
+
l1, l2 = rat._l
|
1037 |
+
s1, s2 = l1._obj, l2._obj
|
1038 |
+
(a1, b1), (c1, d1) = list(s1.points), list(s2.points)
|
1039 |
+
|
1040 |
+
if not g.check_cong([a, b, a1, b1]) or not g.check_cong([c, d, c1, d1]):
|
1041 |
+
continue
|
1042 |
+
|
1043 |
+
self.why = []
|
1044 |
+
for args in [(a, b, a1, b1), (c, d, c1, d1)]:
|
1045 |
+
if len(set(args)) > 2:
|
1046 |
+
dep = Dependency('cong', args, None, None)
|
1047 |
+
self.why.append(dep.why_me_or_cache(g, level))
|
1048 |
+
|
1049 |
+
self.why += gm.why_equal(rat, rat0)
|
1050 |
+
break
|
1051 |
+
|
1052 |
+
else:
|
1053 |
+
raise ValueError('Not recognize', self.name)
|
1054 |
+
|
1055 |
+
def hashed(self, rename: bool = False) -> tuple[str, ...]:
|
1056 |
+
return hashed(self.name, self.args, rename=rename)
|
1057 |
+
|
1058 |
+
|
1059 |
+
def hashed(
|
1060 |
+
name: str, args: list[gm.Point], rename: bool = False
|
1061 |
+
) -> tuple[str, ...]:
|
1062 |
+
if name == 's_angle':
|
1063 |
+
args = [p.name if not rename else p.new_name for p in args[:-1]] + [
|
1064 |
+
str(args[-1])
|
1065 |
+
]
|
1066 |
+
else:
|
1067 |
+
args = [p.name if not rename else p.new_name for p in args]
|
1068 |
+
return hashed_txt(name, args)
|
1069 |
+
|
1070 |
+
|
1071 |
+
def hashed_txt(name: str, args: list[str]) -> tuple[str, ...]:
|
1072 |
+
"""Return a tuple unique to name and args upto arg permutation equivariant."""
|
1073 |
+
|
1074 |
+
if name in ['const', 'aconst', 'rconst']:
|
1075 |
+
a, b, c, d, y = args
|
1076 |
+
a, b = sorted([a, b])
|
1077 |
+
c, d = sorted([c, d])
|
1078 |
+
return name, a, b, c, d, y
|
1079 |
+
|
1080 |
+
if name in ['npara', 'nperp', 'para', 'cong', 'perp', 'collx']:
|
1081 |
+
a, b, c, d = args
|
1082 |
+
|
1083 |
+
a, b = sorted([a, b])
|
1084 |
+
c, d = sorted([c, d])
|
1085 |
+
(a, b), (c, d) = sorted([(a, b), (c, d)])
|
1086 |
+
|
1087 |
+
return (name, a, b, c, d)
|
1088 |
+
|
1089 |
+
if name in ['midp', 'midpoint']:
|
1090 |
+
a, b, c = args
|
1091 |
+
b, c = sorted([b, c])
|
1092 |
+
return (name, a, b, c)
|
1093 |
+
|
1094 |
+
if name in ['coll', 'cyclic', 'ncoll', 'diff', 'triangle']:
|
1095 |
+
return (name,) + tuple(sorted(list(set(args))))
|
1096 |
+
|
1097 |
+
if name == 'circle':
|
1098 |
+
x, a, b, c = args
|
1099 |
+
return (name, x) + tuple(sorted([a, b, c]))
|
1100 |
+
|
1101 |
+
if name in ['eqangle', 'eqratio', 'eqangle6', 'eqratio6']:
|
1102 |
+
a, b, c, d, e, f, g, h = args
|
1103 |
+
a, b = sorted([a, b])
|
1104 |
+
c, d = sorted([c, d])
|
1105 |
+
e, f = sorted([e, f])
|
1106 |
+
g, h = sorted([g, h])
|
1107 |
+
if tuple(sorted([a, b, e, f])) > tuple(sorted([c, d, g, h])):
|
1108 |
+
a, b, e, f, c, d, g, h = c, d, g, h, a, b, e, f
|
1109 |
+
if (a, b, c, d) > (e, f, g, h):
|
1110 |
+
a, b, c, d, e, f, g, h = e, f, g, h, a, b, c, d
|
1111 |
+
|
1112 |
+
if name == 'eqangle6':
|
1113 |
+
name = 'eqangle'
|
1114 |
+
if name == 'eqratio6':
|
1115 |
+
name = 'eqratio'
|
1116 |
+
return (name,) + (a, b, c, d, e, f, g, h)
|
1117 |
+
|
1118 |
+
if name in ['contri', 'simtri', 'simtri2', 'contri2', 'contri*', 'simtri*']:
|
1119 |
+
a, b, c, x, y, z = args
|
1120 |
+
(a, x), (b, y), (c, z) = sorted([(a, x), (b, y), (c, z)], key=sorted)
|
1121 |
+
(a, b, c), (x, y, z) = sorted([(a, b, c), (x, y, z)], key=sorted)
|
1122 |
+
return (name, a, b, c, x, y, z)
|
1123 |
+
|
1124 |
+
if name in ['eqratio3']:
|
1125 |
+
a, b, c, d, o, o = args # pylint: disable=redeclared-assigned-name
|
1126 |
+
(a, c), (b, d) = sorted([(a, c), (b, d)], key=sorted)
|
1127 |
+
(a, b), (c, d) = sorted([(a, b), (c, d)], key=sorted)
|
1128 |
+
return (name, a, b, c, d, o, o)
|
1129 |
+
|
1130 |
+
if name in ['sameside', 's_angle']:
|
1131 |
+
return (name,) + tuple(args)
|
1132 |
+
|
1133 |
+
raise ValueError(f'Not recognize {name} to hash.')
|
ag4masses/alphageometry/problem_test.py
ADDED
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright 2023 DeepMind Technologies Limited
|
2 |
+
#
|
3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4 |
+
# you may not use this file except in compliance with the License.
|
5 |
+
# You may obtain a copy of the License at
|
6 |
+
#
|
7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8 |
+
#
|
9 |
+
# Unless required by applicable law or agreed to in writing, software
|
10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
+
# See the License for the specific language governing permissions and
|
13 |
+
# limitations under the License.
|
14 |
+
# ==============================================================================
|
15 |
+
|
16 |
+
"""Unit tests for problem.py."""
|
17 |
+
import unittest
|
18 |
+
|
19 |
+
from absl.testing import absltest
|
20 |
+
import problem as pr
|
21 |
+
|
22 |
+
|
23 |
+
class ProblemTest(unittest.TestCase):
|
24 |
+
|
25 |
+
@classmethod
|
26 |
+
def setUpClass(cls):
|
27 |
+
super().setUpClass()
|
28 |
+
cls.defs = pr.Definition.from_txt_file('defs.txt', to_dict=True)
|
29 |
+
|
30 |
+
def test_orthocenter_no_translate(self):
|
31 |
+
txt = 'a b c = triangle a b c; h = on_tline h b a c, on_tline h c a b ? perp a h b c' # pylint: disable=line-too-long
|
32 |
+
|
33 |
+
# read the txt into pr.Problem object, do not change the name of points:
|
34 |
+
p = pr.Problem.from_txt(txt, translate=False)
|
35 |
+
|
36 |
+
# This is fed into the LM, translating from constructive to constrained:
|
37 |
+
setup_str = p.setup_str_from_problem(ProblemTest.defs)
|
38 |
+
|
39 |
+
self.assertEqual(
|
40 |
+
setup_str,
|
41 |
+
'{S} a : ; b : ; c : ; h : T a b c h 00 T a c b h 01 ? T a h b c',
|
42 |
+
)
|
43 |
+
|
44 |
+
def test_orthocenter_translate(self):
|
45 |
+
txt = 'a b c = triangle a b c; h = on_tline h b a c, on_tline h c a b ? perp a h b c' # pylint: disable=line-too-long
|
46 |
+
|
47 |
+
# Read the txt into pr.Problem object, change h -> d to match
|
48 |
+
# training data distribution.
|
49 |
+
p = pr.Problem.from_txt(txt, translate=True)
|
50 |
+
|
51 |
+
# This is fed into the LM, translating from constructive to constrained:
|
52 |
+
setup_str = p.setup_str_from_problem(ProblemTest.defs)
|
53 |
+
|
54 |
+
self.assertEqual(
|
55 |
+
setup_str,
|
56 |
+
'{S} a : ; b : ; c : ; d : T a b c d 00 T a c b d 01 ? T a d b c',
|
57 |
+
)
|
58 |
+
|
59 |
+
|
60 |
+
if __name__ == '__main__':
|
61 |
+
absltest.main()
|
ag4masses/alphageometry/rules.txt
ADDED
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
perp A B C D, perp C D E F, ncoll A B E => para A B E F
|
2 |
+
cong O A O B, cong O B O C, cong O C O D => cyclic A B C D
|
3 |
+
eqangle A B P Q C D P Q => para A B C D
|
4 |
+
cyclic A B P Q => eqangle P A P B Q A Q B
|
5 |
+
eqangle6 P A P B Q A Q B, ncoll P Q A B => cyclic A B P Q
|
6 |
+
cyclic A B C P Q R, eqangle C A C B R P R Q => cong A B P Q
|
7 |
+
midp E A B, midp F A C => para E F B C
|
8 |
+
para A B C D, coll O A C, coll O B D => eqratio3 A B C D O O
|
9 |
+
perp A B C D, perp E F G H, npara A B E F => eqangle A B E F C D G H
|
10 |
+
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
|
11 |
+
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
|
12 |
+
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
|
13 |
+
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
|
14 |
+
cong O A O B, ncoll O A B => eqangle O A A B A B O B
|
15 |
+
eqangle6 A O A B B A B O, ncoll O A B => cong O A O B
|
16 |
+
circle O A B C, perp O A A X => eqangle A X A B C A C B
|
17 |
+
circle O A B C, eqangle A X A B C A C B => perp O A A X
|
18 |
+
circle O A B C, midp M B C => eqangle A B A C O B O M
|
19 |
+
circle O A B C, coll M B C, eqangle A B A C O B O M => midp M B C
|
20 |
+
perp A B B C, midp M A C => cong A M B M
|
21 |
+
circle O A B C, coll O A C => perp A B B C
|
22 |
+
cyclic A B C D, para A B C D => eqangle A D C D C D C B
|
23 |
+
midp M A B, perp O M A B => cong O A O B
|
24 |
+
cong A P B P, cong A Q B Q => perp A B P Q
|
25 |
+
cong A P B P, cong A Q B Q, cyclic A B P Q => perp P A A Q
|
26 |
+
midp M A B, midp M C D => para A C B D
|
27 |
+
midp M A B, para A C B D, para A D B C => midp M C D
|
28 |
+
eqratio O A A C O B B D, coll O A C, coll O B D, ncoll A B C, sameside A O C B O D => para A B C D
|
29 |
+
para A B A C => coll A B C
|
30 |
+
midp M A B, midp N C D => eqratio M A A B N C C D
|
31 |
+
eqangle A B P Q C D U V, perp P Q U V => perp A B C D
|
32 |
+
eqratio A B P Q C D U V, cong P Q U V => cong A B C D
|
33 |
+
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
|
34 |
+
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
|
35 |
+
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
|
36 |
+
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
|
37 |
+
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
|
38 |
+
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
|
39 |
+
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
|
40 |
+
eqratio6 B A B C Q P Q R, eqangle6 B A B C Q P Q R, ncoll A B C => simtri* A B C P Q R
|
41 |
+
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
|
42 |
+
para a b c d, coll m a d, coll n b c, eqratio6 m a m d n b n c, sameside m a d n b c => para m n a b
|
43 |
+
para a b c d, coll m a d, coll n b c, para m n a b => eqratio6 m a m d n b n c
|
ag4masses/alphageometry/trace_back.py
ADDED
@@ -0,0 +1,374 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright 2023 DeepMind Technologies Limited
|
2 |
+
#
|
3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4 |
+
# you may not use this file except in compliance with the License.
|
5 |
+
# You may obtain a copy of the License at
|
6 |
+
#
|
7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8 |
+
#
|
9 |
+
# Unless required by applicable law or agreed to in writing, software
|
10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
+
# See the License for the specific language governing permissions and
|
13 |
+
# limitations under the License.
|
14 |
+
# ==============================================================================
|
15 |
+
|
16 |
+
"""Implements DAG-level traceback."""
|
17 |
+
|
18 |
+
from typing import Any
|
19 |
+
|
20 |
+
import geometry as gm
|
21 |
+
import pretty as pt
|
22 |
+
import problem
|
23 |
+
|
24 |
+
|
25 |
+
pretty = pt.pretty
|
26 |
+
|
27 |
+
|
28 |
+
def point_levels(
|
29 |
+
setup: list[problem.Dependency], existing_points: list[gm.Point]
|
30 |
+
) -> list[tuple[set[gm.Point], list[problem.Dependency]]]:
|
31 |
+
"""Reformat setup into levels of point constructions."""
|
32 |
+
levels = []
|
33 |
+
for con in setup:
|
34 |
+
plevel = max([p.plevel for p in con.args if isinstance(p, gm.Point)])
|
35 |
+
|
36 |
+
while len(levels) - 1 < plevel:
|
37 |
+
levels.append((set(), []))
|
38 |
+
|
39 |
+
for p in con.args:
|
40 |
+
if not isinstance(p, gm.Point):
|
41 |
+
continue
|
42 |
+
if existing_points and p in existing_points:
|
43 |
+
continue
|
44 |
+
|
45 |
+
levels[p.plevel][0].add(p)
|
46 |
+
|
47 |
+
cons = levels[plevel][1]
|
48 |
+
cons.append(con)
|
49 |
+
|
50 |
+
return [(p, c) for p, c in levels if p or c]
|
51 |
+
|
52 |
+
|
53 |
+
def point_log(
|
54 |
+
setup: list[problem.Dependency],
|
55 |
+
ref_id: dict[tuple[str, ...], int],
|
56 |
+
existing_points=list[gm.Point],
|
57 |
+
) -> list[tuple[list[gm.Point], list[problem.Dependency]]]:
|
58 |
+
"""Reformat setup into groups of point constructions."""
|
59 |
+
log = []
|
60 |
+
|
61 |
+
levels = point_levels(setup, existing_points)
|
62 |
+
|
63 |
+
for points, cons in levels:
|
64 |
+
for con in cons:
|
65 |
+
if con.hashed() not in ref_id:
|
66 |
+
ref_id[con.hashed()] = len(ref_id)
|
67 |
+
|
68 |
+
log.append((points, cons))
|
69 |
+
|
70 |
+
return log
|
71 |
+
|
72 |
+
|
73 |
+
def setup_to_levels(
|
74 |
+
setup: list[problem.Dependency],
|
75 |
+
) -> list[list[problem.Dependency]]:
|
76 |
+
"""Reformat setup into levels of point constructions."""
|
77 |
+
levels = []
|
78 |
+
for d in setup:
|
79 |
+
plevel = max([p.plevel for p in d.args if isinstance(p, gm.Point)])
|
80 |
+
while len(levels) - 1 < plevel:
|
81 |
+
levels.append([])
|
82 |
+
|
83 |
+
levels[plevel].append(d)
|
84 |
+
|
85 |
+
levels = [lvl for lvl in levels if lvl]
|
86 |
+
return levels
|
87 |
+
|
88 |
+
|
89 |
+
def separate_dependency_difference(
|
90 |
+
query: problem.Dependency,
|
91 |
+
log: list[tuple[list[problem.Dependency], list[problem.Dependency]]],
|
92 |
+
) -> tuple[
|
93 |
+
list[tuple[list[problem.Dependency], list[problem.Dependency]]],
|
94 |
+
list[problem.Dependency],
|
95 |
+
list[problem.Dependency],
|
96 |
+
set[gm.Point],
|
97 |
+
set[gm.Point],
|
98 |
+
]:
|
99 |
+
"""Identify and separate the dependency difference."""
|
100 |
+
setup = []
|
101 |
+
log_, log = log, []
|
102 |
+
for prems, cons in log_:
|
103 |
+
if not prems:
|
104 |
+
setup.extend(cons)
|
105 |
+
continue
|
106 |
+
cons_ = []
|
107 |
+
for con in cons:
|
108 |
+
if con.rule_name == 'c0':
|
109 |
+
setup.append(con)
|
110 |
+
else:
|
111 |
+
cons_.append(con)
|
112 |
+
if not cons_:
|
113 |
+
continue
|
114 |
+
|
115 |
+
prems = [p for p in prems if p.name != 'ind']
|
116 |
+
log.append((prems, cons_))
|
117 |
+
|
118 |
+
points = set(query.args)
|
119 |
+
queue = list(query.args)
|
120 |
+
i = 0
|
121 |
+
while i < len(queue):
|
122 |
+
q = queue[i]
|
123 |
+
i += 1
|
124 |
+
if not isinstance(q, gm.Point):
|
125 |
+
continue
|
126 |
+
for p in q.rely_on:
|
127 |
+
if p not in points:
|
128 |
+
points.add(p)
|
129 |
+
queue.append(p)
|
130 |
+
|
131 |
+
setup_, setup, aux_setup, aux_points = setup, [], [], set()
|
132 |
+
for con in setup_:
|
133 |
+
if con.name == 'ind':
|
134 |
+
continue
|
135 |
+
elif any([p not in points for p in con.args if isinstance(p, gm.Point)]):
|
136 |
+
aux_setup.append(con)
|
137 |
+
aux_points.update(
|
138 |
+
[p for p in con.args if isinstance(p, gm.Point) and p not in points]
|
139 |
+
)
|
140 |
+
else:
|
141 |
+
setup.append(con)
|
142 |
+
|
143 |
+
return log, setup, aux_setup, points, aux_points
|
144 |
+
|
145 |
+
|
146 |
+
def recursive_traceback(
|
147 |
+
query: problem.Dependency,
|
148 |
+
) -> list[tuple[list[problem.Dependency], list[problem.Dependency]]]:
|
149 |
+
"""Recursively traceback from the query, i.e. the conclusion."""
|
150 |
+
visited = set()
|
151 |
+
log = []
|
152 |
+
stack = []
|
153 |
+
|
154 |
+
def read(q: problem.Dependency) -> None:
|
155 |
+
q = q.remove_loop()
|
156 |
+
hashed = q.hashed()
|
157 |
+
if hashed in visited:
|
158 |
+
return
|
159 |
+
|
160 |
+
if hashed[0] in ['ncoll', 'npara', 'nperp', 'diff', 'sameside']:
|
161 |
+
return
|
162 |
+
|
163 |
+
nonlocal stack
|
164 |
+
|
165 |
+
stack.append(hashed)
|
166 |
+
prems = []
|
167 |
+
|
168 |
+
if q.rule_name != problem.CONSTRUCTION_RULE:
|
169 |
+
all_deps = []
|
170 |
+
dep_names = set()
|
171 |
+
for d in q.why:
|
172 |
+
if d.hashed() in dep_names:
|
173 |
+
continue
|
174 |
+
dep_names.add(d.hashed())
|
175 |
+
all_deps.append(d)
|
176 |
+
|
177 |
+
for d in all_deps:
|
178 |
+
h = d.hashed()
|
179 |
+
if h not in visited:
|
180 |
+
read(d)
|
181 |
+
if h in visited:
|
182 |
+
prems.append(d)
|
183 |
+
|
184 |
+
visited.add(hashed)
|
185 |
+
hashs = sorted([d.hashed() for d in prems])
|
186 |
+
found = False
|
187 |
+
for ps, qs in log:
|
188 |
+
if sorted([d.hashed() for d in ps]) == hashs:
|
189 |
+
qs += [q]
|
190 |
+
found = True
|
191 |
+
break
|
192 |
+
if not found:
|
193 |
+
log.append((prems, [q]))
|
194 |
+
|
195 |
+
stack.pop(-1)
|
196 |
+
|
197 |
+
read(query)
|
198 |
+
|
199 |
+
# post process log: separate multi-conclusion lines
|
200 |
+
log_, log = log, []
|
201 |
+
for ps, qs in log_:
|
202 |
+
for q in qs:
|
203 |
+
log.append((ps, [q]))
|
204 |
+
|
205 |
+
return log
|
206 |
+
|
207 |
+
|
208 |
+
def collx_to_coll_setup(
|
209 |
+
setup: list[problem.Dependency],
|
210 |
+
) -> list[problem.Dependency]:
|
211 |
+
"""Convert collx to coll in setups."""
|
212 |
+
result = []
|
213 |
+
for level in setup_to_levels(setup):
|
214 |
+
hashs = set()
|
215 |
+
for dep in level:
|
216 |
+
if dep.name == 'collx':
|
217 |
+
dep.name = 'coll'
|
218 |
+
dep.args = list(set(dep.args))
|
219 |
+
|
220 |
+
if dep.hashed() in hashs:
|
221 |
+
continue
|
222 |
+
hashs.add(dep.hashed())
|
223 |
+
result.append(dep)
|
224 |
+
|
225 |
+
return result
|
226 |
+
|
227 |
+
|
228 |
+
def collx_to_coll(
|
229 |
+
setup: list[problem.Dependency],
|
230 |
+
aux_setup: list[problem.Dependency],
|
231 |
+
log: list[tuple[list[problem.Dependency], list[problem.Dependency]]],
|
232 |
+
) -> tuple[
|
233 |
+
list[problem.Dependency],
|
234 |
+
list[problem.Dependency],
|
235 |
+
list[tuple[list[problem.Dependency], list[problem.Dependency]]],
|
236 |
+
]:
|
237 |
+
"""Convert collx to coll and dedup."""
|
238 |
+
setup = collx_to_coll_setup(setup)
|
239 |
+
aux_setup = collx_to_coll_setup(aux_setup)
|
240 |
+
|
241 |
+
con_set = set([p.hashed() for p in setup + aux_setup])
|
242 |
+
log_, log = log, []
|
243 |
+
for prems, cons in log_:
|
244 |
+
prem_set = set()
|
245 |
+
prems_, prems = prems, []
|
246 |
+
for p in prems_:
|
247 |
+
if p.name == 'collx':
|
248 |
+
p.name = 'coll'
|
249 |
+
p.args = list(set(p.args))
|
250 |
+
if p.hashed() in prem_set:
|
251 |
+
continue
|
252 |
+
prem_set.add(p.hashed())
|
253 |
+
prems.append(p)
|
254 |
+
|
255 |
+
cons_, cons = cons, []
|
256 |
+
for c in cons_:
|
257 |
+
if c.name == 'collx':
|
258 |
+
c.name = 'coll'
|
259 |
+
c.args = list(set(c.args))
|
260 |
+
if c.hashed() in con_set:
|
261 |
+
continue
|
262 |
+
con_set.add(c.hashed())
|
263 |
+
cons.append(c)
|
264 |
+
|
265 |
+
if not cons or not prems:
|
266 |
+
continue
|
267 |
+
|
268 |
+
log.append((prems, cons))
|
269 |
+
|
270 |
+
return setup, aux_setup, log
|
271 |
+
|
272 |
+
|
273 |
+
def get_logs(
|
274 |
+
query: problem.Dependency, g: Any, merge_trivials: bool = False
|
275 |
+
) -> tuple[
|
276 |
+
list[problem.Dependency],
|
277 |
+
list[problem.Dependency],
|
278 |
+
list[tuple[list[problem.Dependency], list[problem.Dependency]]],
|
279 |
+
set[gm.Point],
|
280 |
+
]:
|
281 |
+
"""Given a DAG and conclusion N, return the premise, aux, proof."""
|
282 |
+
query = query.why_me_or_cache(g, query.level)
|
283 |
+
log = recursive_traceback(query)
|
284 |
+
log, setup, aux_setup, setup_points, _ = separate_dependency_difference(
|
285 |
+
query, log
|
286 |
+
)
|
287 |
+
|
288 |
+
setup, aux_setup, log = collx_to_coll(setup, aux_setup, log)
|
289 |
+
|
290 |
+
setup, aux_setup, log = shorten_and_shave(
|
291 |
+
setup, aux_setup, log, merge_trivials
|
292 |
+
)
|
293 |
+
|
294 |
+
return setup, aux_setup, log, setup_points
|
295 |
+
|
296 |
+
|
297 |
+
def shorten_and_shave(
|
298 |
+
setup: list[problem.Dependency],
|
299 |
+
aux_setup: list[problem.Dependency],
|
300 |
+
log: list[tuple[list[problem.Dependency], list[problem.Dependency]]],
|
301 |
+
merge_trivials: bool = False,
|
302 |
+
) -> tuple[
|
303 |
+
list[problem.Dependency],
|
304 |
+
list[problem.Dependency],
|
305 |
+
list[tuple[list[problem.Dependency], list[problem.Dependency]]],
|
306 |
+
]:
|
307 |
+
"""Shorten the proof by removing unused predicates."""
|
308 |
+
log, _ = shorten_proof(log, merge_trivials=merge_trivials)
|
309 |
+
|
310 |
+
all_prems = sum([list(prems) for prems, _ in log], [])
|
311 |
+
all_prems = set([p.hashed() for p in all_prems])
|
312 |
+
setup = [d for d in setup if d.hashed() in all_prems]
|
313 |
+
aux_setup = [d for d in aux_setup if d.hashed() in all_prems]
|
314 |
+
return setup, aux_setup, log
|
315 |
+
|
316 |
+
|
317 |
+
def join_prems(
|
318 |
+
con: problem.Dependency,
|
319 |
+
con2prems: dict[tuple[str, ...], list[problem.Dependency]],
|
320 |
+
expanded: set[tuple[str, ...]],
|
321 |
+
) -> list[problem.Dependency]:
|
322 |
+
"""Join proof steps with the same premises."""
|
323 |
+
h = con.hashed()
|
324 |
+
if h in expanded or h not in con2prems:
|
325 |
+
return [con]
|
326 |
+
|
327 |
+
result = []
|
328 |
+
for p in con2prems[h]:
|
329 |
+
result += join_prems(p, con2prems, expanded)
|
330 |
+
return result
|
331 |
+
|
332 |
+
|
333 |
+
def shorten_proof(
|
334 |
+
log: list[tuple[list[problem.Dependency], list[problem.Dependency]]],
|
335 |
+
merge_trivials: bool = False,
|
336 |
+
) -> tuple[
|
337 |
+
list[tuple[list[problem.Dependency], list[problem.Dependency]]],
|
338 |
+
dict[tuple[str, ...], list[problem.Dependency]],
|
339 |
+
]:
|
340 |
+
"""Join multiple trivials proof steps into one."""
|
341 |
+
pops = set()
|
342 |
+
con2prem = {}
|
343 |
+
for prems, cons in log:
|
344 |
+
assert len(cons) == 1
|
345 |
+
con = cons[0]
|
346 |
+
if con.rule_name == '': # pylint: disable=g-explicit-bool-comparison
|
347 |
+
con2prem[con.hashed()] = prems
|
348 |
+
elif not merge_trivials:
|
349 |
+
# except for the ones that are premises to non-trivial steps.
|
350 |
+
pops.update({p.hashed() for p in prems})
|
351 |
+
|
352 |
+
for p in pops:
|
353 |
+
if p in con2prem:
|
354 |
+
con2prem.pop(p)
|
355 |
+
|
356 |
+
expanded = set()
|
357 |
+
log2 = []
|
358 |
+
for i, (prems, cons) in enumerate(log):
|
359 |
+
con = cons[0]
|
360 |
+
if i < len(log) - 1 and con.hashed() in con2prem:
|
361 |
+
continue
|
362 |
+
|
363 |
+
hashs = set()
|
364 |
+
new_prems = []
|
365 |
+
|
366 |
+
for p in sum([join_prems(p, con2prem, expanded) for p in prems], []):
|
367 |
+
if p.hashed() not in hashs:
|
368 |
+
new_prems.append(p)
|
369 |
+
hashs.add(p.hashed())
|
370 |
+
|
371 |
+
log2 += [(new_prems, [con])]
|
372 |
+
expanded.add(con.hashed())
|
373 |
+
|
374 |
+
return log2, con2prem
|
ag4masses/alphageometry/trace_back_test.py
ADDED
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright 2023 DeepMind Technologies Limited
|
2 |
+
#
|
3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4 |
+
# you may not use this file except in compliance with the License.
|
5 |
+
# You may obtain a copy of the License at
|
6 |
+
#
|
7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8 |
+
#
|
9 |
+
# Unless required by applicable law or agreed to in writing, software
|
10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
+
# See the License for the specific language governing permissions and
|
13 |
+
# limitations under the License.
|
14 |
+
# ==============================================================================
|
15 |
+
|
16 |
+
"""Unit testing for the trace_back code."""
|
17 |
+
|
18 |
+
import unittest
|
19 |
+
|
20 |
+
from absl.testing import absltest
|
21 |
+
import ddar
|
22 |
+
import graph as gh
|
23 |
+
import problem as pr
|
24 |
+
import trace_back as tb
|
25 |
+
|
26 |
+
|
27 |
+
class TracebackTest(unittest.TestCase):
|
28 |
+
|
29 |
+
@classmethod
|
30 |
+
def setUpClass(cls):
|
31 |
+
super().setUpClass()
|
32 |
+
cls.defs = pr.Definition.from_txt_file('defs.txt', to_dict=True)
|
33 |
+
cls.rules = pr.Theorem.from_txt_file('rules.txt', to_dict=True)
|
34 |
+
|
35 |
+
def test_orthocenter_dependency_difference(self):
|
36 |
+
txt = 'a b c = triangle a b c; d = on_tline d b a c, on_tline d c a b; e = on_line e a c, on_line e b d ? perp a d b c' # pylint: disable=line-too-long
|
37 |
+
p = pr.Problem.from_txt(txt)
|
38 |
+
g, _ = gh.Graph.build_problem(p, TracebackTest.defs)
|
39 |
+
|
40 |
+
ddar.solve(g, TracebackTest.rules, p)
|
41 |
+
|
42 |
+
goal_args = g.names2nodes(p.goal.args)
|
43 |
+
query = pr.Dependency(p.goal.name, goal_args, None, None)
|
44 |
+
|
45 |
+
setup, aux, _, _ = tb.get_logs(query, g, merge_trivials=False)
|
46 |
+
|
47 |
+
# Convert each predicates to its hash string:
|
48 |
+
setup = [p.hashed() for p in setup]
|
49 |
+
aux = [p.hashed() for p in aux]
|
50 |
+
|
51 |
+
self.assertCountEqual(
|
52 |
+
setup, [('perp', 'a', 'c', 'b', 'd'), ('perp', 'a', 'b', 'c', 'd')]
|
53 |
+
)
|
54 |
+
|
55 |
+
self.assertCountEqual(
|
56 |
+
aux, [('coll', 'a', 'c', 'e'), ('coll', 'b', 'd', 'e')]
|
57 |
+
)
|
58 |
+
|
59 |
+
|
60 |
+
if __name__ == '__main__':
|
61 |
+
absltest.main()
|