├── article
├── simson1.pdf
├── simson2a.pdf
├── simson2b.pdf
├── simson3.pdf
├── simson4.pdf
├── simson5.pdf
├── geo_logic.pdf
├── screenshot.png
└── logic_system.pdf
├── images
├── icons
│ ├── hide.png
│ ├── label.png
│ ├── line.png
│ ├── move.png
│ ├── point.png
│ ├── circle.png
│ ├── perpline.png
│ ├── reason.png
│ └── circumcircle.png
├── screenshot.png
├── cursors
│ ├── basic.png
│ ├── circle.png
│ ├── hide.png
│ ├── label.png
│ ├── line.png
│ ├── point.png
│ ├── reason.png
│ ├── unhide.png
│ ├── white.png
│ ├── perpline.png
│ └── circumcircle.png
└── svg
│ ├── point.svg
│ ├── label.svg
│ ├── circle.svg
│ ├── circumcircle.svg
│ ├── perpline.svg
│ ├── line.svg
│ ├── hide.svg
│ ├── derive-basic.svg
│ ├── derive.svg
│ ├── derive2.svg
│ └── move.svg
├── saved
├── levels
│ ├── flower.gl
│ ├── euler-line.gl
│ ├── homothety.gl
│ ├── magic.gl
│ ├── mks-2020-4-5.gl
│ ├── simson.gl
│ ├── mks-2020-4-2.gl
│ ├── imo-2019-2.gl
│ ├── mks-2020-4-7.gl
│ ├── pascal.gl
│ ├── imo-2010-4.gl
│ ├── imo-2010-2.gl
│ ├── mks-2020-4-8.gl
│ └── mks-2020-4-4.gl
├── solutions
│ ├── homothety.gl
│ ├── euler-line.gl
│ ├── flower.gl
│ ├── mks-2020-4-5.gl
│ ├── mks-2020-4-2.gl
│ ├── simson.gl
│ ├── mks-2020-4-7.gl
│ ├── pascal.gl
│ ├── imo-2010-4.gl
│ ├── mks-2020-4-8.gl
│ ├── imo-2010-2.gl
│ ├── mks-2020-4-4.gl
│ └── imo-2019-2.gl
├── midpoint_arc.gl
└── pascal_out.gl
├── debug.gl
├── num_duplicities.py
├── primitive_pred.py
├── uf_set.py
├── segment_union_diff.py
├── primitive_constr.py
├── sparse_row.py
├── relstr.py
├── stop_watch.py
├── technical_doc.txt
├── basic_tools.py
├── file_chooser.py
├── gtool_general.py
├── cairo_textedit.py
├── tool_step_utils.py
├── todo
├── uf_dict.py
├── primitive_tools.py
├── basic.gl
├── view_port_ori.py
├── angle_chasing.py
├── label_visualiser.py
├── step_list.py
├── README.md
├── logical_core.py
├── tools.py
└── gtool_label.py
/article/simson1.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mirefek/geo_logic/HEAD/article/simson1.pdf
--------------------------------------------------------------------------------
/article/simson2a.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mirefek/geo_logic/HEAD/article/simson2a.pdf
--------------------------------------------------------------------------------
/article/simson2b.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mirefek/geo_logic/HEAD/article/simson2b.pdf
--------------------------------------------------------------------------------
/article/simson3.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mirefek/geo_logic/HEAD/article/simson3.pdf
--------------------------------------------------------------------------------
/article/simson4.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mirefek/geo_logic/HEAD/article/simson4.pdf
--------------------------------------------------------------------------------
/article/simson5.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mirefek/geo_logic/HEAD/article/simson5.pdf
--------------------------------------------------------------------------------
/article/geo_logic.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mirefek/geo_logic/HEAD/article/geo_logic.pdf
--------------------------------------------------------------------------------
/article/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mirefek/geo_logic/HEAD/article/screenshot.png
--------------------------------------------------------------------------------
/images/icons/hide.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mirefek/geo_logic/HEAD/images/icons/hide.png
--------------------------------------------------------------------------------
/images/icons/label.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mirefek/geo_logic/HEAD/images/icons/label.png
--------------------------------------------------------------------------------
/images/icons/line.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mirefek/geo_logic/HEAD/images/icons/line.png
--------------------------------------------------------------------------------
/images/icons/move.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mirefek/geo_logic/HEAD/images/icons/move.png
--------------------------------------------------------------------------------
/images/icons/point.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mirefek/geo_logic/HEAD/images/icons/point.png
--------------------------------------------------------------------------------
/images/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mirefek/geo_logic/HEAD/images/screenshot.png
--------------------------------------------------------------------------------
/article/logic_system.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mirefek/geo_logic/HEAD/article/logic_system.pdf
--------------------------------------------------------------------------------
/images/cursors/basic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mirefek/geo_logic/HEAD/images/cursors/basic.png
--------------------------------------------------------------------------------
/images/cursors/circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mirefek/geo_logic/HEAD/images/cursors/circle.png
--------------------------------------------------------------------------------
/images/cursors/hide.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mirefek/geo_logic/HEAD/images/cursors/hide.png
--------------------------------------------------------------------------------
/images/cursors/label.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mirefek/geo_logic/HEAD/images/cursors/label.png
--------------------------------------------------------------------------------
/images/cursors/line.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mirefek/geo_logic/HEAD/images/cursors/line.png
--------------------------------------------------------------------------------
/images/cursors/point.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mirefek/geo_logic/HEAD/images/cursors/point.png
--------------------------------------------------------------------------------
/images/cursors/reason.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mirefek/geo_logic/HEAD/images/cursors/reason.png
--------------------------------------------------------------------------------
/images/cursors/unhide.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mirefek/geo_logic/HEAD/images/cursors/unhide.png
--------------------------------------------------------------------------------
/images/cursors/white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mirefek/geo_logic/HEAD/images/cursors/white.png
--------------------------------------------------------------------------------
/images/icons/circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mirefek/geo_logic/HEAD/images/icons/circle.png
--------------------------------------------------------------------------------
/images/icons/perpline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mirefek/geo_logic/HEAD/images/icons/perpline.png
--------------------------------------------------------------------------------
/images/icons/reason.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mirefek/geo_logic/HEAD/images/icons/reason.png
--------------------------------------------------------------------------------
/images/cursors/perpline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mirefek/geo_logic/HEAD/images/cursors/perpline.png
--------------------------------------------------------------------------------
/images/icons/circumcircle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mirefek/geo_logic/HEAD/images/icons/circumcircle.png
--------------------------------------------------------------------------------
/images/cursors/circumcircle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mirefek/geo_logic/HEAD/images/cursors/circumcircle.png
--------------------------------------------------------------------------------
/saved/levels/flower.gl:
--------------------------------------------------------------------------------
1 | _ ->
2 | A <- free_point -130.474609375 26.52557373046875
3 | B <- free_point -18.56854248046875 20.20416259765625
4 | w <- circle B A
5 | a <- circle A B
6 | C <- intersection 0 a w
7 | b <- circle C B
8 | D <- intersection 1 w b A
9 | c <- circle D B
10 | E <- intersection 1 c w C
11 | THEN
12 | <- collinear A B E
13 | PROOF
14 |
15 | label__A -> pos:P
16 | pos <- free_point -20.1424560546875 16.9609375
17 | label__B -> pos:P
18 | pos <- free_point 10.5494384765625 17.07342529296875
19 | label__E -> pos:P
20 | pos <- free_point 13.619934082031278 13.876403808593764
21 |
22 | view__data -> anchor:P zoom:D
23 | anchor <- free_point 0.0 0.0
24 | zoom <- custom_ratio 1.0 0.
25 |
--------------------------------------------------------------------------------
/debug.gl:
--------------------------------------------------------------------------------
1 | fake_lies_on A:P l:L ->
2 | THEN
3 | <- lies_on A l
4 | fake_lies_on A:P c:C ->
5 | THEN
6 | <- lies_on A c
7 | fake_parallel l1:L l2:L ->
8 | d1 <- direction_of l1
9 | d2 <- direction_of l2
10 | THEN
11 | <- == d1 d2
12 | p_line A:P B:P -> l:L
13 | THEN
14 | l <- prim__line A B
15 | p_circum A:P B:P C:P -> c:C
16 | THEN
17 | c <- prim__circumcircle A B C
18 | p_intersect l1:L l2:L -> X:P
19 | THEN
20 | X <- prim__intersection l1 l2
21 | p_intersect_rem cl1:L cl2:C A:P -> X:P
22 | THEN
23 | X <- prim__intersection_remoter cl1 cl2 A
24 | p_intersect_rem cl1:C cl2:L A:P -> X:P
25 | THEN
26 | X <- prim__intersection_remoter cl1 cl2 A
27 | p_intersect_rem cl1:C cl2:C A:P -> X:P
28 | THEN
29 | X <- prim__intersection_remoter cl1 cl2 A
30 |
--------------------------------------------------------------------------------
/saved/levels/euler-line.gl:
--------------------------------------------------------------------------------
1 | _ ->
2 | A <- free_point 272.67291259765625 117.24664306640625
3 | B <- free_point 206.54791259765625 388.65655517578125
4 | C <- free_point 548.4716796875 382.93927001953125
5 | H <- orthocenter A B C
6 | O <- circumcenter A B C
7 | G <- centroid A B C
8 | p <- line A B
9 | a <- line B C
10 | b <- line C A
11 | THEN
12 | <- collinear H O G
13 | PROOF
14 |
15 | label__H -> pos:P
16 | pos <- free_point -9.288343079419121 -16.881466387056378
17 | label__O -> pos:P
18 | pos <- free_point 10.145911041662657 -16.01098799787809
19 | label__G -> pos:P
20 | pos <- free_point -8.179219563802064 -14.2666015625
21 |
22 | view__data -> anchor:P zoom:D
23 | anchor <- free_point 355.59381103515625 285.03375244140625
24 | zoom <- custom_ratio 1.0 0.
25 |
--------------------------------------------------------------------------------
/saved/solutions/homothety.gl:
--------------------------------------------------------------------------------
1 | _ ->
2 | A <- free_point -351.3194319615701 -369.73587975173626
3 | B <- free_point -433.34515380859375 130.55828857421875
4 | C <- free_point 242.4351806640625 123.14581298828125
5 | i <- incircle A B C
6 | e <- excircle A B C
7 | a <- line A B
8 | b <- line A C
9 | c <- line C B
10 | X <- touchpoint c i
11 | D <- touchpoint c e
12 | p <- perpline c X
13 | E <- intersection 1 p i X
14 | THEN
15 | <- collinear A E D
16 | PROOF
17 | F <- center_of i
18 | G <- center_of e
19 | H <- touchpoint i b
20 | I <- touchpoint e b
21 | <- point_on_circle E i
22 | <- sim_aa A H F A I G
23 | <- sim_sas E F A D G A
24 |
25 |
26 | view__data -> anchor:P zoom:D
27 | anchor <- free_point 124.28984225698503 113.21873711674856
28 | zoom <- custom_ratio 0.3874204890000002 0.
29 |
--------------------------------------------------------------------------------
/saved/levels/homothety.gl:
--------------------------------------------------------------------------------
1 | _ ->
2 | A <- free_point -351.3194319615701 -369.73587975173626
3 | B <- free_point -433.34515380859375 130.55828857421875
4 | C <- free_point 242.4351806640625 123.14581298828125
5 | i <- incircle A B C
6 | e <- excircle A B C
7 | a <- line A B
8 | b <- line A C
9 | c <- line C B
10 | X <- touchpoint c i
11 | D <- touchpoint c e
12 | p <- perpline c X
13 | E <- intersection 1 p i X
14 | THEN
15 | <- collinear A E D
16 | PROOF
17 |
18 | label__A -> pos:P
19 | pos <- free_point -8.489120700841683 -23.82159262024068
20 | label__D -> pos:P
21 | pos <- free_point -10.171332647514804 17.96063958636752
22 | label__E -> pos:P
23 | pos <- free_point -11.813436554601044 16.656724047526808
24 |
25 | view__data -> anchor:P zoom:D
26 | anchor <- free_point 306.06683350419166 367.2012576005472
27 | zoom <- custom_ratio 0.28242953648100017 0.
28 |
--------------------------------------------------------------------------------
/saved/levels/magic.gl:
--------------------------------------------------------------------------------
1 | _ ->
2 | A <- free_point -67.4366455078125 -186.94235229492188
3 | B <- free_point -150.71661376953125 -50.786865234375
4 | C <- free_point -77.69537353515625 -4.03399658203125
5 | c <- circumcircle A B C
6 | D <- m_point_on 0.149322913515504 c
7 | a <- line A B
8 | b <- line B C
9 | d <- line C D
10 | e <- line D A
11 | X <- intersection d a
12 | E <- intersection b e
13 | l <- angle_bisector_int D E C
14 | f <- angle_bisector_int C X B
15 | THEN
16 | <- perpendicular f l
17 | PROOF
18 |
19 | label__l -> pos:D offset:D
20 | pos <- custom_ratio 0.14302512205559112 0.0
21 | offset <- custom_ratio -14.420757653691282 0.0
22 | label__f -> pos:D offset:D
23 | pos <- custom_ratio 0.8667681679394779 0.0
24 | offset <- custom_ratio 13.980884447649814 0.0
25 |
26 | view__data -> anchor:P zoom:D
27 | anchor <- free_point 0.0 0.0
28 | zoom <- custom_ratio 1.0 0.
29 |
--------------------------------------------------------------------------------
/saved/solutions/euler-line.gl:
--------------------------------------------------------------------------------
1 | _ ->
2 | A <- free_point 272.67291259765625 117.24664306640625
3 | B <- free_point 206.54791259765625 388.65655517578125
4 | C <- free_point 548.4716796875 382.93927001953125
5 | H <- orthocenter A B C
6 | O <- circumcenter A B C
7 | G <- centroid A B C
8 | p <- line A B
9 | a <- line B C
10 | b <- line C A
11 | THEN
12 | <- collinear H O G
13 | PROOF
14 | M <- midpoint A B
15 | D <- midpoint B C
16 | E <- midpoint C A
17 | F <- orthocenter M E D
18 | C' B' <- midsegment A B C
19 | I J <- midsegment C A B
20 | K L <- midsegment B A C
21 | c <- line A H
22 | d <- line D O
23 | e <- line O E
24 | f <- line H B
25 | <- sim_aa H A B O D E
26 | <- sim_aa G C A G M D
27 | <- sim_sas H A G O D G
28 | g <- line H G
29 |
30 |
31 | view__data -> anchor:P zoom:D
32 | anchor <- free_point 355.59381103515625 285.03375244140625
33 | zoom <- custom_ratio 1.0 0.
34 |
--------------------------------------------------------------------------------
/saved/midpoint_arc.gl:
--------------------------------------------------------------------------------
1 | _ -> A:P B:P C:P E:P I:P M:P a:L ang0:A ang1:A b:L c:L e:L f:L g:L h:L i:L ins_angle:A w:C
2 | A <- free_point -210.9918199218749 -185.58335928955074
3 | B <- free_point -108.4608154296875 31.5020751953125
4 | C <- free_point -10.575404516601544 -110.84469290771483
5 | a <- line B C
6 | b <- line C A
7 | c <- line A B
8 | w <- circumcircle A B C
9 | M <- midpoint_arc C B w
10 | I <- incenter A B C
11 | E <- excenter A B C
12 | ins_angle <- inscribed_angle M A B
13 | ang0 <- inscribed_angle C A M
14 | e <- line B I
15 | f <- line A M
16 | g <- line B M
17 | h <- line M C
18 | ang1 <- inscribed_angle C B M
19 | <- isosceles_aa M B I
20 | i <- line B E
21 | <- isosceles_aa M B E
22 | <- eq_arcs_to_eq_dist M B C M w
23 | o <- circle M I
24 | <- point_to_circle C o
25 | <- point_to_circle E o
26 | <- point_to_circle B o
27 |
28 | view__data -> anchor:P zoom:D
29 | anchor <- free_point -82.03733811035153 -75.77756125488281
30 | zoom <- custom_ratio 1.2345679012345683 0.
31 |
--------------------------------------------------------------------------------
/saved/levels/mks-2020-4-5.gl:
--------------------------------------------------------------------------------
1 | _ ->
2 | O1 <- free_point 264.5248718261719 317.69940185546875
3 | O2 <- free_point 611.6620483398438 301.9326171875
4 | a <- line O2 O1
5 | T <- m_point_on 406.91748046875 343.75726318359375 a
6 | w1 <- circle O1 T
7 | w2 <- circle O2 T
8 | C <- m_point_on -0.6151829349630342 w2
9 | t <- tangent_at C w2
10 | Y X <- intersections t w1
11 | b <- line C T
12 | P <- intersection_remoter w1 b T
13 | THEN
14 | <- eq_dist P X P Y
15 | PROOF
16 |
17 | label__T -> pos:P
18 | pos <- free_point 16.510127196684323 20.804733785048086
19 | label__C -> pos:P
20 | pos <- free_point 3.241689630873225 -29.587546840780277
21 | label__Y -> pos:P
22 | pos <- free_point -9.996488110171128 -19.137488998473174
23 | label__X -> pos:P
24 | pos <- free_point -3.1813872354765067 -25.2467726444267
25 | label__P -> pos:P
26 | pos <- free_point -15.39698893418273 18.271482302967115
27 |
28 | view__data -> anchor:P zoom:D
29 | anchor <- free_point 398.71282958984375 280.089599609375
30 | zoom <- custom_ratio 1.0 0.
31 |
--------------------------------------------------------------------------------
/saved/solutions/flower.gl:
--------------------------------------------------------------------------------
1 | _ ->
2 | A <- free_point -130.474609375 26.52557373046875
3 | B <- free_point -18.56854248046875 20.20416259765625
4 | w <- circle B A
5 | a <- circle A B
6 | C <- intersection 0 a w
7 | b <- circle C B
8 | D <- intersection 1 w b A
9 | c <- circle D B
10 | E <- intersection 1 c w C
11 | THEN
12 | <- collinear A B E
13 | PROOF
14 | <- point_on_circle C a
15 | <- point_on_circle C w
16 | <- point_on_circle D b
17 | <- point_on_circle D w
18 | <- point_on_circle E c
19 | <- point_on_circle E w
20 | <- point_to_circle A b
21 | <- point_to_circle C c
22 | <- equilateral_s C A B
23 | <- equilateral_s B C D
24 | <- equilateral_s D B E
25 |
26 | label__A -> pos:P
27 | pos <- free_point -20.1424560546875 16.9609375
28 | label__B -> pos:P
29 | pos <- free_point 10.5494384765625 17.07342529296875
30 | label__E -> pos:P
31 | pos <- free_point 13.619934082031278 13.876403808593764
32 |
33 | view__data -> anchor:P zoom:D
34 | anchor <- free_point 0.0 0.0
35 | zoom <- custom_ratio 1.0 0.
36 |
--------------------------------------------------------------------------------
/saved/levels/simson.gl:
--------------------------------------------------------------------------------
1 | _ ->
2 | A <- free_point 311.769775390625 94.72988891601562
3 | B <- free_point 257.6204833984375 262.5645446777344
4 | C <- free_point 489.54486083984375 255.80807495117188
5 | w <- circumcircle C A B
6 | a <- line B C
7 | b <- line C A
8 | c <- line A B
9 | D <- m_point_on 0.5862459822802772 w
10 | Fa <- foot D a
11 | Fc <- foot D c
12 | Fb <- foot D b
13 | d <- line Fc Fa
14 | THEN
15 | <- lies_on Fb d
16 | PROOF
17 |
18 | label__A -> pos:P
19 | pos <- free_point -5.7205810546875 -26.283782958984375
20 | label__B -> pos:P
21 | pos <- free_point -26.927883214476115 -13.224564476289405
22 | label__C -> pos:P
23 | pos <- free_point 22.2576904296875 -12.772674560546875
24 | label__Fa -> pos:P
25 | pos <- free_point -6.652826288817323 -25.45527359747649
26 | label__Fc -> pos:P
27 | pos <- free_point -19.753418771961833 -8.656619987053773
28 | label__Fb -> pos:P
29 | pos <- free_point 4.681870754789145 -21.65368028874488
30 | label__d -> pos:D offset:D
31 | pos <- custom_ratio 0.8078323975658995 0.0
32 | offset <- custom_ratio -14.074730440409382 0.0
33 |
34 | view__data -> anchor:P zoom:D
35 | anchor <- free_point 375.0 277.0
36 | zoom <- custom_ratio 1.0 0.
37 |
--------------------------------------------------------------------------------
/saved/levels/mks-2020-4-2.gl:
--------------------------------------------------------------------------------
1 | _ ->
2 | O1 <- free_point 322.4656066894531 353.3541259765625
3 | X <- free_point 431.5154724121094 233.50808715820312
4 | a <- line O1 X
5 | b <- perpline a X
6 | O2 <- m_point_on 546.1598510742188 372.623779296875 b
7 | w2 <- circle O2 X
8 | w1 <- circle O1 X
9 | Y <- intersection_remoter w2 w1 X
10 | c <- line O1 O2
11 | D <- intersection_remoter w1 c X
12 | d <- line D X
13 | P <- intersection_remoter d w2 X
14 | THEN
15 | <- perpendicular P O2 c
16 | PROOF
17 |
18 | label__O1 -> pos:P
19 | pos <- free_point 4.542144775390625 24.79559326171875
20 | label__X -> pos:P
21 | pos <- free_point 0.523773193359375 -20.004974365234375
22 | label__O2 -> pos:P
23 | pos <- free_point -11.432224655624251 27.73633428236934
24 | label__Y -> pos:P
25 | pos <- free_point -0.8865153060221038 29.9868986494467
26 | label__c -> pos:D offset:D
27 | pos <- custom_ratio 0.7991312886287827 0.0
28 | offset <- custom_ratio 13.432303559657782 0.0
29 | label__D -> pos:P
30 | pos <- free_point -12.573319393047257 -13.824641998048776
31 | label__P -> pos:P
32 | pos <- free_point 15.94664811603991 14.769906116264082
33 |
34 | view__data -> anchor:P zoom:D
35 | anchor <- free_point 435.20751953125 327.7374267578125
36 | zoom <- custom_ratio 1.0 0.
37 |
--------------------------------------------------------------------------------
/saved/levels/imo-2019-2.gl:
--------------------------------------------------------------------------------
1 | _ ->
2 | A <- free_point -139.2213134765625 -282.2152404785156
3 | B <- free_point -477.198974609375 210.1524658203125
4 | C <- free_point 233.489990234375 217.9029541015625
5 | a <- line A B
6 | b <- line B C
7 | c <- line C A
8 | D <- m_point_on -349.1357421875 24.8143310546875 a
9 | E <- m_point_on -22.7784423828125 -108.86618041992188 c
10 | d <- line D C
11 | e <- line E B
12 | F <- m_point_on -330.8531494140625 97.859130859375 e
13 | pa <- paraline b F
14 | X <- intersection pa a
15 | G <- intersection pa d
16 | H <- intersection c pa
17 | f <- line F D
18 | g <- line G E
19 | h <- circumcircle A G H
20 | i <- circumcircle A F X
21 | I <- intersection 1 f i F
22 | J <- intersection 1 g h G
23 | THEN
24 | <- concyclic I J F G
25 | PROOF
26 |
27 | label__F -> pos:P
28 | pos <- free_point -5.566352335886441 22.959953895900902
29 | label__G -> pos:P
30 | pos <- free_point 10.119419022938633 17.742230934136785
31 | hide__h ->
32 | hide__i ->
33 | label__I -> pos:P
34 | pos <- free_point -7.416630728129303 23.256965818540525
35 | label__J -> pos:P
36 | pos <- free_point 8.320856089171821 18.643222978309137
37 |
38 | view__data -> anchor:P zoom:D
39 | anchor <- free_point -100.68421934775733 -94.343650628829
40 | zoom <- custom_ratio 0.7290000000000004 0.
41 |
--------------------------------------------------------------------------------
/saved/levels/mks-2020-4-7.gl:
--------------------------------------------------------------------------------
1 | _ ->
2 | A <- free_point 308.853271484375 285.0768737792969
3 | D <- free_point 223.93048095703125 385.48931884765625
4 | B <- free_point 652.8225708007812 282.7047424316406
5 | a <- line B A
6 | c <- paraline a D
7 | d <- line A D
8 | b <- paraline d B
9 | C <- intersection c b
10 | <- parallelogram_aa A B C D
11 | M <- midpoint A B
12 | m <- line D M
13 | w1 <- circumcircle D A B
14 | F <- intersection_remoter m w1 D
15 | H <- foot B d
16 | E <- foot A b
17 | THEN
18 | <- concyclic C D H E
19 | PROOF
20 |
21 | label__A -> pos:P
22 | pos <- free_point -9.2183837890625 -14.857879638671875
23 | label__D -> pos:P
24 | pos <- free_point -1.66949462890625 25.47900390625
25 | label__B -> pos:P
26 | pos <- free_point -0.08953857421875 19.412872314453125
27 | label__C -> pos:P
28 | pos <- free_point 8.12274169921875 20.96527099609392
29 | label__M -> pos:P
30 | pos <- free_point -12.696319580078125 23.56011962890625
31 | label__F -> pos:P
32 | pos <- free_point 7.730074213103535 -20.34331944877104
33 | label__H -> pos:P
34 | pos <- free_point -15.630303489263156 -19.22039744108548
35 | label__E -> pos:P
36 | pos <- free_point -10.292070732543184 28.1793058827986
37 |
38 | view__data -> anchor:P zoom:D
39 | anchor <- free_point 375.0 277.0
40 | zoom <- custom_ratio 1.0 0.
41 |
--------------------------------------------------------------------------------
/saved/solutions/mks-2020-4-5.gl:
--------------------------------------------------------------------------------
1 | _ ->
2 | O1 <- free_point 264.5248718261719 317.69940185546875
3 | O2 <- free_point 611.6620483398438 301.9326171875
4 | a <- line O2 O1
5 | T <- m_point_on 406.91748046875 343.75726318359375 a
6 | w1 <- circle O1 T
7 | w2 <- circle O2 T
8 | C <- m_point_on -0.6151829349630342 w2
9 | t <- tangent_at C w2
10 | Y X <- intersections t w1
11 | b <- line C T
12 | P <- intersection_remoter w1 b T
13 | THEN
14 | <- eq_dist P X P Y
15 | PROOF
16 | c <- line O2 C
17 | d <- paraline c O1
18 | P' <- intersection_closer d w1 P
19 | <- point_on_circle P' w1
20 | <- point_on_circle C w2
21 | <- sim_sas C O2 T P' O1 T
22 | d' <- perp_bisector X Y
23 | <- point_on_circle X w1
24 | <- point_on_circle Y w1
25 | <- point_to_perp_bisector O1 X Y
26 | <- point_on_perp_bisector P' X Y
27 |
28 | label__T -> pos:P
29 | pos <- free_point 16.510127196684323 20.804733785048086
30 | label__C -> pos:P
31 | pos <- free_point 3.241689630873225 -29.587546840780277
32 | label__Y -> pos:P
33 | pos <- free_point -9.996488110171128 -19.137488998473174
34 | label__X -> pos:P
35 | pos <- free_point -3.1813872354765067 -25.2467726444267
36 | label__P -> pos:P
37 | pos <- free_point -15.39698893418273 18.271482302967115
38 |
39 | view__data -> anchor:P zoom:D
40 | anchor <- free_point 398.71282958984375 280.089599609375
41 | zoom <- custom_ratio 1.0 0.
42 |
--------------------------------------------------------------------------------
/num_duplicities.py:
--------------------------------------------------------------------------------
1 | import geo_object
2 | from geo_object import *
3 | import itertools
4 |
5 | def find_duplicities(objs, epsilon = geo_object.epsilon):
6 | glued_to = dict()
7 | d = dict()
8 | def add_to_dict(ident, t, data):
9 | idata = np.floor(data / epsilon).astype(int)
10 | for offset in itertools.product(*((0,1) for _ in idata)):
11 | d_index = idata + np.array(offset, dtype = int)
12 | d_index = tuple(d_index)
13 | ident2 = d.setdefault((t, d_index), ident)
14 | if ident != ident2:
15 | glued_to[ident].append(ident2)
16 | glued_to[ident2].append(ident)
17 |
18 | ident_list = list()
19 | for identifier, obj in objs:
20 | t = type(obj)
21 | ident_list.append(identifier)
22 | glued_to.setdefault(identifier, list())
23 | add_to_dict(identifier, t, obj.data)
24 | if t == Line: add_to_dict(identifier, t, -obj.data)
25 |
26 | for identifier in ident_list:
27 | if identifier not in glued_to: continue
28 | component = list()
29 | def find_component(x):
30 | x_objs = glued_to.pop(x, None)
31 | if x_objs is None: return
32 | component.append(x)
33 | for x2 in x_objs:
34 | find_component(x2)
35 | find_component(identifier)
36 | if component: yield component
37 |
--------------------------------------------------------------------------------
/saved/solutions/mks-2020-4-2.gl:
--------------------------------------------------------------------------------
1 | _ ->
2 | O1 <- free_point 322.4656066894531 353.3541259765625
3 | X <- free_point 431.5154724121094 233.50808715820312
4 | a <- line O1 X
5 | b <- perpline a X
6 | O2 <- m_point_on 546.1598510742188 372.623779296875 b
7 | w2 <- circle O2 X
8 | w1 <- circle O1 X
9 | Y <- intersection_remoter w2 w1 X
10 | c <- line O1 O2
11 | D <- intersection_remoter w1 c X
12 | d <- line D X
13 | P <- intersection_remoter d w2 X
14 | THEN
15 | <- perpendicular P O2 c
16 | PROOF
17 | <- point_on_circle P w2
18 | <- point_on_circle D w1
19 | <- isosceles_ss O1 D X
20 | <- isosceles_ss O2 X P
21 |
22 | label__O1 -> pos:P
23 | pos <- free_point 4.542144775390625 24.79559326171875
24 | label__X -> pos:P
25 | pos <- free_point 0.523773193359375 -20.004974365234375
26 | label__O2 -> pos:P
27 | pos <- free_point -11.432224655624251 27.73633428236934
28 | label__Y -> pos:P
29 | pos <- free_point -0.8865153060221038 29.9868986494467
30 | label__c -> pos:D offset:D
31 | pos <- custom_ratio 0.7991312886287827 0.0
32 | offset <- custom_ratio 13.432303559657782 0.0
33 | label__D -> pos:P
34 | pos <- free_point -12.573319393047257 -13.824641998048776
35 | label__P -> pos:P
36 | pos <- free_point 15.94664811603991 14.769906116264082
37 |
38 | view__data -> anchor:P zoom:D
39 | anchor <- free_point 435.20751953125 327.7374267578125
40 | zoom <- custom_ratio 1.0 0.
41 |
--------------------------------------------------------------------------------
/saved/solutions/simson.gl:
--------------------------------------------------------------------------------
1 | _ ->
2 | A <- free_point 311.769775390625 94.72988891601562
3 | B <- free_point 257.6204833984375 262.5645446777344
4 | C <- free_point 489.54486083984375 255.80807495117188
5 | w <- circumcircle C A B
6 | a <- line B C
7 | b <- line C A
8 | c <- line A B
9 | D <- m_point_on 0.5862459822802772 w
10 | Fa <- foot D a
11 | Fc <- foot D c
12 | Fb <- foot D b
13 | d <- line Fc Fa
14 | THEN
15 | <- lies_on Fb d
16 | PROOF
17 | <- angles_to_concyclic B D Fc Fa
18 | <- angles_to_concyclic C D Fa Fb
19 | <- concyclic_to_angles Fc B Fa D
20 | <- concyclic_to_angles Fb C Fa D
21 | <- concyclic_to_angles A D B C
22 |
23 | label__A -> pos:P
24 | pos <- free_point -5.7205810546875 -26.283782958984375
25 | label__B -> pos:P
26 | pos <- free_point -26.927883214476115 -13.224564476289405
27 | label__C -> pos:P
28 | pos <- free_point 22.2576904296875 -12.772674560546875
29 | label__D -> pos:P
30 | pos <- free_point -1.8651018910266055 29.94196711867958
31 | label__Fa -> pos:P
32 | pos <- free_point -6.652826288817323 -25.45527359747649
33 | label__Fc -> pos:P
34 | pos <- free_point -19.753418771961833 -8.656619987053773
35 | label__Fb -> pos:P
36 | pos <- free_point 4.681870754789145 -21.65368028874488
37 | label__d -> pos:D offset:D
38 | pos <- custom_ratio 0.8078323975658995 0.0
39 | offset <- custom_ratio -14.074730440409382 0.0
40 |
41 | view__data -> anchor:P zoom:D
42 | anchor <- free_point 375.0 277.0
43 | zoom <- custom_ratio 1.0 0.
44 |
--------------------------------------------------------------------------------
/saved/levels/pascal.gl:
--------------------------------------------------------------------------------
1 | _ ->
2 | x0 <- free_point 354.0157775878906 297.92889404296875
3 | w <- m_circle_passing1 202.33023071289062 -0.556182861328125 x0
4 | E <- m_point_on -0.7256600662575139 w
5 | A <- m_point_on -0.5134149201543298 w
6 | C <- m_point_on -0.22296239697679646 w
7 | F <- m_point_on 0.22749244642384586 w
8 | D <- m_point_on 0.41537356812163095 w
9 | B <- m_point_on 0.7561594972416088 w
10 | d <- line E D
11 | c <- line D C
12 | a <- line B A
13 | f <- line A F
14 | e <- line E F
15 | b <- line B C
16 | X <- intersection a d
17 | Y <- intersection e b
18 | Z <- intersection f c
19 | THEN
20 | <- collinear X Y Z
21 | PROOF
22 |
23 | label__E -> pos:P
24 | pos <- free_point -0.43747116962811106 -26.530845736242696
25 | label__A -> pos:P
26 | pos <- free_point 0.0 -20.0
27 | label__C -> pos:P
28 | pos <- free_point 20.694872890205943 0.5610289482471842
29 | label__F -> pos:P
30 | pos <- free_point 25.926239898512904 7.27607612416125
31 | label__D -> pos:P
32 | pos <- free_point 21.074517254240163 14.594050735469352
33 | label__B -> pos:P
34 | pos <- free_point 1.928000037423999 24.082506251115092
35 | label__X -> pos:P
36 | pos <- free_point -23.727930920508584 7.476729546086517
37 | label__Y -> pos:P
38 | pos <- free_point -0.21720308090220897 22.12996661498579
39 | label__Z -> pos:P
40 | pos <- free_point 24.022725675218226 -12.024389497831805
41 |
42 | view__data -> anchor:P zoom:D
43 | anchor <- free_point 582.75390625 298.6510009765625
44 | zoom <- custom_ratio 1.0 0.
45 |
--------------------------------------------------------------------------------
/saved/levels/imo-2010-4.gl:
--------------------------------------------------------------------------------
1 | _ ->
2 | A <- free_point 639.4428421478269 108.40435175781242
3 | B <- free_point 514.2480378265379 220.20705143432605
4 | C <- free_point 694.2080105133055 213.9911206298827
5 | a <- line B C
6 | b <- line C A
7 | c <- line A B
8 | gamma <- circumcircle A C B
9 | t <- tangent_at C gamma
10 | S <- intersection c t
11 | s <- circle S C
12 | P <- m_point_on 0.7040100545568524 s
13 | <- point_on_circle P s
14 | k <- line A P
15 | m <- line C P
16 | l <- line B P
17 | M <- intersection 1 m gamma C
18 | K <- intersection 1 k gamma A
19 | L <- intersection 1 l gamma B
20 | <- isosceles_ss S P C
21 | x <- line S P
22 | THEN
23 | <- eq_dist M K M L
24 | PROOF
25 |
26 | label__A -> pos:P
27 | pos <- free_point -1.3451251329541607 -29.432560957199257
28 | label__B -> pos:P
29 | pos <- free_point -25.90365029010884 15.132775741672255
30 | label__C -> pos:P
31 | pos <- free_point 20.6756553582585 21.737462490054273
32 | label__S -> pos:P
33 | pos <- free_point 14.018015696205737 11.019951306485774
34 | hide__s ->
35 | label__P -> pos:P
36 | pos <- free_point 10.062652026035579 27.25514008625237
37 | label__M -> pos:P
38 | pos <- free_point -5.953582393348517 -18.890970638526895
39 | label__K -> pos:P
40 | pos <- free_point 12.28293325080379 26.242861223596055
41 | label__L -> pos:P
42 | pos <- free_point 11.710976646621793 -22.429578210640056
43 |
44 | view__data -> anchor:P zoom:D
45 | anchor <- free_point 610.8011566467285 152.97315673217776
46 | zoom <- custom_ratio 1.5241579027587262 0.
47 |
--------------------------------------------------------------------------------
/primitive_pred.py:
--------------------------------------------------------------------------------
1 | from geo_object import *
2 | import numpy as np
3 |
4 | """
5 | This file contains annotated geometrical predicates.
6 | They are loaded by primitive_tools.py, and converted into
7 | equally named tools.
8 | Functions here return boolean values. The resulting tools
9 | then fail if the value here is False,
10 | and succeed if it is True.
11 | """
12 |
13 | def not_eq(a, b):
14 | return a != b
15 | def intersecting(cl1 : PointSet, cl2 : PointSet):
16 | if isinstance(cl1, Circle) and isinstance(cl2, Circle):
17 | dist = np.linalg.norm(cl1.c - cl2.c)
18 | return intersecting_cc(cl1, cl2)
19 | else:
20 | if isinstance(cl1, Circle): cl1, cl2 = cl2, cl1
21 | assert(isinstance(cl1, Line) and isinstance(cl2, Circle))
22 | return intersecting_lc(cl1, cl2)
23 |
24 | def oriented_as(a1 : Point, b1 : Point, c1 : Point,
25 | a2 : Point, b2 : Point, c2 : Point):
26 | det1 = np.linalg.det(np.stack([b1.a-a1.a, c1.a-a1.a]))
27 | det2 = np.linalg.det(np.stack([b2.a-a2.a, c2.a-a2.a]))
28 | if eps_bigger(det1, 0) and eps_bigger(det2, 0): return True
29 | if eps_smaller(det1, 0) and eps_smaller(det2, 0): return True
30 | return False
31 |
32 | def dim_less(d1 : Ratio, d2 : Ratio):
33 | return eps_smaller(d1.x, d2.x)
34 |
35 | def not_on(p : Point, cl : PointSet):
36 | return not cl.contains(p.a)
37 |
38 | def not_collinear(A : Point, B : Point, C : Point):
39 | return not eps_zero(np.linalg.det(np.stack([B.a-A.a, C.a-A.a])))
40 |
41 | def lies_on(p : Point, cl : PointSet):
42 | return cl.contains(p.a)
43 |
--------------------------------------------------------------------------------
/uf_set.py:
--------------------------------------------------------------------------------
1 | from collections import defaultdict
2 |
3 | class UnionFindSet:
4 | def __init__(self):
5 | self.obj_to_rels = defaultdict(set)
6 | self.obj_to_root_d = dict()
7 | self.obj_to_children = defaultdict(set)
8 | self.data = set()
9 |
10 | def obj_to_root(self, obj):
11 | return self.obj_to_root_d.get(obj, obj)
12 | def tup_to_root(self, tup):
13 | return tuple(map(self.obj_to_root, tup))
14 |
15 | def add(self, tup):
16 | tup = self.tup_to_root(tup)
17 | for obj in tup:
18 | self.obj_to_rels[obj].add(tup)
19 | self.data.add(tup)
20 |
21 | def is_equal(self, n1, n2):
22 | n1, n2 = map(self.obj_to_root, (n1, n2))
23 | return n1 == n2
24 |
25 | def glue(self, n1, n2):
26 | n1, n2 = map(self.obj_to_root, (n1, n2))
27 | if n1 == n2: return False
28 | c1, c2 = [
29 | len(self.obj_to_children[n]) + len(self.obj_to_rels[n])
30 | for n in (n1, n2)
31 | ]
32 | if c1 > c2: n1, n2 = n2, n1
33 | self.obj_to_root_d[n2] = n1
34 | for child in self.obj_to_children[n2]: self.obj_to_root_d[child] = n1
35 | for tup in tuple(self.obj_to_rels[n2]): # copy the set to avoid undocumented behavior
36 | self.data.remove(tup)
37 | for obj in tup: self.obj_to_rels[obj].discard(tup)
38 | tup = self.tup_to_root(tup)
39 | for obj in tup: self.obj_to_rels[obj].add(tup)
40 | self.data.add(tup)
41 |
42 | return True
43 |
44 | def __contains__(self, tup):
45 | return self.tup_to_root(tup) in self.data
46 |
--------------------------------------------------------------------------------
/saved/levels/imo-2010-2.gl:
--------------------------------------------------------------------------------
1 | _ ->
2 | A <- free_point 518.7274780273438 82.03643798828125
3 | B <- free_point 435.75054931640625 254.43853759765625
4 | C <- free_point 677.7418823242188 258.01605224609375
5 | g <- circumcircle A C B
6 | D <- midpoint_arc C B g
7 | I <- incenter A C B
8 | E <- m_point_on 0.3028842512456856 g
9 | b <- line A C
10 | a <- line C B
11 | c <- line B A
12 | e <- line E I
13 | f <- line E A
14 | h <- isogonal_by_refl E A B C
15 | F <- intersection h a
16 | i <- line F I
17 | G <- midpoint F I
18 | gd <- line G D
19 | ie <- line I E
20 | X <- intersection gd ie
21 | THEN
22 | <- lies_on X g
23 | PROOF
24 |
25 | label__A -> pos:P
26 | pos <- free_point -23.810606403115205 -6.567385449821404
27 | label__B -> pos:P
28 | pos <- free_point -22.639210265359644 11.778779206452649
29 | label__C -> pos:P
30 | pos <- free_point 15.18732593677672 17.230319552951446
31 | label__g -> direction:A offset:D
32 | direction <- custom_angle -0.08223706989931281
33 | offset <- custom_ratio 16.292540611177255 0.0
34 | label__D -> pos:P
35 | pos <- free_point 22.89674515964432 19.38398981361434
36 | label__I -> pos:P
37 | pos <- free_point 18.599814713736077 -6.423202045432413
38 | label__E -> pos:P
39 | pos <- free_point 11.867802448617194 21.76723774238913
40 | label__F -> pos:P
41 | pos <- free_point -16.264397515281978 -16.971329114568654
42 | label__G -> pos:P
43 | pos <- free_point 18.740065776961437 10.459533736405724
44 | label__X -> pos:P
45 | pos <- free_point 4.285859573170151 -26.698972784217176
46 |
47 | view__data -> anchor:P zoom:D
48 | anchor <- free_point 574.9401295776368 222.13408331298834
49 | zoom <- custom_ratio 1.3717421124828537 0.
50 |
--------------------------------------------------------------------------------
/saved/levels/mks-2020-4-8.gl:
--------------------------------------------------------------------------------
1 | _ ->
2 | A <- free_point 414.24658203125 57.627410888671875
3 | B <- free_point 327.0667724609375 436.85919189453125
4 | C <- free_point 741.4964599609375 451.76678466796875
5 | a <- line B C
6 | b <- line C A
7 | c <- line A B
8 | G <- centroid A B C
9 | M_C <- midpoint A B
10 | M_A <- midpoint B C
11 | M_B <- midpoint C A
12 | N_A <- midpoint A G
13 | N_B <- midpoint G B
14 | N_C <- midpoint G C
15 | wA <- circumcircle M_C N_A M_B
16 | wC <- circumcircle M_B N_C M_A
17 | wB <- circumcircle M_A N_B M_C
18 | X <- intersection_remoter wB wA M_C
19 | THEN
20 | <- lies_on X wC
21 | PROOF
22 |
23 | label__A -> pos:P
24 | pos <- free_point 27.275831434461807 -4.993693033854167
25 | label__B -> pos:P
26 | pos <- free_point -15.171678331163195 -17.803439670138914
27 | label__C -> pos:P
28 | pos <- free_point 5.786926269531199 -22.17724609375005
29 | label__G -> pos:P
30 | pos <- free_point 7.908964934172471 -26.655481409143565
31 | label__M_C -> pos:P
32 | pos <- free_point -23.268296983506946 7.257286919487835
33 | label__M_A -> pos:P
34 | pos <- free_point -14.826409233941023 19.765882703993043
35 | label__M_B -> pos:P
36 | pos <- free_point 7.040228949652803 -21.97380574544272
37 | label__N_A -> pos:P
38 | pos <- free_point 8.309805410879676 -22.443666811342606
39 | label__N_B -> pos:P
40 | pos <- free_point 1.4893098054108755 -18.414671721281923
41 | label__N_C -> pos:P
42 | pos <- free_point 2.7482751916957366 22.492846453631298
43 | label__X -> pos:P
44 | pos <- free_point -9.312409163186178 13.040066985769966
45 |
46 | view__data -> anchor:P zoom:D
47 | anchor <- free_point 545.2809143066406 261.18898315429686
48 | zoom <- custom_ratio 1.1111111111111112 0.
49 |
--------------------------------------------------------------------------------
/saved/levels/mks-2020-4-4.gl:
--------------------------------------------------------------------------------
1 | _ ->
2 | A <- free_point 284.7607727050781 329.27679443359375
3 | C <- free_point 716.6341552734375 321.5935974121094
4 | ac <- line A C
5 | X <- m_point_on 432.7585144042969 249.86715698242188 ac
6 | bd <- perpline ac X
7 | B <- m_point_on 436.5653076171875 482.10125732421875 bd
8 | w <- circumcircle A C B
9 | D <- intersection_remoter bd w B
10 | O <- center_of w
11 | w1 <- diacircle A O
12 | w3 <- diacircle C O
13 | w2 <- diacircle B O
14 | w4 <- diacircle D O
15 | S <- intersection_remoter w4 w1 O
16 | R <- intersection_remoter w3 w4 O
17 | Q <- intersection_remoter w2 w3 O
18 | P <- intersection_remoter w1 w2 O
19 | THEN
20 | <- perpendicular P Q Q R
21 | <- perpendicular Q R R S
22 | <- perpendicular R S S P
23 | <- perpendicular S P P Q
24 | PROOF
25 |
26 | label__A -> pos:P
27 | pos <- free_point -25.630150341778783 15.591516714477043
28 | label__C -> pos:P
29 | pos <- free_point 18.714111328125 14.970672607421875
30 | label__X -> pos:P
31 | pos <- free_point 16.507744001225944 19.298526301470986
32 | label__B -> pos:P
33 | pos <- free_point -7.986505089539264 22.641944904064815
34 | label__D -> pos:P
35 | pos <- free_point -13.01832190487903 -16.428699438451474
36 | label__O -> pos:P
37 | pos <- free_point 17.46628074012631 24.391167194439316
38 | label__S -> pos:P
39 | pos <- free_point 18.957468075880797 11.038274792493013
40 | label__R -> pos:P
41 | pos <- free_point 19.10268597138861 -20.39716527098355
42 | label__Q -> pos:P
43 | pos <- free_point 10.423190326324175 19.64844132410269
44 | label__P -> pos:P
45 | pos <- free_point 15.679522723785055 -18.021648397576996
46 |
47 | view__data -> anchor:P zoom:D
48 | anchor <- free_point 507.794189453125 267.37762451171875
49 | zoom <- custom_ratio 1.0 0.
50 |
--------------------------------------------------------------------------------
/saved/solutions/mks-2020-4-7.gl:
--------------------------------------------------------------------------------
1 | _ ->
2 | A <- free_point 308.853271484375 285.0768737792969
3 | D <- free_point 223.93048095703125 385.48931884765625
4 | B <- free_point 652.8225708007812 282.7047424316406
5 | a <- line B A
6 | c <- paraline a D
7 | d <- line A D
8 | b <- paraline d B
9 | C <- intersection c b
10 | <- parallelogram_aa A B C D
11 | M <- midpoint A B
12 | m <- line D M
13 | w1 <- circumcircle D A B
14 | F <- intersection_remoter m w1 D
15 | H <- foot B d
16 | E <- foot A b
17 | THEN
18 | <- concyclic C D H E
19 | PROOF
20 | w2 <- circumcircle C D H
21 | <- point_to_diacircle H A B
22 | <- point_to_diacircle E A B
23 | <- isosceles_ss M A H
24 | <- isosceles_ss M E B
25 | e <- line H M
26 | <- angles_to_concyclic D E H C
27 | <- midpoint_uq M H E
28 | <- concyclic_to_eq_power M A B F D
29 | F' <- intersection_remoter m w2 D
30 | <- concyclic_to_eq_power M H E F' D
31 | ang0 dist0 <- double_direction_inv M F'
32 | ang1 dist1 <- double_direction_inv M F
33 |
34 | label__A -> pos:P
35 | pos <- free_point -9.2183837890625 -14.857879638671875
36 | label__D -> pos:P
37 | pos <- free_point -1.66949462890625 25.47900390625
38 | label__B -> pos:P
39 | pos <- free_point -0.08953857421875 19.412872314453125
40 | label__C -> pos:P
41 | pos <- free_point 8.12274169921875 20.96527099609392
42 | label__M -> pos:P
43 | pos <- free_point -12.696319580078125 23.56011962890625
44 | label__F -> pos:P
45 | pos <- free_point 7.730074213103535 -20.34331944877104
46 | label__H -> pos:P
47 | pos <- free_point -15.630303489263156 -19.22039744108548
48 | label__E -> pos:P
49 | pos <- free_point -10.292070732543184 28.1793058827986
50 |
51 | view__data -> anchor:P zoom:D
52 | anchor <- free_point 375.0 277.0
53 | zoom <- custom_ratio 1.0 0.
54 |
--------------------------------------------------------------------------------
/saved/pascal_out.gl:
--------------------------------------------------------------------------------
1 | _ ->
2 | x0 <- free_point 384.32025146484375 379.5526123046875
3 | w <- m_circle_passing1 101.1852347046879 18.99698632697175 x0
4 | E <- m_point_on -0.38245902789261094 w
5 | A <- m_point_on 0.16914795542754923 w
6 | C <- m_point_on -0.8000463704711748 w
7 | F <- m_point_on -0.16191959274799797 w
8 | D <- m_point_on -0.6310946146785608 w
9 | B <- m_point_on 0.45065363935562824 w
10 | d <- line E D
11 | c <- line D C
12 | a <- line B A
13 | f <- line A F
14 | e <- line E F
15 | b <- line B C
16 | X <- intersection a d
17 | Y <- intersection e b
18 | Z <- intersection f c
19 | Z' <- copy_triangle_r F C Z B E
20 | Z'' <- conjugate Z' B Y E
21 | <- concyclic_to_angles F D C E
22 | <- concyclic_to_angles A C B F
23 | <- sim_aa_r E B Z' C F Z
24 | <- concyclic_to_angles F B E C
25 | <- concyclic_to_angles E C B F
26 | <- sim_aa_r E B Y C F Y
27 | <- sim_sas_r Z' B Y Z F Y
28 |
29 | label__E -> pos:P
30 | pos <- free_point 8.896101415011685 -18.81042737338339
31 | label__A -> pos:P
32 | pos <- free_point 12.722254698095185 13.972733093632826
33 | label__C -> pos:P
34 | pos <- free_point -23.112019569560175 -6.09698976868566
35 | label__F -> pos:P
36 | pos <- free_point 12.548693599381068 -10.21047142438698
37 | label__D -> pos:P
38 | pos <- free_point -4.401498447614927 -17.3781295781846
39 | label__B -> pos:P
40 | pos <- free_point -6.053069194175691 19.144704266087274
41 | label__X -> pos:P
42 | pos <- free_point 2.0389873648790626 22.537508587632146
43 | label__Y -> pos:P
44 | pos <- free_point -15.021437942082429 18.239605764308692
45 | label__Z -> pos:P
46 | pos <- free_point 12.73298535527024 -27.16378257795614
47 | label__Z' -> pos:P
48 | pos <- free_point 10.061549222549388 -19.77606182641057
49 | label__Z'' -> pos:P
50 | pos <- free_point -24.896418469336766 -0.6184242625072898
51 |
52 | view__data -> anchor:P zoom:D
53 | anchor <- free_point 535.1531846788193 276.036376953125
54 | zoom <- custom_ratio 1.0 0.
55 |
--------------------------------------------------------------------------------
/saved/solutions/pascal.gl:
--------------------------------------------------------------------------------
1 | _ ->
2 | x0 <- free_point 354.0157775878906 297.92889404296875
3 | w <- m_circle_passing1 202.33023071289062 -0.556182861328125 x0
4 | E <- m_point_on -0.7256600662575139 w
5 | A <- m_point_on -0.5134149201543298 w
6 | C <- m_point_on -0.22296239697679646 w
7 | F <- m_point_on 0.22749244642384586 w
8 | D <- m_point_on 0.41537356812163095 w
9 | B <- m_point_on 0.7561594972416088 w
10 | d <- line E D
11 | c <- line D C
12 | a <- line B A
13 | f <- line A F
14 | e <- line E F
15 | b <- line B C
16 | X <- intersection a d
17 | Y <- intersection e b
18 | Z <- intersection f c
19 | THEN
20 | <- collinear X Y Z
21 | PROOF
22 | Z' <- copy_triangle_r F C Z B E
23 | Z'' <- conjugate Z' B Y E
24 | <- concyclic_to_angles F D C E
25 | <- concyclic_to_angles A C B F
26 | <- sim_aa_r E B Z' C F Z
27 | <- concyclic_to_angles F B E C
28 | <- concyclic_to_angles E C B F
29 | <- sim_aa_r E B Y C F Y
30 | <- sim_sas_r Z' B Y Z F Y
31 |
32 | label__E -> pos:P
33 | pos <- free_point -0.43747116962811106 -26.530845736242696
34 | label__A -> pos:P
35 | pos <- free_point 0. -20.
36 | label__C -> pos:P
37 | pos <- free_point 20.694872890205943 0.5610289482471842
38 | label__F -> pos:P
39 | pos <- free_point 25.926239898512904 7.27607612416125
40 | label__D -> pos:P
41 | pos <- free_point 21.074517254240163 14.594050735469352
42 | label__B -> pos:P
43 | pos <- free_point 1.928000037423999 24.082506251115092
44 | label__X -> pos:P
45 | pos <- free_point -23.727930920508584 7.476729546086517
46 | label__Y -> pos:P
47 | pos <- free_point -0.21720308090220897 22.12996661498579
48 | label__Z -> pos:P
49 | pos <- free_point 24.022725675218226 -12.024389497831805
50 | label__Z' -> pos:P
51 | pos <- free_point 16.780331887130444 -22.535850725117598
52 | label__Z'' -> pos:P
53 | pos <- free_point -24.896418469336766 -0.6184242625072898
54 |
55 | view__data -> anchor:P zoom:D
56 | anchor <- free_point 582.75390625 298.6510009765625
57 | zoom <- custom_ratio 1.0 0.
58 |
--------------------------------------------------------------------------------
/saved/solutions/imo-2010-4.gl:
--------------------------------------------------------------------------------
1 | _ ->
2 | A <- free_point 639.4428421478269 108.40435175781242
3 | B <- free_point 514.2480378265379 220.20705143432605
4 | C <- free_point 694.2080105133055 213.9911206298827
5 | a <- line B C
6 | b <- line C A
7 | c <- line A B
8 | gamma <- circumcircle A C B
9 | t <- tangent_at C gamma
10 | S <- intersection c t
11 | s <- circle S C
12 | P <- m_point_on 0.7040100545568524 s
13 | <- point_on_circle P s
14 | k <- line A P
15 | m <- line C P
16 | l <- line B P
17 | M <- intersection 1 m gamma C
18 | K <- intersection 1 k gamma A
19 | L <- intersection 1 l gamma B
20 | <- isosceles_ss S P C
21 | x <- line S P
22 | THEN
23 | <- eq_dist M K M L
24 | PROOF
25 | ang0 <- secant_direction C M gamma
26 | ang1 <- secant_direction C A gamma
27 | ang2 <- inscribed_angle A B C
28 | <- sim_aa_r C S A B S C
29 | <- sim_sas_r P S A B S P
30 | ang3 <- secant_direction A B gamma
31 | ang4 <- secant_direction B C gamma
32 | ang5 <- secant_direction C A gamma
33 | ang6 <- secant_direction M C gamma
34 | ang7 <- secant_direction L B gamma
35 | ang8 <- secant_direction K A gamma
36 | <- eq_arcs_to_eq_dist M L K M gamma
37 |
38 | label__A -> pos:P
39 | pos <- free_point -1.3451251329541607 -29.432560957199257
40 | label__B -> pos:P
41 | pos <- free_point -25.90365029010884 15.132775741672255
42 | label__C -> pos:P
43 | pos <- free_point 20.6756553582585 21.737462490054273
44 | label__S -> pos:P
45 | pos <- free_point 14.018015696205737 11.019951306485774
46 | hide__s ->
47 | label__P -> pos:P
48 | pos <- free_point 10.062652026035579 27.25514008625237
49 | label__M -> pos:P
50 | pos <- free_point -5.953582393348517 -18.890970638526895
51 | label__K -> pos:P
52 | pos <- free_point 12.28293325080379 26.242861223596055
53 | label__L -> pos:P
54 | pos <- free_point 11.710976646621793 -22.429578210640056
55 |
56 | view__data -> anchor:P zoom:D
57 | anchor <- free_point 610.8011566467285 152.97315673217776
58 | zoom <- custom_ratio 1.5241579027587262 0.
59 |
--------------------------------------------------------------------------------
/segment_union_diff.py:
--------------------------------------------------------------------------------
1 | from geo_object import eps_smaller
2 |
3 | def segment_union_diff(added_segments, subtracted_segments):
4 | segments = sorted([
5 | sorted([a,b])+[True] for (a,b) in added_segments
6 | ] + [
7 | sorted([a,b])+[False] for (a,b) in subtracted_segments
8 | ])
9 | result = []
10 | cur_a, cur_b = None, None
11 | erased = None
12 | for a,b,added in segments:
13 | if erased: a = max(a, erased)
14 | if not eps_smaller(a, b): continue
15 |
16 | if cur_b is not None and eps_smaller(cur_b, a):
17 | result.append((cur_a, cur_b))
18 | cur_a, cur_b = None, None
19 | if added:
20 | if cur_a is None: cur_a, cur_b = a,b
21 | else: cur_b = max(cur_b, b)
22 | else:
23 | if cur_a is not None:
24 | if eps_smaller(cur_a, a): result.append((cur_a, a))
25 | if erased is None: erased = b
26 | else: erased = max(erased, b)
27 | if cur_a is not None:
28 | cur_a = max(erased, cur_a)
29 | if not eps_smaller(cur_a, cur_b):
30 | cur_a, cur_b = None, None
31 |
32 | if cur_b is not None: result.append((cur_a, cur_b))
33 | return result
34 |
35 | def labeled_segment_diff(labeled, subtracted):
36 | result = []
37 | sub_iter = iter(subtracted + [(None, None)])
38 | sub_a, sub_b = next(sub_iter)
39 | for a,b,label in labeled:
40 | while sub_a is not None and eps_smaller(sub_b, b):
41 | if eps_smaller(a, sub_a): result.append((a,sub_a,label))
42 | a = max(a,sub_b)
43 | sub_a, sub_b = next(sub_iter)
44 | if sub_b is not None: b = min(sub_a, b)
45 | if eps_smaller(a, b): result.append((a,b,label))
46 | return result
47 |
48 | if __name__ == "__main__":
49 | print(segment_union_diff(
50 | [(1,10), (12,16), (14, 20)],
51 | [(-2,0), (13,20), (21,25)],
52 | ))
53 |
54 | print(labeled_segment_diff(
55 | [(0,10,'a'), (10,20,'b'), (20,30, 'c')],
56 | [(-2,-1), (3,5), (18,22)],
57 | ))
58 |
--------------------------------------------------------------------------------
/saved/solutions/mks-2020-4-8.gl:
--------------------------------------------------------------------------------
1 | _ ->
2 | A <- free_point 414.24658203125 57.627410888671875
3 | B <- free_point 327.0667724609375 436.85919189453125
4 | C <- free_point 741.4964599609375 451.76678466796875
5 | a <- line B C
6 | b <- line C A
7 | c <- line A B
8 | G <- centroid A B C
9 | M_C <- midpoint A B
10 | M_A <- midpoint B C
11 | M_B <- midpoint C A
12 | N_A <- midpoint A G
13 | N_B <- midpoint G B
14 | N_C <- midpoint G C
15 | wA <- circumcircle M_C N_A M_B
16 | wC <- circumcircle M_B N_C M_A
17 | wB <- circumcircle M_A N_B M_C
18 | X <- intersection_remoter wB wA M_C
19 | THEN
20 | <- lies_on X wC
21 | PROOF
22 | d <- line G A
23 | e <- line G B
24 | f <- line G C
25 | N'_A M'_B <- midsegment A G C
26 | N'_B M'_A <- midsegment B G C
27 | M'_C N''_A <- midsegment A B G
28 | N'_C M''_B <- midsegment C G A
29 | N''_B M''_C <- midsegment B G A
30 | N''_C M''_A <- midsegment C G B
31 | <- concyclic_to_angles M_C M_B X N_A
32 | <- concyclic_to_angles M_A M_C X N_B
33 | <- angles_to_concyclic M_A M_B X N_C
34 |
35 | label__A -> pos:P
36 | pos <- free_point 27.275831434461807 -4.993693033854167
37 | label__B -> pos:P
38 | pos <- free_point -15.171678331163195 -17.803439670138914
39 | label__C -> pos:P
40 | pos <- free_point 5.786926269531199 -22.17724609375005
41 | label__G -> pos:P
42 | pos <- free_point 7.908964934172471 -26.655481409143565
43 | label__M_C -> pos:P
44 | pos <- free_point -23.268296983506946 7.257286919487835
45 | label__M_A -> pos:P
46 | pos <- free_point -14.826409233941023 19.765882703993043
47 | label__M_B -> pos:P
48 | pos <- free_point 7.040228949652803 -21.97380574544272
49 | label__N_A -> pos:P
50 | pos <- free_point 8.309805410879676 -22.443666811342606
51 | label__N_B -> pos:P
52 | pos <- free_point 1.4893098054108755 -18.414671721281923
53 | label__N_C -> pos:P
54 | pos <- free_point 2.7482751916957366 22.492846453631298
55 | label__X -> pos:P
56 | pos <- free_point -9.312409163186178 13.040066985769966
57 |
58 | view__data -> anchor:P zoom:D
59 | anchor <- free_point 545.2809143066406 261.18898315429686
60 | zoom <- custom_ratio 1.1111111111111112 0.
61 |
--------------------------------------------------------------------------------
/primitive_constr.py:
--------------------------------------------------------------------------------
1 | from geo_object import *
2 | import numpy as np
3 |
4 | """
5 | This file contains annotated geometrical funtions.
6 | They are loaded by primitive_tools.py, and converted into
7 | tools of the names "prim__name", where "name" is the
8 | original name in this file
9 | """
10 |
11 | def radius_of(c : Circle) -> Ratio:
12 | return Ratio((np.log(c.r), 1))
13 | def center_of(c : Circle) -> Point:
14 | return Point(c.c)
15 | def dist(a : Point, b : Point) -> Ratio:
16 | assert((a.a != b.a).any())
17 | return Ratio((np.log(np.linalg.norm(a.a - b.a)), 1))
18 | def direction_of(l : Line) -> Angle:
19 | return Angle(vector_direction(l.v))
20 |
21 | def intersection(l1 : Line, l2 : Line) -> Point:
22 | assert(isinstance(l1, Line) and isinstance(l2, Line))
23 | return Point(intersection_ll(l1, l2))
24 |
25 | def line(p1 : Point, p2 : Point) -> Line:
26 | assert(isinstance(p1, Point))
27 | assert(isinstance(p2, Point))
28 | return line_passing_points(p1, p2)
29 | def circle(center : Point, radius : Ratio) -> Circle:
30 | assert(isinstance(center, Point))
31 | assert(isinstance(radius, Ratio))
32 | return Circle(center.a, np.exp(radius.x))
33 | def line_with_direction(p : Point, d : Angle) -> Line:
34 | assert(isinstance(d, Angle))
35 | assert(isinstance(p, Point))
36 | normal_vector = vector_of_direction(d.data+0.5)
37 | c = np.dot(normal_vector, p.a)
38 | return Line(normal_vector, c)
39 |
40 | def midpoint(A : Point, B : Point) -> Point:
41 | return Point((A.a + B.a)/2)
42 | def half_direction(A : Point, B : Point) -> Angle:
43 | return Angle(vector_direction(B.a - A.a)/2)
44 | def double_direction(A : Point, ang : Angle, d : Ratio) -> Point:
45 | return Point(A.a + vector_of_direction(2*ang.data, d.x))
46 |
47 | def circumcircle(A : Point, B : Point, C : Point) -> Circle:
48 | A,B,C = A.a, B.a, C.a
49 | bc = C-B
50 | ca = A-C
51 | ax_A = Line(bc, np.dot((B+C)/2, bc))
52 | ax_B = Line(ca, np.dot((C+A)/2, ca))
53 | center = intersection_ll(ax_A, ax_B)
54 | return Circle(center, np.linalg.norm(center-C))
55 |
56 | def angle_2_to_3(ang2 : Angle) -> Angle:
57 | x = (ang2.data + 0.5) % 1 - 0.5
58 | return Angle(x*2 / 3)
59 |
--------------------------------------------------------------------------------
/saved/solutions/imo-2010-2.gl:
--------------------------------------------------------------------------------
1 | _ ->
2 | A <- free_point 518.7274780273438 82.03643798828125
3 | B <- free_point 435.75054931640625 254.43853759765625
4 | C <- free_point 677.7418823242188 258.01605224609375
5 | g <- circumcircle A C B
6 | D <- midpoint_arc C B g
7 | I <- incenter A C B
8 | E <- m_point_on 0.3028842512456856 g
9 | b <- line A C
10 | a <- line C B
11 | c <- line B A
12 | e <- line E I
13 | f <- line E A
14 | h <- isogonal_by_refl E A B C
15 | F <- intersection h a
16 | i <- line F I
17 | G <- midpoint F I
18 | gd <- line G D
19 | ie <- line I E
20 | X <- intersection gd ie
21 | THEN
22 | <- lies_on X g
23 | PROOF
24 | Ea <- excenter A B C
25 | X' <- intersection 1 e g E
26 | j <- line X' D
27 | G' <- intersection j i
28 | ang0 <- inscribed_angle C B D
29 | ang1 <- inscribed_angle D A B
30 | ang2 <- inscribed_angle C A D
31 | <- isosceles_aa D I B
32 | <- isosceles_aa D B Ea
33 | <- midpoint_uq D I Ea
34 | G'' D' <- midsegment I F Ea
35 | k <- line F Ea
36 | d <- line A D
37 | <- concyclic_to_angles A C B E
38 | <- sim_aa A B F A E C
39 | <- sim_aa A B I A Ea C
40 | <- sim_sas Ea A F E A I
41 | <- concyclic_to_angles X' A E D
42 |
43 | label__A -> pos:P
44 | pos <- free_point -23.810606403115205 -6.567385449821404
45 | label__B -> pos:P
46 | pos <- free_point -22.639210265359644 11.778779206452649
47 | label__C -> pos:P
48 | pos <- free_point 15.18732593677672 17.230319552951446
49 | label__g -> direction:A offset:D
50 | direction <- custom_angle -0.08223706989931281
51 | offset <- custom_ratio 16.292540611177255 0.0
52 | label__D -> pos:P
53 | pos <- free_point 22.89674515964432 19.38398981361434
54 | label__I -> pos:P
55 | pos <- free_point 18.599814713736077 -6.423202045432413
56 | label__E -> pos:P
57 | pos <- free_point 11.867802448617194 21.76723774238913
58 | label__F -> pos:P
59 | pos <- free_point -16.264397515281978 -16.971329114568654
60 | label__G -> pos:P
61 | pos <- free_point 18.740065776961437 10.459533736405724
62 | label__X -> pos:P
63 | pos <- free_point 4.285859573170151 -26.698972784217176
64 | label__Ea -> pos:P
65 | pos <- free_point 16.26669013706735 -5.651937659193837
66 |
67 | view__data -> anchor:P zoom:D
68 | anchor <- free_point 640.3702575683594 270.6289721679688
69 | zoom <- custom_ratio 1.2345679012345683 0.
70 |
--------------------------------------------------------------------------------
/sparse_row.py:
--------------------------------------------------------------------------------
1 | from fractions import Fraction
2 |
3 | """
4 | SparseRow is a dictionary of the form obj -> Fraction
5 | such that the default value is Fraction(0)
6 | (and also any zero value is automatically removed from the keys)
7 | It supports addition and scalar multiplication as vectors in a vector space.
8 |
9 | SparseRow is used as the type of linear equations in GeoLogic of the form
10 | x_1*c_1 + x_2*c_2 + ... + x_n*c_n = 0,
11 | where x_1, ..., x_n are variables (object references), and c_1, ..., c_n
12 | fractional coefficients.
13 | """
14 |
15 | class SparseRow(dict):
16 | def __init__(self, data):
17 | if isinstance(data, SparseRow):
18 | super(SparseRow, self).__init__(data)
19 | else:
20 | if isinstance(data, dict): data = data.items()
21 | super(SparseRow, self).__init__()
22 | self.__iadd__(data)
23 | def __getitem__(self, key):
24 | return self.get(key, 0)
25 | def __mul__(self, n):
26 | if n == 0: return zero_sr
27 | return SparseRow(
28 | (k, n*x) for (k,x) in self.items()
29 | )
30 | def __rmul__(self, n):
31 | return self.__mul__(n)
32 | def __imul__(self, n):
33 | if n == 0: self.clear()
34 | else:
35 | for k,x in self.items():
36 | self[k] = x*n
37 | return self
38 |
39 | def iadd_coef(self, coef, other): # self += coef*other
40 | if coef == 0: return
41 | other = other.items()
42 | for k,x in other:
43 | if x == 0: continue
44 | x *= coef
45 | x2 = self.get(k, 0)+x
46 | if x2 == 0: del self[k]
47 | else: self[k] = x2
48 | return self
49 |
50 | def __iadd__(self, other):
51 | if isinstance(other, dict): other = other.items()
52 | for k,x in other:
53 | if x == 0: continue
54 | if not isinstance(x, Fraction): x = Fraction(x)
55 | x2 = self.get(k, 0)+x
56 | if x2 == 0: del self[k]
57 | else: self[k] = x2
58 | return self
59 | def __add__(self, other):
60 | res = SparseRow(self)
61 | res.__iadd__(other)
62 | return res
63 | def __isub__(self, other):
64 | self.__iadd__((k,-x) for (k,x) in other.items())
65 | return self
66 | def __sub__(self, other):
67 | res = SparseRow(self.items())
68 | res.__isub__(other)
69 | return res
70 |
71 | zero_sr = SparseRow(())
72 | def equality_sr(a, b):
73 | return SparseRow(((a, Fraction(-1)), (b, Fraction(1))))
74 |
--------------------------------------------------------------------------------
/saved/solutions/mks-2020-4-4.gl:
--------------------------------------------------------------------------------
1 | _ ->
2 | A <- free_point 284.7607727050781 329.27679443359375
3 | C <- free_point 716.6341552734375 321.5935974121094
4 | ac <- line A C
5 | X <- m_point_on 432.7585144042969 249.86715698242188 ac
6 | bd <- perpline ac X
7 | B <- m_point_on 436.5653076171875 482.10125732421875 bd
8 | w <- circumcircle A C B
9 | D <- intersection_remoter bd w B
10 | O <- center_of w
11 | w1 <- diacircle A O
12 | w3 <- diacircle C O
13 | w2 <- diacircle B O
14 | w4 <- diacircle D O
15 | S <- intersection_remoter w4 w1 O
16 | R <- intersection_remoter w3 w4 O
17 | Q <- intersection_remoter w2 w3 O
18 | P <- intersection_remoter w1 w2 O
19 | THEN
20 | <- perpendicular P Q Q R
21 | <- perpendicular Q R R S
22 | <- perpendicular R S S P
23 | <- perpendicular S P P Q
24 | PROOF
25 | d <- line A D
26 | c <- line D C
27 | S0 <- midpoint A D
28 | R0 <- midpoint D C
29 | <- point_on_circle A w
30 | <- point_on_circle A w
31 | <- point_on_circle D w
32 | <- point_on_circle C w
33 | <- point_on_circle B w
34 | <- point_to_perp_bisector O A D
35 | <- point_to_perp_bisector O C D
36 | <- point_to_perp_bisector O B C
37 | <- point_to_perp_bisector O A B
38 | <- point_to_diacircle R0 O C
39 | <- point_to_diacircle R0 O D
40 | <- point_to_diacircle S0 O A
41 | <- point_to_diacircle S0 O D
42 | P0 <- midpoint A B
43 | Q0 <- midpoint B C
44 | <- point_to_diacircle P0 A O
45 | <- point_to_diacircle P0 O B
46 | <- point_to_diacircle Q0 B O
47 | <- point_to_diacircle Q0 C O
48 | S1 R1 <- midsegment D A C
49 | P1 Q1 <- midsegment B A C
50 | Q2 R2 <- midsegment C B D
51 | P2 S2 <- midsegment A B D
52 |
53 | label__A -> pos:P
54 | pos <- free_point -25.630150341778783 15.591516714477043
55 | label__C -> pos:P
56 | pos <- free_point 18.714111328125 14.970672607421875
57 | label__X -> pos:P
58 | pos <- free_point 16.507744001225944 19.298526301470986
59 | label__B -> pos:P
60 | pos <- free_point -7.986505089539264 22.641944904064815
61 | label__D -> pos:P
62 | pos <- free_point -13.01832190487903 -16.428699438451474
63 | label__O -> pos:P
64 | pos <- free_point 17.46628074012631 24.391167194439316
65 | label__S -> pos:P
66 | pos <- free_point 18.957468075880797 11.038274792493013
67 | label__R -> pos:P
68 | pos <- free_point 19.10268597138861 -20.39716527098355
69 | label__Q -> pos:P
70 | pos <- free_point 10.423190326324175 19.64844132410269
71 | label__P -> pos:P
72 | pos <- free_point 15.679522723785055 -18.021648397576996
73 |
74 | view__data -> anchor:P zoom:D
75 | anchor <- free_point 507.794189453125 267.37762451171875
76 | zoom <- custom_ratio 1.0 0.
77 |
--------------------------------------------------------------------------------
/images/svg/point.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
83 |
--------------------------------------------------------------------------------
/relstr.py:
--------------------------------------------------------------------------------
1 | from collections import defaultdict
2 | from stop_watch import StopWatch
3 |
4 | """
5 | Alternative view to the lookup table, used by triggers.
6 | It sees every value: (label, input) -> output
7 | as a relation (label, data = input+output), and it allows to access
8 | all such relations given label and a single element of data.
9 | """
10 |
11 | class RelStr:
12 | def __init__(self):
13 | self.t_to_data = defaultdict(set) # t -> set of tuples(x1,x2,...,xn)
14 | self.tobj_to_nb = defaultdict(set) # t,xi,i -> set of tuples(x1,x2,...,xn)
15 | self.obj_to_ti = defaultdict(set) # xi -> set of pairs t,i
16 |
17 | def add_rel(self, t, data):
18 | if data in self.t_to_data[t]: return False
19 | self.t_to_data[t].add(data)
20 | for i,x in enumerate(data):
21 | self.tobj_to_nb[t,x,i].add(data)
22 | self.obj_to_ti[x].add((t,i))
23 | return True
24 |
25 | # removing a node (called upon gluing)
26 | def discard_node(self, obj, store_disc_edges = None):
27 | for t,i in self.obj_to_ti[obj]:
28 | edges = self.tobj_to_nb.pop((t,obj,i))
29 | self.t_to_data[t].difference_update(edges)
30 | for edge in edges:
31 | for i2,obj2 in enumerate(edge):
32 | if obj2 != obj:
33 | self.tobj_to_nb[t,obj2,i2].discard(edge)
34 | if store_disc_edges is not None:
35 | store_disc_edges.extend(
36 | (t, data)
37 | for data in edges
38 | )
39 | del self.obj_to_ti[obj]
40 |
41 | # debug function
42 | def check_consistency(self):
43 | test_tobj_to_nb = defaultdict(set)
44 | test_obj_to_ti = defaultdict(set)
45 | for t, edges in self.t_to_data.items():
46 | for edge in edges:
47 | for i,x in enumerate(edge):
48 | test_obj_to_ti[x].add((t,i))
49 | test_tobj_to_nb[t,x,i].add(edge)
50 |
51 | test_tobj_to_nb2 = dict(
52 | (key, s)
53 | for (key, s) in self.tobj_to_nb.items()
54 | if s
55 | )
56 | test_obj_to_ti2 = dict(
57 | (key, s)
58 | for (key, s) in self.obj_to_ti.items()
59 | if s
60 | )
61 | assert(test_tobj_to_nb == test_tobj_to_nb2)
62 | objs = set(test_obj_to_ti.keys()) | set(test_obj_to_ti2.keys())
63 | for obj in objs:
64 | s = test_obj_to_ti[obj]
65 | s2 = test_obj_to_ti2.get(obj, set())
66 | assert(s <= s2)
67 |
68 | # currently not used
69 | def copy(self):
70 | res = Relstr()
71 | res.t_to_data = self.t_to_data.copy()
72 | res.tobj_to_nb = self.tobj_to_nb.copy()
73 | res.obj_to_ti = self.obj_to_ti.copy()
74 | return res
75 |
--------------------------------------------------------------------------------
/stop_watch.py:
--------------------------------------------------------------------------------
1 | from time import time, gmtime, strftime
2 | import sys
3 |
4 | """
5 | This is a debugging tool for measuring time taken by certain operations
6 | For usage, wrap any piece of code into:
7 | with StopWatch("Any label"):
8 | do
9 | some
10 | code
11 | It summarizes the parts of the code which were under a given label.
12 | In the end, call print_times() for printing how much time
13 | the operations have taken.
14 | """
15 |
16 | _d = dict()
17 | _l = list()
18 | _stack = ()
19 |
20 | class StopWatch:
21 | def __init__(self, name):
22 | self.name = name
23 |
24 | def __enter__(self):
25 | global _stack
26 | self.ori_stack = _stack
27 |
28 | my_id = _stack+(self.name,)
29 | _stack = my_id
30 |
31 | global _d, _l
32 |
33 | self.last_time = time()
34 | if my_id not in _d:
35 | _d[my_id] = [0.0, 0]
36 | _l.append(my_id)
37 | self.el = _d[my_id]
38 |
39 | def __exit__(self, *exception_data):
40 |
41 | duration = time() - self.last_time
42 | self.el[0] += duration
43 | self.el[1] += 1
44 |
45 | global _stack
46 | _stack = self.ori_stack
47 |
48 |
49 | def seconds_to_readable(secs):
50 | ori_secs = secs
51 | secs = int(secs)
52 | minutes = secs // 60
53 | secs %= 60
54 | if not minutes: return "{:.3} sec".format(ori_secs)
55 |
56 | hours = minutes // 60
57 | minutes %= 60
58 | if not hours: return "{:02}:{:02}".format(minutes,secs)
59 |
60 | days = hours // 24
61 | hours %= 24
62 | result = ["{:02}:{:02}:{:02}".format(hours,minutes,secs)]
63 |
64 | weeks = days // 7
65 | days %= 7
66 | if days > 0:
67 | days_str = "{} day".format(days)
68 | if days > 1: days_str += "s"
69 | result.append(days_str)
70 | if weeks > 0:
71 | weeks_str = "{} week".format(weeks)
72 | if weeks > 1: weeks_str += "s"
73 | result.append(weeks_str)
74 | result.reverse()
75 | return ", ".join(result)
76 |
77 | def print_times():
78 | if not _l: return
79 |
80 | scopes = {() : []}
81 | for scope in _l:
82 | scopes[scope[:-1]].append(scope[-1])
83 | scopes[scope] = []
84 |
85 | scope_list = []
86 |
87 | def process_scope(scope):
88 | if len(scope) > 0: scope_list.append(scope)
89 | for next_name in scopes[scope]:
90 | process_scope(scope+(next_name,))
91 | process_scope(())
92 |
93 | print("Time performance")
94 | for mode in scope_list:
95 | secs, enters = _d[mode]
96 | print("{}{}: {} = {} * {}".format(
97 | " "*len(mode), mode[-1],
98 | seconds_to_readable(secs),
99 | enters, secs / enters,
100 | ))
101 | sys.stdout.flush()
102 |
--------------------------------------------------------------------------------
/technical_doc.txt:
--------------------------------------------------------------------------------
1 | Here are basic descriptions and categorization
2 | of the individual files of the source code,
3 |
4 | Main application
5 | * geo_logic.py
6 |
7 | Numerical basics
8 | * geo_object.py
9 | = Five numerical geometrical objects (Point, Line, Circle, Angle, Ratio)
10 | and basic numerical operations with them
11 | * primitive_constr.py
12 | = file investigated by primitive_tools.py
13 | for making primitive construction tools (such as line, circumcenter)
14 | * primitive_pred.py
15 | = file investigated by primitive_tools.py
16 | for making primitive predicate tools (such as lies_on, oriented_as)
17 |
18 | Logic
19 | * logical_core.py
20 | * uf_dict.py = structure for lookup table
21 | * Gaussian elimination (angles, ratios)
22 | * sparse_row.py
23 | = dictionary : object -> Fraction
24 | capable of addition and constant (Fraction) multiplication
25 | * sparse_elim.py
26 | ElimMatrix = structure representing the linear span
27 | of SparseRow, capable of dynamic addition,
28 | used for automatic ratio calculations
29 | * angle_chasing.py
30 | = extension of ElimMatrix of numerical detection modulo 1
31 | used for automatic angle calculations
32 | * tools.py
33 | = tools which can interract with the logical core
34 | * primitive_tools.py
35 | = loading tools available at the beginning of basic.gl
36 | * tool_step.py
37 | = composite tools and (parallel) proof checking
38 | * externally loaded tools:
39 | * basic.gl = axioms and elementary tools
40 | * macros.gl = majority of tools
41 | * parse.py
42 | = loading gl files
43 | * basic_tools.py
44 | = python access to tools loaded from gl files
45 | + additional tweaks
46 | * movable_tools.py
47 | = loaded after parsing basic.gl, including (movable) intersection
48 | * triggers.py
49 | = automatic deduction of facts such as
50 | a <- line A B
51 | <- lies_on C l
52 | b <- line B C
53 | THEN
54 | <- == a b
55 | * relstr.py
56 |
57 | GUI
58 | * viewport.py
59 | = main drawable area, it takes data of what
60 | to draw from knowledge_visualisation.py
61 | * label_visualiser.py
62 | = drawing object labels
63 | * toolbar.py
64 | * gtool.py = GUI Tool, also with simple tools GToolMove and GToolHide
65 | * gtool_constr.py
66 | = The five construction tools:
67 | Point, Parallel, Perpendicular, Circle, Circumcircle
68 | * gtool_general.py = tool activated by the input bar
69 | * gtool_label.py
70 | * gtool_logic.py = reasoning tool
71 | * step_list.py
72 | = sidebar with steps
73 | * file_chooser.py
74 |
75 | Between logic and GUI
76 | * graphical_env.py
77 | = dynamic structure storing the current construction
78 | and running it using a logical core
79 | * knowledge_visualisation.py
80 | = investigating the inner state of a logical core
81 | and deciding what to draw,
82 | also contains some additional data such as label positions
83 |
--------------------------------------------------------------------------------
/basic_tools.py:
--------------------------------------------------------------------------------
1 | from geo_object import *
2 | from parse import Parser
3 | from movable_tools import add_movable_tools, MovableTool
4 |
5 | # class for extracting some tools from the tool dictionary into a python object,
6 | # also contains the original dictionary as "tool_dict" property
7 | class ImportedTools:
8 | def __init__(self, tool_dict):
9 | # for triggers and movables
10 | self.lies_on_l = tool_dict['lies_on', (Point, Line)]
11 | self.lies_on_c = tool_dict['lies_on', (Point, Circle)]
12 | self.direction_of = tool_dict['direction_of', (Line,)]
13 | self.radius_of = tool_dict['radius_of', (Circle,)]
14 | self.center_of = tool_dict['center_of', (Circle,)]
15 | self.circle = tool_dict['circle', (Point, Ratio)]
16 | # for symmetries
17 | self.line = tool_dict[('line', (Point, Point))]
18 | self.dist = tool_dict[('dist', (Point, Point))]
19 | self.midpoint = tool_dict[('midpoint', (Point, Point))]
20 | self.intersection_ll = tool_dict[('intersection', (Line, Line))]
21 |
22 | # for GUI
23 | self.arc_length = tool_dict.get(('arc_length', (Point, Point, Circle)), None)
24 | self.angle_ll = tool_dict.get(('angle', (Line, Line)), None)
25 | self.angle_ppl = tool_dict.get(('angle', (Point, Point, Line)), None)
26 | self.angle_lpp = tool_dict.get(('angle', (Line, Point, Point)), None)
27 | self.angle_pppp = tool_dict.get(('angle', (Point, Point, Point, Point)), None)
28 | self.angle_tools = (
29 | self.angle_ll,
30 | self.angle_ppl,
31 | self.angle_lpp,
32 | self.angle_pppp,
33 | )
34 | self.is_tangent_cl = tool_dict.get(('is_tangent', (Circle, Line)), None)
35 |
36 | # special dictionary for movable tools
37 | self.tool_dict = dict(tool_dict)
38 | self.m = dict()
39 | for (name, arg_types), tool in tool_dict.items():
40 | if isinstance(tool, MovableTool):
41 | obj_types = tuple(x for x in arg_types if issubclass(x, GeoObject))
42 | out_type, = tool.out_types
43 | self.m[name, obj_types, out_type] = tool
44 |
45 | def __getitem__(self, key):
46 | return self.tool_dict[key]
47 |
48 | # load basic.gl and the primitive tools around
49 | def load_basic_tools(fname = "basic.gl"):
50 | parser = Parser()
51 | parser.parse_file(fname)
52 | tool_dict = parser.tool_dict
53 | basic_tools = ImportedTools(tool_dict)
54 | basic_tools.line.add_symmetry((1,0))
55 | basic_tools.dist.add_symmetry((1,0))
56 | basic_tools.intersection_ll.add_symmetry((1,0))
57 | basic_tools.midpoint.add_symmetry((1,0))
58 | add_movable_tools(tool_dict, basic_tools)
59 | return ImportedTools(tool_dict)
60 |
61 | # load macros.gl
62 | def load_tools(fname):
63 | basic_tools = load_basic_tools()
64 | parser = Parser(basic_tools.tool_dict)
65 | parser.parse_file(fname, axioms = False, basic_tools = basic_tools)
66 | return ImportedTools(parser.tool_dict)
67 |
--------------------------------------------------------------------------------
/file_chooser.py:
--------------------------------------------------------------------------------
1 | import gi
2 | gi.require_version('Gtk', '3.0')
3 | from gi.repository import Gtk
4 | import os
5 |
6 | """
7 | Dialog window for opening / saving
8 | """
9 |
10 | # show svg files, for image export
11 | def add_svg_filters(dialog):
12 | filter_gl = Gtk.FileFilter()
13 | filter_gl.set_name("SVG Images")
14 | filter_gl.add_mime_type("image/svg+xml")
15 | filter_gl.add_pattern("*.svg")
16 | dialog.add_filter(filter_gl)
17 |
18 | filter_any = Gtk.FileFilter()
19 | filter_any.set_name("Any files")
20 | filter_any.add_pattern("*")
21 | dialog.add_filter(filter_any)
22 |
23 | # show GeoLogic files
24 | def add_gl_filters(dialog):
25 | filter_gl = Gtk.FileFilter()
26 | filter_gl.set_name("GeoLogic Files")
27 | filter_gl.add_mime_type("text/geo_logic")
28 | filter_gl.add_pattern("*.gl")
29 | dialog.add_filter(filter_gl)
30 |
31 | filter_any = Gtk.FileFilter()
32 | filter_any.set_name("Any files")
33 | filter_any.add_pattern("*")
34 | dialog.add_filter(filter_any)
35 |
36 | def select_file_open(win, add_filters = add_gl_filters):
37 | dialog = Gtk.FileChooserDialog("Open a file", win,
38 | Gtk.FileChooserAction.OPEN,
39 | (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
40 | Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
41 | dialog.set_current_folder("saved")
42 |
43 | add_filters(dialog)
44 |
45 | response = dialog.run()
46 |
47 | if response == Gtk.ResponseType.OK:
48 | res = dialog.get_filename()
49 | else: res = None
50 |
51 | dialog.destroy()
52 | return res
53 |
54 | class DialogSaveFile(Gtk.Dialog):
55 | def __init__(self, parent, db):
56 | Gtk.Dialog.__init__(self, "Confirm overwrite", parent, 0,
57 | (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
58 | Gtk.STOCK_OK, Gtk.ResponseType.OK))
59 | self.box = self.get_content_area()
60 | self.label = Gtk.Label("The file `" + db + "` exists.\nDo you want it to be overwritten?")
61 | self.box.add(self.label)
62 | self.show_all()
63 |
64 | def select_file_save(win, win_title = "Save file", folder = "saved", add_filters = add_gl_filters):
65 | dialog = Gtk.FileChooserDialog(win_title, win,
66 | Gtk.FileChooserAction.SAVE,
67 | (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
68 | Gtk.STOCK_SAVE, Gtk.ResponseType.OK))
69 | add_filters(dialog)
70 | dialog.set_current_folder(folder)
71 |
72 | while True:
73 |
74 | response = dialog.run()
75 |
76 | if response == Gtk.ResponseType.OK:
77 | res = dialog.get_filename()
78 | else:
79 | res = None
80 | break
81 |
82 | # check overwrite
83 |
84 | cansave = True
85 | if os.path.exists(dialog.get_filename()):
86 | dialog2 = DialogSaveFile(dialog, dialog.get_filename()) # ask to confirm overwrite
87 | response = dialog2.run()
88 | if response == Gtk.ResponseType.OK:
89 | dialog2.destroy()
90 | else:
91 | cansave = False
92 | dialog2.destroy()
93 |
94 | if cansave: break
95 |
96 | dialog.destroy()
97 | return res
98 |
--------------------------------------------------------------------------------
/saved/solutions/imo-2019-2.gl:
--------------------------------------------------------------------------------
1 | _ ->
2 | A <- free_point -139.2213134765625 -282.2152404785156
3 | B <- free_point -477.198974609375 210.1524658203125
4 | C <- free_point 233.489990234375 217.9029541015625
5 | a <- line A B
6 | b <- line B C
7 | c <- line C A
8 | D <- m_point_on -349.1357421875 24.8143310546875 a
9 | E <- m_point_on -22.7784423828125 -108.86618041992188 c
10 | d <- line D C
11 | e <- line E B
12 | F <- m_point_on -330.8531494140625 97.859130859375 e
13 | pa <- paraline b F
14 | X <- intersection pa a
15 | G <- intersection pa d
16 | H <- intersection c pa
17 | f <- line F D
18 | g <- line G E
19 | h <- circumcircle A G H
20 | i <- circumcircle A F X
21 | I <- intersection 1 f i F
22 | J <- intersection 1 g h G
23 | THEN
24 | <- concyclic I J F G
25 | PROOF
26 | j <- circumcircle A C B
27 | K <- intersection 1 d j C
28 | L <- intersection 1 e j B
29 | M <- intersection f b
30 | N <- intersection g b
31 | k <- circumcircle A M B
32 | <- concyclic_to_angles X I A F
33 | <- angles_to_concyclic B I M A
34 | l <- circumcircle A N C
35 | <- concyclic_to_angles J H A G
36 | <- angles_to_concyclic J C A N
37 | <- concyclic_to_angles B K L C
38 | <- angles_to_concyclic F K L G
39 | <- angles_to_concyclic L G K F
40 | <- concyclic_to_angles B A L C
41 | <- concyclic_to_angles G A J H
42 | <- angles_to_concyclic E A J L
43 | <- angles_to_concyclic J L A E
44 | <- concyclic_to_angles L E A J
45 | <- angles_to_concyclic F J L G
46 | <- concyclic_to_angles L C K B
47 | <- concyclic_to_angles A M I B
48 | <- concyclic_to_angles A C K B
49 | <- angles_to_concyclic K I A D
50 | <- concyclic_to_angles D K I A
51 | m <- circumcircle L G F
52 | <- angles_to_concyclic I G K F
53 |
54 | label__A -> pos:P
55 | pos <- free_point -4.372583868382273 -24.148514075750228
56 | label__B -> pos:P
57 | pos <- free_point -9.033194586155552 -16.32288373517428
58 | label__C -> pos:P
59 | pos <- free_point 6.136512933375392 -17.829373969549287
60 | label__D -> pos:P
61 | pos <- free_point 21.300249298701036 -10.746178651410457
62 | label__E -> pos:P
63 | pos <- free_point -24.56636579569613 -4.666047132312733
64 | label__F -> pos:P
65 | pos <- free_point -6.402900187448927 20.11785184511968
66 | label__X -> pos:P
67 | pos <- free_point -16.194741899321436 -14.275939277591947
68 | label__G -> pos:P
69 | pos <- free_point 8.146701737782388 19.853009742730546
70 | label__H -> pos:P
71 | pos <- free_point 9.367471470861895 -15.22327675946273
72 | hide__h ->
73 | hide__i ->
74 | label__I -> pos:P
75 | pos <- free_point -19.006382925394913 10.528648557798311
76 | label__J -> pos:P
77 | pos <- free_point 15.163507456359335 -7.193431684776774
78 | hide__j ->
79 | label__K -> pos:P
80 | pos <- free_point -14.316304688833997 -16.90177471151407
81 | label__L -> pos:P
82 | pos <- free_point 20.140589971236814 11.598626640289503
83 | label__M -> pos:P
84 | pos <- free_point -3.5600681886512073 17.677700588709737
85 | label__N -> pos:P
86 | pos <- free_point 3.879097598319495 19.81413569263927
87 | hide__k ->
88 | hide__l ->
89 | hide__m ->
90 |
91 | view__data -> anchor:P zoom:D
92 | anchor <- free_point -100.68421934775733 -94.343650628829
93 | zoom <- custom_ratio 0.7290000000000004 0.
94 |
--------------------------------------------------------------------------------
/images/svg/label.svg:
--------------------------------------------------------------------------------
1 |
2 |
93 |
--------------------------------------------------------------------------------
/images/svg/circle.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
93 |
--------------------------------------------------------------------------------
/gtool_general.py:
--------------------------------------------------------------------------------
1 | from geo_object import Point, Line, Circle
2 | from gtool import GTool
3 | from tools import Tool
4 | from tool_step import ToolStep
5 | from collections import defaultdict
6 | from parse import Parser, type_to_c
7 |
8 | """
9 | GToolDict investigates a dictionary of parsed (and primitive) tools,
10 | selects the geometrical ones, and is capable of creating the appropriate
11 | general tools that can be reached from GUI from the input entry in the toolbar.
12 | """
13 | class GToolDict:
14 | def __init__(self, tool_dict):
15 | """
16 | a "prefix" here is a tuple of types that were selected so far,
17 | prefix_d is a dictionary from prefixes to the appropriate actions
18 | appropriate action can be either
19 | * a tool (if the tool already have anough data), or
20 | * a set of types -- the available next objects that can be selected
21 | """
22 | self.name_to_prefixes = defaultdict(dict)
23 | for (name, arg_types), tool in tool_dict.items():
24 | if arg_types is None: continue
25 | if name.startswith("prim__"): continue
26 | if not all(
27 | issubclass(arg, (Point, Line, Circle))
28 | for arg in arg_types
29 | ): continue
30 | if len(arg_types) == 0: continue
31 | prefix_d = self.name_to_prefixes[name]
32 | prefix_d[arg_types] = tool
33 | for i,t in enumerate(arg_types):
34 | prefix = tuple(arg_types[:i])
35 | type_set = prefix_d.setdefault(prefix, set())
36 | if isinstance(type_set, set): type_set.add(t)
37 |
38 | def make_tool(self, name):
39 | if name not in self.name_to_prefixes: return None
40 | return GToolGeneral(self.name_to_prefixes[name], name)
41 |
42 | class GToolGeneral(GTool):
43 |
44 | def __init__(self, prefix_d, name):
45 | self.name = name
46 | GTool.__init__(self)
47 | types_to_selector = {
48 | frozenset((Point, Line, Circle)): self.select_pcl,
49 | frozenset((Point, Line)): self.select_pl,
50 | frozenset((Point, Circle)): self.select_pc,
51 | frozenset((Line, Circle)): self.select_cl,
52 | frozenset((Point,)): self.select_point,
53 | frozenset((Line,)): self.select_line,
54 | frozenset((Circle,)): self.select_circle,
55 | }
56 | self.prefix_d = dict()
57 | for prefix,tool_or_types in prefix_d.items():
58 | if isinstance(tool_or_types, Tool):
59 | self.prefix_d[prefix] = tool_or_types
60 | else:
61 | types = frozenset(tool_or_types)
62 | selector = types_to_selector[types]
63 | self.prefix_d[prefix] = selector
64 |
65 | def enter(self, viewport):
66 | GTool.enter(self, viewport)
67 | # print self
68 | name = None
69 | ttypes = []
70 | for itype, tool in self.prefix_d.items():
71 | if isinstance(tool, Tool):
72 | name = tool.name
73 | otype = self.tools[name,itype].out_types
74 | output = ' '.join(type_to_c[t] for t in otype)
75 | args = ' '.join(type_to_c[t] for t in itype)
76 | ttypes.append("{} -> {}".format(args, output))
77 | print("Tool: {} : {}".format(name, ', or '.join(ttypes)))
78 |
79 | def update_basic(self, coor, args = (), types = ()):
80 |
81 | selector = self.prefix_d[types]
82 | obj,objn = selector(coor)
83 | if obj is not None:
84 | args = args+(obj,)
85 | types = types+(type(objn),)
86 | tool = self.prefix_d[types]
87 | if isinstance(tool, Tool):
88 | self.confirm = self.run_tool, tool, *args
89 | else: self.confirm_next = self.update_basic, args, types
90 |
--------------------------------------------------------------------------------
/images/svg/circumcircle.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
106 |
--------------------------------------------------------------------------------
/images/svg/perpline.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
104 |
--------------------------------------------------------------------------------
/images/svg/line.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
110 |
--------------------------------------------------------------------------------
/images/svg/hide.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
110 |
--------------------------------------------------------------------------------
/cairo_textedit.py:
--------------------------------------------------------------------------------
1 | import gi
2 | gi.require_version("Gtk", "3.0")
3 | from gi.repository import Gtk, Gdk, GObject
4 | import cairo
5 | import numpy as np
6 | from label_visualiser import LabelVisualiser
7 |
8 | class Drawing(Gtk.Window):
9 | def __init__(self):
10 | super(Drawing, self).__init__()
11 |
12 | self.darea = Gtk.DrawingArea()
13 | self.darea.connect("draw", self.on_draw)
14 | self.darea.set_events(Gdk.EventMask.BUTTON_PRESS_MASK |
15 | Gdk.EventMask.KEY_PRESS_MASK
16 | )
17 | self.add(self.darea)
18 |
19 | self.darea.connect("button-press-event", self.on_button_press)
20 | self.connect("key-press-event", self.on_key_press)
21 |
22 | self.text = 'A_1_Ba_c_X'
23 | self.text_coor = None
24 | self.cursor = None
25 |
26 | self.set_title("Drawing")
27 | self.resize(600, 400)
28 | self.set_position(Gtk.WindowPosition.CENTER)
29 | self.connect("delete-event", Gtk.main_quit)
30 | self.show_all()
31 |
32 | self.label_vis = LabelVisualiser()
33 |
34 | self.timer = None
35 | self.circle = None
36 |
37 | def on_draw(self, wid, cr):
38 |
39 | size = self.get_size()
40 | cr.rectangle(0, 0, size[0], size[1])
41 | cr.set_source_rgb(1, 1, 1)
42 | cr.fill()
43 |
44 | cr.translate(200, 100)
45 |
46 | cr.select_font_face('serif', cairo.FONT_SLANT_ITALIC)
47 | if self.cursor is None:
48 | texts, subscripts, extents = self.label_vis.parse(cr, self.text)
49 | width, height = extents[2:4]
50 | cr.rectangle(-width/2,-height/2,width,height)
51 | cr.set_source_rgb(0, 1, 1)
52 | cr.fill()
53 | cr.set_source_rgb(0, 0, 0)
54 | self.label_vis.show_center(cr, texts, subscripts, extents)
55 | self.text_coor = self.label_vis.get_center_start(extents)
56 | else:
57 | cr.set_source_rgb(0, 0, 0)
58 | self.label_vis.show_edit(cr, self.text, self.cursor, self.text_coor)
59 |
60 | def on_key_press(self,w,e):
61 |
62 | keyval = e.keyval
63 | keyval_name = Gdk.keyval_name(keyval)
64 | print(keyval_name)
65 | #if keyval_name == 'p':
66 |
67 | if keyval_name.startswith("KP_"):
68 | keyval_name = keyval_name[3:]
69 | print(" [kp]", keyval_name)
70 |
71 | if keyval_name == "Escape":
72 | Gtk.main_quit()
73 | elif keyval_name == "Left":
74 | if self.cursor is None or self.cursor < 2:
75 | self.cursor = 0
76 | else: self.cursor -= 1
77 | elif keyval_name == "Return":
78 | print("Confirm:", self.text)
79 | self.cursor = None
80 | elif keyval_name == "Right":
81 | if self.cursor is None or self.cursor >= len(self.text):
82 | self.cursor = len(self.text)
83 | else: self.cursor += 1
84 | elif keyval_name in ("BackSpace", "Delete"):
85 | if self.cursor is None:
86 | self.cursor = 0
87 | self.text = ""
88 | else:
89 | if keyval_name == "BackSpace":
90 | if self.cursor == 0: return True
91 | else: self.cursor -= 1
92 | if self.cursor == len(self.text): return True
93 | print(self.text)
94 | self.text = self.text[:self.cursor]+self.text[self.cursor+1:]
95 | print("--", self.text)
96 | elif len(e.string) == 1 and ord(e.string) >= ord(' '):
97 | #print(ord(e.string))
98 | if self.cursor is None:
99 | if e.string.islower() and self.text[:1].isupper():
100 | self.text = e.string.upper()
101 | else: self.text = e.string
102 | self.cursor = 1
103 | else:
104 | self.text = self.text[:self.cursor] + e.string + self.text[self.cursor:]
105 | self.cursor += 1
106 | else:
107 | return False
108 |
109 | self.darea.queue_draw()
110 | return True
111 |
112 | def on_button_press(self, w, e):
113 |
114 | #if e.button != 1: return
115 | if e.type != Gdk.EventType.BUTTON_PRESS: return
116 |
117 | self.darea.queue_draw()
118 |
119 | def on_button_release(self, w, e):
120 |
121 | if e.type != Gdk.EventType.BUTTON_RELEASE: return
122 |
123 | self.tool.on_button_release(e.x, e.y)
124 | self.darea.queue_draw()
125 |
126 |
127 | if __name__ == "__main__":
128 | win = Drawing()
129 | Gtk.main()
130 |
--------------------------------------------------------------------------------
/tool_step_utils.py:
--------------------------------------------------------------------------------
1 | from logical_core import LogicalCore
2 | from tools import ToolError
3 | from tool_step import ToolStep, ToolStepEnv, CompositeTool
4 |
5 | def check_steps(steps, goals, imported_tools):
6 | logic = LogicalCore(basic_tools = imported_tools)
7 | step_env = ToolStepEnv(logic)
8 |
9 | try:
10 | step_env.run_steps(steps, 0, catch_errors = False)
11 | step_env.run_steps(goals, 0, catch_errors = False)
12 | return True
13 | except ToolError:
14 | return False
15 |
16 | def compute_num_objs(steps, imported_tools):
17 | logic = LogicalCore(basic_tools = imported_tools)
18 | step_env = ToolStepEnv(logic)
19 | step_env.run_steps(steps, 0, catch_errors = False)
20 | return [logic.num_model[li] for li in step_env.local_to_global]
21 |
22 | def copy_steps(steps):
23 | return [
24 | ToolStep(
25 | tool = step.tool,
26 | hyper_params = step.hyper_params,
27 | local_args = step.local_args,
28 | start_out = step.start_out,
29 | debug_msg = step.debug_msg,
30 | )
31 | for step in steps
32 | ]
33 |
34 | def steps_var_replace(steps, old_to_new):
35 | new_steps = []
36 | for step in steps:
37 | new_local_args = tuple(old_to_new[x] for x in step.local_args)
38 | new_step = ToolStep(step.tool, step.hyper_params, new_local_args,
39 | step.start_out, step.debug_msg)
40 | new_steps.append(new_step)
41 |
42 | return new_steps
43 |
44 | def merge_duplicities(steps, imported_tools):
45 | logic = LogicalCore(basic_tools = imported_tools)
46 | step_env = ToolStepEnv(logic)
47 | step_env.run_steps(steps, 0, catch_errors = False)
48 | global_to_local = dict()
49 | old_to_new = []
50 | for loc, glob in enumerate(step_env.local_to_global):
51 | glob = logic.ufd.obj_to_root(glob)
52 | old_to_new.append(global_to_local.setdefault(glob, loc))
53 |
54 | new_steps = steps_var_replace(steps, old_to_new)
55 | for step in steps:
56 | new_local_args = tuple(old_to_new[x] for x in step.local_args)
57 | new_step = ToolStep(step.tool, step.hyper_params, new_local_args,
58 | len(old_to_new), step.debug_msg)
59 | new_steps.append(new_step)
60 |
61 | return new_steps, old_to_new
62 |
63 | def remove_redundant(steps, outputs):
64 | obj_to_step = []
65 | for i,step in enumerate(steps):
66 | obj_to_step.extend([i]*len(step.tool.out_types))
67 | used = [False]*len(steps)
68 | stack = list(outputs)
69 | while stack:
70 | obj = stack.pop()
71 | step_i = obj_to_step[obj]
72 | if used[step_i]: continue
73 | used[step_i] = True
74 | stack.extend(steps[step_i].local_args)
75 |
76 | new_steps = []
77 | old_to_new = dict()
78 | obj_index = 0
79 | for step,u in zip(steps, used):
80 | if not u: continue
81 | new_local_args = tuple(old_to_new[x] for x in step.local_args)
82 | new_step = ToolStep(step.tool, step.hyper_params, new_local_args,
83 | obj_index, step.debug_msg)
84 | obj_index += len(step.tool.out_types)
85 | old_to_new.update(zip(step.local_outputs, new_step.local_outputs))
86 | new_steps.append(new_step)
87 |
88 | return new_steps, old_to_new, used
89 |
90 | def expand_step(steps, expand_predicate):
91 |
92 | old_to_new = []
93 | obj_index = 0
94 | new_steps = []
95 | for step_i, step in enumerate(steps):
96 | new_local_args = tuple(old_to_new[x] for x in step.local_args)
97 | if expand_predicate(step_i, step):
98 | assert isinstance(step.tool, CompositeTool)
99 | assert not step.tool.implications
100 | assert not step.tool.proof
101 |
102 | subvars = list(new_local_args)
103 | for substep in step.tool.assumptions:
104 | sub_local_args = tuple(subvars[x] for x in substep.local_args)
105 | new_substep = ToolStep(
106 | substep.tool, substep.hyper_params, sub_local_args,
107 | obj_index, substep.debug_msg,
108 | )
109 | new_steps.append(new_substep)
110 | out_len = len(substep.tool.out_types)
111 | subvars.extend(range(obj_index, obj_index+out_len))
112 | obj_index += out_len
113 |
114 | old_to_new.extend(subvars[x] for x in step.tool.result)
115 | else:
116 | new_step = ToolStep(step.tool, step.hyper_params, new_local_args,
117 | obj_index, step.debug_msg)
118 | obj_index += len(step.tool.out_types)
119 | old_to_new.extend(new_step.local_outputs)
120 | new_steps.append(new_step)
121 |
122 | return new_steps, old_to_new
123 |
--------------------------------------------------------------------------------
/todo:
--------------------------------------------------------------------------------
1 | 0.3 (during following months, ideally until ICMS-2020 13.7. -- deeper connection between GUI and logic)
2 |
3 | Main objectives for 0.3:
4 | * (1) ability to create custom tools / lemmata
5 | * (1a) level creation
6 | * (1b) more flexible step editing
7 | * (2) level solving
8 | * (2a) capture basic solving steps (problem reformulation, partial goal)
9 | * (2b) detect solved level (including sublemmata)
10 | * (3) support for problems without direct construction
11 | * (3a) (re)capturing objects by a construction
12 | * examples
13 | * three points, circumcenter, and a point on it
14 | -> first a circle, then 4 points on it
15 | * triangle ABC, its incenter I
16 | -> first triangle ABI, constructing C out of it
17 | * given X,Y on a line so that XA = YB, make X movable and Y constructed using compass
18 |
19 | Co je obecný stav?
20 | * (1,3) (nehýbatelné) vstupní objekty
21 | * (1) předpoklady (postulate)
22 | * (1) proof (check, nevadí, když jednotlivý krok selže)
23 | * (1,1b) závěry (check, možnost vytvořit pouze pár typů)
24 |
25 | * Dorozmyslet (!):
26 | * Pravidla přechycení,
27 | a jak zajistit kompatibilitu s původním levelem
28 |
29 | Akce:
30 | * (3a) "zafixovat" objekt závislý jenom na vstupních objektech
31 | * (přidat předpoklady)?
32 | * (3a) Jak něco odfixovat?
33 | * TODO
34 | * (1a) fact tool (pro začátek funkčnost jako v geogebře -- numerický check)
35 | * přidat předpoklad, přidat cíl
36 | * odebrat předpoklad / cíl
37 | * (1b) přesunout / smazat krok
38 | * (1b) analýza, proč krok selhal
39 | * (1b) otevřít krok
40 | * (1b,2a) přehled lemmat, která nejsou dokázaná
41 | * (2b) reset input, check všech důkazů
42 | * (1b) editace, k čemu je chycený průsečík
43 | * (2a)
44 | * stanovení podcíle
45 | * vybrat vstupní objekty
46 | * vybrat předpoklady
47 | * podcíl
48 | * (export podcíle jako nástroje)
49 | * reformulace cíle
50 | * TODO
51 | * jak zkombinovat fix / unfix a editaci předpokladů s řešením levelu?
52 | * undo / redo v rámci komplexnějšich akcí
53 |
54 | Remained
55 | * more straightforward drawing of lies_on, computation of missing line:
56 | * own implementation of line / circle cut for points not contained by them
57 | -> cleaner exported picture, less drawing moves
58 | * completely movable objects line / circle
59 |
60 | long-term (more features)
61 | * display equations
62 | * geometrical mappings
63 | * general similar mapping without exceptions (copy_triangle does not support collinear points)
64 | * transferring objects, facts
65 | * automatic proof of compatibility of a symmetry and a tool
66 | * separate logic and automatization
67 | * trace the assumptions used for a proof
68 | * other intersection options?
69 | * So that XOO' has the same orientation as ABC
70 | * with a line AB firther in the direction AB
71 | (both cases can be emulated using current tools)
72 |
73 | * more sophisticated tools
74 | * hidden construction
75 | * so there are less known facts in the end
76 | * non-expanding composed predicated (such as tangent)
77 | * num_check optimisation
78 | (find the propositions in a proof that should be checked without
79 | executing the entire proof from scratch)
80 | * alternative proofs (if the original proof fails)
81 | * allow proof more mixed with conclusions?
82 | * rigid check of elementary facts (topological reasoning)
83 | * case split
84 | * proof by contradiction
85 | * inequalities -> linear programming?
86 | * predicates:
87 | * ordered: P P P, P L P, P P L
88 | * oriented_as as a predicate about directions (Angle)
89 | * axioms / lemmata?
90 | * COMPLEMENTARY: A == B, A not_eq B
91 | * COMPLEMENTARY: A lies_on pc, A not_on pc
92 | * INCOMPATIBLE:
93 | oriented_as a0 a1 a2 b0 b1 b2
94 | oriented_as a0 a1 a2 b0 b2 b1
95 | * COVERING:
96 | oriented_as a0 a1 a2 b0 b1 b2
97 | oriented_as a0 a1 a2 b0 b2 b1
98 | a0 == a1
99 | a1 == a2
100 | a2 == a0
101 | b0 == b1
102 | b1 == b2
103 | b2 == b0
104 | * COVERING:
105 | A == B
106 | B == C
107 | C == A
108 | l = line A B, C not_on l
109 | ordered A B C
110 | ordered A C B
111 | * A not_on l, B lies_on l -> A != B
112 | * ordered A p B -> A != B, intersecting (line A B) p
113 | * A not_on (line B C) -> B not_on (line A C)
114 | * M = midpoint A B -> ordered A M B
115 | * ordered: transitivity + symmetry
116 | * ordered A B C -> not_eq A B, not_eq A C
117 | * ordered A p B -> not_on A p, not_eq A B
118 | * ordered A B p -> not_on A p, not_on B p
119 | * ordered A B C, B lies_on p, A not_on p -> diff_side A p C
120 | + similar analogous
121 | * oriented_as: transitivity + symmetry
122 | * A not_on p, B C D on p, ordered B C D
123 | -> oriented_as A B C A B D
124 | * angle_pred a == (const != 0) + b -> not_eq a b
125 | * (direction_of l1) != (direction_of l2) -> intersecting l1, l2
126 |
--------------------------------------------------------------------------------
/uf_dict.py:
--------------------------------------------------------------------------------
1 | from collections import defaultdict
2 | from stop_watch import StopWatch
3 |
4 | """
5 | UnionFindDict is a dictionary-like structure for the lookup table of the logical core.
6 | It is a dictionary of the form (label, tuple of objects) -> (tuple of objects)
7 | which can be set using
8 | add(label, input, output)
9 | and retrieved using
10 | get(label, input)
11 | The "add" function cannot overwrite, that is, it raises an exception
12 | if the (label, input) is already in the database. The "get" function
13 | returns a tuple, or None if it is not in the database.
14 | Moreover, the structure allows "gluing" of objects. The function
15 | glue(obj1, obj2)
16 | makes the two objects equal from the perspective of the UnionFindDict.
17 | The "glue" function returns the list of all pairs (a,b) that were glued,
18 | they include the initial (obj1, obj2) and other pairs glued
19 | due to extensionality.
20 | """
21 |
22 | class UnionFindDict:
23 | def __init__(self):
24 | self.data = dict() # the main dictionary
25 | self.obj_to_root_d = dict() # obj -> (representative) obj
26 | self.obj_to_children = defaultdict(set) # inverse of obj_to_root
27 | self.obj_to_keys = defaultdict(set) # obj -> (label, input) such that obj in input or output
28 |
29 | def obj_to_root(self, obj):
30 | return self.obj_to_root_d.get(obj, obj)
31 | def tup_to_root(self, tup):
32 | return tuple(map(self.obj_to_root, tup))
33 |
34 | def _data_add(self, label, args, vals):
35 | #print("_data_add", label, args, vals)
36 | key = label, args
37 | if key in self.data:
38 | if self.data[key] == vals: return
39 | raise KeyError("key {} is already in the uf_dictionary".format(key))
40 | self.data[key] = vals
41 | for obj in args + vals:
42 | #print(" obj_to_keys[{}] :".format(obj))
43 | #print(" {}".format(self.obj_to_keys[obj]))
44 | self.obj_to_keys[obj].add(key)
45 | #print(" {}".format(self.obj_to_keys[obj]))
46 |
47 | def _data_remove(self, label, args):
48 | #print("_data_remove", label, args)
49 | key = label, args
50 | vals = self.data[key]
51 | del self.data[key]
52 | for obj in args + vals:
53 | #print(" obj_to_keys[{}] :".format(obj))
54 | #print(" {}".format(self.obj_to_keys[obj]))
55 | self.obj_to_keys[obj].discard(key)
56 | #print(" {}".format(self.obj_to_keys[obj]))
57 | return vals
58 |
59 | def add(self, label, args, vals):
60 | #print('add', label, args, vals)
61 | args, vals = map(self.tup_to_root, (args, vals))
62 | self._data_add(label, args, vals)
63 | return args, vals
64 |
65 | def is_equal(self, n1, n2):
66 | n1, n2 = map(self.obj_to_root, (n1, n2))
67 | return n1 == n2
68 |
69 | def glue(self, n1, n2):
70 | #print('glue', n1, n2)
71 | result = self.multi_glue((n1, n2))
72 |
73 | return result
74 |
75 | def multi_glue(self, *pairs):
76 | changed = []
77 | to_glue = list(pairs)
78 | while to_glue:
79 | n1, n2 = to_glue.pop()
80 | n1, n2 = map(self.obj_to_root, (n1, n2))
81 | if n1 == n2: continue
82 |
83 | c1, c2 = [
84 | len(self.obj_to_children[n]) + len(self.obj_to_keys[n])
85 | for n in (n1, n2)
86 | ]
87 | if c1 < c2: n1, n2 = n2, n1
88 |
89 | changed.append((n1, n2))
90 | self.obj_to_root_d[n2] = n1
91 | children1 = self.obj_to_children[n1]
92 | children2 = self.obj_to_children[n2]
93 | for child in children2:
94 | self.obj_to_root_d[child] = n1
95 | children1.update(children2)
96 | children1.add(n2)
97 | children2.clear()
98 | #print("{} : {}".format(n2, self.obj_to_keys[n2]))
99 | for key in tuple(self.obj_to_keys[n2]):
100 | label, args = key
101 | vals = self._data_remove(label, args)
102 | args, vals = map(self.tup_to_root, (args, vals))
103 | ori_val = self.get(label, args)
104 | if ori_val is not None:
105 | assert(len(vals) == len(ori_val))
106 | to_glue.extend(zip(vals, ori_val))
107 | else: self._data_add(label, args, vals)
108 |
109 | return changed
110 |
111 | def __contains__(self, key):
112 | return self.tup_to_root(key) in self.data
113 |
114 | def get(self, label, args): # default = None, otherwise tuple
115 | args = self.tup_to_root(args)
116 | return self.data.get((label, args), None)
117 |
118 | if __name__ == "__main__":
119 | d = UnionFindDict()
120 | d.add("A", (1, 0), ())
121 | d.add("B", (1, 0), (2,))
122 | d.add("C", (1, 0), (2,))
123 | d.add("D", (1, 2), (3,))
124 | d.add("E", (3,), (4,))
125 | d.add("F", (3,), (4,))
126 | d.add("G", (3,), (5,))
127 | d.add("H", (3,), (5,))
128 | d.glue(1, 4)
129 | d.glue(2, 5)
130 | print(d.data)
131 |
--------------------------------------------------------------------------------
/primitive_tools.py:
--------------------------------------------------------------------------------
1 | import geo_object
2 | from geo_object import *
3 | from tools import *
4 | import primitive_constr
5 | import primitive_pred
6 | from itertools import product
7 |
8 | """
9 | The main function here is "make_primitive_tool_dict" which
10 | creates the initial dictionary of tools. The dictionary
11 | is of the format
12 | (name, tool_name_or_none) -> tool,
13 | and it is then used in parse.py for loading basic.gl
14 | """
15 |
16 | ### Functions for specific instances of DimCompute and DimPred
17 |
18 | def angle_num_comp(obj_sum, frac_const):
19 | return (float(frac_const%1) + obj_sum)%1
20 | def angle_num_check(obj_sum, frac_const):
21 | return eps_identical((float(frac_const%1) + obj_sum+0.5)%1, 0.5)
22 | def angle_postulate(logic, *args): logic.add_angle_equation(*args)
23 | def angle_check(logic, *args): return logic.check_angle_equation(*args)
24 |
25 | def ratio_num_comp(obj_sum, frac_const):
26 | return np.array((np.log(float(frac_const)), 0),
27 | dtype = float) + obj_sum
28 | def ratio_num_check(obj_sum, frac_const):
29 | return eps_zero(np.array((np.log(float(frac_const)), 0),
30 | dtype = float) + obj_sum)
31 | def ratio_postulate(logic, *args): logic.add_ratio_equation(*args)
32 | def ratio_check(logic, *args): return logic.check_ratio_equation(*args)
33 |
34 | ### A few more tools
35 |
36 | class CustomRatio(Tool):
37 | def __init__(self):
38 | Tool.__init__(self, (float, float), (), (Ratio,), "custom_ratio")
39 | def run(self, hyper_params, obj_args, logic, strictness):
40 | return logic.add_obj(Ratio(hyper_params)),
41 | class CustomAngle(Tool):
42 | def __init__(self):
43 | Tool.__init__(self, (float,), (), (Angle,), "custom_angle")
44 | def run(self, hyper_params, obj_args, logic, strictness):
45 | float_angle, = hyper_params
46 | return logic.add_obj(Angle(float_angle)),
47 |
48 | """
49 | The following function is used for analyzing functions in
50 | primitive_constr.py and primitive_pred.py. The input types
51 | of such a function are anotated. They are mostly of the five
52 | bacis types -- Point, Line, Circle, Angle, Ratio.
53 | However, PointSet can be also there,
54 | or an input that is not annotated at all in the case of "not_eq".
55 | The function intypes_iter yields all the possible input types as
56 | tuples of the five basic geometrical types.
57 | """
58 | def intypes_iter(f):
59 | in_varnames = f.__code__.co_varnames[:f.__code__.co_argcount]
60 | in_types = [f.__annotations__.get(name, None) for name in in_varnames]
61 | in_types_tups = [
62 | (Line, Circle) if t == PointSet else (t,)
63 | for t in in_types
64 | ]
65 | for in_types in product(*in_types_tups):
66 | if None not in in_types: yield in_types
67 | else:
68 | for joker_t in (Point, Line, Circle, Angle, Ratio):
69 | yield tuple(joker_t if t is None else t for t in in_types)
70 |
71 | def make_primitive_tool_dict():
72 | d = dict()
73 |
74 | # load predicates
75 | for name, f in primitive_pred.__dict__.items():
76 | if not callable(f) or name.startswith('_') or name in geo_object.__dict__:
77 | continue
78 | for in_types in intypes_iter(f):
79 | if name == "intersecting" and in_types[:2] == (Line, Line):
80 | continue # exception, skip intersecting : L L ->
81 | if name == "lies_on": willingness = 0
82 | else: willingness = 1
83 | d[name, in_types] = PrimitivePred(f, in_types, name = name, willingness = willingness)
84 |
85 | # load constructions
86 | for fname, f in primitive_constr.__dict__.items():
87 | if not callable(f) or fname.startswith('_') or fname in geo_object.__dict__:
88 | continue
89 | out_type = f.__annotations__['return']
90 | for in_types in intypes_iter(f):
91 | out_types = (out_type,)
92 | name = "prim__"+fname
93 | tool = PrimitiveConstr(f, in_types, out_types, name = name)
94 |
95 | d[name, in_types] = tool
96 |
97 | # dimension tools
98 | dim_tools = [
99 | DimCompute(Angle, angle_num_comp, angle_postulate, "angle_compute"),
100 | DimCompute(Ratio, ratio_num_comp, ratio_postulate, "ratio_compute"),
101 | DimPred(Angle, angle_num_check, angle_postulate, angle_check, "angle_pred"),
102 | DimPred(Ratio, ratio_num_check, ratio_postulate, ratio_check, "ratio_pred"),
103 | CustomRatio(),
104 | CustomAngle(),
105 | ]
106 | for tool in dim_tools: tool.add_to_dict(d)
107 |
108 | # equality tool
109 | eq_tool = EqualObjects()
110 | for t in (Point, Line, Circle, Angle, Ratio):
111 | d[eq_tool.name, (t,t)] = eq_tool
112 |
113 | return d
114 |
115 | if __name__ == "__main__":
116 | d = make_primitive_tool_dict()
117 | for key, value in d.items():
118 | if isinstance(key, tuple):
119 | name, in_types = key
120 | in_types = ' '.join(x.__name__ for x in in_types)
121 | else:
122 | name, in_types = key, "???"
123 | out_types = ' '.join(x.__name__ for x in value.out_types)
124 | if out_types == '': out_types = '()'
125 | print("{} : {} -> {}".format(name, in_types, out_types))
126 |
--------------------------------------------------------------------------------
/images/svg/derive-basic.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
132 |
--------------------------------------------------------------------------------
/basic.gl:
--------------------------------------------------------------------------------
1 | line A:P B:P -> a:L
2 | <- not_eq A B
3 | THEN
4 | a <- prim__line A B
5 | <- lies_on A a
6 | <- lies_on B a
7 |
8 | direction_of l:L -> dir:A
9 | THEN
10 | dir <- prim__direction_of l
11 | direction_of A:P B:P -> dir:A
12 | <- not_eq A B
13 | l <- line A B
14 | dir <- direction_of l
15 | perp_direction l:L -> dir_p:A
16 | dir <- direction_of l
17 | dir_p <- angle_compute 1/2 dir 1
18 | perp_direction A:P B:P -> dir_p:A
19 | dir <- direction_of A B
20 | dir_p <- angle_compute 1/2 dir 1
21 |
22 | line_with_direction A:P d:A -> p:L
23 | THEN
24 | p <- prim__line_with_direction A d
25 | <- lies_on A p
26 | d' <- direction_of p
27 | <- == d' d
28 |
29 | dist A:P B:P -> d:D
30 | <- not_eq A B
31 | THEN
32 | d <- prim__dist A B
33 |
34 | eq_dist A0:P B0:P d1:D ->
35 | d0 <- dist A0 B0
36 | <- == d0 d1
37 | eq_dist d1:D A0:P B0:P ->
38 | d0 <- dist A0 B0
39 | <- == d0 d1
40 | eq_dist A0:P B0:P A1:P B1:P ->
41 | d0 <- dist A0 B0
42 | d1 <- dist A1 B1
43 | <- == d0 d1
44 |
45 | dist_ratio A0:P B0:P A1:P B1:P -> r:D
46 | <- not_eq A0 B0
47 | <- not_eq A1 B1
48 | d0 <- dist A0 B0
49 | d1 <- dist A1 B1
50 | r <- ratio_compute 1 d0 1 d1 -1
51 |
52 | circumcircle A:P B:P C:P -> c:C
53 | <- not_collinear A B C
54 | THEN
55 | c <- prim__circumcircle A B C
56 | <- lies_on A c
57 | <- lies_on B c
58 | <- lies_on C c
59 |
60 | radius_of c:C -> r:D
61 | THEN
62 | r <- prim__radius_of c
63 | center_of c:C -> C:P
64 | THEN
65 | C <- prim__center_of c
66 | circle C:P r:D -> c:C
67 | THEN
68 | c <- prim__circle C r
69 | r' <- radius_of c
70 | C' <- center_of c
71 | <- == r r'
72 | <- == C C'
73 |
74 | point_on_circle X:P c:C ->
75 | <- lies_on X c
76 | THEN
77 | C <- center_of c
78 | d <- dist X C
79 | r <- radius_of c
80 | <- == r d
81 | point_to_circle X:P c:C ->
82 | C <- center_of c
83 | r <- radius_of c
84 | <- eq_dist X C r
85 | THEN
86 | <- lies_on X c
87 |
88 | not_parallel l0:L l1:L ->
89 | x <- direction_of l0
90 | y <- direction_of l1
91 | <- not_eq x y
92 |
93 | intersection a:L b:L -> X:P
94 | <- not_parallel a b
95 | THEN
96 | X <- prim__intersection a b
97 | <- lies_on X a
98 | <- lies_on X b
99 |
100 | angle l0:L l1:L -> alpha:A
101 | d0 <- direction_of l0
102 | d1 <- direction_of l1
103 | alpha <- angle_compute 0 d0 -1 d1 1
104 | #angle l0:L A1:P B1:P -> alpha:A
105 | # l1 <- line A1 B1
106 | # alpha <- angle l0 l1
107 | angle l0:L A1:P B1:P -> alpha:A
108 | d0 <- direction_of l0
109 | d1 <- direction_of A1 B1
110 | alpha <- angle_compute 0 d0 -1 d1 1
111 | angle A0:P B0:P l1:L -> alpha:A
112 | d0 <- direction_of A0 B0
113 | d1 <- direction_of l1
114 | alpha <- angle_compute 0 d0 -1 d1 1
115 | #angle A0:P B0:P A1:P B1:P -> alpha:A
116 | # l1 <- line A1 B1
117 | # alpha <- angle A0 B0 l1
118 | angle A0:P B0:P A1:P B1:P -> alpha:A
119 | d0 <- direction_of A0 B0
120 | d1 <- direction_of A1 B1
121 | alpha <- angle_compute 0 d0 -1 d1 1
122 | #angle A:P B:P C:P -> alpha:A
123 | # l1 <- line B C
124 | # alpha <- angle B A l1
125 | angle A:P B:P C:P -> alpha:A
126 | alpha <- angle B A B C
127 |
128 | eq_angle a0:L b0:L a1:L b1:L ->
129 | alpha0 <- angle a0 b0
130 | alpha1 <- angle a1 b1
131 | <- == alpha0 alpha1
132 | eq_angle A0:P B0:P C0:P a1:L b1:L ->
133 | alpha0 <- angle A0 B0 C0
134 | alpha1 <- angle a1 b1
135 | <- == alpha0 alpha1
136 | eq_angle a0:L b0:L A1:P B1:P C1:P ->
137 | alpha0 <- angle a0 b0
138 | alpha1 <- angle A1 B1 C1
139 | <- == alpha0 alpha1
140 | eq_angle A0:P B0:P C0:P A1:P B1:P C1:P ->
141 | alpha0 <- angle A0 B0 C0
142 | alpha1 <- angle A1 B1 C1
143 | <- == alpha0 alpha1
144 | eq_angle A0:P B0:P C0:P alpha1:A ->
145 | alpha0 <- angle A0 B0 C0
146 | <- == alpha0 alpha1
147 |
148 | sim_aa A0:P B0:P C0:P A1:P B1:P C1:P ->
149 | <- not_collinear A0 B0 C0
150 | <- eq_angle C0 A0 B0 C1 A1 B1
151 | <- eq_angle A0 B0 C0 A1 B1 C1
152 | ra <- dist_ratio B0 C0 B1 C1
153 | rb <- dist_ratio C0 A0 C1 A1
154 | rc <- dist_ratio A0 B0 A1 B1
155 | THEN
156 | <- == ra rb
157 | <- == ra rc
158 | sim_aa_r A0:P B0:P C0:P A1:P B1:P C1:P ->
159 | <- not_collinear A0 B0 C0
160 | ra <- dist_ratio B0 C0 B1 C1
161 | rb <- dist_ratio C0 A0 C1 A1
162 | rc <- dist_ratio A0 B0 A1 B1
163 | <- eq_angle C0 A0 B0 B1 A1 C1
164 | <- eq_angle A0 B0 C0 C1 B1 A1
165 | THEN
166 | <- == ra rb
167 | <- == ra rc
168 |
169 | isosceles_ss A:P B:P C:P ->
170 | <- not_eq B C
171 | <- eq_dist A B A C
172 | THEN
173 | <- eq_angle A B C B C A
174 |
175 | midpoint A:P B:P -> M:P
176 | l <- line A B
177 | THEN
178 | M <- prim__midpoint A B
179 | <- lies_on M l
180 | <- eq_dist A M M B
181 | r <- dist_ratio A B A M
182 | <- ratio_pred 1/2 r 1
183 |
184 | perpline l:L A:P -> p:L
185 | dir <- perp_direction l
186 | p <- line_with_direction A dir
187 | perpline X0:P X1:P A:P -> p:L
188 | l <- line X0 X1
189 | p <- perpline l A
190 | perp_bisector A:P B:P -> p:L
191 | M <- midpoint A B
192 | ab <- line A B
193 | p <- perpline ab M
194 | point_on_perp_bisector X:P A:P B:P ->
195 | p <- perp_bisector A B
196 | <- lies_on X p
197 | THEN
198 | <- eq_dist X A X B
199 | a <- line A X
200 | b <- line B X
201 | <- eq_angle a p p b
202 | point_to_perp_bisector X:P A:P B:P ->
203 | <- eq_dist X A X B
204 | p <- perp_bisector A B
205 | THEN
206 | <- lies_on X p
207 |
208 | half_direction A:P B:P -> dir:A
209 | <- not_eq A B
210 | THEN
211 | dir <- prim__half_direction A B
212 | dir2 <- direction_of A B
213 | <- angle_pred 0 dir 2 dir2 -1
214 |
215 | double_direction A:P dir:A d:D -> B:P
216 | THEN
217 | B <- prim__double_direction A dir d
218 | dir2 <- direction_of A B
219 | <- angle_pred 0 dir 2 dir2 -1
220 | d2 <- dist A B
221 | <- == d d2
222 |
223 | angle_2_to_3 ang2:A -> ang3:A
224 | a90 <- angle_compute 1/2
225 | <- not_eq ang2 a90
226 | THEN
227 | ang3 <- prim__angle_2_to_3 ang2
228 | <- angle_pred 0 ang3 3 ang2 -2
229 |
--------------------------------------------------------------------------------
/view_port_ori.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | from itertools import islice
3 | from geo_object import Point, Line, Circle, vector_perp_rot
4 | from gi.repository import Gtk
5 |
6 | class ViewPort:
7 | def __init__(self, scale = 1, shift = (0,0)):
8 | self.scale = scale
9 | self.shift = np.array(shift)
10 |
11 | def draw_point(self, cr, p):
12 | cr.arc(p.a[0], p.a[1], 3/self.scale, 0, 2*np.pi)
13 | cr.fill()
14 | def point_shadow(self, cr, p):
15 | cr.arc(p.a[0], p.a[1], 10/self.scale, 0, 2*np.pi)
16 |
17 | def draw_circle(self, cr, c, colorization = None):
18 | cr.set_line_width(1/self.scale)
19 | if colorization is not None:
20 | cr.save()
21 | print("circle colorized")
22 | for a,b, color in colorization:
23 | cr.arc(c.c[0], c.c[1], c.r, a, b)
24 | cr.set_source_rgb(*self.get_color(color))
25 | cr.stroke()
26 | cr.restore()
27 | else:
28 | cr.arc(c.c[0], c.c[1], c.r, 0, 2*np.pi)
29 | cr.stroke()
30 |
31 | def draw_line(self, cr, l, colorization = None):
32 | endpoints = [None, None]
33 | boundaries = list(zip(*self.corners))
34 |
35 | if np.prod(l.n) > 0:
36 | boundaries[1] = boundaries[1][1], boundaries[1][0]
37 | for coor in (0,1):
38 | if l.n[1-coor] == 0: continue
39 | for i, bound in enumerate(boundaries[coor]):
40 | p = np.zeros([2])
41 | p[coor] = bound
42 | p[1-coor] = (l.c - bound*l.n[coor])/l.n[1-coor]
43 | if (p[1-coor] - boundaries[1-coor][0]) * (p[1-coor] - boundaries[1-coor][1]) <= 0:
44 | endpoints[i] = p
45 |
46 | if endpoints[0] is None or endpoints[1] is None: return
47 |
48 | cr.set_line_width(1/self.scale)
49 | if colorization is not None:
50 | print("line colorized")
51 | e1, e2 = endpoints
52 | e1c = np.dot(e1, l.v)
53 | e2c = np.dot(e2, l.v)
54 | if e1c > e2c:
55 | e1,e2 = e2,e1
56 | e1c,e2c = e2c,e1c
57 |
58 | cr.save()
59 | for a,b, color in colorization:
60 | if a is None or a <= e1c: a = e1c
61 | elif a >= e2c: continue
62 | if b is None or b >= e2c: b = e2c
63 | elif b <= e1c: continue
64 | cr.move_to(l.point_by_c(a))
65 | cr.line_to(l.point_by_c(b))
66 | cr.set_source_rgb(*self.get_color(color))
67 | cr.stroke()
68 | cr.restore()
69 | else:
70 | cr.move_to(*endpoints[0])
71 | cr.line_to(*endpoints[1])
72 | cr.stroke()
73 |
74 | def draw_cline(self, cr, obj):
75 | if isinstance(obj, Line): self.draw_line(cr, obj)
76 | elif isinstance(obj, Circle): self.draw_circle(cr, obj)
77 | else: raise Exception("Unexpected type {}".format(type(obj)))
78 |
79 | def draw_dist(self, cr, a,b,lev):
80 | if lev % 2 == 0: lev = -lev//2
81 | else: lev = (lev+1) // 2
82 | ab_v = (b-a) / np.linalg.norm(b-a)
83 | ab_n = vector_perp_rot(ab_v)
84 | a = a+(5*ab_v + 5*lev*ab_n)/self.scale
85 | b = b+(-5*ab_v + 5*lev*ab_n)/self.scale
86 | cr.move_to(*a)
87 | cr.line_to(*b)
88 | cr.set_line_width(1/self.scale)
89 | cr.stroke()
90 |
91 |
92 | def mouse_coor(self, e):
93 | return np.array([e.x, e.y])/self.scale - self.shift
94 | def shift_to_mouse(self, coor, mouse):
95 | self.shift = np.array([mouse.x, mouse.y])/self.scale - coor
96 | def zoom(self, scale_change, e):
97 | coor = self.mouse_coor(e)
98 | self.scale *= scale_change
99 | self.shift_to_mouse(coor, e)
100 | print("zoom {}".format(self.scale))
101 |
102 | def set_corners(self, width, height):
103 | self.corners = corners = np.array([
104 | [0, 0],
105 | [width, height],
106 | ])/self.scale - self.shift
107 |
108 | def get_color(self, col_index):
109 | hsv = Gtk.HSV()
110 | denom = 3
111 | start = 0
112 | jump = 1
113 | while col_index >= denom // jump:
114 | col_index -= denom // jump
115 | denom *= 2
116 | start = 1
117 | jump = 2
118 |
119 | hue = (start+col_index*jump)/denom
120 | res = hsv.to_rgb(hue, 1, 1)
121 | lum = 1.2*res.r + 1.5*res.g + res.b
122 | return (res.r/lum, res.g/lum, res.b/lum)
123 |
124 | def draw(self, cr, env):
125 |
126 | # cr transform
127 | cr.scale(self.scale, self.scale)
128 | cr.translate(*self.shift)
129 | # erase background
130 | size = self.corners[1] - self.corners[0]
131 | cr.rectangle(*(list(self.corners[0])+list(size)))
132 | cr.set_source_rgb(1,1,1)
133 | cr.fill()
134 |
135 | # draw circles and lines
136 | cr.set_source_rgb(0,0,0)
137 | for _,obj in env.visible_clines: self.draw_cline(cr, obj)
138 |
139 | # draw points shadows
140 | cr.set_source_rgb(1,1,1)
141 | for _,p in env.visible_points:
142 | self.point_shadow(cr, p)
143 | cr.fill()
144 |
145 | # draw lies_on
146 | cr.set_source_rgb(0,0,0)
147 | for p_li, ps_li in env.lies_on_data:
148 | p = env.li_to_num(p_li)
149 | ps = env.li_to_num(ps_li)
150 | cr.save()
151 | self.point_shadow(cr, p)
152 | cr.clip()
153 | self.draw_cline(cr, ps)
154 | cr.restore()
155 |
156 | # draw distances
157 | cr.save()
158 | cr.set_dash([3 / self.scale])
159 | for a,b,col,lev in env.dist_col_lev:
160 | cr.set_source_rgb(*self.get_color(col))
161 | self.draw_dist(cr, a,b,lev)
162 | cr.restore()
163 |
164 | # draw points
165 | cr.set_source_rgb(0,0,0)
166 | for _,p in env.visible_points:
167 | self.draw_point(cr, p)
168 |
--------------------------------------------------------------------------------
/angle_chasing.py:
--------------------------------------------------------------------------------
1 | from sparse_row import SparseRow
2 | from sparse_elim import ElimMatrix, lcm, EquationIndex
3 | from math import floor
4 | from fractions import Fraction
5 | from geo_object import eps_identical
6 |
7 | """
8 | AngleChasing is a superstructure of ElimMatrix in sparse_elim specifically
9 | targeted to handling equations about angles.
10 | Angles use the straight-angle units here, that is
11 | 1/2 = 90 degrees, 1 = 180 degrees, 2 = 360 degrees, ...
12 | Equations about angles are considered modulo 1 (modulo 180 degrees)
13 | since the basic usage of an angle is a direction of a line, and a direction
14 | of a line is unambiguous modulo 180 degrees only.
15 |
16 | Since AngleChasing is using gaussian elimination (over a field)
17 | taking equations modulo 1 is equivalent to taking equations modulo rationals.
18 | Therefore, the logic checks only whether a certain equation is satisfied modulo
19 | rational numbers, and the rest is checked numerically.
20 |
21 | An equation is of the form
22 | x_1*c_1 + x_2*c_2 + ... + x_n*c_n + C = 0.
23 | C is an extra constant called "frac_offset", and
24 | the rest is encoded using a SparseRow.
25 | """
26 |
27 | class AngleChasing:
28 | def __init__(self):
29 | self.elim = ElimMatrix()
30 | self.equal_to = dict() # var -> root, frac_dist
31 | self.root_to_vars = dict() # root -> size, dict( frac_diff -> var_list )
32 | self.value = dict() # var -> value
33 |
34 | # var is the variable (geometrical reference)
35 | # value is a numerical value (object of type Angle)
36 | def add_var(self, var, value):
37 | #print(" angles.add_var({}, {})".format(var, value))
38 | self.value[var] = value
39 | self.equal_to[var] = var, Fraction(0)
40 | self.root_to_vars[var] = 1, { Fraction(0) : [var] }
41 |
42 | def query(self, equation, frac_offset):
43 | #print(" print(angles.query(SparseRow({}), Fraction({})))".format(
44 | # equation, frac_offset
45 | #))
46 | denom = self.elim.query(equation)
47 | if denom == 0 or denom % frac_offset.denominator != 0: return False
48 |
49 | return self.num_check(equation, frac_offset)
50 |
51 | def num_check(self, equation, frac_offset):
52 | num_val = 0
53 | for v,coef in equation.items():
54 | if isinstance(v, EquationIndex): continue
55 | assert(coef.denominator == 1)
56 | num_val += self.value[v] * coef.numerator
57 |
58 | num_val += float(frac_offset)
59 | num_val = (num_val+0.5) % 1 - 0.5
60 | return eps_identical(num_val, 0)
61 |
62 | def has_exact_difference(self, a, b):
63 | return self.equal_to[a][0] == self.equal_to[b][0]
64 |
65 | def _get_frac_dist(self, f1, f2, denom):
66 | # find a fraction with denominator denom approximatelly equal to f2-f1
67 | numer_f = (f2 - f1) * denom + 0.5
68 | numer = int(floor(numer_f)) % denom
69 | numer_f = numer_f % denom - 0.5
70 | assert(eps_identical(numer, numer_f))
71 | return Fraction(numer, denom)
72 |
73 | def postulate(self, equation, frac_offset):
74 | #print(" angles.postulate(SparseRow({}), Fraction({}))".format(
75 | # equation, frac_offset
76 | #))
77 | assert(self.num_check(equation, frac_offset))
78 | denom = frac_offset.denominator
79 | if denom > 1: equation *= denom
80 | changed, to_glue = self.elim.add(equation)
81 | to_glue_out = []
82 | for x,y,denom in to_glue:
83 | #print("{} == {} (mod 1/{})".format(x,y,denom))
84 | x,fd_x = self.equal_to[x]
85 | y,fd_y = self.equal_to[y]
86 | if x == y: continue
87 | denom = lcm(denom, (fd_x-fd_y).denominator)
88 |
89 | x_size, x_dict = self.root_to_vars[x]
90 | y_size, y_dict = self.root_to_vars[y]
91 | if y_size > x_size:
92 | x,y = y,x
93 | x_dict,y_dict = y_dict,x_dict
94 | x_size,y_size = y_size,x_size
95 |
96 | # find fractional difference between x,y
97 |
98 | frac_dist = self._get_frac_dist(self.value[y], self.value[x], denom)
99 |
100 | # add y_dict to x_dict, calculate to_glue_out
101 |
102 | for fd2, y_var_list in y_dict.items():
103 | fd_sum = (fd2 + frac_dist)%1
104 | for y_var in y_var_list: self.equal_to[y_var] = x, fd_sum
105 | x_var_list = x_dict.setdefault(fd_sum, y_var_list)
106 | if x_var_list is not y_var_list:
107 | to_glue_out.append((x_var_list[0], y_var_list[0]))
108 | x_var_list.extend(y_var_list)
109 |
110 | del self.root_to_vars[y]
111 | self.root_to_vars[x] = (x_size + y_size), x_dict
112 |
113 | return to_glue_out
114 |
115 | # find angle y such that it is proven that y == -x
116 | def get_complement(self, x):
117 | r, d_xr = self.equal_to[x]
118 | inv, denom = self.elim.get_inverse(r)
119 | if inv is None: return None
120 | d_ri = self._get_frac_dist(-self.value[r],self.value[inv], denom)
121 | y, d_iy = self.equal_to[inv]
122 | d_nxy = (-d_xr + d_ri + d_iy)%1
123 | #print("{} +{} -> {} inv[{}] -> {} +{} -> {}".format(x, d_xr, r, denom, inv, d_iy, y))
124 | nx_list = self.root_to_vars[y][1].get(d_nxy, ())
125 | if not nx_list: return None
126 | return nx_list[0]
127 |
128 | if __name__ == "__main__":
129 |
130 | angles = AngleChasing()
131 |
132 | angles.add_var('X', 0.6)
133 | angles.add_var('A', 0.1)
134 | angles.postulate(SparseRow({'X': Fraction(1, 1), 'A': Fraction(-1, 1)}), Fraction(1,2))
135 | angles.add_var('B', 0.3)
136 | angles.postulate(SparseRow({'A': Fraction(1, 1), 'B': Fraction(-1, 1)}), Fraction(1,5))
137 | angles.add_var('Y', 0.4)
138 | angles.add_var('C', 0.9)
139 | angles.add_var('D', 0.7)
140 | angles.postulate(SparseRow({'Y': Fraction(1, 1), 'C': Fraction(-1, 1)}), Fraction(-1,2))
141 | angles.postulate(SparseRow({'C': Fraction(1, 1), 'D': Fraction(-1, 1)}), Fraction(-1,5))
142 | angles.postulate(SparseRow({'A': Fraction(1, 1), 'C': Fraction(1, 1)}), Fraction(0))
143 | print(angles.get_complement('B'))
144 | print(angles.equal_to)
145 |
--------------------------------------------------------------------------------
/label_visualiser.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | """
4 | Drawing labels of geometrical objects (with indices)
5 | """
6 | class LabelVisualiser:
7 | def __init__(self, std_size = 48, sub_size = 30, sub_lower = 10):
8 | self.lookup_table = dict()
9 | self.std_size = std_size
10 | self.sub_size = sub_size
11 | self.sub_lower = sub_lower
12 |
13 | """
14 | parse(cr, s) converts the string into instructions for drawing
15 | the parsing rules are approximatelly:
16 | * if there is an underscore in the string,
17 | it swaps the index mode and normal mode
18 | * otherwise, there are three basic character types:
19 | uppercase letters, lowercase letters, numbers
20 | * change between these types causes mode swap
21 | * other characters, such as comma, does not cause mode swap
22 | * prime is never put into index
23 | The output is of the form
24 | (texts, subscripts, extents).
25 | extents are analogous of the standard cairo.text_extents
26 | texts and subscripts are lists of instructions of the form
27 | (x, y, text) for
28 | cr.move_to(x,y)
29 | cr.show_text(text)
30 | Note that the parsing results are cached, so the parse function should
31 | be always called with the same font set in the cairo context.
32 | """
33 | def parse(self, cr, s):
34 | if s in self.lookup_table:
35 | return self.lookup_table[s]
36 |
37 | self.cr = cr
38 | self.texts = []
39 | self.subscripts = []
40 | self.cur_x = 0
41 | self.min_coor = None
42 | self.max_coor = None
43 |
44 | self.std_chars = []
45 | self.sub_chars = []
46 |
47 | has_underscore = '_' in s
48 |
49 | last_sub = None
50 | last_ctype = None
51 | for c in s:
52 | if c == '_':
53 | last_sub = not last_sub
54 | continue
55 |
56 | cur_ctype = self._char_type(c)
57 | if has_underscore:
58 | cur_sub = last_sub
59 | else:
60 | if cur_ctype is None or last_ctype is None:
61 | cur_sub = last_sub
62 | elif cur_ctype == last_ctype: cur_sub = last_sub
63 | elif c == "'": cur_sub = False
64 | else: cur_sub = not last_sub
65 |
66 | if cur_sub: self._add_sub_char(c)
67 | else: self._add_std_char(c)
68 | if c != "'":
69 | last_sub = cur_sub
70 | last_ctype = cur_ctype
71 |
72 | self._finish_block()
73 |
74 | if self.min_coor is None:
75 | self.min_coor = np.zeros(2)
76 | self.max_coor = np.zeros(2)
77 | min_x, min_y = self.min_coor
78 | width, height = self.max_coor - self.min_coor
79 | extents = min_x, min_y, width, height, self.cur_x, 0
80 |
81 | self.cr = None
82 |
83 | result = self.texts, self.subscripts, extents
84 | self.lookup_table[s] = result
85 | return result
86 |
87 | # draw the text in the format given by self.parse
88 | def show(self, cr, texts, subscripts):
89 | cr.set_font_size(self.std_size)
90 | for x,y,text in texts:
91 | cr.move_to(x,y)
92 | cr.show_text(text)
93 | cr.set_font_size(self.sub_size)
94 | for x,y,text in subscripts:
95 | cr.move_to(x,y)
96 | cr.show_text(text)
97 | cr.new_path()
98 |
99 | # draw so that the center of the text is at (0,0)
100 | def show_center(self, cr, texts, subscripts, extents):
101 | x, y, width, height, dx, dy = extents
102 | cr.save()
103 | cr.translate(*self.get_center_start(extents))
104 | self.show(cr, texts, subscripts)
105 | cr.restore()
106 | def get_center_start(self, extents):
107 | x, y, width, height, dx, dy = extents
108 | return np.array([-(x+width/2), -(y+height/2)])
109 |
110 | # draw text in "edit mode", as is, without smart parsing, with a cursor
111 | def show_edit(self, cr, text, curs_index, position = (0,0),
112 | curs_color = (0,0,1), curs_by = 'I', curs_w = 2):
113 |
114 | cr.save()
115 | cr.translate(*position)
116 | cr.set_font_size(self.std_size)
117 | _, curs_y, _, curs_h, _, _ = cr.text_extents(curs_by)
118 | curs_pos = cr.text_extents(text[:curs_index])[4]
119 |
120 | cr.move_to(0, 0)
121 | cr.show_text(text)
122 |
123 | cr.rectangle(curs_pos, curs_y, curs_w, curs_h)
124 | cr.set_source_rgb(*curs_color)
125 | cr.fill()
126 |
127 | cr.restore()
128 |
129 | ### Helper functions for parsing
130 |
131 | def _char_type(self, c):
132 | if c.islower(): return 0
133 | elif c.isupper(): return 1
134 | elif c.isnumeric(): return 2
135 | elif c == "'": return 3
136 | else: return None
137 |
138 | def _add_text(self, text, cur_y, size, l):
139 | if text == "": return self.cur_x
140 | l.append((self.cur_x, cur_y, text))
141 | self.cr.set_font_size(size)
142 | x, y, width, height, dx, dy = self.cr.text_extents(text)
143 | min_coor = np.array([self.cur_x+x, cur_y+y])
144 | max_coor = min_coor + (width, height)
145 | if self.min_coor is None:
146 | self.min_coor = min_coor
147 | self.max_coor = max_coor
148 | else:
149 | self.min_coor = np.minimum(self.min_coor, min_coor)
150 | self.max_coor = np.maximum(self.max_coor, max_coor)
151 |
152 | return self.cur_x + dx
153 |
154 | def _finish_block(self):
155 | text = ''.join(self.std_chars)
156 | self.std_chars = []
157 | subscript = ''.join(self.sub_chars)
158 | self.sub_chars = []
159 |
160 | next_x1 = self._add_text(text, 0, self.std_size, self.texts)
161 | self.cur_x += self.cr.text_extents(text.rstrip("'"))[4]
162 | next_x2 = self._add_text(subscript, self.sub_lower, self.sub_size, self.subscripts)
163 | self.cur_x = max(next_x1, next_x2)
164 |
165 | def _add_std_char(self, c):
166 | if self.sub_chars and c != "'":
167 | self._finish_block()
168 | self.std_chars.append(c)
169 | def _add_sub_char(self, c):
170 | self.sub_chars.append(c)
171 |
--------------------------------------------------------------------------------
/step_list.py:
--------------------------------------------------------------------------------
1 | import gi as gtk_import
2 | gtk_import.require_version("Gtk", "3.0")
3 | from gi.repository import Gtk, GLib
4 |
5 | """
6 | StepList is an GUI element showing steps
7 | on the left side of the window.
8 | """
9 |
10 | # Label of a variable which can be also selected or undefined (disabled)
11 | class VarLabel(Gtk.Label):
12 | def __init__(self, env, gi):
13 | Gtk.Label.__init__(self)
14 | self.env = env
15 | self.vis = env.vis
16 | self.gi = gi
17 | self.selected = None
18 | self.defined = None
19 | self.name = env.gi_to_name[gi]
20 | self.update()
21 |
22 | def update(self):
23 | li = self.vis.gi_to_li(self.gi)
24 | defined = li is not None
25 | selected = li in self.vis.obj_is_selected
26 | name = self.env.gi_to_name[self.gi]
27 | if selected is self.selected\
28 | and defined is self.defined\
29 | and name is self.name:
30 | return
31 | self.selected = selected
32 | self.defined = defined
33 | self.name = name
34 | markup = GLib.markup_escape_text(name)
35 | if selected: markup = ""+markup+""
36 | elif not defined:
37 | markup = ""+markup+""
38 | self.set_markup(markup)
39 |
40 | # Label of a step -- the name of the tool -- can be unsuccessful (disabled)
41 | class StepMainLabel(Gtk.Label):
42 | def __init__(self, env, step):
43 | Gtk.Label.__init__(self)
44 | self.env = env
45 | self.step = step
46 | self.success = None
47 | self.update()
48 |
49 | def update(self):
50 | success = self.step.success
51 | if success is self.success: return
52 | self.success = success
53 | markup = GLib.markup_escape_text("<- "+self.step.tool.name)
54 | if not success:
55 | markup = ""+markup+""
56 | self.set_markup(markup)
57 |
58 | # One row in the list
59 | class StepListRow(Gtk.ListBoxRow):
60 | def __init__(self, step, env):
61 | Gtk.ListBoxRow.__init__(self)
62 | self.step = step
63 | self.env = env
64 | step.gui_row = self
65 | self.hbox = Gtk.HBox()
66 | self.add(self.hbox)
67 |
68 | self.output_widgets = self.make_var_widgets(self.step.local_outputs)
69 | self.label = StepMainLabel(env, step)
70 | self.hyperpar_widget = Gtk.Label(self.get_hyperpar_str())
71 | self.arg_widgets = self.make_var_widgets(self.step.local_args)
72 |
73 | for w in self.output_widgets:
74 | self.hbox.pack_start(w, False, False, 3)
75 | self.hbox.pack_start(self.label, False, False, 0)
76 | self.hbox.pack_start(self.hyperpar_widget, False, False, 0)
77 | for w in self.arg_widgets:
78 | self.hbox.pack_start(w, False, False, 3)
79 |
80 | def update_hyperpar(self):
81 | self.hyperpar_widget.set_text(self.get_hyperpar_str())
82 |
83 | def make_var_widgets(self, var_list):
84 | return [VarLabel(self.env, gi) for gi in var_list]
85 | def get_hyperpar_str(self):
86 | res = ' '
87 | for hyper_param in self.step.hyper_params:
88 | if isinstance(hyper_param, float):
89 | res += "{:.3}".format(hyper_param)
90 | else: res += str(hyper_param)
91 | res += ' '
92 | return res
93 |
94 | def update_selected(self):
95 | self.label.update()
96 | for w in self.output_widgets: w.update()
97 | for w in self.arg_widgets: w.update()
98 |
99 | class ListRowSeparator(Gtk.ListBoxRow):
100 | def __init__(self):
101 | Gtk.ListBoxRow.__init__(self)
102 | #self.set_activatable(False)
103 | self.set_selectable(False)
104 | self.add(Gtk.HSeparator())
105 | self.show_all()
106 | def update_hyperpar(self):
107 | pass
108 | def update_selected(self):
109 | pass
110 |
111 | class StepList(Gtk.ScrolledWindow):
112 | def __init__(self, env):
113 | super(StepList, self).__init__()
114 | self.listbox = Gtk.ListBox()
115 | self.add(self.listbox)
116 | self.insert_position = 0
117 | #self.listbox.set_selection_mode(Gtk.SelectionMode.MULTIPLE)
118 | #self.listbox.set_activate_on_single_click(False)
119 | self.listbox.set_selection_mode(Gtk.SelectionMode.NONE)
120 |
121 | env.add_step_hook = self.add_step
122 | env.remove_step_hook = self.remove_step
123 | env.reload_steps_hook = self.load_steps
124 | env.update_hyperpar_hook = self.update_hyperpar
125 | env.vis.update_selected_hook = self.update_selected
126 |
127 | self.env = env
128 | self.load_steps()
129 |
130 | def load_steps(self):
131 | for row in self.listbox.get_children():
132 | self.listbox.remove(row)
133 | self.insert_position = 0
134 |
135 | steps = self.env.steps
136 | goals = self.env.goals
137 | use_sep = goals is not None
138 | if use_sep:
139 | proof = self.env.steps[self.env.min_steps:]
140 | steps = self.env.steps[:self.env.min_steps]
141 |
142 | for step in steps:
143 | self.add_step(step)
144 |
145 | if use_sep:
146 | self.add_separator()
147 | if proof:
148 | for step in proof:
149 | self.add_step(step)
150 | insert_pos = self.insert_position
151 | self.add_separator()
152 | for step in goals:
153 | self.add_step(step)
154 | self.insert_position = insert_pos
155 |
156 | def add_separator(self):
157 | self.listbox.insert(ListRowSeparator(), self.insert_position)
158 | self.insert_position += 1
159 |
160 | def add_step(self, step):
161 | row = StepListRow(step, self.env)
162 | row.show_all()
163 | self.listbox.insert(row, self.insert_position)
164 | self.insert_position += 1
165 |
166 | def update_hyperpar(self, step):
167 | step.gui_row.update_hyperpar()
168 |
169 | def remove_step(self, step):
170 | self.listbox.remove(step.gui_row)
171 | step.gui_row = None
172 | self.insert_position -= 1
173 |
174 | def update_selected(self):
175 | for row in self.listbox.get_children():
176 | row.update_selected()
177 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GeoLogic
2 | Tool for euclidean geometry aware of logic
3 |
4 | For studying the source code, start [here](technical_doc.txt).
5 |
6 | 
7 |
8 | ## Dependencies
9 | + Python3
10 | + [pyGtk3](https://pygobject.readthedocs.io/en/latest/getting_started.html)
11 | + [numpy](https://pypi.org/project/numpy/) (on Windows: "pacman -S mingw-w64-x86_64-python3-numpy")
12 |
13 | ## Data types
14 |
15 | GeoLogic can handle five types of objects: Point (P), Line (L), Circle
16 | (C), Angle (A) (which include a direction of a line or a "length" of
17 | an arc), and Ratio (D) (ratio of products of lengths, including a
18 | length itself). From these five data types, only three of them can be
19 | matipulated using the GUI -- points, lines and circles. Every object
20 | in the GL file is of one of these types, the types of input and output
21 | objects are in the header of every tool.
22 |
23 | ## Semi-formal logic
24 |
25 | The background logical system requires proofs of exact statements
26 | (e.q. line contain a point, two angles are identical, ...) but
27 | for topological facts (two circles intersect each other, two
28 | triangles are identically oriented) it only checks numerically.
29 |
30 | It is possible to write lemmata for GeoLogic -- some are in the file
31 | macros.gl. However, whenever a lemma is used, GeoLogic also checks whether
32 | the proof of the lemma works in this particular numerical setting.
33 |
34 | It is not possible to create lemmata in the user interface yet
35 | (it is a planned feature).
36 |
37 | ## Automation
38 |
39 | As an interactive theorem prover, GeoLogic intentionally does not possess
40 | much automation, especially if it can be circumvented with visual steps.
41 | There are basically four ways in which GeoLogic makes automatic decisions:
42 |
43 | + Gaussian elimination for angles: GeoLogic uses "oriented angle chasing"
44 | for deriving facts about angles (similar to Full Angle Method).
45 | Every angle is defined as a difference between the directions of corresponding lines.
46 | Whenever an equality (modulo rational multiple of pi) can be infered,
47 | GeoLogic recognizes it.
48 | + Gaussian elimination for log-distances: Whenever an equality of the form of a ratio
49 | of products of certain lengths can be infered, GeoLogic recognizes it. Note that
50 | GeoLogic cannot "add" distances, only multiply, as it is the more common operation
51 | with them.
52 | + Extensionality: Whenever x0=y0, x1=y1, ..., xn=yn, then f(x0,...,xn) = f(y0,...,yn)
53 | for any f being a primitive command / defined macro.
54 | + Equality triggers: Besides extensionality, there are a few extra ways how GeoLogic
55 | automatically detect identical objects: two lines passing two disting points are equal,
56 | two points lying on two intersecting clines are equal, etc.
57 |
58 | ## Controls
59 |
60 | All non-movable logical tools which take only graphical objects (Point / Line / Circle)
61 | on input can be reached using the entry-area in the up right corner of the window.
62 |
63 | Tools that can turn out to be useful:
64 | + (triangle centers) centroid, orthocenter, circumcenter, incenter, excenter
65 | + (lemma) midsegment -- recognizes that the midsegment of a triangle is parallel and half long
66 | + (lemma) midpoint_uq -- recognizes that a point on line having the same distances is a midpoint
67 | + copy_triangle, copy_triangle_r -- constructions of similar / congruent triangles
68 | + sim_, cong_ -- lemmata about similar / congruent triangles
69 |
70 | There are also six "smart" graphical tools that automaticaly run the appropriate logical tool,
71 | and can be used for constructing movable objects.
72 | Right click resets the current tool.
73 |
74 | ### Ambiguous objects
75 |
76 | If two numerically identical objects are about to be shown, GeoLogic
77 | shows only one of them. They can be swapped using shift-click. During holding shift,
78 | the ambigous object turn slightly red.
79 |
80 | ### Point
81 |
82 | + Free point: click into space
83 | + Point on line / circle: click on it
84 | + Intersection: click near intersection
85 | + More detailed intersection of clines 'a', 'b': drag from 'a' to 'b'
86 | + Midpoint: click on two points
87 | + Circle center: drag from circle to its center
88 | + Foot: click on point, then line / circle near foot
89 | + Foot: click to point, then drag from one point to another
90 | + Opposite point on circle: click on point X,
91 | then on circle containing X near the opposite point
92 | + Midpoint on an arc: select point X, circle containing X,
93 | the arc direction, and the second point
94 |
95 | ### (Parallel) Line
96 |
97 | + Line through two points: click the points (can be free if the second click is into space)
98 | + Parallel: click on a line (of drag between points), and then click on a point (or into space)
99 | + Tangent: click on point, and then on circle near possible touchpoint.
100 | + Angle bisector A B C: select B, drag A->C
101 |
102 | ### Perpendicular Line
103 |
104 | + Perpendibular bisector: click two points
105 | + Perpendicular line: click on a line (of drag between points), and then click on a point (or into space)
106 | + External Angle bisector A B C: select B, drag A->C
107 |
108 | ### Circle
109 |
110 | + Circle with center O: select O, then click to space / to a point / to a line near possible touchpoint
111 | + Compass: select circle / drag between points, then select another point / click into space
112 |
113 | ### Circumcircle
114 |
115 | + Free circle through 1 or 2 points: click the point(s), then into space
116 | + Circle with diameter: select the diameter, and confirm by clicking on one of the two points
117 | + Circumcircle: select three points
118 |
119 | ### Reason
120 |
121 | + Given point X lies on a circle w, infer its distance from center: Select X w
122 | + Given point X has the right distance from the center of w, infer that w contains X: Select w X
123 | + Given X Y see the segment A B at the same angle, infer they are concyclic: Select X Y, drag A->B
124 | + Given X Y A B are concyclic, infer angle equality AXB = AYB: Drag A->B, select X Y
125 | + Inscribed angle theorem about AXB: Select X, drag A->B
126 | + Inverse inscribed angle theorem about AXB on a circle w, infer w contains X: Drag A->B, then select X and w
127 | + Given X lies on a perpendicular bisector l of AB, infer AX=BX: Drag A->B, select l, then X
128 | + Given AX=BX, infer X lies on a perpendicular bisector l of AB: Drag A->B, select X, then l
129 | + Given two arcs AB, CD of the same circle w are equal, infer equal distances:
130 | select w, drag A->B, drag C->D (in the same direction)
131 | + Given AB=CD on a circle w, infer equal arcs:
132 | select w, then select consecutively A, B, and C, D (in the same direction)
133 |
134 | ### Label
135 |
136 | + Hide / Show label
137 | + drag -> change label position
138 | + when appears, labels captures the keyboard and allows editing, confirmed by enter / click
139 | + In the label, everything can be in subscript or in the main text. It tries to figure out automatically how it should be, eventually it can be adjusted by adding underscores.
140 |
--------------------------------------------------------------------------------
/logical_core.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | from geo_object import Angle, Ratio
3 | from sparse_row import SparseRow, equality_sr
4 | from sparse_elim import EquationIndex, ElimMatrix
5 | from angle_chasing import AngleChasing
6 | from uf_dict import UnionFindDict
7 | from fractions import Fraction
8 | from triggers import TriggerEnv, RelStrEnv
9 | from stop_watch import StopWatch
10 |
11 | # Returns list of pairs (prime, exponent), used for ratio equations
12 | def prime_decomposition(n):
13 | assert(n > 0)
14 | p2 = 0
15 | while n % 2 == 0:
16 | p2 += 1
17 | n //= 2
18 | if p2 > 0: result = [(2, p2)]
19 | else: result = []
20 | d = 3
21 | while d**2 <= n:
22 | if n % d == 0:
23 | p = 0
24 | while n % d == 0:
25 | p += 1
26 | n //= d
27 | result.append((d, p))
28 | d += 2
29 | if n > 1: result.append((n, 1))
30 | return result
31 |
32 |
33 | """
34 | A short notice on the format of (angle / ratio) equations:
35 | Both equational types: angles and ratios contain an equation of the type
36 | SparseRow (geometrical reference x_i -> int c_i)
37 | and a fractional constant.
38 | Ratios: the general equation is of the form x_1^c_1 * x_2^c_2 * ... * const = 1
39 | Angles: the general equation is of the form x_1*c_1 + x_2*c_2 + ... + const = 0
40 | """
41 |
42 | class LogicalCore():
43 | def __init__(self, basic_tools = None):
44 | self.obj_types = [] # array : geometrical reference -> type (Point, Line, ...)
45 | self.num_model = [] # array : geometrical reference -> numerical representation (object of the type)
46 | self.ratios = ElimMatrix() # known equation about distances / ratios
47 | self.ratio_consts = dict() # prime number -> reference to a ratio object representing it
48 | self.angles = AngleChasing() # known equation about angles
49 | self.ufd = UnionFindDict() # lookup table for memoized tools
50 |
51 | # for using triggers, we need to have access to the basic tools
52 | # if we don't have it, triggers are not applied (RelStrEnv is a dummy structure)
53 | if basic_tools is None: self.triggers = RelStrEnv(self)
54 | self.triggers = TriggerEnv(basic_tools, self)
55 |
56 | # zero angle
57 | self.exact_angle = self.add_obj(Angle(0))
58 | self.angles.postulate(SparseRow({self.exact_angle : 1}), Fraction(0))
59 |
60 | # given a numerical representation, create a new geometrical reference
61 | def add_obj(self, num_obj):
62 | index = len(self.num_model)
63 | self.num_model.append(num_obj)
64 | t = type(num_obj)
65 | self.obj_types.append(t)
66 | if t == Angle: self.angles.add_var(index, num_obj.data)
67 | #print("add {} : {}".format(index, t.__name__))
68 | return index
69 |
70 | def add_objs(self, num_objs):
71 | return tuple(self.add_obj(obj) for obj in num_objs)
72 |
73 | ### checking functions, they do not modify the logical core
74 |
75 | def check_equal(self, obj1, obj2): # equality
76 | return self.ufd.is_equal(obj1, obj2)
77 | def get_constr(self, identifier, args): # lookup table
78 | return self.ufd.get(identifier, args)
79 | def check_angle_equation(self, equation : SparseRow, frac_const : Fraction):
80 | return self.angles.query(equation, frac_const)
81 | def check_ratio_equation(self, equation : SparseRow, frac_const : Fraction):
82 | equation = self._make_ratio_equation(equation, frac_const, new_const = False)
83 | if equation is None: return False
84 | return bool(self.ratios.query(equation))
85 |
86 | ### postulating functions
87 |
88 | def glue(self, obj1, obj2): # equality
89 | if self.check_equal(obj1, obj2): return
90 | self._glue_reaction([(obj1, obj2)])
91 | def add_constr(self, identifier, args, vals): # lookup table
92 | args, vals = self.ufd.add(identifier, args, vals)
93 | self.triggers.add(identifier, args+vals)
94 | self.triggers.run()
95 | def add_angle_equation(self, equation : SparseRow, frac_const : Fraction):
96 | to_glue_dict = list(self.angles.postulate(equation, frac_const))
97 | self._glue_reaction(to_glue_dict)
98 | def add_ratio_equation(self, equation : SparseRow, frac_const : Fraction):
99 | equation = self._make_ratio_equation(equation, frac_const, new_const = True)
100 | to_glue_dict = [
101 | (a,b)
102 | for (a,b,d) in self.ratios.add(equation)[1]
103 | ]
104 | self._glue_reaction(to_glue_dict)
105 |
106 | ### helper functions
107 |
108 | """
109 | _glue_reaction expects a list of the form of pairs (a,b)
110 | where a,b are geometrical references proven to be equal.
111 | The list will be eventually made empty, while the functions
112 | traces extensionality and equality forced by
113 | the ratio and angle equations.
114 | """
115 | def _glue_reaction(self, to_glue_dict):
116 | to_glue_elim = []
117 | dnodes_moved = []
118 | while True:
119 | if to_glue_dict:
120 | a,b = to_glue_dict.pop()
121 | assert(self.obj_types[a] == self.obj_types[b])
122 | to_glue_elim.extend(self.ufd.glue(a, b))
123 | elif to_glue_elim:
124 | a,b = to_glue_elim.pop()
125 | dnodes_moved.append(b)
126 | na, nb = self.num_model[a], self.num_model[b]
127 | assert(na.identical_to(nb))
128 | t = type(na)
129 | if t == Ratio:
130 | equation = equality_sr(a, b)
131 | to_glue_dict.extend(
132 | (a,b)
133 | for (a,b,d) in self.ratios.add(equation)[1]
134 | )
135 | elif t == Angle:
136 | equation = equality_sr(a,b)
137 | to_glue_dict.extend(
138 | self.angles.postulate(equation, Fraction(0))
139 | )
140 | else: break
141 |
142 | self.triggers.glue_nodes(dict(
143 | (x, self.ufd.obj_to_root(x))
144 | for x in dnodes_moved
145 | ))
146 | self.triggers.run()
147 |
148 | # embeds a fractional constant of an equation about ratios into the equation
149 | def _make_ratio_equation(self, equation, frac_const = 1, new_const = True):
150 | if frac_const == 1: return SparseRow(equation)
151 | primes = prime_decomposition(frac_const.numerator)
152 | primes.extend(
153 | (d,-p)
154 | for (d,p) in prime_decomposition(frac_const.denominator)
155 | )
156 | for d,_ in primes:
157 | if d not in self.ratio_consts:
158 | if new_const:
159 | self.ratio_consts[d] = self.add_obj(Ratio((np.log(d), 0)))
160 | else: return None
161 | equation = equation + SparseRow(
162 | (self.ratio_consts[d], Fraction(p))
163 | for d,p in primes
164 | )
165 | return equation
166 |
--------------------------------------------------------------------------------
/images/svg/derive.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
91 |
--------------------------------------------------------------------------------
/images/svg/derive2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
117 |
--------------------------------------------------------------------------------
/tools.py:
--------------------------------------------------------------------------------
1 | from fractions import Fraction
2 | from sparse_row import SparseRow
3 | from stop_watch import StopWatch
4 |
5 | ### Tool Exceptions
6 | # used for tracing failed tools
7 |
8 | class ToolError(Exception):
9 | def __init__(self, *args, **kwargs):
10 | self.tool_traceback = []
11 | Exception.__init__(self, *args, **kwargs)
12 | def __str__(self):
13 | msg = Exception.__str__(self)
14 | msg = "{}\n Tool Traceback:\n ".format(msg)+"\n ".join(
15 | "{}".format(line)
16 | for line in self.tool_traceback
17 | )
18 | return msg
19 |
20 | class ToolErrorNum(ToolError):
21 | def __init__(self):
22 | ToolError.__init__(self, "Numerical check failed")
23 | class ToolErrorLog(ToolError):
24 | def __init__(self):
25 | ToolError.__init__(self, "Fact not supported by logic")
26 |
27 | class ToolErrorException(ToolError):
28 | def __init__(self, e):
29 | ToolError.__init__(self, "Python exception occured")
30 | self.e = e
31 |
32 | ### Tools
33 |
34 | class Tool:
35 | def __init__(self, hyper_types, arg_types, out_types, name):
36 | self.hyper_types = hyper_types # types of hyperparameter, can be None
37 | self.arg_types = arg_types # geometrical types of arguments, can be None
38 | self.out_types = out_types # types of output
39 | self.name = name # string, can be none
40 |
41 | # if possible, add the tool to a dictionary of the form:
42 | # (name, input_types_or_none) -> tool
43 | def add_to_dict(self, d):
44 | if self.name is None: return
45 | if self.hyper_types is None or self.arg_types is None: in_types = None
46 | else: in_types = self.hyper_types+self.arg_types
47 | key = self.name, in_types
48 | assert(key not in d)
49 | d[key] = self
50 |
51 | def run(self, hyper_params, obj_args, logic, strictness):
52 | # strictness: 0 = postulate, 1 = check
53 | raise Exception("Not implemented")
54 |
55 | class EqualObjects(Tool):
56 | def __init__(self, willingness = 0, name = "=="):
57 | self.willingness = willingness
58 | Tool.__init__(self, (), None, (), name)
59 |
60 | def run(self, hyper_params, obj_args, logic, strictness):
61 | assert(len(hyper_params) == 0)
62 | a,b = obj_args
63 | if not logic.num_model[a].identical_to(logic.num_model[b]):
64 | raise ToolErrorNum()
65 | elif strictness <= self.willingness: # postulate
66 | logic.glue(a,b)
67 | return ()
68 | else: # check
69 | if logic.check_equal(a,b): return ()
70 | else:
71 | #print('not provably equal', a, b)
72 | raise ToolErrorLog()
73 |
74 | class MemoizedTool(Tool):
75 | def __init__(self, arg_types, out_types, name):
76 | Tool.__init__(self, (), arg_types, out_types, name)
77 | self.symmetries = []
78 | def add_symmetry(self, perm): # for storing symmetrical inputs to the lookup table
79 | perm = tuple(perm)
80 | assert(set(perm) == set(range(len(perm)))) # check permutation
81 | assert(len(perm) == len(self.arg_types)) # check size
82 | # check preserved types
83 | perm_types = tuple(self.arg_types[i] for i in perm)
84 | assert(perm_types == self.arg_types)
85 |
86 | self.symmetries.append(perm)
87 |
88 | def memoize(self, args, logic, vals):
89 | logic.add_constr(self, args, vals)
90 | for perm in self.symmetries:
91 | perm_args = tuple(args[i] for i in perm)
92 | logic.add_constr(self, perm_args, vals)
93 |
94 | def run(self, hyper_params, obj_args, logic, strictness):
95 |
96 | memoized = logic.get_constr(self, obj_args)
97 | if memoized is not None: return memoized
98 | result = self.run_no_mem(obj_args, logic, strictness)
99 | self.memoize(obj_args, logic, result)
100 |
101 | return result
102 |
103 | def run_no_mem(self, args):
104 | raise Exception("Not implemented")
105 |
106 | class PrimitivePred(MemoizedTool):
107 | def __init__(self, num_check, arg_types, name, willingness = 0):
108 | MemoizedTool.__init__(self, arg_types, (), name)
109 | # willingness: 0 = exact predicate, 1 = coexact
110 | self.willingness = willingness
111 | self.num_check = num_check
112 |
113 | def run_no_mem(self, args, logic, strictness):
114 | num_args = tuple(logic.num_model[arg] for arg in args)
115 | if not self.num_check(*num_args): raise ToolErrorNum()
116 | elif strictness > self.willingness: raise ToolErrorLog()
117 | else: return ()
118 |
119 | class PrimitiveConstr(MemoizedTool):
120 | def __init__(self, num_eval, arg_types, out_types, name):
121 | MemoizedTool.__init__(self, arg_types, out_types, name)
122 | self.num_eval = num_eval
123 |
124 | def run_no_mem(self, args, logic, strictness):
125 | if strictness > 0:
126 | raise ToolError("Primitive construction cannot be run in check-mode")
127 | num_args = (logic.num_model[arg] for arg in args)
128 | num_outs = self.num_eval(*num_args)
129 | if len(self.out_types) == 1 and not isinstance(num_outs, (list, tuple)):
130 | num_outs = num_outs,
131 | assert(len(num_outs) == len(self.out_types))
132 | return logic.add_objs(num_outs)
133 |
134 | # class for construction tools angle_compute and ratio_compute
135 | class DimCompute(Tool):
136 | def __init__(self, obj_type, num_comp, postulate, name):
137 | Tool.__init__(self, None, None, (obj_type,), name)
138 | self.obj_type = obj_type # Angle / Ratio
139 | self.num_comp = num_comp # function for final construction
140 | self.postulate = postulate # function interacting with the logical core
141 | def run(self, hyper_params, args, logic, strictness):
142 | coefs = hyper_params[1:]
143 | assert(len(coefs) == len(args))
144 |
145 | frac_const = hyper_params[0]
146 | obj_sum = sum(logic.num_model[arg].data*float(coef)
147 | for coef, arg in zip(coefs, args))
148 | new_obj_num = self.num_comp(obj_sum, frac_const)
149 | new_obj = logic.add_obj(self.obj_type(new_obj_num))
150 |
151 | equation = SparseRow(zip(args, coefs))
152 | equation[new_obj] = Fraction(-1)
153 | assert(all(isinstance(x, Fraction) for x in equation.values()))
154 | self.postulate(logic, equation, frac_const)
155 |
156 | return (new_obj,)
157 |
158 | # class for predicate tools angle_pred and ratio_pred
159 | class DimPred(Tool):
160 | def __init__(self, obj_type, num_check, postulate, check, name,
161 | willingness = 0):
162 | self.obj_type = obj_type # Angle / Ratio
163 | self.num_check = num_check
164 | self.postulate = postulate
165 | self.check = check
166 | self.willingness = willingness
167 | Tool.__init__(self, None, None, (), name)
168 | def run(self, hyper_params, args, logic, strictness):
169 | coefs = hyper_params[1:]
170 | assert(len(coefs) == len(args))
171 | frac_const = hyper_params[0]
172 | obj_sum = sum(logic.num_model[arg].data*float(coef)
173 | for coef, arg in zip(coefs, args))
174 | if not self.num_check(obj_sum, frac_const):
175 | raise ToolErrorNum()
176 |
177 | equation = SparseRow(zip(args, coefs))
178 |
179 | if strictness <= self.willingness:
180 | self.postulate(logic, equation, frac_const)
181 | return ()
182 | elif self.check(logic, equation, frac_const):
183 | return ()
184 | else: raise ToolErrorLog()
185 |
--------------------------------------------------------------------------------
/images/svg/move.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
172 |
--------------------------------------------------------------------------------
/gtool_label.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | from geo_object import *
3 | from gtool import GTool
4 | from gi.repository import Gtk, Gdk, GLib
5 |
6 | class GToolLabel(GTool):
7 |
8 | icon_name = "label"
9 | key_shortcut = 'a'
10 | label = "Label Tool"
11 |
12 | def __init__(self, *args, **kwargs):
13 | GTool.__init__(self, *args, **kwargs)
14 | self.label_edit = None
15 | self.obj_edit = None
16 | self.no_cancel = False
17 |
18 | def update_basic(self, coor):
19 | obj,objn = self.select_pcl(coor)
20 | if obj is not None:
21 | #self.hl_select((obj, "label"))
22 | prev_hidden = not self.vis.gi_label_show[obj]
23 | if prev_hidden:
24 | self.confirm = self.label_activate, obj, objn, coor
25 | else: self.confirm = self.label_hide, obj
26 | self.drag = self.label_drag, obj, objn, prev_hidden
27 |
28 | # mouse movement with edited label (with cursor)
29 | def update_edited(self, coor):
30 | self.confirm = self.confirm_edit
31 | obj = self.obj_edit
32 | nobj = self.vis.gi_to_num(self.obj_edit)
33 | if nobj.dist_from(coor) < 2*self.find_radius:
34 | self.drag = self.label_drag, obj, nobj
35 |
36 | def label_hide(self, obj):
37 | self.vis.gi_label_show[obj] = False
38 | self.vis.visible_export()
39 |
40 | # unhide and select, capture keyboard for editing the current label
41 | def label_activate(self, obj, objn, coor = None):
42 | if self.obj_edit is not None and obj == self.obj_edit:
43 | self.on_reset()
44 | return
45 | self.on_reset()
46 |
47 | self.vis.gi_label_show[obj] = False
48 | self.vis.view_changed = True
49 | self.no_cancel = True
50 | self.label_edit = [
51 | None, self.env.gi_to_name[obj],
52 | ]
53 | self.obj_edit = obj
54 | position = self.vis.get_label_position(obj)
55 | if isinstance(objn, Circle):
56 | direction, offset = position
57 | label_coor = objn.c + objn.r * vector_of_direction(direction)
58 | c1, c2 = self.viewport.corners
59 | if ((label_coor <= c1).any() or (label_coor >= c2).any())\
60 | and not eps_identical(objn.c, coor):
61 | direction = vector_direction(coor - objn.c)
62 | position = direction, offset
63 | self.vis.gi_label_position[obj] = position
64 |
65 | self.viewport.edited_label = [
66 | self.env.gi_to_name[obj], objn,
67 | position,
68 | self.label_edit
69 | ]
70 | self.viewport.app.keyboard_capture = self.on_key_edit
71 | #self.vis.gi_label_show[obj] = not self.vis.gi_label_show[obj]
72 | self.viewport.darea.queue_draw()
73 |
74 | ## Properties active during editing -- cursor position and the current text
75 |
76 | @property
77 | def cursor(self):
78 | return self.label_edit[0]
79 | @cursor.setter
80 | def cursor(self, value):
81 | if self.cursor is None: self.edit_start()
82 | self.label_edit[0] = value
83 | @property
84 | def new_text(self):
85 | return self.label_edit[1]
86 | @new_text.setter
87 | def new_text(self, value):
88 | self.label_edit[1] = value
89 |
90 | # when starting writing, or moving cursor
91 | def edit_start(self):
92 | self.update = self.update_edited
93 |
94 | # highlight only the edited object
95 | self.hl_selected.ini = [(self.obj_edit, True)]
96 | self._hl_load()
97 | self._hl_update()
98 |
99 | # key event handler
100 | def on_key_edit(self, e):
101 | keyval = e.keyval
102 | keyval_name = Gdk.keyval_name(keyval)
103 | if keyval_name.startswith("KP_"):
104 | keyval_name = keyval_name[3:]
105 | if keyval_name == "Escape":
106 | self.reset()
107 | return True
108 | elif keyval_name == "Left":
109 | if self.cursor is None or self.cursor < 2:
110 | self.cursor = 0
111 | else: self.cursor = self.cursor - 1
112 | elif keyval_name == "Return":
113 | self.confirm_edit()
114 | return True
115 | elif keyval_name == "Right":
116 | if self.cursor is None or self.cursor >= len(self.new_text):
117 | self.cursor = len(self.new_text)
118 | else: self.cursor = self.cursor + 1
119 | elif keyval_name in ("BackSpace", "Delete"):
120 | if self.cursor is None:
121 | self.cursor = 0
122 | self.new_text = ""
123 | else:
124 | if keyval_name == "BackSpace":
125 | if self.cursor == 0: return True
126 | else: self.cursor = self.cursor - 1
127 | if self.cursor == len(self.new_text): return True
128 | print(self.new_text)
129 | self.new_text = self.new_text[:self.cursor]+self.new_text[self.cursor+1:]
130 | elif len(e.string) == 1 and ord(e.string) >= ord(' '):
131 | #print(ord(e.string))
132 | if self.cursor is None:
133 | if e.string.islower() and self.new_text[:1].isupper():
134 | self.new_text = e.string.upper()
135 | else: self.new_text = e.string
136 | self.cursor = 1
137 | else:
138 | self.new_text = self.new_text[:self.cursor] + e.string + self.new_text[self.cursor:]
139 | self.cursor = self.cursor + 1
140 | else:
141 | return False
142 |
143 | self.viewport.darea.queue_draw()
144 | return True
145 |
146 | def confirm_edit(self):
147 | self.env.change_name(self.obj_edit, self.new_text)
148 | self.reset()
149 |
150 | def on_reset(self):
151 | if self.no_cancel:
152 | self.no_cancel = False
153 | return
154 | if self.obj_edit is None: return
155 | if self.obj_edit < len(self.vis.gi_label_show):
156 | self.vis.gi_label_show[self.obj_edit] = True
157 | self.obj_edit = None
158 | self.viewport.edited_label = None
159 | self.viewport.app.keyboard_capture = None
160 | self.vis.visible_export()
161 | self.viewport.darea.queue_draw()
162 |
163 | def label_drag(self, coor, obj, objn, prev_hidden = True):
164 | scale = self.viewport.scale
165 | max_offset = 30
166 | if isinstance(objn, Point):
167 | pos = (coor - objn.a) * scale
168 | d = np.linalg.norm(pos) / max_offset
169 | if d > 1: pos /= d
170 | elif isinstance(objn, Circle):
171 | v = (coor - objn.c)
172 | if eps_zero(v): return
173 | direction = vector_direction(v)
174 | offset = (np.linalg.norm(v) - objn.r)*scale
175 | offset = np.clip(offset, -max_offset, max_offset)
176 | pos = direction, offset
177 | elif isinstance(objn, Line):
178 | endpoints = self.viewport.get_line_endpoints_ordered(objn)
179 | if endpoints is None: return
180 | v,n,c,e1,e2 = endpoints
181 | p1, p, p2 = (np.dot(e, v) for e in (e1, coor, e2))
182 | line_pos = np.clip((p-p1) / (p2-p1), 0.05, 0.95)
183 | offset = np.dot(n, coor) - c
184 | offset = np.clip(offset, -max_offset, max_offset)
185 | pos = line_pos, offset
186 | else: raise Exception("Unexpected type {}".format(type(objn)))
187 |
188 | self.vis.gi_label_position[obj] = pos
189 | if obj != self.obj_edit: self.on_reset()
190 | if self.obj_edit is None:
191 | self.vis.gi_label_show[obj] = True
192 | self.vis.visible_export()
193 | if prev_hidden:
194 | self.confirm = self.label_activate, obj, objn
195 | else:
196 | self.viewport.edited_label[0] = self.new_text
197 | self.viewport.edited_label[2] = pos
198 | self.viewport.darea.queue_draw()
199 |
200 | if self.cursor is None: self.confirm_next = self.update_basic
201 | else: self.confirm_next = self.update_edited
202 |
--------------------------------------------------------------------------------