Skip to content

Pretty print - #1162

Draft
mmatera wants to merge 27 commits into
masterfrom
pretty_print
Draft

Pretty print#1162
mmatera wants to merge 27 commits into
masterfrom
pretty_print

Conversation

@mmatera

@mmatera mmatera commented Nov 10, 2024

Copy link
Copy Markdown
Contributor

This is a (partial) reimplementation of #1155, based on sympy.printing.pretty.stringpict.prettyForm objects. #1155 was written following the same idea of that class, but in a way that supports "standard" string operations instead of "prettyPrint". With this I mean for example, that text1_TextBlock + text2_TextBlock returns the concatenation of both text blocks.

In prettyForm objects, text1_TextBlock + text2_TextBlock produces a block with a sign + or - depending on the second argument represents a positive or a negative quantity. I find it weird, but it seems it works for Sympy.

Issues with the current implementation of (Sympy) PrettyForm

sympy.printing.pretty.stringpict.prettyForm also has other problems, like bugs in the implementation of prettyForm.root, and a weird interface to concatenate text blocks. Also, some features I would like to have for these objects are still not implemented, like handling text aligning.

All of this can be improved by asking Sympy's guys to consider implementing some fixes, something that I have eventually planned to do.

Why not building over the higher level PrettyPrinter?

Regarding using PrettyPrinter instead of this lower-level class is motivated in that PrettyPrinter is designed to work with Sympy objects. The equivalent of the "head" of a Mathics expression in Sympy is the class of the object. So, to support Mathics objects by subclassing PrettyPrinter essentially would mean to define methods _print_Expression or _print_Atom, and do what I have already done in mathics.format.prettyprint: define a dispatch table to associate heads and Mathics types to formatter functions.

Another possibility would be to implement a special converter from Mathics expressions to Sympy, which would require duplicating the conversion function. Also, it would necessarily be a less flexible approach, and the representation of certain WL objects would be less precise or even more hacky.

@rocky

rocky commented Nov 10, 2024

Copy link
Copy Markdown
Member

This is a (partial) reimplementation of #1155, based on sympy.printing.pretty.stringpict.prettyForm objects. #1155 was written following the same idea of that class, but in a way that supports "standard" string operations instead of "prettyPrint". With this I mean for example, that text1_TextBlock + text2_TextBlock returns the concatenation of both text blocks.

In prettyForm objects, text1_TextBlock + text2_TextBlock produces a block with a sign + or - depending on the second argument represents a positive or a negative quantity. I find it weird, but it seems it works for Sympy.

Issues with the current implementation of (Sympy) PrettyForm

sympy.printing.pretty.stringpict.prettyForm also has other problems, like bugs in the implementation of prettyForm.root, and a weird interface to concatenate text blocks. Also, some features I would like to have for these objects are still not implemented, like handling text aligning.

All of this can be improved by asking Sympy's guys to consider implementing some fixes, something that I have eventually planned to do.

Why not building over the higher level PrettyPrinter?

Regarding using PrettyPrinter instead of this lower-level class is motivated in that PrettyPrinter is designed to work with Sympy objects. The equivalent of the "head" of a Mathics expression in Sympy is the class of the object. So, to support Mathics objects by subclassing PrettyPrinter essentially would mean to define methods _print_Expression or _print_Atom, and do what I have already done in mathics.format.prettyprint: define a dispatch table to associate heads and Mathics types to formatter functions.

Another possibility would be to implement a special converter from Mathics expressions to Sympy, which would require duplicating the conversion function. Also, it would necessarily be a less flexible approach, and the representation of certain WL objects would be less precise or even more hacky.

Briefly looking at the code, I have a gut feeling that something is amiss. It doesn't fully follow the kind of tree parsing pattern that I often have seen in code. And instead, the code keeps stuff like String("/") which is reminiscent of boxing code from Mathics that feels a little too rigid and should be more table-based.

An approach that can build on and contribute back to SymPy feels like a better approach overall, but this is more of a gut feeling that digging into the code deeply right now with more concrete suggestions.

Let's defer merging this until we have boxing under better control.

@mmatera

mmatera commented Nov 13, 2024

Copy link
Copy Markdown
Contributor Author

@rocky, I put a PR in Sympy related to this sympy/sympy#27257
If it get's merged, then I can reformulate this using the sympy.printing.pretty.prettyForm class directly.

@rocky

rocky commented Nov 14, 2024

Copy link
Copy Markdown
Member

@rocky, I put a PR in Sympy related to this sympy/sympy#27257 If it get's merged, then I can reformulate this using the sympy.printing.pretty.prettyForm class directly.

I am very glad to see some sort of collaboration (as opposed to reinvention) here. Thanks for engaging with SymPy.

Let's wait on this to see where things go.

@mmatera mmatera mentioned this pull request Jan 31, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'Mathics3 Core Benchmarks'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.50.

Benchmark suite Current: 90cf764 Previous: 11107cf Ratio
test/timings/test_patterns.py::test_orderless_plus_match_benchmark[n=11] 434.35271281151756 iter/sec (stddev: 0.00022144598453726307) 655.9624096804085 iter/sec (stddev: 0.00015810020296261142) 1.51
test/timings/test_patterns.py::test_orderless_exhaustive_failure_benchmark[n=4] 114.95294056605488 iter/sec (stddev: 0.00019718394612353718) 181.22826733279368 iter/sec (stddev: 0.0001616213814867379) 1.58
test/timings/test_patterns.py::test_orderless_exhaustive_failure_benchmark[n=5] 16.49105688336866 iter/sec (stddev: 0.002548120461168035) 26.07389735356884 iter/sec (stddev: 0.0014981448856669808) 1.58
test/timings/test_patterns.py::test_orderless_exhaustive_failure_benchmark[n=6] 2.009063345496612 iter/sec (stddev: 0.0007365444892242935) 3.248505988002981 iter/sec (stddev: 0.0018704372126017697) 1.62
test/timings/test_patterns.py::test_blank_sequence_backtracking_benchmark[MatchQ[bigList, {a___, 3000, b___}]-SystemTrue]` 0.6704533134771515 iter/sec (stddev: 0.009384368005040146) 1.0186038696427617 iter/sec (stddev: 0.008724957153457408) 1.52
test/timings/test_patterns.py::test_blank_sequence_backtracking_benchmark[MatchQ[bigList, {a___, 3001, b___}]-SystemFalse]` 304.71032803850466 iter/sec (stddev: 0.000024038523929241808) 468.351290351065 iter/sec (stddev: 0.0001732508127826809) 1.54
test/timings/test_patterns.py::test_get_match_candidates_benchmark[n=10] 4840.1122388022695 iter/sec (stddev: 0.000009149153798856202) 7644.386015362796 iter/sec (stddev: 0.00000589139036201033) 1.58
test/timings/test_patterns.py::test_get_match_candidates_benchmark[n=100] 512.3374918187573 iter/sec (stddev: 0.00003728734139466469) 785.9403487585644 iter/sec (stddev: 0.000012527751978590962) 1.53
test/timings/test_patterns.py::test_get_match_candidates_benchmark[n=1000] 50.881604133700314 iter/sec (stddev: 0.00019704314389202258) 79.18347198457376 iter/sec (stddev: 0.00013507608507845872) 1.56
test/timings/test_patterns.py::test_get_match_candidates_benchmark[n=5000] 10.33860924655988 iter/sec (stddev: 0.0019397741571019439) 15.572208767590576 iter/sec (stddev: 0.0009039125485853565) 1.51
test/timings/test_patterns.py::test_get_match_candidates_count_benchmark[n=10] 4871.208366170652 iter/sec (stddev: 0.00000893435656554904) 7399.00043134964 iter/sec (stddev: 0.000016605594559593932) 1.52
test/timings/test_patterns.py::test_get_match_candidates_count_benchmark[n=100] 521.3848985123985 iter/sec (stddev: 0.00004062416866103663) 783.0177452265693 iter/sec (stddev: 0.00001651809868056019) 1.50
test/timings/test_patterns.py::test_get_match_candidates_count_benchmark[n=1000] 51.029987012576335 iter/sec (stddev: 0.00033107862848192655) 78.53635216307391 iter/sec (stddev: 0.00007747215001173897) 1.54
test/timings/test_patterns.py::test_get_match_candidates_count_benchmark[n=5000] 10.268729342221524 iter/sec (stddev: 0.001743346486800647) 15.851448797502806 iter/sec (stddev: 0.00023081424027309718) 1.54
test/timings/test_patterns.py::test_orderless_repeated_names_scaling_benchmark[match-n=6] 57.27288459101047 iter/sec (stddev: 0.052829514048921916) 1615.5529932884122 iter/sec (stddev: 0.00040016768863210684) 28.21
test/timings/test_patterns.py::test_orderless_repeated_names_scaling_benchmark[fail-n=4] 1101.0332315894289 iter/sec (stddev: 0.00011387646617462781) 1730.8680233807067 iter/sec (stddev: 0.000014109838657042502) 1.57
test/timings/test_patterns.py::test_orderless_repeated_names_scaling_benchmark[fail-n=6] 766.8776935092071 iter/sec (stddev: 0.00039340122714096834) 1536.5661250463622 iter/sec (stddev: 0.00001857806356873446) 2.00
test/timings/test_patterns.py::test_orderless_repeated_names_scaling_benchmark[fail-n=8] 855.1391683410718 iter/sec (stddev: 0.00013283350346074343) 1303.6470307997545 iter/sec (stddev: 0.0001068584128537411) 1.52
test/timings/test_patterns.py::test_orderless_repeated_names_scaling_benchmark[fail-n=10] 750.0384957286614 iter/sec (stddev: 0.0001400139217057392) 1172.3407764761887 iter/sec (stddev: 0.00010819719463373896) 1.56
test/timings/test_patterns.py::test_orderless_repeated_names_scaling_benchmark[fail-n=12] 697.8922259114946 iter/sec (stddev: 0.00010709230434587356) 1056.6642546590003 iter/sec (stddev: 0.0001363997596266633) 1.51
test/timings/test_patterns.py::test_blank_sequence_position_scaling_benchmark[literal-first-n=300] 1030.5981499157544 iter/sec (stddev: 0.000013277216999397663) 1556.2543137123205 iter/sec (stddev: 0.000016140743232458547) 1.51
test/timings/test_patterns.py::test_blank_sequence_position_scaling_benchmark[literal-first-n=1000] 522.326425186307 iter/sec (stddev: 0.000021947639228805754) 805.6797846653858 iter/sec (stddev: 0.000030387126789931407) 1.54
test/timings/test_patterns.py::test_blank_sequence_position_scaling_benchmark[literal-first-n=2000] 310.95867502830424 iter/sec (stddev: 0.000042668428683861946) 480.11823199515936 iter/sec (stddev: 0.00002396762316410957) 1.54
test/timings/test_patterns.py::test_blank_sequence_position_scaling_benchmark[literal-middle-n=100] 373.2911663659443 iter/sec (stddev: 0.00002007018566392978) 599.1934496642235 iter/sec (stddev: 0.00003329315150473894) 1.61
test/timings/test_patterns.py::test_blank_sequence_position_scaling_benchmark[literal-middle-n=300] 67.22178144895754 iter/sec (stddev: 0.00012307815341901565) 113.31064317912264 iter/sec (stddev: 0.00007063256806277752) 1.69
test/timings/test_patterns.py::test_blank_sequence_position_scaling_benchmark[literal-middle-n=1000] 7.5229727684098995 iter/sec (stddev: 0.0006323454341728561) 12.173808092548846 iter/sec (stddev: 0.00037301159839629716) 1.62
test/timings/test_patterns.py::test_blank_sequence_position_scaling_benchmark[literal-middle-n=2000] 2.0847672870897087 iter/sec (stddev: 0.003059402222316694) 3.137322234063359 iter/sec (stddev: 0.002356055380067824) 1.50
test/timings/test_patterns.py::test_blank_sequence_position_scaling_benchmark[literal-last-n=100] 262.6952607298852 iter/sec (stddev: 0.0000655382649982556) 417.7978369013429 iter/sec (stddev: 0.000036304085821634565) 1.59
test/timings/test_patterns.py::test_blank_sequence_position_scaling_benchmark[literal-last-n=300] 47.49645858100445 iter/sec (stddev: 0.0002173858393236407) 78.1919646981202 iter/sec (stddev: 0.00005245127442438013) 1.65
test/timings/test_patterns.py::test_blank_sequence_position_scaling_benchmark[literal-last-n=1000] 5.621736062907763 iter/sec (stddev: 0.0008979337441516396) 8.584320523612341 iter/sec (stddev: 0.0016037790488133848) 1.53
test/timings/test_patterns.py::test_blank_sequence_position_scaling_benchmark[literal-absent-n=300] 1256.7204698396897 iter/sec (stddev: 0.00001666940692797309) 1905.4544013269442 iter/sec (stddev: 0.000014838427721074915) 1.52
test/timings/test_patterns.py::test_blank_sequence_position_scaling_benchmark[literal-absent-n=1000] 684.8329096823023 iter/sec (stddev: 0.000038263707741485334) 1075.2120506287617 iter/sec (stddev: 0.000016446512819736496) 1.57
test/timings/test_patterns.py::test_blank_sequence_position_scaling_benchmark[literal-absent-n=2000] 428.22242729143136 iter/sec (stddev: 0.000018368602447607362) 660.9040678561984 iter/sec (stddev: 0.000018755371151916586) 1.54
test/timings/test_patterns.py::test_multiple_blank_sequences_benchmark[match-n=30] 859.9114033030986 iter/sec (stddev: 0.000024883840911586914) 1320.334551652687 iter/sec (stddev: 0.00002297207280147043) 1.54
test/timings/test_patterns.py::test_multiple_blank_sequences_benchmark[match-n=60] 523.889299664117 iter/sec (stddev: 0.000016924891407931474) 830.3645034494309 iter/sec (stddev: 0.00001799547015017952) 1.58
test/timings/test_patterns.py::test_multiple_blank_sequences_benchmark[match-n=100] 302.1913648544599 iter/sec (stddev: 0.000024177791872838412) 482.6182852785447 iter/sec (stddev: 0.00009652555124457837) 1.60
test/timings/test_patterns.py::test_multiple_blank_sequences_benchmark[match-n=150] 170.9483939960532 iter/sec (stddev: 0.00012304789067537868) 286.56340583568476 iter/sec (stddev: 0.000018330704785767418) 1.68
test/timings/test_patterns.py::test_multiple_blank_sequences_benchmark[fail-n=30] 1508.2150968151868 iter/sec (stddev: 0.00004034819631581197) 2279.032942510329 iter/sec (stddev: 0.00002058310221697063) 1.51
test/timings/test_patterns.py::test_multiple_blank_sequences_benchmark[fail-n=100] 1384.3455436791728 iter/sec (stddev: 0.000017893319923863104) 2098.562337891957 iter/sec (stddev: 0.000012195621502051837) 1.52
test/timings/test_uniform_tables.py::test_evaluate_benchmark[Plus@@uniformTable-2.075674547634748] 135.1944398203771 iter/sec (stddev: 0.00009045592861755487) 205.9796107581378 iter/sec (stddev: 0.00009345957581376688) 1.52
test/timings/test_uniform_tables.py::test_evaluate_benchmark[MatchQ[uniformTable,{__Real}]-SystemTrue]` 657.6627160338596 iter/sec (stddev: 0.0001195450298578684) 988.9830257873544 iter/sec (stddev: 0.000118320287910051) 1.50
test/timings/test_uniform_tables.py::test_evaluate_benchmark[Length[F@@uniformTable]-0] 316.8836847206561 iter/sec (stddev: 0.000019505555665841485) 495.57811752004164 iter/sec (stddev: 0.00003106871342288367) 1.56
test/timings/test_uniform_tables.py::test_evaluate_benchmark[Plus@@nonuniformTable-2.075674547634748] 134.80046383813365 iter/sec (stddev: 0.00004518833388496668) 207.5541857302082 iter/sec (stddev: 0.000037145532188691984) 1.54

This comment was automatically generated by workflow using github-action-benchmark.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants