Compare commits

...

42 Commits

Author SHA1 Message Date
35831eaa72 add repo links and table of contents 2025-08-10 05:01:46 -05:00
40e3eb831a render site again 2025-08-08 04:25:27 -05:00
e693e840ae fix remaining links 2025-08-08 04:25:11 -05:00
e30024cbd5 fix freeze 2025-08-08 04:22:49 -05:00
b4ed75e95c update links following renaming 2025-08-08 04:22:45 -05:00
baf09ec891 rename posts to posts/math 2025-08-08 04:06:40 -05:00
b4813a5862 add topics to navbar 2025-08-08 04:01:33 -05:00
77897b5488 disable breadcrumbs on topic indices 2025-08-08 03:38:37 -05:00
9d63317254 moved posts by topic page to posts/index, add reverse chronological listing on site index 2025-08-08 03:37:37 -05:00
318338e39b add about page 2025-08-08 03:22:06 -05:00
5a696f143e add sidebars 2025-08-08 02:26:38 -05:00
5cab55a9fd fix typo 2025-08-07 22:52:41 -05:00
bd8f52d1bb freeze type-algebra 2025-08-07 22:41:46 -05:00
3291ddded3 add index to type-algebra 2025-08-07 22:38:19 -05:00
64d2b07865 ensure consistent function order in type-algebra.* 2025-08-07 22:33:15 -05:00
71b4f64594 update dates on type-algebra.* 2025-08-07 22:16:34 -05:00
12fe38bfd5 revisions to type-algebra.3 2025-08-07 18:24:02 -05:00
dd3b9a4ee7 revisions to type-algebra.2 2025-08-07 00:54:51 -05:00
125c7a6c02 revisions to type-algebra.1 2025-08-06 04:11:37 -05:00
c47d9cb6f1 import type-algebra from from-wordpress 2025-08-05 23:37:15 -05:00
3e7746714c freeze posts 2025-08-05 04:07:50 -05:00
a699e24eaf add finite-field index 2025-08-05 04:05:56 -05:00
5c6795163f add extra post as a child of finite-field.2 2025-08-05 04:05:36 -05:00
46128385fc add taglines to finite-field.{2,3,4} 2025-08-05 03:32:38 -05:00
93889bc2a9 add cleanup makefile for removing build artifacts 2025-08-05 03:20:27 -05:00
3581c804cf haskellify finite-field.4 2025-08-05 03:17:36 -05:00
91cfd2397a haskellify finite-field.3 2025-08-04 05:33:30 -05:00
8d14c5b414 minor cleanups in finite-field.2 2025-08-03 22:56:15 -05:00
52913ce62d relocate gitignore 2025-08-03 22:43:19 -05:00
ee4b2cd0a5 haskellify finite-field.2 2025-08-03 22:40:35 -05:00
9385797c63 haskellify finite-field.1 2025-08-01 04:32:34 -05:00
86778da52a posts/finite-field/2/char_2_irreducibles_graphs.png: convert to Git LFS 2025-07-31 23:54:38 -05:00
bee03e467f remove extraneous quotes from descriptions 2025-07-31 23:36:17 -05:00
8d590251f6 initial revisions to finite.4 2025-07-31 23:36:17 -05:00
b0ce5e8f83 initial revisions to finite.3 2025-07-31 23:36:16 -05:00
72e2489e3c initial revisions to finite.2 2025-07-31 23:36:16 -05:00
a293cd57e9 revisions to finite.1 2025-07-31 23:36:16 -05:00
6fc358b8a5 add finite-field from from-wordpress 2025-07-31 23:36:16 -05:00
dea3fa5e5b revisions to misc.infinitesimals 2025-07-31 23:12:35 -05:00
8c0839c086 import misc.infinitesimals from from-wordpress 2025-07-31 00:42:37 -05:00
398eedc07a remove code block hiding from number-number 2025-07-30 17:59:37 -05:00
497f057b9b add frozen output for stereo.1 2025-07-30 03:37:32 -05:00
358 changed files with 31050 additions and 246 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,334 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="460.8pt" height="345.6pt" viewBox="0 0 460.8 345.6" xmlns="http://www.w3.org/2000/svg" version="1.1">
<metadata>
<rdf:RDF xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<cc:Work>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:date>2025-08-03T22:53:53.738594</dc:date>
<dc:format>image/svg+xml</dc:format>
<dc:creator>
<cc:Agent>
<dc:title>Matplotlib v3.10.3, https://matplotlib.org/</dc:title>
</cc:Agent>
</dc:creator>
</cc:Work>
</rdf:RDF>
</metadata>
<defs>
<style type="text/css">*{stroke-linejoin: round; stroke-linecap: butt}</style>
</defs>
<g id="figure_1">
<g id="patch_1">
<path d="M 0 345.6
L 460.8 345.6
L 460.8 0
L 0 0
z
" style="fill: #ffffff"/>
</g>
<g id="axes_1">
<g id="patch_2">
<path d="M 57.6 307.584
L 414.72 307.584
L 414.72 41.472
L 57.6 41.472
z
" style="fill: #ffffff"/>
</g>
<g id="patch_3">
<path d="M 390.514075 187.957836
Q 236.158737 122.454172 82.832593 57.387266
" clip-path="url(#p8fa51a7528)" style="fill: none; stroke: #000000; stroke-linecap: round"/>
<path d="M 85.733454 60.790941
L 82.832593 57.387266
L 87.29605 57.108782
z
" clip-path="url(#p8fa51a7528)" style="stroke: #000000; stroke-linecap: round"/>
</g>
<g id="patch_4">
<path d="M 279.499516 173.899003
Q 334.710372 181.992361 388.815017 189.92356
" clip-path="url(#p8fa51a7528)" style="fill: none; stroke: #000000; stroke-linecap: round"/>
<path d="M 385.147394 187.364549
L 388.815017 189.92356
L 384.567234 191.322252
z
" clip-path="url(#p8fa51a7528)" style="stroke: #000000; stroke-linecap: round"/>
</g>
<g id="patch_5">
<path d="M 263.518245 168.164289
Q 172.381729 113.105228 82.202167 58.624299
" clip-path="url(#p8fa51a7528)" style="fill: none; stroke: #000000; stroke-linecap: round"/>
<path d="M 84.591676 62.404543
L 82.202167 58.624299
L 86.660068 58.980838
z
" clip-path="url(#p8fa51a7528)" style="stroke: #000000; stroke-linecap: round"/>
</g>
<g id="patch_6">
<path d="M 267.221177 180.467066
Q 241.802173 234.06546 216.862251 286.653664
" clip-path="url(#p8fa51a7528)" style="fill: none; stroke: #000000; stroke-linecap: round"/>
<path d="M 220.383344 283.89651
L 216.862251 286.653664
L 216.769184 282.182497
z
" clip-path="url(#p8fa51a7528)" style="stroke: #000000; stroke-linecap: round"/>
</g>
<g id="patch_7">
<path d="M 220.228666 291.252913
Q 305.581235 243.413972 389.958514 196.121668
" clip-path="url(#p8fa51a7528)" style="fill: none; stroke: #000000; stroke-linecap: round"/>
<path d="M 385.491361 196.332723
L 389.958514 196.121668
L 387.447066 199.822023
z
" clip-path="url(#p8fa51a7528)" style="stroke: #000000; stroke-linecap: round"/>
</g>
<g id="patch_8">
<path d="M 208.361368 287.975988
Q 143.251403 174.525827 78.697949 62.045353
" clip-path="url(#p8fa51a7528)" style="fill: none; stroke: #000000; stroke-linecap: round"/>
<path d="M 78.954355 66.510133
L 78.697949 62.045353
L 82.423616 64.519096
z
" clip-path="url(#p8fa51a7528)" style="stroke: #000000; stroke-linecap: round"/>
</g>
<g id="patch_9">
<path d="M 216.383108 287.663982
Q 241.802112 234.065589 266.742034 181.477385
" clip-path="url(#p8fa51a7528)" style="fill: none; stroke: #000000; stroke-linecap: round"/>
<path d="M 263.22094 184.234539
L 266.742034 181.477385
L 266.835101 185.948552
z
" clip-path="url(#p8fa51a7528)" style="stroke: #000000; stroke-linecap: round"/>
</g>
<g id="matplotlib.axis_1">
<g id="xtick_1"/>
<g id="xtick_2"/>
<g id="xtick_3"/>
<g id="xtick_4"/>
<g id="xtick_5"/>
<g id="xtick_6"/>
<g id="xtick_7"/>
<g id="xtick_8"/>
<g id="xtick_9"/>
</g>
<g id="matplotlib.axis_2">
<g id="ytick_1"/>
<g id="ytick_2"/>
<g id="ytick_3"/>
<g id="ytick_4"/>
<g id="ytick_5"/>
<g id="ytick_6"/>
<g id="ytick_7"/>
</g>
<g id="PathCollection_1">
<path d="M 398.487273 200.00167
C 400.783999 200.00167 402.986966 199.089171 404.610997 197.465141
C 406.235028 195.84111 407.147527 193.638142 407.147527 191.341416
C 407.147527 189.04469 406.235028 186.841722 404.610997 185.217692
C 402.986966 183.593661 400.783999 182.681162 398.487273 182.681162
C 396.190547 182.681162 393.987579 183.593661 392.363548 185.217692
C 390.739518 186.841722 389.827019 189.04469 389.827019 191.341416
C 389.827019 193.638142 390.739518 195.84111 392.363548 197.465141
C 393.987579 199.089171 396.190547 200.00167 398.487273 200.00167
z
" clip-path="url(#p8fa51a7528)" style="fill: #1f78b4; stroke: #1f78b4"/>
<path d="M 73.832727 62.228254
C 76.129453 62.228254 78.332421 61.315755 79.956452 59.691724
C 81.580482 58.067694 82.492981 55.864726 82.492981 53.568
C 82.492981 51.271274 81.580482 49.068306 79.956452 47.444276
C 78.332421 45.820245 76.129453 44.907746 73.832727 44.907746
C 71.536001 44.907746 69.333034 45.820245 67.709003 47.444276
C 66.084972 49.068306 65.172473 51.271274 65.172473 53.568
C 65.172473 55.864726 66.084972 58.067694 67.709003 59.691724
C 69.333034 61.315755 71.536001 62.228254 73.832727 62.228254
z
" clip-path="url(#p8fa51a7528)" style="fill: #1f78b4; stroke: #1f78b4"/>
<path d="M 270.931712 181.303303
C 273.228439 181.303303 275.431406 180.390804 277.055437 178.766773
C 278.679467 177.142742 279.591966 174.939775 279.591966 172.643049
C 279.591966 170.346322 278.679467 168.143355 277.055437 166.519324
C 275.431406 164.895294 273.228439 163.982795 270.931712 163.982795
C 268.634986 163.982795 266.432019 164.895294 264.807988 166.519324
C 263.183957 168.143355 262.271458 170.346322 262.271458 172.643049
C 262.271458 174.939775 263.183957 177.142742 264.807988 178.766773
C 266.432019 180.390804 268.634986 181.303303 270.931712 181.303303
z
" clip-path="url(#p8fa51a7528)" style="fill: #1f78b4; stroke: #1f78b4"/>
<path d="M 212.672572 304.148254
C 214.969299 304.148254 217.172266 303.235755 218.796297 301.611724
C 220.420327 299.987694 221.332826 297.784726 221.332826 295.488
C 221.332826 293.191274 220.420327 290.988306 218.796297 289.364276
C 217.172266 287.740245 214.969299 286.827746 212.672572 286.827746
C 210.375846 286.827746 208.172879 287.740245 206.548848 289.364276
C 204.924817 290.988306 204.012318 293.191274 204.012318 295.488
C 204.012318 297.784726 204.924817 299.987694 206.548848 301.611724
C 208.172879 303.235755 210.375846 304.148254 212.672572 304.148254
z
" clip-path="url(#p8fa51a7528)" style="fill: #1f78b4; stroke: #1f78b4"/>
</g>
<g id="patch_10">
<path d="M 57.6 307.584
L 57.6 41.472
" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/>
</g>
<g id="patch_11">
<path d="M 414.72 307.584
L 414.72 41.472
" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/>
</g>
<g id="patch_12">
<path d="M 57.6 307.584
L 414.72 307.584
" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/>
</g>
<g id="patch_13">
<path d="M 57.6 41.472
L 414.72 41.472
" style="fill: none; stroke: #000000; stroke-width: 0.8; stroke-linejoin: miter; stroke-linecap: square"/>
</g>
<g id="text_1">
<!-- 10 -->
<g transform="translate(390.852273 194.652666) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-31" d="M 794 531
L 1825 531
L 1825 4091
L 703 3866
L 703 4441
L 1819 4666
L 2450 4666
L 2450 531
L 3481 531
L 3481 0
L 794 0
L 794 531
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-30" d="M 2034 4250
Q 1547 4250 1301 3770
Q 1056 3291 1056 2328
Q 1056 1369 1301 889
Q 1547 409 2034 409
Q 2525 409 2770 889
Q 3016 1369 3016 2328
Q 3016 3291 2770 3770
Q 2525 4250 2034 4250
z
M 2034 4750
Q 2819 4750 3233 4129
Q 3647 3509 3647 2328
Q 3647 1150 3233 529
Q 2819 -91 2034 -91
Q 1250 -91 836 529
Q 422 1150 422 2328
Q 422 3509 836 4129
Q 1250 4750 2034 4750
z
" transform="scale(0.015625)"/>
</defs>
<use xlink:href="#DejaVuSans-31"/>
<use xlink:href="#DejaVuSans-30" transform="translate(63.623047 0)"/>
</g>
</g>
<g id="text_2">
<!-- 16 -->
<g transform="translate(66.197727 56.87925) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-36" d="M 2113 2584
Q 1688 2584 1439 2293
Q 1191 2003 1191 1497
Q 1191 994 1439 701
Q 1688 409 2113 409
Q 2538 409 2786 701
Q 3034 994 3034 1497
Q 3034 2003 2786 2293
Q 2538 2584 2113 2584
z
M 3366 4563
L 3366 3988
Q 3128 4100 2886 4159
Q 2644 4219 2406 4219
Q 1781 4219 1451 3797
Q 1122 3375 1075 2522
Q 1259 2794 1537 2939
Q 1816 3084 2150 3084
Q 2853 3084 3261 2657
Q 3669 2231 3669 1497
Q 3669 778 3244 343
Q 2819 -91 2113 -91
Q 1303 -91 875 529
Q 447 1150 447 2328
Q 447 3434 972 4092
Q 1497 4750 2381 4750
Q 2619 4750 2861 4703
Q 3103 4656 3366 4563
z
" transform="scale(0.015625)"/>
</defs>
<use xlink:href="#DejaVuSans-31"/>
<use xlink:href="#DejaVuSans-36" transform="translate(63.623047 0)"/>
</g>
</g>
<g id="text_3">
<!-- 14 -->
<g transform="translate(263.296712 175.954299) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-34" d="M 2419 4116
L 825 1625
L 2419 1625
L 2419 4116
z
M 2253 4666
L 3047 4666
L 3047 1625
L 3713 1625
L 3713 1100
L 3047 1100
L 3047 0
L 2419 0
L 2419 1100
L 313 1100
L 313 1709
L 2253 4666
z
" transform="scale(0.015625)"/>
</defs>
<use xlink:href="#DejaVuSans-31"/>
<use xlink:href="#DejaVuSans-34" transform="translate(63.623047 0)"/>
</g>
</g>
<g id="text_4">
<!-- 17 -->
<g transform="translate(205.037572 298.79925) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-37" d="M 525 4666
L 3525 4666
L 3525 4397
L 1831 0
L 1172 0
L 2766 4134
L 525 4134
L 525 4666
z
" transform="scale(0.015625)"/>
</defs>
<use xlink:href="#DejaVuSans-31"/>
<use xlink:href="#DejaVuSans-37" transform="translate(63.623047 0)"/>
</g>
</g>
</g>
</g>
<defs>
<clipPath id="p8fa51a7528">
<rect x="57.6" y="41.472" width="357.12" height="266.112"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -6,7 +6,7 @@
<rdf:RDF xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<cc:Work>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:date>2025-07-30T03:24:44.195833</dc:date>
<dc:date>2025-07-30T17:57:28.746680</dc:date>
<dc:format>image/svg+xml</dc:format>
<dc:creator>
<cc:Agent>
@ -41,12 +41,12 @@ z
<g id="xtick_1">
<g id="line2d_1">
<defs>
<path id="m892aac2c98" d="M 0 0
<path id="m704ac02017" d="M 0 0
L 0 3.5
" style="stroke: #000000; stroke-width: 0.8"/>
</defs>
<g>
<use xlink:href="#m892aac2c98" x="50.822178" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m704ac02017" x="50.822178" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_1">
@ -91,7 +91,7 @@ z
<g id="xtick_2">
<g id="line2d_2">
<g>
<use xlink:href="#m892aac2c98" x="125.939735" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m704ac02017" x="125.939735" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_2">
@ -132,7 +132,7 @@ z
<g id="xtick_3">
<g id="line2d_3">
<g>
<use xlink:href="#m892aac2c98" x="201.057292" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m704ac02017" x="201.057292" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_3">
@ -168,7 +168,7 @@ z
<g id="xtick_4">
<g id="line2d_4">
<g>
<use xlink:href="#m892aac2c98" x="276.174848" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m704ac02017" x="276.174848" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_4">
@ -215,7 +215,7 @@ z
<g id="xtick_5">
<g id="line2d_5">
<g>
<use xlink:href="#m892aac2c98" x="351.292405" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m704ac02017" x="351.292405" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_5">
@ -271,7 +271,7 @@ z
<g id="xtick_6">
<g id="line2d_6">
<g>
<use xlink:href="#m892aac2c98" x="426.409961" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m704ac02017" x="426.409961" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_6">
@ -304,12 +304,12 @@ z
<g id="ytick_1">
<g id="line2d_7">
<defs>
<path id="m75d00b8c11" d="M 0 0
<path id="m3fb0ce5ed4" d="M 0 0
L -3.5 0
" style="stroke: #000000; stroke-width: 0.8"/>
</defs>
<g>
<use xlink:href="#m75d00b8c11" x="33.73" y="307.955174" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m3fb0ce5ed4" x="33.73" y="307.955174" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_7">
@ -324,7 +324,7 @@ L -3.5 0
<g id="ytick_2">
<g id="line2d_8">
<g>
<use xlink:href="#m75d00b8c11" x="33.73" y="271.90043" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m3fb0ce5ed4" x="33.73" y="271.90043" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_8">
@ -339,7 +339,7 @@ L -3.5 0
<g id="ytick_3">
<g id="line2d_9">
<g>
<use xlink:href="#m75d00b8c11" x="33.73" y="235.845686" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m3fb0ce5ed4" x="33.73" y="235.845686" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_9">
@ -388,7 +388,7 @@ z
<g id="ytick_4">
<g id="line2d_10">
<g>
<use xlink:href="#m75d00b8c11" x="33.73" y="199.790942" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m3fb0ce5ed4" x="33.73" y="199.790942" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_10">
@ -403,7 +403,7 @@ z
<g id="ytick_5">
<g id="line2d_11">
<g>
<use xlink:href="#m75d00b8c11" x="33.73" y="163.736198" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m3fb0ce5ed4" x="33.73" y="163.736198" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_11">
@ -445,7 +445,7 @@ z
<g id="ytick_6">
<g id="line2d_12">
<g>
<use xlink:href="#m75d00b8c11" x="33.73" y="127.681455" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m3fb0ce5ed4" x="33.73" y="127.681455" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_12">
@ -460,7 +460,7 @@ z
<g id="ytick_7">
<g id="line2d_13">
<g>
<use xlink:href="#m75d00b8c11" x="33.73" y="91.626711" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m3fb0ce5ed4" x="33.73" y="91.626711" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_13">
@ -487,7 +487,7 @@ z
<g id="ytick_8">
<g id="line2d_14">
<g>
<use xlink:href="#m75d00b8c11" x="33.73" y="55.571967" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m3fb0ce5ed4" x="33.73" y="55.571967" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_14">
@ -502,7 +502,7 @@ z
<g id="ytick_9">
<g id="line2d_15">
<g>
<use xlink:href="#m75d00b8c11" x="33.73" y="19.517223" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m3fb0ce5ed4" x="33.73" y="19.517223" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_15">
@ -711,7 +711,7 @@ L 417.607123 38.931316
L 420.541402 34.969256
L 423.475682 28.530909
L 423.475682 28.530909
" clip-path="url(#pcdb8677c09)" style="fill: none; stroke: #1f77b4; stroke-width: 1.5; stroke-linecap: square"/>
" clip-path="url(#p6dddd11e9f)" style="fill: none; stroke: #1f77b4; stroke-width: 1.5; stroke-linecap: square"/>
</g>
<g id="patch_3">
<path d="M 33.73 317.72
@ -736,7 +736,7 @@ L 442.035 14.76
</g>
</g>
<defs>
<clipPath id="pcdb8677c09">
<clipPath id="p6dddd11e9f">
<rect x="33.73" y="14.76" width="408.305" height="302.96"/>
</clipPath>
</defs>

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -6,7 +6,7 @@
<rdf:RDF xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<cc:Work>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:date>2025-07-30T03:24:45.626240</dc:date>
<dc:date>2025-07-30T17:57:30.086732</dc:date>
<dc:format>image/svg+xml</dc:format>
<dc:creator>
<cc:Agent>
@ -41,12 +41,12 @@ z
<g id="xtick_1">
<g id="line2d_1">
<defs>
<path id="m5acaa62661" d="M 0 0
<path id="m82b750d18c" d="M 0 0
L 0 3.5
" style="stroke: #000000; stroke-width: 0.8"/>
</defs>
<g>
<use xlink:href="#m5acaa62661" x="46.890244" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m82b750d18c" x="46.890244" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_1">
@ -105,7 +105,7 @@ z
<g id="xtick_2">
<g id="line2d_2">
<g>
<use xlink:href="#m5acaa62661" x="95.481913" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m82b750d18c" x="95.481913" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_2">
@ -146,7 +146,7 @@ z
<g id="xtick_3">
<g id="line2d_3">
<g>
<use xlink:href="#m5acaa62661" x="144.073583" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m82b750d18c" x="144.073583" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_3">
@ -195,7 +195,7 @@ z
<g id="xtick_4">
<g id="line2d_4">
<g>
<use xlink:href="#m5acaa62661" x="192.665252" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m82b750d18c" x="192.665252" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_4">
@ -231,7 +231,7 @@ z
<g id="xtick_5">
<g id="line2d_5">
<g>
<use xlink:href="#m5acaa62661" x="241.256921" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m82b750d18c" x="241.256921" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_5">
@ -273,7 +273,7 @@ z
<g id="xtick_6">
<g id="line2d_6">
<g>
<use xlink:href="#m5acaa62661" x="289.848591" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m82b750d18c" x="289.848591" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_6">
@ -320,7 +320,7 @@ z
<g id="xtick_7">
<g id="line2d_7">
<g>
<use xlink:href="#m5acaa62661" x="338.44026" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m82b750d18c" x="338.44026" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_7">
@ -347,7 +347,7 @@ z
<g id="xtick_8">
<g id="line2d_8">
<g>
<use xlink:href="#m5acaa62661" x="387.03193" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m82b750d18c" x="387.03193" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_8">
@ -403,7 +403,7 @@ z
<g id="xtick_9">
<g id="line2d_9">
<g>
<use xlink:href="#m5acaa62661" x="435.623599" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m82b750d18c" x="435.623599" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_9">
@ -452,12 +452,12 @@ z
<g id="ytick_1">
<g id="line2d_10">
<defs>
<path id="m37c12ea835" d="M 0 0
<path id="me91bacd9d9" d="M 0 0
L -3.5 0
" style="stroke: #000000; stroke-width: 0.8"/>
</defs>
<g>
<use xlink:href="#m37c12ea835" x="33.73" y="305.0377" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#me91bacd9d9" x="33.73" y="305.0377" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_10">
@ -472,7 +472,7 @@ L -3.5 0
<g id="ytick_2">
<g id="line2d_11">
<g>
<use xlink:href="#m37c12ea835" x="33.73" y="249.300898" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#me91bacd9d9" x="33.73" y="249.300898" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_11">
@ -487,7 +487,7 @@ L -3.5 0
<g id="ytick_3">
<g id="line2d_12">
<g>
<use xlink:href="#m37c12ea835" x="33.73" y="193.564096" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#me91bacd9d9" x="33.73" y="193.564096" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_12">
@ -502,7 +502,7 @@ L -3.5 0
<g id="ytick_4">
<g id="line2d_13">
<g>
<use xlink:href="#m37c12ea835" x="33.73" y="137.827294" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#me91bacd9d9" x="33.73" y="137.827294" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_13">
@ -517,7 +517,7 @@ L -3.5 0
<g id="ytick_5">
<g id="line2d_14">
<g>
<use xlink:href="#m37c12ea835" x="33.73" y="82.090492" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#me91bacd9d9" x="33.73" y="82.090492" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_14">
@ -532,7 +532,7 @@ L -3.5 0
<g id="ytick_6">
<g id="line2d_15">
<g>
<use xlink:href="#m37c12ea835" x="33.73" y="26.35369" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#me91bacd9d9" x="33.73" y="26.35369" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_15">
@ -718,7 +718,7 @@ L 409.458854 32.885347
L 414.798598 30.708128
L 423.475682 28.530909
L 423.475682 28.530909
" clip-path="url(#p5e155994b3)" style="fill: none; stroke: #1f77b4; stroke-width: 1.5; stroke-linecap: square"/>
" clip-path="url(#p34cae5d2d5)" style="fill: none; stroke: #1f77b4; stroke-width: 1.5; stroke-linecap: square"/>
</g>
<g id="patch_3">
<path d="M 33.73 317.72
@ -743,7 +743,7 @@ L 442.035 14.76
</g>
</g>
<defs>
<clipPath id="p5e155994b3">
<clipPath id="p34cae5d2d5">
<rect x="33.73" y="14.76" width="408.305" height="302.96"/>
</clipPath>
</defs>

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -6,7 +6,7 @@
<rdf:RDF xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<cc:Work>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:date>2025-07-30T03:24:47.581890</dc:date>
<dc:date>2025-07-30T17:57:32.094511</dc:date>
<dc:format>image/svg+xml</dc:format>
<dc:creator>
<cc:Agent>
@ -41,12 +41,12 @@ z
<g id="xtick_1">
<g id="line2d_1">
<defs>
<path id="m046502f3ac" d="M 0 0
<path id="m89e3a504e2" d="M 0 0
L 0 3.5
" style="stroke: #000000; stroke-width: 0.8"/>
</defs>
<g>
<use xlink:href="#m046502f3ac" x="52.289318" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m89e3a504e2" x="52.289318" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_1">
@ -91,7 +91,7 @@ z
<g id="xtick_2">
<g id="line2d_2">
<g>
<use xlink:href="#m046502f3ac" x="101.129629" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m89e3a504e2" x="101.129629" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_2">
@ -157,7 +157,7 @@ z
<g id="xtick_3">
<g id="line2d_3">
<g>
<use xlink:href="#m046502f3ac" x="149.96994" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m89e3a504e2" x="149.96994" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_3">
@ -172,7 +172,7 @@ z
<g id="xtick_4">
<g id="line2d_4">
<g>
<use xlink:href="#m046502f3ac" x="198.810251" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m89e3a504e2" x="198.810251" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_4">
@ -199,7 +199,7 @@ z
<g id="xtick_5">
<g id="line2d_5">
<g>
<use xlink:href="#m046502f3ac" x="247.650562" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m89e3a504e2" x="247.650562" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_5">
@ -231,7 +231,7 @@ z
<g id="xtick_6">
<g id="line2d_6">
<g>
<use xlink:href="#m046502f3ac" x="296.490873" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m89e3a504e2" x="296.490873" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_6">
@ -247,7 +247,7 @@ z
<g id="xtick_7">
<g id="line2d_7">
<g>
<use xlink:href="#m046502f3ac" x="345.331184" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m89e3a504e2" x="345.331184" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_7">
@ -263,7 +263,7 @@ z
<g id="xtick_8">
<g id="line2d_8">
<g>
<use xlink:href="#m046502f3ac" x="394.171495" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m89e3a504e2" x="394.171495" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_8">
@ -281,12 +281,12 @@ z
<g id="ytick_1">
<g id="line2d_9">
<defs>
<path id="m47bb0dc095" d="M 0 0
<path id="ma9be8e3392" d="M 0 0
L -3.5 0
" style="stroke: #000000; stroke-width: 0.8"/>
</defs>
<g>
<use xlink:href="#m47bb0dc095" x="33.73" y="285.587879" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#ma9be8e3392" x="33.73" y="285.587879" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_9">
@ -335,7 +335,7 @@ z
<g id="ytick_2">
<g id="line2d_10">
<g>
<use xlink:href="#m47bb0dc095" x="33.73" y="248.865455" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#ma9be8e3392" x="33.73" y="248.865455" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_10">
@ -371,7 +371,7 @@ z
<g id="ytick_3">
<g id="line2d_11">
<g>
<use xlink:href="#m47bb0dc095" x="33.73" y="212.14303" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#ma9be8e3392" x="33.73" y="212.14303" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_11">
@ -386,7 +386,7 @@ z
<g id="ytick_4">
<g id="line2d_12">
<g>
<use xlink:href="#m47bb0dc095" x="33.73" y="175.420606" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#ma9be8e3392" x="33.73" y="175.420606" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_12">
@ -433,7 +433,7 @@ z
<g id="ytick_5">
<g id="line2d_13">
<g>
<use xlink:href="#m47bb0dc095" x="33.73" y="138.698182" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#ma9be8e3392" x="33.73" y="138.698182" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_13">
@ -448,7 +448,7 @@ z
<g id="ytick_6">
<g id="line2d_14">
<g>
<use xlink:href="#m47bb0dc095" x="33.73" y="101.975758" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#ma9be8e3392" x="33.73" y="101.975758" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_14">
@ -504,7 +504,7 @@ z
<g id="ytick_7">
<g id="line2d_15">
<g>
<use xlink:href="#m47bb0dc095" x="33.73" y="65.253333" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#ma9be8e3392" x="33.73" y="65.253333" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_15">
@ -551,7 +551,7 @@ z
<g id="ytick_8">
<g id="line2d_16">
<g>
<use xlink:href="#m47bb0dc095" x="33.73" y="28.530909" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#ma9be8e3392" x="33.73" y="28.530909" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_16">
@ -585,7 +585,7 @@ L 364.867309 273.348939
L 384.403433 273.346137
L 403.939557 273.347538
L 423.475682 273.346837
" clip-path="url(#pa627a7ffc5)" style="fill: none; stroke: #1f77b4; stroke-width: 1.5; stroke-linecap: square"/>
" clip-path="url(#pe6fd9f8a67)" style="fill: none; stroke: #1f77b4; stroke-width: 1.5; stroke-linecap: square"/>
</g>
<g id="patch_3">
<path d="M 33.73 317.72
@ -610,7 +610,7 @@ L 442.035 14.76
</g>
</g>
<defs>
<clipPath id="pa627a7ffc5">
<clipPath id="pe6fd9f8a67">
<rect x="33.73" y="14.76" width="408.305" height="302.96"/>
</clipPath>
</defs>

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -6,7 +6,7 @@
<rdf:RDF xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<cc:Work>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:date>2025-07-30T03:24:48.966696</dc:date>
<dc:date>2025-07-30T17:57:33.434057</dc:date>
<dc:format>image/svg+xml</dc:format>
<dc:creator>
<cc:Agent>
@ -41,12 +41,12 @@ z
<g id="xtick_1">
<g id="line2d_1">
<defs>
<path id="m07f79dce64" d="M 0 0
<path id="m833a6f6579" d="M 0 0
L 0 3.5
" style="stroke: #000000; stroke-width: 0.8"/>
</defs>
<g>
<use xlink:href="#m07f79dce64" x="52.289318" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m833a6f6579" x="52.289318" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_1">
@ -91,7 +91,7 @@ z
<g id="xtick_2">
<g id="line2d_2">
<g>
<use xlink:href="#m07f79dce64" x="101.129629" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m833a6f6579" x="101.129629" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_2">
@ -157,7 +157,7 @@ z
<g id="xtick_3">
<g id="line2d_3">
<g>
<use xlink:href="#m07f79dce64" x="149.96994" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m833a6f6579" x="149.96994" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_3">
@ -172,7 +172,7 @@ z
<g id="xtick_4">
<g id="line2d_4">
<g>
<use xlink:href="#m07f79dce64" x="198.810251" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m833a6f6579" x="198.810251" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_4">
@ -199,7 +199,7 @@ z
<g id="xtick_5">
<g id="line2d_5">
<g>
<use xlink:href="#m07f79dce64" x="247.650562" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m833a6f6579" x="247.650562" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_5">
@ -231,7 +231,7 @@ z
<g id="xtick_6">
<g id="line2d_6">
<g>
<use xlink:href="#m07f79dce64" x="296.490873" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m833a6f6579" x="296.490873" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_6">
@ -247,7 +247,7 @@ z
<g id="xtick_7">
<g id="line2d_7">
<g>
<use xlink:href="#m07f79dce64" x="345.331184" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m833a6f6579" x="345.331184" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_7">
@ -263,7 +263,7 @@ z
<g id="xtick_8">
<g id="line2d_8">
<g>
<use xlink:href="#m07f79dce64" x="394.171495" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m833a6f6579" x="394.171495" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_8">
@ -281,12 +281,12 @@ z
<g id="ytick_1">
<g id="line2d_9">
<defs>
<path id="mb3f1fe34b9" d="M 0 0
<path id="ma722f2b5b1" d="M 0 0
L -3.5 0
" style="stroke: #000000; stroke-width: 0.8"/>
</defs>
<g>
<use xlink:href="#mb3f1fe34b9" x="33.73" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#ma722f2b5b1" x="33.73" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_9">
@ -335,7 +335,7 @@ z
<g id="ytick_2">
<g id="line2d_10">
<g>
<use xlink:href="#mb3f1fe34b9" x="33.73" y="276.407273" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#ma722f2b5b1" x="33.73" y="276.407273" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_10">
@ -371,7 +371,7 @@ z
<g id="ytick_3">
<g id="line2d_11">
<g>
<use xlink:href="#mb3f1fe34b9" x="33.73" y="235.094545" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#ma722f2b5b1" x="33.73" y="235.094545" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_11">
@ -386,7 +386,7 @@ z
<g id="ytick_4">
<g id="line2d_12">
<g>
<use xlink:href="#mb3f1fe34b9" x="33.73" y="193.781818" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#ma722f2b5b1" x="33.73" y="193.781818" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_12">
@ -433,7 +433,7 @@ z
<g id="ytick_5">
<g id="line2d_13">
<g>
<use xlink:href="#mb3f1fe34b9" x="33.73" y="152.469091" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#ma722f2b5b1" x="33.73" y="152.469091" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_13">
@ -448,7 +448,7 @@ z
<g id="ytick_6">
<g id="line2d_14">
<g>
<use xlink:href="#mb3f1fe34b9" x="33.73" y="111.156364" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#ma722f2b5b1" x="33.73" y="111.156364" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_14">
@ -504,7 +504,7 @@ z
<g id="ytick_7">
<g id="line2d_15">
<g>
<use xlink:href="#mb3f1fe34b9" x="33.73" y="69.843636" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#ma722f2b5b1" x="33.73" y="69.843636" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_15">
@ -551,7 +551,7 @@ z
<g id="ytick_8">
<g id="line2d_16">
<g>
<use xlink:href="#mb3f1fe34b9" x="33.73" y="28.530909" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#ma722f2b5b1" x="33.73" y="28.530909" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_16">
@ -585,7 +585,7 @@ L 364.867309 283.857633
L 384.403433 283.857595
L 403.939557 283.857609
L 423.475682 283.857604
" clip-path="url(#pd6aa75d2de)" style="fill: none; stroke: #1f77b4; stroke-width: 1.5; stroke-linecap: square"/>
" clip-path="url(#p415d5a15a1)" style="fill: none; stroke: #1f77b4; stroke-width: 1.5; stroke-linecap: square"/>
</g>
<g id="patch_3">
<path d="M 33.73 317.72
@ -610,7 +610,7 @@ L 442.035 14.76
</g>
</g>
<defs>
<clipPath id="pd6aa75d2de">
<clipPath id="p415d5a15a1">
<rect x="33.73" y="14.76" width="408.305" height="302.96"/>
</clipPath>
</defs>

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -6,7 +6,7 @@
<rdf:RDF xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<cc:Work>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:date>2025-07-30T03:24:50.499195</dc:date>
<dc:date>2025-07-30T17:57:34.918996</dc:date>
<dc:format>image/svg+xml</dc:format>
<dc:creator>
<cc:Agent>
@ -41,12 +41,12 @@ z
<g id="xtick_1">
<g id="line2d_1">
<defs>
<path id="mc3d70383f3" d="M 0 0
<path id="m6d98df33d4" d="M 0 0
L 0 3.5
" style="stroke: #000000; stroke-width: 0.8"/>
</defs>
<g>
<use xlink:href="#mc3d70383f3" x="45.540475" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m6d98df33d4" x="45.540475" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_1">
@ -82,7 +82,7 @@ z
<g id="xtick_2">
<g id="line2d_2">
<g>
<use xlink:href="#mc3d70383f3" x="99.531219" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m6d98df33d4" x="99.531219" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_2">
@ -111,7 +111,7 @@ z
<g id="xtick_3">
<g id="line2d_3">
<g>
<use xlink:href="#mc3d70383f3" x="153.521963" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m6d98df33d4" x="153.521963" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_3">
@ -150,7 +150,7 @@ z
<g id="xtick_4">
<g id="line2d_4">
<g>
<use xlink:href="#mc3d70383f3" x="207.512707" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m6d98df33d4" x="207.512707" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_4">
@ -197,7 +197,7 @@ z
<g id="xtick_5">
<g id="line2d_5">
<g>
<use xlink:href="#mc3d70383f3" x="261.50345" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m6d98df33d4" x="261.50345" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_5">
@ -231,7 +231,7 @@ z
<g id="xtick_6">
<g id="line2d_6">
<g>
<use xlink:href="#mc3d70383f3" x="315.494194" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m6d98df33d4" x="315.494194" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_6">
@ -271,7 +271,7 @@ z
<g id="xtick_7">
<g id="line2d_7">
<g>
<use xlink:href="#mc3d70383f3" x="369.484938" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m6d98df33d4" x="369.484938" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_7">
@ -316,7 +316,7 @@ z
<g id="xtick_8">
<g id="line2d_8">
<g>
<use xlink:href="#mc3d70383f3" x="423.475682" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m6d98df33d4" x="423.475682" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_8">
@ -343,12 +343,12 @@ z
<g id="ytick_1">
<g id="line2d_9">
<defs>
<path id="m25ae3c1345" d="M 0 0
<path id="mba0142a176" d="M 0 0
L -3.5 0
" style="stroke: #000000; stroke-width: 0.8"/>
</defs>
<g>
<use xlink:href="#m25ae3c1345" x="33.73" y="305.0377" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#mba0142a176" x="33.73" y="305.0377" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_9">
@ -373,7 +373,7 @@ z
<g id="ytick_2">
<g id="line2d_10">
<g>
<use xlink:href="#m25ae3c1345" x="33.73" y="270.202199" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#mba0142a176" x="33.73" y="270.202199" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_10">
@ -389,7 +389,7 @@ z
<g id="ytick_3">
<g id="line2d_11">
<g>
<use xlink:href="#m25ae3c1345" x="33.73" y="235.366698" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#mba0142a176" x="33.73" y="235.366698" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_11">
@ -405,7 +405,7 @@ z
<g id="ytick_4">
<g id="line2d_12">
<g>
<use xlink:href="#m25ae3c1345" x="33.73" y="200.531197" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#mba0142a176" x="33.73" y="200.531197" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_12">
@ -421,7 +421,7 @@ z
<g id="ytick_5">
<g id="line2d_13">
<g>
<use xlink:href="#m25ae3c1345" x="33.73" y="165.695695" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#mba0142a176" x="33.73" y="165.695695" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_13">
@ -437,7 +437,7 @@ z
<g id="ytick_6">
<g id="line2d_14">
<g>
<use xlink:href="#m25ae3c1345" x="33.73" y="130.860194" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#mba0142a176" x="33.73" y="130.860194" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_14">
@ -453,7 +453,7 @@ z
<g id="ytick_7">
<g id="line2d_15">
<g>
<use xlink:href="#m25ae3c1345" x="33.73" y="96.024693" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#mba0142a176" x="33.73" y="96.024693" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_15">
@ -469,7 +469,7 @@ z
<g id="ytick_8">
<g id="line2d_16">
<g>
<use xlink:href="#m25ae3c1345" x="33.73" y="61.189192" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#mba0142a176" x="33.73" y="61.189192" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_16">
@ -485,7 +485,7 @@ z
<g id="ytick_9">
<g id="line2d_17">
<g>
<use xlink:href="#m25ae3c1345" x="33.73" y="26.35369" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#mba0142a176" x="33.73" y="26.35369" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_17">
@ -635,7 +635,7 @@ L 315.494194 35.062566
L 369.484938 30.708128
L 423.475682 28.530909
L 423.475682 28.530909
" clip-path="url(#p73b8bb224c)" style="fill: none; stroke: #1f77b4; stroke-width: 1.5; stroke-linecap: square"/>
" clip-path="url(#p3e78a3e766)" style="fill: none; stroke: #1f77b4; stroke-width: 1.5; stroke-linecap: square"/>
</g>
<g id="patch_3">
<path d="M 33.73 317.72
@ -660,7 +660,7 @@ L 442.035 14.76
</g>
</g>
<defs>
<clipPath id="p73b8bb224c">
<clipPath id="p3e78a3e766">
<rect x="33.73" y="14.76" width="408.305" height="302.96"/>
</clipPath>
</defs>

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -6,7 +6,7 @@
<rdf:RDF xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<cc:Work>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:date>2025-07-30T03:24:51.868847</dc:date>
<dc:date>2025-07-30T17:57:36.284432</dc:date>
<dc:format>image/svg+xml</dc:format>
<dc:creator>
<cc:Agent>
@ -41,12 +41,12 @@ z
<g id="xtick_1">
<g id="line2d_1">
<defs>
<path id="mc193fece69" d="M 0 0
<path id="m0e596f21b4" d="M 0 0
L 0 3.5
" style="stroke: #000000; stroke-width: 0.8"/>
</defs>
<g>
<use xlink:href="#mc193fece69" x="59.614794" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m0e596f21b4" x="59.614794" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_1">
@ -123,7 +123,7 @@ z
<g id="xtick_2">
<g id="line2d_2">
<g>
<use xlink:href="#mc193fece69" x="105.720873" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m0e596f21b4" x="105.720873" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_2">
@ -180,7 +180,7 @@ z
<g id="xtick_3">
<g id="line2d_3">
<g>
<use xlink:href="#mc193fece69" x="151.826952" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m0e596f21b4" x="151.826952" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_3">
@ -196,7 +196,7 @@ z
<g id="xtick_4">
<g id="line2d_4">
<g>
<use xlink:href="#mc193fece69" x="197.93303" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m0e596f21b4" x="197.93303" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_4">
@ -212,7 +212,7 @@ z
<g id="xtick_5">
<g id="line2d_5">
<g>
<use xlink:href="#mc193fece69" x="244.039109" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m0e596f21b4" x="244.039109" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_5">
@ -227,7 +227,7 @@ z
<g id="xtick_6">
<g id="line2d_6">
<g>
<use xlink:href="#mc193fece69" x="290.145188" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m0e596f21b4" x="290.145188" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_6">
@ -242,7 +242,7 @@ z
<g id="xtick_7">
<g id="line2d_7">
<g>
<use xlink:href="#mc193fece69" x="336.251266" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m0e596f21b4" x="336.251266" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_7">
@ -257,7 +257,7 @@ z
<g id="xtick_8">
<g id="line2d_8">
<g>
<use xlink:href="#mc193fece69" x="382.357345" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m0e596f21b4" x="382.357345" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_8">
@ -272,7 +272,7 @@ z
<g id="xtick_9">
<g id="line2d_9">
<g>
<use xlink:href="#mc193fece69" x="428.463424" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m0e596f21b4" x="428.463424" y="317.72" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_9">
@ -289,12 +289,12 @@ z
<g id="ytick_1">
<g id="line2d_10">
<defs>
<path id="m395566e9f4" d="M 0 0
<path id="m82b563dc58" d="M 0 0
L -3.5 0
" style="stroke: #000000; stroke-width: 0.8"/>
</defs>
<g>
<use xlink:href="#m395566e9f4" x="33.73" y="305.0377" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m82b563dc58" x="33.73" y="305.0377" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_10">
@ -310,7 +310,7 @@ L -3.5 0
<g id="ytick_2">
<g id="line2d_11">
<g>
<use xlink:href="#m395566e9f4" x="33.73" y="270.202199" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m82b563dc58" x="33.73" y="270.202199" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_11">
@ -326,7 +326,7 @@ L -3.5 0
<g id="ytick_3">
<g id="line2d_12">
<g>
<use xlink:href="#m395566e9f4" x="33.73" y="235.366698" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m82b563dc58" x="33.73" y="235.366698" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_12">
@ -342,7 +342,7 @@ L -3.5 0
<g id="ytick_4">
<g id="line2d_13">
<g>
<use xlink:href="#m395566e9f4" x="33.73" y="200.531197" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m82b563dc58" x="33.73" y="200.531197" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_13">
@ -370,7 +370,7 @@ z
<g id="ytick_5">
<g id="line2d_14">
<g>
<use xlink:href="#m395566e9f4" x="33.73" y="165.695695" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m82b563dc58" x="33.73" y="165.695695" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_14">
@ -386,7 +386,7 @@ z
<g id="ytick_6">
<g id="line2d_15">
<g>
<use xlink:href="#m395566e9f4" x="33.73" y="130.860194" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m82b563dc58" x="33.73" y="130.860194" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_15">
@ -402,7 +402,7 @@ z
<g id="ytick_7">
<g id="line2d_16">
<g>
<use xlink:href="#m395566e9f4" x="33.73" y="96.024693" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m82b563dc58" x="33.73" y="96.024693" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_16">
@ -418,7 +418,7 @@ z
<g id="ytick_8">
<g id="line2d_17">
<g>
<use xlink:href="#m395566e9f4" x="33.73" y="61.189192" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m82b563dc58" x="33.73" y="61.189192" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_17">
@ -434,7 +434,7 @@ z
<g id="ytick_9">
<g id="line2d_18">
<g>
<use xlink:href="#m395566e9f4" x="33.73" y="26.35369" style="stroke: #000000; stroke-width: 0.8"/>
<use xlink:href="#m82b563dc58" x="33.73" y="26.35369" style="stroke: #000000; stroke-width: 0.8"/>
</g>
</g>
<g id="text_18">
@ -614,7 +614,7 @@ L 401.237608 32.885347
L 409.261115 30.708128
L 423.475682 28.530909
L 423.475682 28.530909
" clip-path="url(#p1ecf935b40)" style="fill: none; stroke: #1f77b4; stroke-width: 1.5; stroke-linecap: square"/>
" clip-path="url(#pde11ae1218)" style="fill: none; stroke: #1f77b4; stroke-width: 1.5; stroke-linecap: square"/>
</g>
<g id="patch_3">
<path d="M 33.73 317.72
@ -639,7 +639,7 @@ L 442.035 14.76
</g>
</g>
<defs>
<clipPath id="p1ecf935b40">
<clipPath id="pde11ae1218">
<rect x="33.73" y="14.76" width="408.305" height="302.96"/>
</clipPath>
</defs>

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -2,18 +2,180 @@ project:
type: website
website:
favicon: ./logo-favicon.png
favicon: "./logo-favicon.png"
title: "zenzicubi.co"
repo-url: https://git.zenzicubi.co/cube/zenzicubi.co
repo-branch: master
repo-actions: [source]
repo-link-target: "_blank"
navbar:
logo: "/logo-vector.svg"
logo: "./logo-vector.svg"
left:
- text: "Math"
menu:
- ./posts/math/polycount/index.qmd
- ./posts/math/pentagons/index.qmd
- ./posts/math/chebyshev/index.qmd
- ./posts/math/stereo/index.qmd
- ./posts/math/permutations/index.qmd
- ./posts/math/type-algebra/index.qmd
- ./posts/math/number-number/index.qmd
- ./posts/math/finite-field/index.qmd
- ./posts/math/misc/index.qmd
right:
# - about.qmd
- ./about/index.qmd
- icon: git
href: https://git.zenzicubi.co/cube/zenzicubi.co
- icon: github
href: https://github.com/queue-miscreant
background: primary
search: true
draft-mode: unlinked
sidebar:
- id: topic-sidebar
style: "floating"
contents:
- section: "Topics"
contents:
- ./posts/math/polycount/index.qmd
- ./posts/math/pentagons/index.qmd
- ./posts/math/chebyshev/index.qmd
- ./posts/math/stereo/index.qmd
- ./posts/math/permutations/index.qmd
- ./posts/math/type-algebra/index.qmd
- ./posts/math/number-number/index.qmd
- ./posts/math/finite-field/index.qmd
- ./posts/math/misc/index.qmd
- id: misc-sidebar
style: "floating"
contents:
- section: "Miscellaneous"
contents:
- ./posts/math/misc/platonic-volume/index.qmd
- ./posts/math/misc/infinitesimals/index.qmd
- id: polycount-sidebar
style: "floating"
contents:
- section: "Polynomial Counting"
href: ./posts/math/polycount/index.qmd
contents:
- text: "Part 1: A primer"
href: ./posts/math/polycount/1/index.qmd
- text: "Part 2: Binary and beyond"
href: ./posts/math/polycount/2/index.qmd
- text: "Part 3: The third degree"
href: ./posts/math/polycount/3/index.qmd
- text: "Part 4: Two twos"
href: ./posts/math/polycount/4/index.qmd
contents:
- text: "Appendix"
href: ./posts/math/polycount/4/appendix/index.qmd
- text: "Part 5: Pentamerous multiplication"
href: ./posts/math/polycount/5/index.qmd
- section: 2D
contents:
- text: "Part 1: Lines, leaves, and sand"
href: ./posts/math/polycount/sand-1/index.qmd
- text: "Part 2: Reorienting Polynomials"
href: ./posts/math/polycount/sand-2/index.qmd
- id: pentagons-sidebar
style: "floating"
contents:
- section: "12 Pentagons"
href: ./posts/math/pentagons/index.qmd
contents:
- text: "Part 1"
href: ./posts/math/pentagons/1/index.qmd
- text: "Part 2"
href: ./posts/math/pentagons/2/index.qmd
- text: "Part 3"
href: ./posts/math/pentagons/3/index.qmd
- id: chebyshev-sidebar
style: "floating"
contents:
- section: "Generating Polynomials"
href: ./posts/math/chebyshev/index.qmd
contents:
- text: "Part 1: Regular Constructability"
href: ./posts/math/chebyshev/1/index.qmd
- text: "Part 2: Ghostly Chains"
href: ./posts/math/chebyshev/2/index.qmd
- text: "Extra: Legendary"
href: ./posts/math/chebyshev/extra/index.qmd
- id: stereography-sidebar
style: "floating"
contents:
- section: "Algebraic Stereography"
href: ./posts/math/stereo/index.qmd
contents:
- ./posts/math/stereo/1/index.qmd
- ./posts/math/stereo/2/index.qmd
- id: permutations-sidebar
style: "floating"
contents:
- section: "A Game of Permutations"
href: ./posts/math/permutations/index.qmd
contents:
- text: "Part 1"
href: ./posts/math/permutations/1/index.qmd
- text: "Part 2"
href: ./posts/math/permutations/2/index.qmd
- text: "Part 3"
href: ./posts/math/permutations/3/index.qmd
- text: "Appendix"
href: ./posts/math/permutations/appendix/index.qmd
- id: type-algebra-sidebar
style: "floating"
contents:
- section: "Type Algebra and You"
href: ./posts/math/type-algebra/index.qmd
contents:
- text: "Part 1: Basics"
href: ./posts/math/type-algebra/1/index.qmd
- text: "Part 2: A Fixer-upper"
href: ./posts/math/type-algebra/2/index.qmd
- text: "Part 3: Combinatorial Types"
href: ./posts/math/type-algebra/3/index.qmd
- id: number-number-sidebar
style: "floating"
contents:
- section: "Numbering Numbers"
href: ./posts/math/number-number/index.qmd
contents:
- text: "From 0 to ∞"
href: ./posts/math/number-number/1/index.qmd
- text: "Ordering Obliquely"
href: ./posts/math/number-number/2/index.qmd
- id: finite-field-sidebar
style: "floating"
contents:
- section: "Exploring Finite Fields"
href: ./posts/math/finite-field/index.qmd
contents:
- text: "Part 1: Preliminaries"
href: ./posts/math/finite-field/1/index.qmd
- text: "Part 2: Matrix Boogaloo"
href: ./posts/math/finite-field/2/index.qmd
contents:
- text: "Appendix"
href: ./posts/math/finite-field/2/extra/index.qmd
- text: "Part 3: Roll a d20"
href: ./posts/math/finite-field/2/index.qmd
- text: "Part 5: The Power of Forgetting"
href: ./posts/math/finite-field/2/index.qmd
format:
html:
theme:

63
about/index.qmd Normal file
View File

@ -0,0 +1,63 @@
---
title: "About"
---
This is my personal website (and the third iteration thereof).
The first version used Wordpress since it was quite easy to get into,
didn't require much research, and web hosting services made it easy to set up.
It lasted around three months near the end of 2020, after which I lost my posts because of
hosting troubles and because I wasn't using proper backups.
The second version also used Wordpress, and lasted until the start of 2025
(though the last post I had written up to that point was from the start of 2024).
This version uses [Quarto](https://quarto.org/), an open-source publishing platform that has
some nice features like text-based configuration and Jupyter integration.
As a bonus, it also produces static web pages.
Why Quarto?
-----------
I had a couple of reasons for switching platforms:
- Wordpress is either overkill or not enough.
I don't need a block editor or multiple users, and I don't want to make custom content
just for it to be specific to Wordpress.
- I write a lot of code and LaTeX, which Wordpress relies on plugins for.
Quarto uses (primarily) Pandoc-style Markdown, which allows for inlining of both out of the box.
- Also, because of Jupyter integration, code cells can generate output for the page they're in.
- Since pages are written in Markdown, everything can be edited locally and version-controlled in Git.
The last two are particularly nice in ensuring that the site is reproducibile,
technically even without Quarto.
Instead of articles that live in a Wordpress database or as scattered random files,
I have the complete documents in a structure 1:1 with how the website is organized.
Mathematics
-----------
As of writing, all posts on this site are about math.
In particular, they are dedicated to certain non-obvious insights I choose to investigate.
Typically, although information about these subjects may exist online, it does not exist in a single,
easily-accessible source.
I find writing math posts to be an excellent motivator when it comes to researching things.
It also gives me a chance to learn new tools that otherwise I would not have a reason to use,
not to mention being a good exercise in writing and diagram creation.
An example of this (and one that relates to the creation of the site) is when I was writing code
for what would become the contents of [this post](/posts/polycount/5/).
It was easy enough to learn a library for rendering images (or GIFs),
but I didn't have a gallery to host them, nor a means to share the rationale which produced them.
In a frenzy, I tried gathering my notes in a single text file before eventually putting them on a website.
Along the way, I learned LaTeX to typeset the relevant equations.
I do my best to attribute the programs I use and direct sources I consult along the way,
but extra information is frequently available on Wikipedia,
which I may link to in order to give my explanation some grounding.
Unless otherwise stated, the figures and articles in this category are available under
[CC BY-SA](https://creativecommons.org/licenses/by-sa/4.0/).

View File

@ -1,13 +1,15 @@
---
title: "Posts by topic"
title: "Posts"
listing:
contents:
- posts/number-number/index.*
- posts/permutations/index.*
- posts/stereo/index.*
- posts/chebyshev/index.*
- posts/pentagons/index.*
- posts/polycount/index.*
- posts/misc/*/index.*
sort: false
- posts/math/polycount/*/index.*
- posts/math/pentagons/*/index.*
- posts/math/chebyshev/*/index.*
- posts/math/stereo/*/index.*
- posts/math/permutations/*/index.*
- posts/math/type-algebra/*/index.*
- posts/math/number-number/*/index.*
- posts/math/finite-field/*/index.*
- posts/math/misc/*/index.*
sort: "date desc"
---

View File

@ -27,9 +27,10 @@ Each term of the series is weighted by a geometrically decreasing coefficient *c
$$
[...d_2 d_1 d_0]_p \mapsto e^{2\pi i [d_0] / p}
+ c e^{2\pi i [d_1 d_0] / p^2}
+ c^2 e^{2\pi i [d_2 d_1 d_0] / p^2}
+ ... \\
+ c e^{2\pi i [d_1 d_0] / p^2}
+ c^2 e^{2\pi i [d_2 d_1 d_0] / p^2}
+ ...
\\
f_N(d; p) = \sum_{n = 0}^N c^n e^{2\pi i \cdot [d_{n:0}]_p / p^{n + 1}}
$$

View File

@ -1,5 +0,0 @@
# freeze computational output
freeze: auto
# Enable banner style title blocks
title-block-banner: true

3
posts/math/_metadata.yml Normal file
View File

@ -0,0 +1,3 @@
toc: true
toc-location: right
toc-title: " "

View File

@ -38,7 +38,7 @@ from sympy.abc import z
```
[Recently](/posts/misc/platonic-volume), I used coordinate-free geometry to derive
[Recently](/posts/math/misc/platonic-volume), I used coordinate-free geometry to derive
the volumes of the Platonic solids, a problem which was very accessible to the ancient Greeks.
On the other hand, they found certain problems regarding which figures can be constructed via
compass and straightedge to be very difficult. For example, they struggled with problems
@ -742,16 +742,13 @@ My initial jumping off point for writing this article was completely different.
However, in the process of writing, its share of the article shrank and shrank until its
introduction was only vaguely related to what preceded it.
But alas, the introduction via geometric constructions flows better coming off my
[post about the Platonic solids](/posts/misc/platonic-volume).
[post about the Platonic solids](/posts/math/misc/platonic-volume).
Also, it reads better if I rely less on "if you search for this sequence of numbers"
and more on how to interpret the definition.
Consider reading [the follow-up](../2) to this post if you're interested in another way
one can obtain the Chebyshev polynomials.
I have since rederived the Chebyshev polynomials without the complex exponential,
which you can read about in [this post](/posts/math/stereo/2).
Diagrams created with GeoGebra.
<!--
Update: I have since rederived the Chebyshev polynomials without the complex exponential,
which you can read about in [this post]().
-->

View File

@ -0,0 +1,4 @@
# freeze computational output
freeze: auto
sidebar: chebyshev-sidebar

View File

@ -3,6 +3,9 @@ title: "Chebyshev Polynomials"
listing:
contents: .
sort: "date"
bread-crumbs: false
sidebar:
---
Articles about the generating Chebyshev polynomials (and other related families).

View File

@ -0,0 +1,813 @@
---
title: "Exploring Finite Fields: Preliminaries"
description: |
How to do arithmetic when there is a finite number of numbers
format:
html:
html-math-method: katex
date: "2024-01-09"
date-modified: "2025-07-16"
categories:
- algebra
- finite field
- haskell
---
<style>
.cayley-table .header,
.cayley-table td:nth-child(1),
.cayley-table th:nth-child(1) {
color: green;
}
</style>
```{haskell}
--|echo: false
import Data.List (intercalate)
import Data.Profunctor (rmap)
import Colonnade
import qualified Colonnade.Encode as CE
import IHaskell.Display (markdown)
markdownTable col rows = unlines $ h:h':r where
toColumns = ("| " ++) . (++ " |") . intercalate " | " . foldr (:) []
h = toColumns $ CE.header id col
h' = [if x == '|' then x else '-' | x <- h]
r = map (toColumns . CE.row id col) rows
```
[Fields](https://en.wikipedia.org/wiki/Field_%28mathematics%29) are a basic structure in abstract algebra.
Roughly, a field is a collection of elements paired with two operations,
addition and multiplication, along with particular rules about their interactions.
The most important elements of a field are 0 (the additive identity),
1 (the multiplicative identity), and -1 (which forms additive inverses).
Moreover, multiplicative inverses must also exist for everything but 0.
Certain fields are widely-known, such as the rational numbers $\mathbb{Q}$
and complex numbers $\mathbb{C}$.
Finite fields also exist, such as the field with two elements.
This field only contains the elements 0 and 1[^1].
The addition and multiplication tables are consequently the simplest possible according to familiar rules.
[^1]: The elements -1 and 1 are identical, unlike in most fields.
:::: {layout-ncol="2"}
::: {.cayley-table}
| + | 0 | 1 |
|---|---|---|
| 0 | 0 | 1 |
| 1 | 1 | 0 |
:::
::: {.cayley-table}
| × | 0 | 1 |
|---|---|---|
| 0 | 0 | 0 |
| 1 | 0 | 1 |
:::
::::
This field expresses the parity of sums and products of two integers since,
replacing "0" with "even" and "1" with odd:
:::: {layout-ncol="2"}
::: {}
- even + even = even
- even + odd = odd
- odd + even = odd
- odd + odd = even
:::
::: {}
- even × even = even
- even × odd = even
- odd × even = even
- odd × odd = odd
:::
::::
In One's Prime
--------------
Two is not unique as the only possible size for a finite field -- all prime numbers are also candidates.
Such fields are referred to as GF(*p*) or $\mathbb{F}_p$,
where the prime *p* is the *order* of the field.
These fields inherit the properties of integer arithmetic.
Addition is cyclic mod *p*: 0 and *p* are equivalent to one another.
The role of -1 taken up by *p* - 1, and the additive inverse of an element *x* can be viewed in two ways:
- Multiplying -1 in the field with x, (i.e., $(p - 1)x \mod p$)
- Counting backwards from zero (i.e., $p - x$)
For this to be a field, the product of any two elements in the field cannot be a multiple of *p*,
since that would be congruent to 0.
If there were such a product, then *p* would share factors (other than 1) with one of the two terms.
But the order is prime, so this is impossible.
More strongly, multiplicative inverses [can be found algorithmically](
https://en.wikipedia.org/wiki/Modular_multiplicative_inverse#Extended_Euclidean_algorithm
), although it is a somewhat tricky task.
Polynomials
-----------
For a given field *K*, we can also consider polynomials with coefficients
from elements in the field, K\[*x*\].
The mathematical structure that polynomials belong to is called a
[*ring*](https://en.wikipedia.org/wiki/Ring_%28mathematics%29).
Rings are slightly weaker than fields in the sense that there are not generally multiplicative inverses.
Additive and multiplicative identities must still exist however, corresponding to
the zero polynomial and constant polynomial 1, respectively.
Since GF(*p*) has a finite number of elements to consider, there are only so many choices
for polynomial coefficients for a given degree.
Contrast this with for polynomials over the integers, where there are infinitely many monomials $mx + n$.
Looking at polynomials over GF(2), we have:
| Degree | Polynomial *q(x)* | List of coefficients of *q(x)* (ascending) | *q*(2) | *q*(2) (Binary) |
|--------|-------------------|--------------------------------------------|--------|-----------------|
| 1 | $x$ | [0, 1] | 2 | 10 |
| 1 | $1 + x$ | [1, 1] | 3 | 11 |
| 2 | $x^2$ | [0, 0, 1] | 4 | 100 |
| 2 | $1 + x^2$ | [1, 0, 1] | 5 | 101 |
| 2 | $x + x^2$ | [0, 1, 1] | 6 | 110 |
| 2 | $1 + x + x^2$ | [1, 1, 1] | 7 | 111 |
| 3 | $x^3$ | [0, 0, 0, 1] | 8 | 1000 |
| 3 | $1 + x^3$ | [1, 0, 0, 1] | 9 | 1001 |
| 3 | $x + x^3$ | [0, 1, 0, 1] | 10 | 1010 |
| 3 | $1 + x + x^3$ | [1, 1, 0, 1] | 11 | 1011 |
| ... | ... | ... | ... | ... |
### The Base-ics
There is a very close correspondence between binary expansions and polynomials over GF(2).
This is evident by comparing the list of coefficients in the polynomial (column 3)
with the binary expansions of the polynomial evaluated at 2 (column 5).
This gives a handy way of referring to polynomials (mod *p*) without having to write out
each individual "×" or "+".
In fact, this is commonly used to compactly compute with (and refer to)
[polynomials used in cyclic redundancy checks](
https://en.wikipedia.org/wiki/Mathematics_of_cyclic_redundancy_checks
).
Again, 2 is not unique among primes.
Polynomials over any prime field GF(*p*) can be expressed as integers in base *p*.
To make the correspondence more explicit, I'll use the $_p m$ to denote a decimal integer *m*
whose base *p* expansion should be interpreted as a polynomial (mod *p*).
*m* is also the value of the polynomial at *p*.
For example, $_2 11 = 1 + x + x^3$, and 1 + 2 + 2^3^ = 11.
<details>
<summary>
Haskell implementation of duality between polynomials mod *p* and base *p* expansions of integers
</summary>
<br>
This implementation actually works for any base *b*, which is not necessarily prime.
The only difference is that the coefficients lose "field-ness" for composite *b*.
```{haskell}
import Data.List (unfoldr)
import Data.Tuple (swap)
-- A polynomial is its ascending list of coefficients (of type a)
newtype Polynomial a = Poly { coeffs :: [a] } deriving Functor
-- Interpret a number's base-b expansion as a polynomial
asPoly :: Int -> Int -> Polynomial Int
-- Build a list with f, which returns either Nothing
-- or Just (next element of list, next argument to f)
asPoly b = Poly . unfoldr f where
-- Divide x by b. Emit the remainder and recurse with the quotient.
f x | x /= 0 = Just $ swap $ divMod x b
-- If there's nothing left to divide out, terminate
| otherwise = Nothing
-- Horner evaluation of a polynomial at the integer b
evalPoly :: Int -> Polynomial Int -> Int
-- Start with the highest coefficient
-- Multiply by b at each step and add the coefficient of the next term
evalPoly b (Poly p) = foldr (\y acc -> acc*b + y) 0 p
-- evalPoly n . asPoly n = id :: Int -> Int
```
An interesting detail here is that the duality is expressed through `foldr` using multiplication
and addition and `unfoldr` using divMod.
</details>
### Mono, not Stereo
With respect to their roots (which will soon become of primary interest), polynomials are *projective*.
That is, any scalar multiple of the polynomial has the same roots.
For GF(2), this is insignificant since 1 is the only nonzero element, but over GF(5),
the following polynomials have the same roots:
$$
\begin{align*}
x^2 + 2 \text{ over GF$(5)$} \quad
&\longleftrightarrow \quad {_5 27}
\\
2x^2 + 4 \text{ over GF$(5)$} \quad
&\longleftrightarrow \quad {_5 54}
\\
3x^2 + 1 \text{ over GF$(5)$} \quad
&\longleftrightarrow \quad {_5 76}
\\
4x^2 + 3 \text{ over GF$(5)$} \quad
&\longleftrightarrow \quad {_5 103}
\end{align*}
$$
Only the first polynomial is a *monic* polynomial, or has a leading coefficient of 1.
It is preferable to work with these since the product of two monic polynomials is also monic.
An equivalent condition to this is that when evaluated with the order of the field,
the polynomial has a value between 5^2^ and 2×5^2^ - 1 = 49.
In general, monic polynomials over GF(*p*) as integers fall in the range *p*^2^ and 2*p*^2^ - 1.
<details>
<summary>
Haskell implementation of monic polynomials over GF(*p*)
</summary>
Again, nothing about this definition depends on the base being prime.
```{haskell}
-- All monic polynomials of degree d with coefficients mod n
monics :: Int -> Int -> [Polynomial Int]
monics n d = map (asPoly n) [n^d..2*(n^d) - 1]
-- All monic polynomials with coefficients mod n, ordered by degree
allMonics :: Int -> [Polynomial Int]
allMonics n = concat [monics n d | d <- [1..]]
```
</details>
As an aside, one can also read out monics by counting normally by using the digit alphabet
{1, 0, -1, ..., -*p* + 2}.
Unfortunately, these base-*p* expansions are more difficult to obtain algorithmically,
and I'll leave this as an exercise to the reader.
### Sieving out Irreducibles
Over the integers, we can factor a number into primes.
To decide if a number is prime, we just divide it (using an algorithm like long division)
by numbers less than it and see if we get a nonzero remainder.
Similarly, we can factor polynomials into *irreducible* polynomials,
which have no "smaller" polynomial factors other than 1.
More precisely, by "smaller", we mean those of lesser degree.
For example, over the integers, the polynomial $x^2 - 1$ (degree 2) factors into
$(x + 1)(x - 1)$ (both degree 1), but $x^2 + 1$ is irreducible.
In general, a factorization of a polynomial over the integers implies a factorization of one over GF(*p*),
since the coefficients for each factor may be taken mod *p*.
However, the converse does not hold.
Over GF(2),
$$
(x + 1)^2 = x^2 + 2x + 1 \equiv x^2 + 1 \text{ over GF$(2)$}
$$
...but as just mentioned, the right-hand side is irreducible over the integers.
Just like integers, we can use [polynomial long division](
https://en.wikipedia.org/wiki/Polynomial_long_division
) with these objects to decide if a polynomial is irreducible.
[Synthetic division](https://en.wikipedia.org/wiki/Synthetic_division) is an alternative which is
slightly easier to implement (especially over GF(2), where it is, again, used in CRCs).
It only works for monic polynomials, but this is all we need.
<details>
<summary>
Haskell implementation of synthetic division
</summary>
The algorithm is similar to table-less algorithms for CRCs, but we don't have the luxury
of working at the bit level with XOR for addition.
We also have to watch out for negation and coefficients other than 1 for when not working over GF(2).
```{haskell}
zipAdd :: Num a => [a] -> [a] -> [a]
zipAdd [] ds = ds
zipAdd cs [] = cs
zipAdd (c:cs) (d:ds) = (c + d):zipAdd cs ds
-- Divide the polynomial ps by qs (coefficients in descending degree order)
synthDiv' :: (Eq a, Num a) => [a] -> [a] -> ([a], [a])
synthDiv' ps qs
| head qs /= 1 = error "Cannot divide by non-monic polynomial"
| otherwise = splitAt deg $ doDiv ps deg
where
-- Negate the denominator and ignore leading term
qNeg = map negate $ tail qs
-- The degree of the result, based on the degrees of the numerator and denominator
deg = max 0 (length ps - length qs + 1)
-- Pluck off the head of the list and add a shifted and scaled version of
-- qs to the tail of the list. Repeat this d times
doDiv xs 0 = xs
doDiv (x:xs) d = x:doDiv (zipAdd xs $ map (*x) qNeg) (d - 1)
-- Use Polynomial (coefficients in ascending degree order) instead of lists
synthDiv :: (Eq a, Num a) => Polynomial a -> Polynomial a
-> (Polynomial a, Polynomial a)
synthDiv (Poly p) (Poly q) = (Poly $ reverse quot, Poly $ reverse rem)
where (quot, rem) = synthDiv' (reverse p) (reverse q)
```
</details>
Then, using our list of monic polynomials, we can use the same strategy
for sieving out primes to find (monic) irreducibles.
<details>
<summary>
Haskell implementation of an irreducible polynomial sieve over GF(*p*)
</summary>
```{haskell}
-- All irreducible monic polynomials with coefficients mod n
irreducibles :: Int -> [Polynomial Int]
irreducibles n = go [] $ allMonics n where
-- Divide the polynomial x by i, then take the remainder mod n
remModN x i = fmap (`mod` n) $ snd $ synthDiv x i
-- Find remainders of x divided by every irreducible in "is".
-- If any give the zero polynomial, then x is a multiple of an irreducible
notMultiple x is = and [not $ all (==0) $ coeffs $ remModN x i | i <- is]
-- Sieve out by notMultiple
go is (x:xs)
| notMultiple x is = x:go (x:is) xs
| otherwise = go is xs
```
</details>
Since we can denote polynomials by numbers, it may be tempting to freely switch
between primes and irreducibles.
However, irreducibles depend on the chosen field and do not generally correspond
to the base-*p* expansion of a prime.
```{haskell}
--|code-fold: true
--|classes: plain
texifyPoly :: (Num a, Eq a, Show a) => Polynomial a -> String
texifyPoly (Poly xs) = ("$" ++) $ (++ "$") $ texify' $ zip xs [0..] where
texify' [] = "0"
texify' ((c, n):xs)
| all ((==0) . fst) xs = showPow c n
| c == 0 = texify' xs
| otherwise = showPow c n ++ " + " ++ texify' xs
showPow c 0 = show c
showPow 1 1 = "x"
showPow c 1 = show c ++ showPow 1 1
showPow 1 n = "x^{" ++ show n ++ "}"
showPow c n = show c ++ showPow 1 n
-- Simple prime sieve
primes = primes' [] 2 where
primes' ps n
| and [n `mod` p /= 0 | p <- ps] = n:primes' (n:ps) (n+1)
| otherwise = primes' ps (n+1)
somePrimes = takeWhile (<50) primes
someIrr2 = takeWhile (<50) $ map (evalPoly 2) $ irreducibles 2
irr2PrimeTable = columns (\(_, f) r -> f r) (\(c, _) -> Headed c) [
("Irreducible over GF(2), *q*(x)", texifyPoly . (irreducibles 2 !!)),
("*q*(2), [OEIS A014580](https://oeis.org/A014580)",
(\x -> if x `notElem` somePrimes then redSpan $ show x else show x) .
(someIrr2 !!)),
("Prime",
(\x -> if x `notElem` someIrr2 then greenSpan $ show x else show x) .
(primes !!))
] where
greenSpan x = "<span style=\"color: green\">" ++ x ++ "</span>"
redSpan x = "<span style=\"color: red\">" ++ x ++ "</span>"
markdown $ markdownTable irr2PrimeTable [0..10]
```
The red entry in column 2 is not prime.
Dually, the green entries in column 3 do not have binary expansions which correspond
to irreducible polynomials over GF(2).
Matrices
--------
Along with polynomials over a finite field, we can also look at matrices.
The most interesting matrices are square ones, since the product of two square matrices
is another square matrix.
With the zero matrix ($\bf 0_n$) as the additive identity and the identity matrix
($\bf 1_n$) as the multiplicative, square matrices also form a ring over the field *K*,
denoted $K^{n \times n}$.
Square matrices are associated to a [determinant](https://en.wikipedia.org/wiki/Determinant),
which is an element from the underlying field.
Determinants are nice, since the determinant of the product of two matrices is
the product of the determinants.
The determinant can be implemented using [Laplace expansion](
https://en.wikipedia.org/wiki/Laplace_expansion
), which is also useful for inductive proofs.
<details>
<summary>
Haskell implementation of Laplace expansion
</summary>
Laplace expansion is ludicrously inefficient compared to other algorithms,
and is only shown here due to its "straightforward" implementation and use in proof.
Numeric computation will not be used to keep the arithmetic exact.
```{haskell}
import Data.Array
newtype Matrix a = Mat { unMat :: Array (Int, Int) a } deriving Functor
-- Simple function for building a Matrix from lists
toMatrix :: [[a]] -> Matrix a
toMatrix l = Mat $ listArray ((0,0),(n-1,m-1)) $ concat l where
m = length $ head l
n = length l
determinant :: (Num a, Eq a) => Matrix a -> a
determinant (Mat xs) = determinant' xs where
-- Evaluate (-1)^i without repeated multiplication
parity i = if even i then 1 else -1
-- Map old array addresses to new ones when eliminating row 0, column i
rowMap i (x,y) = (x+1, if y >= i then y+1 else y)
-- Recursive determinant Array
determinant' xs
-- Base case: 1x1 matrix
| n == 0 = xs!(0,0)
-- Sum of cofactor expansions
| otherwise = sum $ map cofactor [0..n] where
-- Produce the cofactor of row 0, column i
cofactor i
| xs!(0,i) == 0 = 0
| otherwise = (parity i) * xs!(0,i) * determinant' (minor i)
-- Furthest extent of the bounds, i.e., the size of the matrix
(_,(n,_)) = bounds xs
-- Build a new Array by eliminating row 0 and column i
minor i = ixmap ((0,0),(n-1,n-1)) (rowMap i) xs
```
</details>
### Back to Polynomials
The [characteristic polynomial](https://en.wikipedia.org/wiki/Characteristic_polynomial)
is a stronger invariant which follows from the determinant.
It is defined as, for *λ* a scalar variable:
$$
\begin{gather*}
\text{charpoly}(A) = p_A(\lambda)
= \left| \lambda I - A \right|
\\ \\
= \left| \begin{matrix*}
\lambda - a_{00} & -a_{01} & ... & -a_{0n} \\
-a_{10} & \lambda - a_{11} & ... & -a_{1n} \\
\vdots & \vdots & \ddots & \vdots \\
-a_{n0} & -a_{n1} & ... & \lambda - a_{nn} \\
\end{matrix*} \right|
\end{gather*}
$$
Laplace expansion never gives *λ* a coefficient before recursing,
so the characteristic polynomial is always monic.
Also, the characteristic polynomial is over the same field that the entries of the matrix were from.
<details>
<summary>
Haskell implementation of the characteristic polynomial
</summary>
Since `determinant` was defined for all `Num` and `Eq`, it can immediately be applied
if these instances are defined for polynomials.
```{haskell}
instance (Eq a, Num a) => (Num (Polynomial a)) where
(+) x@(Poly as) y@(Poly bs) = Poly $ zipAdd as bs
--convolution
(*) (Poly []) (Poly bs) = Poly []
(*) (Poly as) (Poly []) = Poly []
(*) (Poly as) (Poly bs) = Poly $ zeroize $ finish $ foldl convolve' ([], []) as
where convolve' (xs, ys) a = (a:xs, sum (zipWith (*) (a:xs) bs):ys)
finish (xs, ys) = (reverse ys ++) $ finish' xs $ tail bs
finish' xs [] = []
finish' xs ys = sum (zipWith (*) xs ys):finish' xs (tail ys)
zeroize xs = if all (==0) xs then [] else xs
-- this definition works
negate = fmap negate
-- but these two might run into some problems
abs = fmap abs
signum = fmap signum
fromInteger 0 = Poly []
fromInteger a = Poly [fromInteger a]
instance Eq a => Eq (Polynomial a) where
(==) (Poly xs) (Poly ys) = xs == ys
```
Then, along with some helper matrix functions, we can build a function for the characteristic polynomial.
```{haskell}
-- Zero matrix
zero :: Num a => Int -> Matrix a
zero n = Mat $ listArray ((0,0),(n-1,n-1)) $ repeat 0
-- Identity matrix
eye :: Num a => Int -> Matrix a
eye n = Mat $ unMat (zero n) // take n [((i,i), 1) | i <- [0..]]
-- Maps
mapRange :: Ix i => (i -> e) -> (i, i) -> [(i, e)]
mapRange g r = map (\x -> (x, g x)) $ range r
-- Pointwise application
zipWithArr :: Ix i => (a -> b -> c)
-> Array i a -> Array i b -> Array i c
zipWithArr f a b
| ab == bb = array ab $ map (\x -> (x, f (a!x) (b!x))) $ indices a
| otherwise = error "Array dimension mismatch" where
ab = bounds a
bb = bounds b
(|+|) (Mat x) (Mat y) = Mat $ zipWithArr (+) x y
-- Characteristic Polynomial
charpoly :: Matrix Int -> Polynomial Int
charpoly xs = determinant $ eyeLambda |+| negPolyXs where
-- Furthest extent of the bounds, i.e., the size of the matrix
(_,(n,_)) = bounds $ unMat xs
-- Negative of input matrix, after being converted to polynomials
negPolyXs :: Matrix (Polynomial Int)
negPolyXs = fmap (\x -> Poly [-x]) xs
-- Identity matrix times lambda (encoded as Poly [0, 1])
eyeLambda :: Matrix (Polynomial Int)
eyeLambda = (\x -> Poly [x] * Poly [0, 1]) <$> eye (n+1)
markdown $ texifyPoly $ charpoly $ toMatrix [[1,0],[0,1]]
```
</details>
Computation using this definition is only good for demonstrative purposes.
The [Faddeev-LeVerrier algorithm](
https://en.wikipedia.org/wiki/Faddeev%E2%80%93LeVerrier_algorithm
) circumvents Laplace expansion entirely and happens to generate the determinant along the way.
However, it has some problems:
- It inverts the order in which the determinant and characteristic polynomial are defined
- It introduces division, which makes it unsuitable for direct use with matrices with entries mod *p*
Fortunately, we can just work with a matrix over the integers and mod out at the end instead,
as the following diagram conveys:
$$
\begin{array}{}
\mathbb{F}_p ^{n \times n} & \textcolor{green}{\hookrightarrow} &
\normalsize \mathbb{Z}^{n \times n} &
\overset{\mod p ~~}{\longrightarrow} &
\mathbb{F}_p^{n \times n} & \scriptsize \phantom{text{charpoly}}
\\[10pt]
& \scriptsize \textcolor{green}{\text{charpoly (FL)}} &
\textcolor{green}{\downarrow} & &
\textcolor{red}{\downarrow} & \scriptsize \textcolor{red}{\text{charpoly (LE)}}
\\[12pt]
& & \mathbb{Z}[\lambda] &
\textcolor{green}{\underset{\mod p ~~}{\longrightarrow}} &
\mathbb{F}_p[\lambda] & \scriptsize \phantom{text{charpoly}}
\end{array}
$$
The top row are matrices and the bottom row are polynomials.
To get to the bottom-right, which contains the characteristic polynomial over GF(*p*),
we can avoid the red arrow and follow the path in green instead.
### Friends Among Matrices
In the reverse direction, a matrix with a specific characteristic polynomial
can be constructed from a polynomal.
The matrix is called the [companion matrix](https://en.wikipedia.org/wiki/Companion_matrix),
and is defined as
$$
\begin{gather*}
p(\lambda) = \lambda^n + p_{n-1}\lambda^{n-1} + ... + p_1 \lambda + p_0
\\[10pt]
C_{p(\lambda)} = \left( \begin{matrix}
0 & 1 & 0 & ... & 0 \\
0 & 0 & 1 & ... & 0 \\
\vdots & \vdots & \vdots & \ddots & \vdots \\
0 & 0 & 0 & ... & 1 \\
-p_0 & -p_1 & -p_2 & ... & -p_{n-1}
\end{matrix} \right)
= \left( \begin{matrix}
\overrightharpoon 0_{n-1} & \bold{1}_{n-1} \\
-p_0 & -(\overrightharpoon{p}_{1:n-1})^T
\end{matrix} \right)
\\ \\[1pt]
\text{charpoly}(C_{p}) = p_{C_{p}}(\lambda) = p(\lambda)
\end{gather*}
$$
The definition of the companion matrix only depends on elements having an additive inverse,
which is always true in a field.
Therefore, there are always matrices over a field that have a monic polynomial
as their characteristic polynomial.
Proving that the companion matrix has the characteristic polynomial it was constructed from
can be done via Laplace expansion:
$$
\begin{gather*}
p_{0:n-1}(\lambda)
= \left| \begin{matrix}
\textcolor{red}{\lambda} & -1 & 0 & ... & 0 \\
0 & \lambda & -1 & ... & 0 \\
\vdots & \vdots & \vdots & \ddots & \vdots \\
0 & 0 & 0 & ... & -1 \\
\textcolor{green}{p_0} & p_1 & p_2 & ... & \lambda + p_{n-1}
\end{matrix} \right|
\\ \\[1pt]
= \textcolor{green}{p_0} \cdot (-1)^{n-1}
\left| \begin{matrix}
-1 & 0 & ... & 0 \\
\lambda & -1 & ... & 0 \\
\vdots & \vdots & \ddots & \vdots \\
0 & 0 & ... & -1
\end{matrix} \right|
+ \textcolor{red}{\lambda}
\left| \begin{matrix}
\lambda & -1 & ... & 0 \\
\vdots & \vdots & \ddots & \vdots \\
0 & 0 & ... & -1 \\
p_1 & p_2 & ... & \lambda + p_{n-1}
\end{matrix} \right|
\\ \\[1pt]
= \textcolor{green}{p_0} \cdot (-1)^{n-1} \cdot (-1)^{n-1}
+ \textcolor{red}{\lambda} \cdot p_{1:n-1}(\lambda)
\\
= p_0 + \lambda(p_1 + \lambda ( \cdots (p_{n-1} + \lambda ) \cdots )))
\end{gather*}
$$
Pleasantly, this yields the Horner form, which was used above to evaluate polynomials.
<details>
<summary>
Haskell implementation of the companion matrix
</summary>
```{haskell}
companion :: Polynomial Int -> Matrix Int
companion (Poly ps)
| last ps' /= 1 = error "Cannot find companion matrix of non-monic polynomial"
| otherwise = Mat $ array ((0,0), (n-1,n-1)) $ lastRow ++ shiftI where
-- The degree of the polynomial, as well as the size of the matrix
n = length ps' - 1
-- Remove trailing 0s from ps
ps' = reverse $ dropWhile (==0) $ reverse ps
-- Address/value tuples for a shifted identity matrix:
-- 1s on the diagonal just above the main diagonal, 0s elsewhere
shiftI = map (\p@(x,y) -> (p, if y == x + 1 then 1 else 0)) $
range ((0,0),(n-2,n-1))
-- Address/value tuples for the last row of the companion matrix:
-- ascending powers of the polynomial
lastRow = zipWith (\x y -> ((n-1, x), y)) [0..n-1] $ map negate ps'
-- (charpoly . companion) = id :: Polynomial Int -> Polynomial Int
```
Applying this to $1 - 2x + x^2$ gives us:
```{haskell}
--| code-fold: true
reshape :: Int -> [a] -> [[a]]
reshape n = unfoldr (reshape' n) where
reshape' n x = if null x then Nothing else Just $ splitAt n x
fromMatrix :: Matrix a -> [[a]]
fromMatrix (Mat m) = let (_,(_,n)) = bounds m in reshape (n+1) $ elems m
texifyMatrix mat = surround mat' where
mat' = intercalate " \\\\ " $ map (intercalate " & " . map show) $
fromMatrix mat
surround = ("\\left( \\begin{matrix}" ++) . (++ "\\end{matrix} \\right)")
markdown $ ("$$" ++) $ (++ "$$") $ texifyMatrix $ companion $ Poly [1, -2, 1]
```
</details>
Field Extensions
----------------
Aside from those of degree 1, the irreducible polynomials over a field cannot be factored
into monomials over the field.
In other words, irreducibles have roots which do not exist as elements of the field.
A *field extension* formalizes the notion by which one can make a larger field from another
by adding roots of a polynomial.
$x^2 + 1$ is irreducible, both over the integers and over an actual field like $\mathbb{R}$.
On the other hand, it can be factored into $(x + i)(x - i)$ over $\mathbb{C}$.
We can construct the latter field from the former if an extra number *i* exists alongside everything
in $\mathbb{R}$ such that *i*^2^ = -1.
Elements of this new field are linear combinations of multiples of powers of *i*
less than the degree (in this case, 0 and 1; i.e., $a + bi$).
The equation that *i* obeys can be rewritten as $i^2 + 1 = 0$, which is the original polynomial,
evaluated at *i*.
In order to refer explicitly to the construction of the bigger field from the polynomial,
we write[^2] $\mathbb{R}[x] / (x^2 + 1) \cong \mathbb{C}$.
[^2]: *Technically*, the left hand side refers to something else
(cosets of polynomials, from which we extract the canonical member *i*), but this description is good enough.
### The Power of Primes
We can extend a finite field in the same way.
Over GF(2), the smallest irreducible of degree 2 is $x^2 + x + 1$.
Using the same logic as before, we construct
$\mathbb{F}_2[x] / (x^2 + x + 1) \cong \mathbb{F}_2[\alpha]$.
The new element *α* is a root of the polynomial and obeys the relations:
$$
\begin{gather*}
\alpha^2 + \alpha + 1 = 0
\\
\alpha^2 = -\alpha - 1 \equiv \alpha + 1 \mod 2
\\
\alpha^3 = \alpha^2 + \alpha = (\alpha + 1) + \alpha \equiv 1 \mod 2
\end{gather*}
$$
Just like *i*, only powers of *α* less than 2 (again, 0 and 1) are necessary
to express elements of the field.
Skipping a few steps, we can accumulate all possible sums and products over this new field
into two new tables:
:::: {layout-ncol="2"}
::: {.cayley-table}
| + | 0 | 1 | *α* | *α* + 1 |
|---------|---------|---------|---------|---------|
| 0 | 0 | 1 | *α* | *α* + 1 |
| 1 | 1 | 0 | *α* + 1 | *α* |
| *α* | *α* | *α* + 1 | 0 | 1 |
| *α* + 1 | *α* + 1 | *α* | 1 | 0 |
:::
::: {.cayley-table}
| × | 0 | 1 | *α* | *α* + 1 |
|---------|---------|---------|---------|---------|
| 0 | 0 | 0 | 0 | 0 |
| 1 | 0 | 1 | *α* | *α* + 1 |
| *α* | 0 | *α* | *α* + 1 | 1 |
| *α* + 1 | 0 | *α* + 1 | 1 | *α* |
:::
::::
As you might expect, the resulting field has 4 elements, so it's called GF(4) (or $\mathbb{F}_4$).
In general, when adjoining an irreducible of degree *d* to GF(*p*), the resulting field has *p*^*d*^ elements,
naturally denoted GF(p^d^) (or $\mathbb{F}_{p^d}$).
*p* is still special, and is called the *characteristic* of the field.
It denotes how many repeated additions are needed to get to 0.
From the above table, it's clear that the characteristic is 2 since
$1 + 1 = \alpha + \alpha = (\alpha + 1) + (\alpha + 1) = 0$.
### ...and beyond?
All of this is manageable when you're adjoining a root of a degree 2 polynomial like *α* or *i*,
but things get difficult when you start to work with higher degrees.
The powers of the root form the basis for a *d*-dimensional vector space over GF(*p*)
(hence the order of the field being *p*^*d*^).
Proceeding as before, we'd have to be able to:
- recognize equality in the new field based on sums of powers of roots (times elements of the field)
- have a canonical method of expressing other elements after adjoining a root
- ideally, handle both with an algorithm that gives canonical forms from noncanonical ones
- know when we've found every element of the new field
These problems make it difficult to study prime power fields on a computer without the use of
a CAS like Maple or Mathematica.
They're capable of taking care of these issues symbolically, working with the expressions
in the same a mathematician would (or at least appearing to do so).
As someone who likes to do things himself, implementing a CAS from scratch seemed a little too cumbersome.
Furthermore, even a more direct approach using the previously-mentioned
"canonical members of cosets of polynomials" was more annoying than I was willing to put up with.
Fortunately, there's a detour that makes it much easier to dodge all of these problems,
and it has some interesting consequences.
Join me in [the next post](../2) for a direct, non-symbolic way to work with prime power fields.

View File

@ -0,0 +1 @@
../../number-number/1/MplIHaskell.hs

View File

@ -0,0 +1,234 @@
module Previous where
import Control.Applicative (liftA2)
import Data.Array
import Data.List (unfoldr, transpose)
import Data.Tuple (swap)
zipAdd :: Num a => [a] -> [a] -> [a]
zipAdd [] ds = ds
zipAdd cs [] = cs
zipAdd (c:cs) (d:ds) = (c + d):zipAdd cs ds
{------------------------------------------------------------------------------
- ___ _ _ _
- | _ \___| |_ _ _ _ ___ _ __ (_)__ _| |___
- | _/ _ \ | || | ' \/ _ \ ' \| / _` | (_-<
- |_| \___/_|\_, |_||_\___/_|_|_|_\__,_|_/__/
- |__/
------------------------------------------------------------------------------}
-- A polynomial is its ascending list of coefficients (of type a)
newtype Polynomial a = Poly { coeffs :: [a] } deriving (Functor)
instance (Eq a, Num a) => Num (Polynomial a) where
(+) x@(Poly as) y@(Poly bs) = Poly $ zipAdd as bs
--convolution
(*) (Poly []) (Poly bs) = Poly []
(*) (Poly as) (Poly []) = Poly []
(*) (Poly as) (Poly bs) = Poly $ zeroize $ finish $ foldl convolve' ([], []) as
where convolve' (xs, ys) a = (a:xs, sum (zipWith (*) (a:xs) bs):ys)
finish (xs, ys) = (reverse ys ++) $ finish' xs $ tail bs
finish' xs [] = []
finish' xs ys = sum (zipWith (*) xs ys):finish' xs (tail ys)
zeroize xs = if all (==0) xs then [] else xs
-- this definition works
negate = fmap negate
-- but these two might run into some problems
abs = fmap abs
signum = fmap signum
fromInteger 0 = Poly []
fromInteger a = Poly [fromInteger a]
instance (Eq a, Num a) => Eq (Polynomial a) where
(==) as bs = all (==0) $ coeffs $ as - bs
-- Interpret a number's base-b expansion as a polynomial
-- Build a list with f, which returns either Nothing
-- or Just (next element of list, next argument to f)
asPoly :: Int -> Int -> Polynomial Int
asPoly b = Poly . unfoldr f where
-- Divide x by b. Emit the remainder and recurse with the quotient.
f x | x /= 0 = Just $ swap $ divMod x b
-- If there's nothing left to divide out, terminate
| otherwise = Nothing
-- Horner evaluation of a polynomial at the integer b
-- Start with the highest coefficient
-- Multiply by b at each step and add the coefficient of the next term
evalPoly b (Poly p) = foldr (\y acc -> b*acc + y) 0 p
-- Divide the polynomial ps by qs (coefficients in descending degree order)
synthDiv' :: (Eq a, Num a) => [a] -> [a] -> ([a], [a])
synthDiv' ps qs
| head qs /= 1 = error "Cannot divide by non-monic polynomial"
| otherwise = splitAt deg $ doDiv ps deg
where
-- Negate the denominator and ignore leading term
qNeg = map negate $ tail qs
-- The degree of the result, based on the degrees of the numerator and denominator
deg = max 0 (length ps - length qs + 1)
-- Pluck off the head of the list and add a shifted and scaled version of
-- qs to the tail of the list. Repeat this d times
doDiv xs 0 = xs
doDiv (x:xs) d = x:doDiv (zipAdd xs $ map (*x) qNeg) (d - 1)
-- Use Polynomial (coefficients in ascending degree order) instead of lists
synthDiv :: (Eq a, Num a) => Polynomial a -> Polynomial a
-> (Polynomial a, Polynomial a)
synthDiv (Poly p) (Poly q) = (Poly $ reverse quot, Poly $ reverse rem)
where (quot, rem) = synthDiv' (reverse p) (reverse q)
{------------------------------------------------------------------------------
- ___ _ _ _ ___
- | _ \___| |_ _ _ _ ___ _ __ (_)__ _| | / __| ___ __ _ _ _ ___ _ _ __ ___ ___
- | _/ _ \ | || | ' \/ _ \ ' \| / _` | | \__ \/ -_) _` | || / -_) ' \/ _/ -_|_-<
- |_| \___/_|\_, |_||_\___/_|_|_|_\__,_|_| |___/\___\__, |\_,_\___|_||_\__\___/__/
- |__/ |_|
------------------------------------------------------------------------------}
-- All monic polynomials of degree d with coefficients mod n
monics :: Int -> Int -> [Polynomial Int]
monics n d = map (asPoly n) [n^d..2*(n^d) - 1]
-- All monic polynomials with coefficients mod n, ordered by degree
allMonics :: Int -> [Polynomial Int]
allMonics n = concat [monics n d | d <- [1..]]
-- All irreducible monic polynomials with coefficients mod n
irreducibles :: Int -> [Polynomial Int]
irreducibles n = go [] $ allMonics n where
-- Divide the polynomial x by i, then take the remainder mod n
remModN x i = fmap (`mod` n) $ snd $ synthDiv x i
-- Find remainders of x divided by every irreducible in "is".
-- If any give the zero polynomial, then x is a multiple of an irreducible
notMultiple x is = and [not $ all (==0) $ coeffs $ remModN x i | i <- is]
-- Sieve out by notMultiple
go is (x:xs)
| notMultiple x is = x:go (x:is) xs
| otherwise = go is xs
{------------------------------------------------------------------------------
- __ __ _ _
- | \/ |__ _| |_ _ _(_)__ ___ ___
- | |\/| / _` | _| '_| / _/ -_|_-<
- |_| |_\__,_|\__|_| |_\__\___/__/
-
------------------------------------------------------------------------------}
newtype Matrix a = Mat { unMat :: Array (Int, Int) a } deriving (Functor)
asScalar = Mat . listArray ((0,0),(0,0)) . pure
transposeM :: Matrix a -> Matrix a
transposeM (Mat m) = Mat $ ixmap (bounds m) swap m
dotMul :: Num a => [a] -> [a] -> a
dotMul = (sum .) . zipWith (*)
matMul' xs ys = reshape (length ys) $ liftA2 dotMul xs ys
matMul xs ys = matMul' xs $ transpose ys
-- Pointwise application
zipWithArr :: Ix i => (a -> b -> c)
-> Array i a -> Array i b -> Array i c
zipWithArr f a b
| ab == bb = array ab $ map (\x -> (x, f (a!x) (b!x))) $ indices a
| otherwise = error "Array dimension mismatch" where
ab = bounds a
bb = bounds b
-- Maps
mapRange :: Ix i => (i -> e) -> (i, i) -> [(i, e)]
mapRange g r = map (\x -> (x, g x)) $ range r
instance (Num a, Eq a) => Eq (Matrix a) where
(==) (Mat x) (Mat y)
| (0,0) == (snd $ bounds x) = and [x!(0,0) == if m == n then u else 0 | ((m,n), u) <- assocs y]
| (0,0) == (snd $ bounds y) = and [x!(0,0) == if m == n then u else 0 | ((m,n), u) <- assocs x]
| otherwise = x == y
instance Num a => Num (Matrix a) where
(+) (Mat x) (Mat y)
| (0,0) == (snd $ bounds x) && (0,0) == (snd $ bounds y) = Mat $ zipWithArr (+) x y
| (0,0) == (snd $ bounds x) = Mat y + (((x!(0,0)) *) <$> eye ((+1) $ snd $ snd $ bounds y)) --adding scalars
| (0,0) == (snd $ bounds y) = Mat x + (((y!(0,0)) *) <$> eye ((+1) $ snd $ snd $ bounds x)) --adding scalars
| otherwise = Mat $ zipWithArr (+) x y
(*) x y
| (0,0) == (snd $ bounds $ unMat x) = fmap (((unMat x)!(0,0))*) y --multiplying scalars
| (0,0) == (snd $ bounds $ unMat y) = fmap (((unMat y)!(0,0))*) x --multiplying scalars
| otherwise = toMatrix $ matMul' (fromMatrix x) (fromMatrix $ transposeM y)
abs = fmap abs
negate = fmap negate
signum = undefined
fromInteger = asScalar . fromInteger --"scalars"
reshape :: Int -> [a] -> [[a]]
reshape n = unfoldr (reshape' n) where
reshape' n x = if null x then Nothing else Just $ splitAt n x
-- Simple function for building a Matrix from lists
toMatrix :: [[a]] -> Matrix a
toMatrix l = Mat $ listArray ((0,0),(n-1,m-1)) $ concat l where
m = length $ head l
n = length l
-- ...and its inverse
fromMatrix :: Matrix a -> [[a]]
fromMatrix (Mat m) = let (_,(_,n)) = bounds m in reshape (n+1) $ elems m
-- Zero matrix
zero :: Num a => Int -> Matrix a
zero n = Mat $ listArray ((0,0),(n-1,n-1)) $ repeat 0
-- Identity matrix
eye :: Num a => Int -> Matrix a
eye n = Mat $ unMat (zero n) // take n [((i,i), 1) | i <- [0..]]
-- Companion matrix
companion :: (Eq a, Num a) => Polynomial a -> Matrix a
companion (Poly ps)
| last ps' /= 1 = error "Cannot find companion matrix of non-monic polynomial"
| otherwise = Mat $ array ((0,0), (n-1,n-1)) $ lastRow ++ shiftI where
-- The degree of the polynomial, as well as the size of the matrix
n = length ps' - 1
-- Remove trailing 0s from ps
ps' = reverse $ dropWhile (==0) $ reverse ps
-- Address/value tuples for a shifted identity matrix:
-- 1s on the diagonal just above the main diagonal, 0s elsewhere
shiftI = map (\p@(x,y) -> (p, if y == x + 1 then 1 else 0)) $
range ((0,0),(n-2,n-1))
-- Address/value tuples for the last row of the companion matrix:
-- ascending powers of the polynomial
lastRow = zipWith (\x y -> ((n-1, x), y)) [0..n-1] $ map negate ps'
mDims = snd . bounds . unMat
-- trace of a matrix: sum of diagonal entries
mTrace :: (Num a) => Matrix a -> a
mTrace a = sum [(unMat a)!(i,i) | i <- [0..(uncurry min $ mDims a)]]
-- Faddeev-Leverrier algorithm for computing the determinant, characteristic polynomial, and inverse matrix
-- this relies on dividing the trace by the current iteration count, so it is only suitable for Integral types
fadeev :: (Num a, Integral a) => Matrix a -> ([a], a, Matrix a)
fadeev a = fadeev' [1] (eye $ n+1) 1 where
n = snd $ mDims a
fadeev' rs m i
| all (==0) (unMat nextm) = (replicate (n - (fromIntegral i) + 1) 0 ++ tram:rs , -tram, m)
| otherwise = fadeev' (tram:rs) nextm (i+1) where
am = a * m
tram = -mTrace am `div` i
nextm = am + (fmap (tram *) $ eye (n+1))
-- determinant from Faddeev-Leverrier algorithm
determinant :: (Num a, Integral a) => Matrix a -> a
determinant = (\(_,x,_) -> x) . fadeev
-- characteristic polynomial from Faddeev-Leverrier algorithm
charpoly :: (Num a, Integral a) => Matrix a -> Polynomial a
charpoly = Poly . (\(x,_,_) -> x) . fadeev

BIN
posts/math/finite-field/2/char_2_irreducibles_graphs.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,287 @@
---
title: "Exploring Finite Fields, Part 2 Appendix"
description: |
Additional notes about polynomial evaluation.
format:
html:
html-math-method: katex
date: "2024-01-15"
date-modified: "2025-07-16"
categories:
- algebra
- finite field
- haskell
---
In the [second post in this series](../), we briefly discussed alternate means
of evaluating polynomials by "plugging in" different structures.
Different Kinds of Polynomials
------------------------------
Rather than redefining evaluation for each of these cases,
we can map the polynomial into a structure compatible with it should be evaluated.
Essentially, this means that from a polynomial in the base structure,
we can derive polynomials in these other structures.
In particular, there is a distinction between a matrix of polynomials or a polynomial in matrices:
:::: {layout-ncol="2"}
::: {}
$$
\begin{align*}
p &: K[x]
\\
p(x) &= x^n + p_{n-1}x^{n-1} + ...
\\
\phantom{= p} & + p_1 x + p_0
\end{align*}
$$
:::
::: {}
$x$ is a scalar indeterminate
```haskell
p :: Polynomial k
```
:::
::::
:::: {layout-ncol="2"}
::: {}
$$
\begin{align*}
P &: (K[x])^{m \times m}
\\
P(x I) &= (x I)^n + (p_{n-1})(x I)^{n-1} + ...
\\
& + p_1(x I)+ p_0 I
\end{align*}
$$
:::
::: {}
$x$ is a scalar indeterminate, $P(x I)= p(x) I$ is a matrix of polynomials in $x$
```haskell
asPolynomialMatrix
:: Polynomial k -> Matrix (Polynomial k)
pMat :: Matrix (Polynomial k)
pMat = asPolynomialMatrix p
```
:::
::::
:::: {layout-ncol="2"}
::: {}
$$
\begin{align*}
\hat P &: K^{m \times m}[X]
\\
\hat P(X) &= X^n + (p_{n-1}I)X^{n-1} + ...
\\
& + (p_1 I) X + (p_0 I)
\end{align*}
$$
:::
::: {}
$X$ is a matrix indeterminate, $\hat P(X)$ is a polynomial over matrices
```haskell
asMatrixPolynomial
:: Polynomial k -> Polynomial (Matrix k)
pHat :: Polynomial (Matrix k)
pHat = asMatrixPolynomial p
```
:::
::::
It's easy to confuse the latter two, but the Haskell makes the difference in types clearer.
There exists a natural isomorphism between the two, which is discussed further
in the [fourth post in this series](../../4/).
Cayley-Hamilton Theorem, Revisited
----------------------------------
As a reminder, the
[Cayley-Hamilton theorem](https://en.wikipedia.org/wiki/Cayley%E2%80%93Hamilton_theorem)
says that a matrix satisfies its own characteristic polynomial.
In a type-stricter sense, it says the following relationship holds:
```haskell
evalPoly :: a -> Polynomial a -> a
mA :: Matrix a
charpolyA :: Polynomial a
charpolyA = charpoly mA
charpolyA :: Polynomial (Matrix a)
matCharpolyA = asMatrixPolynomial charPolyA
evalPoly mA matCharpolyA == (0 :: Matrix a)
```
Due to the aformentioned isomorphism, factoring a polynomial "inside" a matrix turns
out to give the same answer as factoring a polynomial over matrices.
:::: {layout-ncol="2"}
::: {}
$$
\begin{gather*}
P(xI) = \left( \begin{matrix}
x^2 + x + 1 & 0 \\
0 & x^2 + x + 1
\end{matrix}\right)
\\ \\
= (xI - C)(xI - C')
\\ \\
= \left( \begin{matrix}
x & -1 \\
1 & x + 1
\end{matrix} \right)
\left( \begin{matrix}
x - a & -b \\
-c & x - d
\end{matrix} \right)
\\ \\
\begin{align*}
x(x - a) + c &= x^2 + x + 1
\\
\textcolor{green}{x(-b) - (x - d)} &\textcolor{green}{= 0}
\\
\textcolor{blue}{(x - a) + (x + 1)(-c)} &\textcolor{blue}{= 0}
\\
(-b) + (x + 1)(x - d) &= x^2 + x + 1
\end{align*}
\\ \\
\textcolor{green}{(-b -1)x +d = 0} \implies b = -1, ~ d = 0 \\
\textcolor{blue}{(1 - c)x - a - c = 0} \implies c = 1, ~ a = -1
\\ \\
C' =
\left( \begin{matrix}
-1 & -1 \\
1 & 0
\end{matrix} \right)
\end{gather*}
$$
:::
::: {}
$$
\begin{gather*}
\hat P(X) = X^2 + X + 1I
\\[10pt]
= (X - C)(X - C')
\\[10pt]
= X^2 - (C + C')X + CC'
\\[10pt]
\implies
\\[10pt]
C + C' = -I, ~ C' = -I - C
\\[10pt]
CC' = I, ~ C^{-1} = C'
\\[10pt]
C' = \left( \begin{matrix}
-1 & -1 \\
1 & 0
\end{matrix} \right)
\end{gather*}
$$
:::
::::
It's important to not that a matrix factorization is not unique.
*Any* matrix with a given characteristic polynomial can be used as a root of that polynomial.
Of course, choosing one root affects the other matrix roots.
### Moving Roots
All matrices commute with the identity and zero matrices.
A less obvious fact is that for a matrix factorization, all roots *also* commute with one another.
By the Fundamental Theorem of Algebra,
[Vieta's formulas](https://en.wikipedia.org/wiki/Vieta%27s_formulas) state:
$$
\begin{gather*}
\hat P(X)
= \prod_{[i]_n} (X - \Xi_i)
= (X - \Xi_0) (X - \Xi_1)...(X - \Xi_{n-1})
\\
= \left\{ \begin{align*}
& \phantom{+} X^n
\\
& - (\Xi_0 + \Xi_1 + ... + \Xi_{n-1}) X^{n-1}
\\
& + (\Xi_0 \Xi_1+ \Xi_0 \Xi_2 + ... + \Xi_0 \Xi_{n-1} + \Xi_1 \Xi_2 + ... + \Xi_{n-2} \Xi_{n-1})X^{n-2}
\\
& \qquad \vdots
\\
& + (-1)^n \Xi_0 \Xi_1 \Xi_2...\Xi_n
\end{align*} \right.
\\
= X^n -\sigma_1([\Xi]_n)X^{n-1} + \sigma_2([\Xi]_n)X^{n-2} + ... + (-1)^n \sigma_n([\Xi]_n)
\end{gather*}
$$
The product range \[*i*\]~*n*~ means that the terms are ordered from 0 to *n* - 1 over the index given.
On the bottom line, *σ* are
[elementary symmetric polynomials](https://en.wikipedia.org/wiki/Elementary_symmetric_polynomial)
and \[*Ξ*\]~*n*~ is the list of root matrices from *Ξ*~*0*~ to Ξ~*n-1*~.
By factoring the matrix with the roots in a different order, we get another factorization.
It suffices to only focus on *σ*~2~, which has all pairwise products.
$$
\begin{gather*}
\pi \in S_n
\\
\qquad
\pi \circ \hat P(X) = \prod_{\pi ([i]_n)} (X - \Xi_i)
\\ \\
= X^n
- \sigma_1 \left(\pi ([\Xi]_n) \vphantom{^{1}} \right)X^{n-1}
+ \sigma_2 \left(\pi ([\Xi]_n) \vphantom{^{1}} \right)X^{n-2} + ...
+ (-1)^n \sigma_n \left(\pi ([\Xi]_n) \vphantom{^{1}} \right)
\\[10pt]
\pi_{(0 ~ 1)} \circ \hat P(X) = (X - \Xi_{1}) (X - \Xi_0)(X - \Xi_2)...(X - \Xi_{n-1})
\\
= X^n + ... + \sigma_2(\Xi_1, \Xi_0, \Xi_2, ...,\Xi_{n-1})X^{n-2} + ...
\\[10pt]
\begin{array}{}
e & (0 ~ 1) & (1 ~ 2) & ... & (n-2 ~~ n-1)
\\ \hline
\textcolor{red}{\Xi_0 \Xi_1} & \textcolor{red}{\Xi_1 \Xi_0} & \Xi_0 \Xi_1 & & \Xi_0 \Xi_1
\\
\Xi_0 \Xi_2 & \Xi_0 \Xi_2 & \Xi_0 \Xi_2 & & \Xi_0 \Xi_2
\\
\Xi_0 \Xi_3 & \Xi_0 \Xi_3 & \Xi_0 \Xi_3 & & \Xi_0 \Xi_3
\\
\vdots & \vdots & \vdots & & \vdots
\\
\Xi_0 \Xi_{n-1} & \Xi_0 \Xi_{n-1} & \Xi_{0} \Xi_{n-1} & & \Xi_{n-1} \Xi_0
\\
\textcolor{green}{\Xi_1 \Xi_2} & \Xi_1 \Xi_2 & \textcolor{green}{\Xi_2 \Xi_1} & & \Xi_1 \Xi_2
\\
\vdots & \vdots & \vdots & & \vdots
\\
\textcolor{blue}{\Xi_{n-2} \Xi_{n-1}} & \Xi_{n-2} \Xi_{n-1} & \Xi_{n-2} \Xi_{n-1} & & \textcolor{blue}{\Xi_{n-1} \Xi_{n-2}}
\end{array}
\end{gather*}
$$
The "[path swaps](/posts/math/permutations/1/)" shown commute only the adjacent elements.
By contrast, the permutation $(0 ~ 2)$ commutes *Ξ*~0~ past both *Ξ*~1~ and *Ξ*~2~.
But since we already know *Ξ*~0~ and *Ξ*~1~ commute by the above list,
we learn at this step that *Ξ*~0~ and *Ξ*~2~ commute.
This can be repeated until we reach the permutation $(0 ~ n-1)$ to prove commutativity between all pairs.

View File

@ -0,0 +1,926 @@
---
title: "Exploring Finite Fields, Part 2: Matrix Boogaloo"
description: |
How do we extend a field non-symbolically?
format:
html:
html-math-method: katex
date: "2024-01-15"
date-modified: "2025-08-03"
categories:
- algebra
- finite field
- haskell
---
```{haskell}
--| echo: false
:l Previous.hs
:l MplIHaskell.hs
import Data.Array ((//), listArray, bounds)
import Data.Bifunctor (bimap)
import Data.Profunctor (rmap)
import Data.List (unfoldr, intercalate, nub, (\\), subsequences, sortOn, groupBy)
import Data.Maybe (fromMaybe, fromJust, listToMaybe)
import Data.Either (partitionEithers)
import Data.Function (on)
import Colonnade hiding (fromMaybe)
import qualified Colonnade.Encode as CE
import qualified Graphics.Matplotlib.Internal as MPLI
import IHaskell.Display (markdown)
import Previous (
Matrix(Mat, unMat), Polynomial(Poly, coeffs),
asPoly, evalPoly, synthDiv, irreducibles,
charpoly, companion, toMatrix, fromMatrix, zero, eye
)
import qualified MplIHaskell
markdownTable col rows = unlines $ h:h':r where
toColumns = ("| " ++) . (++ " |") . intercalate " | " . foldr (:) []
h = toColumns $ CE.header id col
h' = [if x == '|' then x else '-' | x <- h]
r = map (toColumns . CE.row id col) rows
-- Convert Polynomial to LaTeX string
texifyPoly :: (Num a, Eq a, Show a) => Polynomial a -> String
texifyPoly (Poly xs) = texify' $ zip xs [0..] where
texify' [] = "0"
texify' ((c, n):xs)
| all ((==0) . fst) xs = showPow c n
| c == 0 = texify' xs
| otherwise = showPow c n ++ " + " ++ texify' xs
showPow c 0 = show c
showPow 1 1 = "x"
showPow c 1 = show c ++ showPow 1 1
showPow 1 n = "x^{" ++ show n ++ "}"
showPow c n = show c ++ showPow 1 n
-- Convert matrix to LaTeX string
texifyMatrix mat = surround mat' where
mat' = intercalate " \\\\ " $ map (intercalate " & " . map show) $
fromMatrix mat
surround = ("\\left( \\begin{matrix}" ++) . (++ "\\end{matrix} \\right)")
-- networkx.display tries printing a string that ruins the SVG this generates
plotDigraph x = MPLI.mplotString $
"import networkx as nx \n\
\import os \n\
\original_stdout = sys.stdout \n\
\sys.stdout = open(os.devnull, 'w') \n\
\try: \n\
\ nx.display(nx.DiGraph(" ++ MPLI.toPython x ++ ")) \n\
\finally: \n\
\ sys.stdout.close() \n\
\ sys.stdout = original_stdout"
```
In the [last post](../1/), we discussed finite fields, polynomials and matrices over them, and the typical,
symbolic way of extending fields with polynomials.
This post will will focus on circumventing symbolic means with numeric ones.
More about Matrices (and Polynomials)
-------------------------------------
Recall the definition of polynomial evaluation.
Since a polynomial is defined with respect to a field or ring, we expect only to be able to evaluate the
polynomial at values *in* that field or ring.
$$
\begin{gather*}
K[x] \times K \overset{\text{eval}}{\longrightarrow} K
\\
(p(x), n) \overset{\text{eval}}{\mapsto} p(n)
\end{gather*}
$$
However, there's nothing wrong with evaluating polynomials with another polynomial,
as long as they're defined over the same structure.
After all, we can take powers of polynomials, scalar-multiply them with coefficients from *K*,
and add them together.
The same holds for matrices, or any "collection" structure *F* over *K* which has those properties.
$$
\begin{align*}
K[x] \times K[x]
&\overset{\text{eval}_{poly}}{\longrightarrow} K[x]
\\
(p(x), q(x)) \mapsto p(q(x))
\\[10pt]
K[x] \times K^{n \times n}
&\overset{\text{eval}_{mat}}{\longrightarrow} K^{n \times n}
(p(x), A) \overset{?}{\mapsto} p(A)
\\[10pt]
K[x] \times F(K)
&\overset{\text{eval}_F}{\longrightarrow} F(K)
\end{align*}
$$
### Cayley-Hamilton Theorem
When evaluating the characteristic polynomial of a matrix *with* that matrix,
something strange happens.
Continuing from the previous article, using $x^2 + x + 1$ and its companion matrix, we have:
$$
\begin{gather*}
p(x) = x^2 + x + 1 \qquad C_{p} = C
= \left( \begin{matrix}
0 & 1 \\
-1 & -1
\end{matrix} \right)
\\ \\
\begin{align*}
\text{eval}_{mat}(p, C) &= C^2 + C + (1 \cdot I)
\\
&= \left( \begin{matrix}
-1 & -1 \\
1 & 0
\end{matrix} \right)
+ \left( \begin{matrix}
0 & 1 \\
-1 & -1
\end{matrix} \right)
+ \left( \begin{matrix}
1 & 0 \\
0 & 1
\end{matrix} \right)
\\
&= \left( \begin{matrix}
0 & 0 \\
0 & 0
\end{matrix} \right)
\end{align*}
\end{gather*}
$$
The result is the zero matrix.
This tells us that, at least in this case, the matrix *C* is a root of its own characteristic polynomial.
By the [Cayley-Hamilton theorem](https://en.wikipedia.org/wiki/Cayley%E2%80%93Hamilton_theorem),
this is true in general, no matter the degree of *p*, no matter its coefficients,
and importantly, no matter the choice of field.
In addition to this, we can also note the following:
- Irreducible polynomials cannot have a constant term 0, otherwise *x* could be factored out.
The constant term is equal to the determinant of the companion matrix (up to sign),
so *C*~*p*~ must be invertible.
- All powers of *C*~*p*~ are guaranteed to commute over multiplication,
since this follows from associativity.
Both of these facts narrow the ring of matrices to a full-on field.
This absolves us of needing to adjoin roots symbolically using α.
Instead, we can take the companion matrix of an irreducible polynomial *p*
and work with its powers in the same way we would a typical root[^1].
[^1]: For finite fields, it might make sense to do the following procedure
to generate every possible element:
- Take all powers of a companion matrix *C*
- Add all powers of *C* with prior elements of the field (times identity matrices)
- Repeat until no new elements are generated
In fact, we can usually do a little better, as we'll see.
GF(8)
-----
This is all rather abstract, so let's look at an example before we proceed any further.
The next smallest field of characteristic 2 is GF(8).
We can construct this field from the two irreducible polynomials of degree 3 over GF(2):
```{haskell}
--| code-fold: true
irrsOfDegree d n = map fst $ takeWhile ((==d) . snd) $ dropWhile ((/=d) . snd) $
map ((,) <*> (+(-1)) . length . coeffs) $ irreducibles n
-- First (and only) two polynomials of degree 3
qPoly:rPoly:_ = irrsOfDegree 3 2
-- Display a polynomial in positional notation, base x
texPolyAsPositional (Poly xs) = (++ "_{x}") $
reverse xs >>= (\x -> if x < 0 then "\\bar{" ++ show (-x) ++ "}" else show x)
-- Display a polynomial as its encoding in base b
texPolyAsNumeric b p = (("{}_{" ++ show b ++ "} ") ++) $ show $ evalPoly b p
-- Display a polynomial and equivalent notations
texPolyPosNum b p = texifyPoly p ++ " = " ++
texPolyAsPositional p ++ "\\sim" ++
texPolyAsNumeric b p
-- Display a polynomial and its companion matrix
texPolyAndMatrix b p name = name ++ "(x) = " ++ texPolyPosNum b p ++
"\\qquad C_{" ++ name ++ "} = " ++ texifyMatrix ((`mod` b) <$> companion p)
markdown $ "$$\\begin{gather*}" ++
texPolyAndMatrix 2 qPoly "q" ++
"\\\\" ++ texPolyAndMatrix 2 rPoly "r" ++
"\\end{gather*}$$"
```
Notice how the bit strings for either of these polynomials is the other, reversed.
Arbitrarily, let's work with C~r~.
The powers of this matrix (mod 2) are as follows:
```{haskell}
--| code-fold: true
-- Compute all powers of a matrix, starting with the first
matrixPowersMod b mat = iterate (((`mod` b) <$>) . (mat*)) mat
-- Show a matrix power
texMatrixPower n b mat name = "(" ++ name ++ ")^{" ++ show n ++ "} = " ++
texifyMatrix ((`mod` b) <$> mat)
-- Show all matrix powers
texPows b mat name = [texMatrixPower n b matPow name |
(n, matPow) <- zip [1..] (matrixPowersMod b mat)]
let pows = texPows 2 (companion rPoly) "C_r" in
markdown $ "$$\\begin{gather*}" ++
concat (take 3 pows) ++ "\\\\" ++
concat (take 3 $ drop 3 pows) ++ "\\\\" ++
(pows !! (7 - 1)) ++ " = I = (C_r)^0 \\quad" ++
(pows !! (8 - 1)) ++ " = C_r" ++
"\\end{gather*}$$"
```
As a reminder, these matrices are taken mod 2, so the elements can only be 0 or 1.
The seventh power of *C*~*r*~ is just the identity matrix,
meaning that the eighth power is the original matrix.
This means that *C*~*r*~ is cyclic of order 7 with respect to self-multiplication mod 2.
Along with the zero matrix, this fully characterizes GF(8).
If we picked *C*~*q*~ instead, we would have gotten different matrices.
I'll omit writing them here, but we get the same result: *C*~*q*~ is also cyclic of order 7.
Since every nonzero element of the field can be written as a power of the root,
the root (as well as the polynomial) is termed
[primitive](https://en.wikipedia.org/wiki/Primitive_polynomial_%28field_theory%29).
### Condensing
Working with matrices directly, as a human, is very cumbersome.
While it makes computation explicit, it makes presentation difficult.
One of the things in which we know we should be interested is the characteristic polynomial,
since it is central to the definition and behavior of the matrices.
Let's focus only on the characteristic polynomial for successive powers of *C*~*r*~
```{haskell}
--| code-fold: true
-- Create sequence of charpolys (mod b) from the powers of its companion matrix
charPolyPows b = map (((`mod` b) <$>) . charpoly) . matrixPowersMod b . companion
-- Display row of charpoly array
texCharPolyRow b poly name extra = "\\text{charpoly}(" ++ name ++ ")" ++ "&=&" ++
fst (extra poly) ++ texifyPoly poly ++ "&=&" ++
fst (extra poly) ++ texPolyAsPositional poly ++ "\\sim" ++
texPolyAsNumeric b poly ++ " = " ++ snd (extra poly)
markdown $ "$$\\begin{array}{}" ++
intercalate " \\\\ " [
texCharPolyRow 2 mat ("(C_r)^{" ++ show n ++ "}") (\x ->
if x == rPoly then ("\\color{blue}", "r")
else if x == qPoly then ("\\color{red}", "q")
else ("", "(x + 1)^3")
)
| (n, mat) <- zip [1..7] (charPolyPows 2 rPoly)
] ++
"\\end{array}$$"
```
Somehow, even though we start with one characteristic polynomial, the other manages to work its way in here.
Both polynomials are of degree 3 and have 3 matrix roots (distinguished in red and blue).
If we chose to use *C*~*q*~, we'd actually get the same sequence backwards (starting with ~2~11).
It's beneficial to remember that 6, 5, and 3 can also be written as 7 - 1, 7 - 2, and 7 - 4.
This makes it clear that the powers of 2 (the field characteristic) less than the 8 (the order of the field)
play a role with respect to both the initial and terminal items.
### Factoring
Intuitively, you may try using the roots to factor the matrix into powers of *C*~*r*~.
This turns out to work:
```{haskell}
--| code-fold: true
-- Convert a list of roots to a polynomial with those as its roots
rootsToPoly :: (Num a, Eq a) => [a] -> Polynomial a
rootsToPoly xs = Poly $ reverse $ zipWith (*) (cycle [1,-1]) vieta where
-- Group by degree of subsequence
elemSyms = groupBy ((==) `on` length) . sortOn length . subsequences
-- Vieta's formulas over xs
vieta = map (sum . map product) $ elemSyms xs
-- Make a polynomial from the powers of the companion matrix of p (mod b)
companionPowerPoly b p = fmap (fmap (`mod` b)) . rootsToPoly .
map ((matrixPowersMod b (companion p) !!) . (+(-1)))
-- Show a polynomial over matrices
showPolyMat :: (Show a, Num a, Eq a) => Polynomial (Matrix a) -> String
showPolyMat = intercalate " + " . showCoeffs where
showCoeffs = zipWith showCoeff [0..] . map showMatrix . coeffs
-- Show the indeterminate as "X"
showCoeff 0 x = x
showCoeff 1 x = x ++ "X"
showCoeff n x = x ++ "X^{" ++ show n ++ "}"
-- Show identity matrices (but not their multiples) as "I"
showMatrix x
| x `elem` [eye 1, eye $ mSize x] = "I"
| x `elem` [zero 1, zero $ mSize x] = "0"
| otherwise = texifyMatrix x
mSize = (+1) . snd . snd . bounds . unMat
markdown $ "$$\\begin{align*}" ++
"\\hat{R}(X) &= (X - (C_r)^1)(X - (C_r)^2)(X - (C_r)^4)" ++
" \\\\ " ++
" &= " ++ showPolyMat (companionPowerPoly 2 rPoly [1,2,4]) ++
" \\\\[10pt] " ++
"\\hat{Q}(X) &= (X - (C_r)^3)(X - (C_r)^5)(X - (C_r)^6)" ++
" \\\\ " ++
" &= " ++ showPolyMat (companionPowerPoly 2 rPoly [3,5,6]) ++
"\\end{align*}$$"
```
We could have factored our polynomials differently if we used *C*~*q*~ instead.
However, the effect of splitting both polynomials into monomial factors is the same.
GF(16)
------
GF(8) is simple to study, but too simple to study the sequence of characteristic polynomials alone.
Let's widen our scope to GF(16).
There are three irreducible polynomials of degree 4 over GF(2).
```{haskell}
--| code-fold: true
-- First (and only) three polynomials of degree 4
sPoly:tPoly:uPoly:_ = irrsOfDegree 4 2
markdown $ "$$\\begin{gather*}" ++
texPolyAndMatrix 2 sPoly "s" ++ "\\\\" ++
texPolyAndMatrix 2 tPoly "t" ++ "\\\\" ++
texPolyAndMatrix 2 uPoly "u" ++
"\\end{gather*}$$"
```
Again, *s* and *t* form a pair under the reversal of their bit strings, while *u* is palindromic.
Both *C*~*s*~ and *C*~*t*~ are cyclic of order 15, so *s* and *t* are primitive polynomials.
Using *s* = ~2~19 to generate the field, the powers of its companion matrix *C*~*s*~
have the following characteristic polynomials:
```{haskell}
--| code-fold: true
--| classes: plain
sPolyCharPowers = charPolyPows 2 sPoly
-- Horizontal table of entries
fromIndices ns = columns (\(_, f) r -> f r) (\(c, _) -> Headed c) $
map (\i -> (show i, (!! i))) ns
fromIndices' = (singleton (Headed "m") head <>) . fromIndices
-- Symbolic representation of a power of a companion matrix (in Markdown)
compPowSymbolic "" m = "*f*(*C*^*" ++ m ++ "*^)"
compPowSymbolic x m = "*f*((*C*~*" ++ x ++ "*~)^*" ++ m ++ "*^)"
-- Spans of a given color
spanColor color = (("<span style=\"color: " ++ color ++ "\">") ++) . (++ "</span>")
markdown $ markdownTable (fromIndices' [1..15]) [
compPowSymbolic "s" "m":
map ((
\x -> if x == 19 then spanColor "blue" (show x)
else if x == 25 then spanColor "red" (show x)
else show x
) . evalPoly 2) sPolyCharPowers
]
```
The polynomial ~2~19 occurs at positions 1, 2, 4, and 8.
These are obviously powers of 2, the characteristic of the field.
Similarly, the polynomial *t* = ~2~25 occurs at positions 14 (= 15 - 1), 13 (= 15 - 2),
11 (= 15 - 4), and 7 (= 15 - 8).
We'd get the same sequence backwards if we used *C*~*t*~ instead, just like in GF(8).
### Non-primitive
The polynomial *u* = ~2~31 occurs at positions 3, 6, 9, and 12
-- multiples of 3, which is a factor of *15*.
It follows that the roots of *u* are cyclic of order 5, so this polynomial is irreducible,
but *not* primitive.
Naturally, *u* (or a polynomial isomorphic to it) can be factored as powers of (*C*~*s*~)^3^.
We can also factor it more naively as powers of *C*~*u*~.
Either way, we get the same sequence.
:::: {layout-ncol="2" layout-valign="center"}
::: {}
```{haskell}
--| code-fold: true
--| classes: plain
-- Get every entry of an (infinite) list which is a multiple of n
entriesEvery n = map head . unfoldr (Just . splitAt n)
markdown $ markdownTable (fromIndices' [1..5]) [
compPowSymbolic "s" "3m":
map (show . evalPoly 2)
(entriesEvery 3 $ drop 2 sPolyCharPowers),
compPowSymbolic "u" "m":
map (show . evalPoly 2) (charPolyPows 2 uPoly)
]
```
Both of the matrices in column 5 happen to be the identity matrix.
It follows that this root is only cyclic of order 5.
The polynomials ~2~19 and ~2~25 are reversals of one another, and the sequences that their companion matrices
generate end one with another -- in this regard, they are dual.
However, ${}_2 31 = 11111_x$ is a palindrome and its sequence ends where it begins, so it is self-dual.
:::
::: {width = "33%"}
$$
\begin{gather*}
(C_u)^1 =\left( \begin{matrix}
0 & 1 & 0 & 0 \\
0 & 0 & 1 & 0 \\
0 & 0 & 0 & 1 \\
1 & 1 & 1 & 1
\end{matrix} \right)
\\ \\
(C_u)^2 =\left( \begin{matrix}
0 & 0 & 1 & 0 \\
0 & 0 & 0 & 1 \\
1 & 1 & 1 & 1 \\
1 & 0 & 0 & 0
\end{matrix} \right)
\\ \\
(C_u)^3 =\left( \begin{matrix}
0 & 0 & 0 & 1 \\
1 & 1 & 1 & 1 \\
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0
\end{matrix} \right)
\\ \\
(C_u)^4 =\left( \begin{matrix}
1 & 1 & 1 & 1 \\
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & 1 & 0 \\
\end{matrix} \right)
\\ \\
(C_u)^5 =\left( \begin{matrix}
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & 1 & 0 \\
0 & 0 & 0 & 1 \\
\end{matrix} \right)
\\
= I = (C_u)^0
\end{gather*}
$$
:::
::::
### Non-irreducible
In addition to the three irreducibles, a fourth polynomial, ${}_2 21 \sim 10101_x$,
also appears in the sequence on entries 5 and 10 -- multiples of 5, which is also a factor of 15.
Like ~2~31, this polynomial is palindromic.
This polynomial is *not* irreducible mod 2, and factors as:
```{haskell}
--| code-fold: true
poly21 = asPoly 2 21
markdown $ "$$\\begin{gather*}" ++
texPolyAsPositional poly21 ++ " = " ++ texifyPoly poly21 ++
" = \\left( 1 + x + x^2 \\right)^2 \\mod 2" ++
" \\\\[10pt] " ++
"(X - (C_s)^5)(X - (C_s)^{10}) = " ++
showPolyMat (companionPowerPoly 2 sPoly [5,10]) ++
"\\end{gather*}$$"
```
Just like how the fields we construct are powers of a prime, this extra element is a power
of a smaller irreducible.
This is unexpected, but perhaps not surprising.
Something a little more surprising is that the companion matrix is cyclic of degree *6*,
rather than of degree 3 like the matrices encountered in GF(8).
The powers of its companion matrix are:
```{haskell}
--| code-fold: true
--| classes: plain
companion21Pows = matrixPowersMod 2 (companion poly21)
markdown $ markdownTable (fromIndices' [1..6]) [
compPowSymbolic "s" "5m":
map (show . evalPoly 2) (entriesEvery 5 $ drop 4 sPolyCharPowers),
compPowSymbolic "21" "m":
map (\x -> let
p = (`mod` 2) <$> charpoly x
p' = evalPoly 2 p
comp21 = head companion21Pows
in
-- x shares its characteristic polynomial with the identity
if p' == 17 then
show p' ++
(if x == eye 4 then " (identity)" else " (*Not* the identity)")
-- x is either the companion matrix of the polynomial 21 or its inverse
else if x == comp21 ||
eye 4 == ((`mod` 2) <$> x * comp21) then
spanColor "red" $ show p'
else spanColor "blue" $ show p')
companion21Pows
]
```
We can think of the repeated sequence as ensuring that there are enough roots of ~2~21.
The Fundamental Theorem of Algebra states that there must be 4 roots.
For *numbers*, we'd allow duplicate roots with multiplicities greater than 1,
but the matrix roots are all distinct.
Basic group theory tells us that as a cyclic group, the matrix's first and fifth powers
(in red) are pairs of inverses.
The constant term of the characteristic polynomial is the product of all four roots and,
as a polynomial over matrices, must be some nonzero multiple of the identity matrix.
Since the red roots are a pair of inverses, the blue roots are, too.
GF(32)
------
GF(32) turns out to be special.
There are six irreducible polynomials of degree 5 over GF(2).
Picking the "smallest" at random, ~2~37, and looking at the polynomial sequence it generates, we see:
```{haskell}
--| code-fold: true
--| classes: plain
-- Get all degree 5 polynomials over GF(2)
deg5Char2Polys = irrsOfDegree 5 2
leastDeg5Char2Poly = head deg5Char2Polys
colorByEval b ps x = (maybe show (. show) . getColor <*> id) $ evalPoly b x where
getColor = flip lookup $ map (fmap spanColor) ps
colorDeg5Char2 = colorByEval 2 [
(37, "red"),
(47, "orange"),
(55, "yellow"),
(41, "green"),
(61, "blue"),
(59, "purple")
]
markdown $ markdownTable (fromIndices' [1..16]) [
compPowSymbolic "" "m":
map colorDeg5Char2 (charPolyPows 2 leastDeg5Char2Poly)
]
markdown $ markdownTable (fromIndices' [17..31]) [
compPowSymbolic "" "m":
map colorDeg5Char2 (charPolyPows 2 leastDeg5Char2Poly)
]
```
31 is prime, so we don't have any sub-patterns that appear on multiples of factors.
In fact, all six irreducible polynomials are present in this table.
The pairs in complementary colors form pairs under reversing the polynomials:
<span style="color: red">~2~37</span> and <span style="color: green">~2~41</span>,
<span style="color: blue">~2~61</span> and <span style="color: orange">~2~47</span>,
and <span style="color: yellow">~2~55</span> and <span style="color: purple">~2~59</span>.
Since their roots have order 31, these polynomials are actually
the distinct factors of *x*^31^ - 1 mod 2:
$$
\begin{gather*}
x^{31} -1 = (x-1)(x^{30} +x^{29} + ... + x + 1)
\\
(x^{30} +x^{29} + ... + x + 1) =
\left\{ \begin{align*}
&\phantom\cdot (x^5 + x^2 + 1) &\sim \quad {}_2 37
\\
&\cdot (x^5 + x^3 + 1) &\sim \quad {}_2 41 \\
&\cdot (x^5 + x^4 + x^3 + x^2 + 1) &\sim \quad {}_2 61
\\
&\cdot (x^5 + x^3 + x^2 + x + 1) &\sim \quad {}_2 47
\\
&\cdot (x^5 + x^4 + x^2 + x + 1) &\sim \quad {}_2 55
\\
&\cdot (x^5 + x^4 + x^3 + x + 1) &\sim \quad {}_2 59
\end{align*} \right.
\end{gather*}
$$
This is a feature special to fields of characteristic 2.
2 is the only prime number whose powers can be one more than another prime,
since all other prime powers are one more than even numbers.
31 is a [Mersenne prime](https://en.wikipedia.org/wiki/Mersenne_prime),
so all integers less than 31 are coprime to it.
Thus, there is no room for the "extra" entries we observed in GF(16) which occurred
on factors of 15 = 16 - 1.
No entry can be irreducible (but not primitive) or the power of an irreducible of lower degree.
In other words, *only primitive polynomials exist of degree* p *if 2^p^ - 1 is a Mersenne prime*.
### Counting Irreducibles
The remark about coprimes to 31 may inspire you to think of the
[totient function](https://en.wikipedia.org/wiki/Euler%27s_totient_function).
We have *φ*(2^5^ - 1) = 30 = 5⋅6, where 5 is the degree and 6 is the number of primitive polynomials.
We also have *φ*(24 - 1) = 8 = 4⋅2 and *φ*(23 - 1) = 6 = 3⋅2.
In general, it is true that there are *φ*(*pm* - 1) / *m* primitive polynomials of degree m over GF(p).
Polynomial Reversal
-------------------
We've only been looking at fields of characteristic 2, where the meaning of
"palindrome" and "reversed polynomial" is intuitive.
Let's look at an example over characteristic 3.
One primitive of degree 2 is ~3~14, which gives rise to the following sequence over GF(9):
```{haskell}
--| code-fold: true
--| classes: plain
-- Get all degree 2 polynomials over GF(3)
deg2Char3Polys = map fst $ takeWhile ((==3) . snd) $ dropWhile ((/=3) . snd) $
map ((,) <*> length . coeffs) $ irreducibles 3
leastDeg2Char3Poly = deg2Char3Polys !! 1
colorDeg3Char3 = colorByEval 3 [(14, "red"), (17, "blue")]
markdown $ markdownTable (fromIndices' [1..8]) [
compPowSymbolic "14" "m":
map colorDeg3Char3 (charPolyPows 3 leastDeg2Char3Poly)
]
```
The table suggests that ${}_3 14 = 112_x = x^2 + x + 2$ and ${}_3 17 = 122_x = x^2 + 2x + 2$
are reversals of one another.
More naturally, you'd think that 112~*x*~ reversed is 211~*x*~.
But remember that we prefer to work with monic polynomials.
By multiplying the polynomial by the multiplicative inverse of the leading coefficient (in this case, 2),
we get $422_x \equiv 122_x \mod 3$.
This is a rule that applies over larger characteristics in general.
Note that ${}_3 16 \sim 121_x = x^2 + 2x + 1$ and ${}_3 13 \sim 111_x = x^2 + x + 1 = x^2 - 2x + 1$,
both of which have factors over GF(3).
Irreducible Graphs
------------------
We can study the interplay of primitives, irreducibles, and their powers by converting
our sequences into (directed) graphs.
Each node in the graph will represent an irreducible polynomial over the field.
Call the one under consideration *a*.
If the sequence of characteristic polynomials generated by powers of *C*~*a*~ contains
contains another polynomial *b*, then there is an edge from *a* to *b*.
```{haskell}
-- Convert a polynomial to the integer representing it in characteristic n
asPolyNum n = evalPoly n . fmap (`mod` n)
irreducibleGraph d n = concatMap (\(x:xs) -> map (x,) xs) polyKinClasses where
-- All irreducible polynomials of degree d in characteristic n
irrsOfDegree' = irrsOfDegree d n
-- Get "kin" polynomials as integers -- all those who appear as characteristic
-- polynomials in the powers of its companion matrix
getKinPolys = map (asPolyNum n . charpoly) . matrixPowersMod n . companion
-- Kin classes corresponding to each irreducible polynomial,
-- which is the first entry
polyKinClasses = map (nub . take (n^d) . getKinPolys) irrsOfDegree'
```
We can do this for every GF(*p*^*m*^).
Let's start with the first few fields of characteristic 2.
We get the following graphs:
![](./char_2_irreducibles_graphs.png)
All nodes connect to the node corresponding to the identity matrix, since all roots are cyclic.
Also, since all primitive polynomials are interchangeable with one another,
they are all interconnected and form a [complete](https://en.wikipedia.org/wiki/Complete_graph) clique.
This means that, excluding the identity node, the graphs for fields of order one more
than a Mersenne prime are just the complete graphs.
Since all of the graphs share the identity node as a feature
-- a node with incoming edges from every other node -- its convenient to omit it.
Here are a few more of these graphs after doing so, over fields of other characteristics:
::: {layout="[[1,1], [1,1], [1,1,1]]"}
```{haskell}
--| code-fold: true
--| fig-cap: "GF(9)"
-- Characteristic polynomial of the identity matrix
eyePoly d n = asPolyNum n $ charpoly $ eye d
-- Remove edges directed toward the characteristic polynomial of the identity
irreducibleGraphNoEye d n = filter ((/=eyePoly d n) . snd) $ irreducibleGraph d n
-- Only plot the graph for GF(9), since the others take too long to render
plotDigraph $ irreducibleGraphNoEye 2 3
```
![
GF(25)
](./irreducibles-graphs/irred_25.png)
![
GF(49)
](./irreducibles-graphs/irred_49.png)
![
GF(121)
](./irreducibles-graphs/irred_121.png)
![
GF(27)
](./irreducibles-graphs/irred_27.png)
![
GF(125)
](./irreducibles-graphs/irred_125.png)
![
GF(343)
](./irreducibles-graphs/irred_343.png)
:::
<!-- This code cell is not evaluated because it takes too long -->
```{haskell}
--| eval: false
--| echo: false
--| layout: [[1,1], [1,1], [1,1,1]]
--| fig-cap:
--| - GF(9)
--| - GF(25)
--| - GF(49)
--| - GF(121)
--| - GF(27)
--| - GF(125)
--| - GF(343)
plotDigraph $ irreducibleGraphNoEye 2 3
plotDigraph $ irreducibleGraphNoEye 2 5
plotDigraph $ irreducibleGraphNoEye 2 7
plotDigraph $ irreducibleGraphNoEye 2 11
plotDigraph $ irreducibleGraphNoEye 3 3
plotDigraph $ irreducibleGraphNoEye 3 5
plotDigraph $ irreducibleGraphNoEye 3 7
```
### Spectra
Again, since visually interpreting graphs is difficult, we can study an invariant.
From these graphs of polynomials, we can compute *their* characteristic polynomials
(to add another layer to this algebraic cake) and look at their spectra.
It turns out that a removing a fully-connected node (like the one for the identity matrix)
has a simple effect on characteristic polynomial of a graph: it just removes a factor of *x*.
Here are a few of the (identity-reduced) spectra, arranged into a table.
```{haskell}
--| code-fold: true
--| classes: plain
-- Not technically correct, but enough for this example
edgesToAdjacency [] = toMatrix [[0]]
edgesToAdjacency es = Mat asArray where
-- Vertices from the edge list
vs = nub $ es >>= (\(x,y) -> [x,y])
-- Lookup table for new vertices
vs' = zip vs [0..]
-- Largest vertex index for array bounds
b = maximum $ map snd vs'
-- Lookup function for new edge reindexing
lookupVs = fromJust . flip lookup vs'
-- List of reindexed edges
reindexed = map (bimap lookupVs lookupVs) es
-- Use a list of 2-tuples to set addresses in a matrix to 1
asArray = listArray ((0,0),(b,b)) (repeat 0) // map (, 1) reindexed
-- Find roots of `p` by trial dividing the entries of `xs`
findRootsFrom xs p = fmap head $ partitionEithers $ recurse p xs where
-- We only need to test roots we haven't failed to divide
tails [] = []
tails x@(_:xs) = x:tails xs
-- Try dividing `p` by every remaining 'integer' root from xs
trialDivisions p xs = map (\x -> (x, p `synthDiv` Poly [-head x, 1])) $ tails xs
-- Find the first root which has a zero remainder, or Nothing if none exists
firstRoot p xs = listToMaybe $ dropWhile ((/= 0) . snd . snd) $
trialDivisions p xs
-- We either found a root (r) and need to recurse with the quotient (q)
-- Or we couldn't find a root, and terminate with the number of unfound roots
recurse p xs = case firstRoot p xs of
(Just (next@(r:_), (q,_))) -> Left r:recurse q next
_ -> [Right $ length (coeffs p) - 1]
-- Show the spectrum
showSpectrum (xs, y) = intercalate ", " showMults ++ showMissing where
showMults = map showMult (rle Nothing xs)
-- Markdown notation for a root x repeated y times
showMult (x,y) = show x ++ "^" ++ show y ++ "^"
showMissing = if y == 0 then "" else " " ++ show y ++ " other roots"
-- Run-length encode a list to a list containing (original entry, count)
rle Nothing [] = []
rle (Just x) [] = [x]
rle Nothing (x:xs) = rle (Just (x, 1)) xs
rle (Just (y, c)) (x:xs)
| x == y = rle (Just (y, c+1)) xs
| otherwise = (y, c):rle (Just (x, 1)) xs
-- Characteristic, degree, remark
data CharGraphRow = CGR {
cgrCharacteristic :: Int,
cgrDegree :: Int,
cgrRemark :: String
}
charGraphTable = columns (\(_, f) r -> f r) (\(c, _) -> Headed c) [
("Characteristic", \(CGR n d _) -> if d == 2 then show n else ""),
("Order", show . \(CGR n d _) -> n^d),
("Spectrum", \(CGR n d _) -> showSpectrum $ findRootsFrom [-1..35] $
charpoly $ fmap fromIntegral $ edgesToAdjacency $
irreducibleGraphNoEye d n),
("Remark", cgrRemark)
]
markdown $ markdownTable charGraphTable [
CGR 2 2 "",
CGR 2 3 "Mersenne",
CGR 2 4 "",
CGR 2 5 "Mersenne",
CGR 3 2 "",
CGR 3 3 "Pseudo-Mersenne?",
CGR 5 2 "",
CGR 5 3 "Prime power in spectrum",
CGR 7 2 "",
CGR 7 3 "Composite in spectrum",
CGR 11 2 "Composite in spectrum"
]
```
Incredibly, all spectra shown are composed exclusively of integers, and thus,
each of these graphs are integral graphs.
Moreover, it does not appear that any integer sequences that one may try extracting from this table
(for example, the multiplicity of -1) can be found in the
[Online Encyclopedia of Integer Sequences](https://oeis.org/).
From what I was able to tell, the following subgraphs were *also* integral over the range I tested:
- the induced subgraph of vertices corresponding to non-primitives
- the complement of the previous graph with respect to the whole graph
- the induced subgraph of vertices corresponding only to irreducibles
Unfortunately, proving any such relationship is out of the scope of this post (and my abilities).
Closing
-------
This concludes the first foray into using matrices as elements of prime power fields.
It is a subject which, using the tools of linear algebra, makes certain aspects of field theory
more palatable and constructs some objects with fairly interesting properties.
One of the most intriguing parts to me is the sequence of polynomials generated by a companion matrix.
Though I haven't proven it, I suspect that it suffices to study only the sequence generated
by a primitive polynomial.
It seems to be possible to get the non-primitive sequences by looking at the subsequences
where the indices are multiples of a factor of the length of the sequence.
But this means that the entire story about polynomials and finite fields can be foregone entirely,
and the problem instead becomes one of number theory.
This post has [an addendum](./extra/) to it which discusses some additional notes about matrix roots and the
Cayley-Hamilton theorem.
The [next post](../3/) will focus on an "application" of matrix roots to other areas of abstract algebra.
Diagrams made with Geogebra and NetworkX (GraphViz).

BIN
posts/math/finite-field/2/irreducibles-graphs/irred_121.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
posts/math/finite-field/2/irreducibles-graphs/irred_125.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
posts/math/finite-field/2/irreducibles-graphs/irred_25.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
posts/math/finite-field/2/irreducibles-graphs/irred_27.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
posts/math/finite-field/2/irreducibles-graphs/irred_343.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
posts/math/finite-field/2/irreducibles-graphs/irred_49.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1 @@
../2/Previous.hs

BIN
posts/math/finite-field/3/a5_psl24_cayley.png (Stored with Git LFS) Normal file

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More