├── 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 | 22 | 24 | 46 | 52 | 53 | 55 | 56 | 58 | image/svg+xml 59 | 61 | 62 | 63 | 64 | 65 | 67 | 68 | 69 | 70 | 71 | 75 | 81 | 82 | 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 | 21 | 23 | 24 | 26 | image/svg+xml 27 | 29 | 30 | 31 | 32 | 33 | 57 | 59 | 61 | 65 | 70 | 71 | 75 | 80 | 81 | 82 | 83 | 92 | 93 | -------------------------------------------------------------------------------- /images/svg/circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 44 | 50 | 51 | 53 | 54 | 56 | image/svg+xml 57 | 59 | 60 | 61 | 62 | 63 | 65 | 66 | 67 | 68 | 69 | 73 | 79 | 85 | 91 | 92 | 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 | 22 | 24 | 45 | 51 | 52 | 54 | 55 | 57 | image/svg+xml 58 | 60 | 61 | 62 | 63 | 64 | 66 | 67 | 68 | 69 | 70 | 74 | 80 | 86 | 92 | 98 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /images/svg/perpline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 46 | 52 | 53 | 55 | 56 | 58 | image/svg+xml 59 | 61 | 62 | 63 | 64 | 65 | 67 | 68 | 69 | 70 | 71 | 75 | 80 | 86 | 92 | 97 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /images/svg/line.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 46 | 52 | 53 | 55 | 56 | 58 | image/svg+xml 59 | 61 | 62 | 63 | 64 | 65 | 67 | 68 | 69 | 70 | 71 | 75 | 78 | 83 | 88 | 89 | 94 | 101 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /images/svg/hide.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 44 | 51 | 52 | 54 | 55 | 57 | image/svg+xml 58 | 60 | 61 | 62 | 63 | 64 | 66 | 67 | 68 | 69 | 70 | 74 | 80 | 92 | 97 | 102 | 108 | 109 | 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 | 22 | 24 | 44 | 51 | 52 | 54 | 55 | 57 | image/svg+xml 58 | 60 | 61 | 62 | 63 | 64 | 66 | 67 | 68 | 69 | 70 | 74 | 81 | 87 | 93 | 100 | 106 | 112 | 118 | 124 | 130 | 131 | 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 | ![GeoLogic Screenshot](images/screenshot.png) 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 | 22 | 24 | 44 | 51 | 52 | 54 | 55 | 57 | image/svg+xml 58 | 60 | 61 | 62 | 63 | 64 | 66 | 67 | 68 | 69 | 70 | 74 | 79 | 84 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /images/svg/derive2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 25 | 28 | 32 | 36 | 37 | 48 | 49 | 69 | 76 | 77 | 79 | 80 | 82 | image/svg+xml 83 | 85 | 86 | 87 | 88 | 89 | 91 | 92 | 93 | 94 | 95 | 99 | 103 | 107 | 111 | 115 | 116 | 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 | 20 | 22 | 43 | 48 | 51 | 54 | 60 | 66 | 72 | 78 | 84 | 90 | 96 | 102 | 103 | 106 | 112 | 118 | 119 | 120 | 121 | 123 | 124 | 125 | image/svg+xml 126 | 128 | 130 | 131 | 133 | Openclipart 134 | 135 | 136 | 137 | 2011-09-15T11:19:02 138 | a dedicated set of mutlitouch functions icons in cartoon style 139 | https://openclipart.org/detail/160435/multitouch-interface-mouse-theme-1-finger-double-tap-by-benbois 140 | 141 | 142 | BenBois 143 | 144 | 145 | 146 | 147 | android 148 | cartoon 149 | gesture 150 | ios 151 | ipad 152 | iphone 153 | mickey 154 | mouse 155 | multitouch 156 | tablet 157 | 158 | 159 | 160 | 162 | 164 | 166 | 168 | 169 | 170 | 171 | 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 | --------------------------------------------------------------------------------