{"id":24321196,"url":"https://github.com/cfcs/lilsumthing","last_synced_at":"2025-07-20T06:32:14.253Z","repository":{"id":65434036,"uuid":"581230426","full_name":"cfcs/lilsumthing","owner":"cfcs","description":"AST rewriting of Python for-loop summations as closed-form Gauss summations","archived":false,"fork":false,"pushed_at":"2023-01-23T19:18:44.000Z","size":39,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-18T00:58:24.581Z","etag":null,"topics":["abstract-syntax-tree","arithmetic","arithmetic-computation","ast","constant-folding","constant-time","gauss","python","summation"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cfcs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-12-22T16:07:36.000Z","updated_at":"2022-12-23T12:35:14.000Z","dependencies_parsed_at":"2023-02-12T22:40:18.551Z","dependency_job_id":null,"html_url":"https://github.com/cfcs/lilsumthing","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/cfcs/lilsumthing","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cfcs%2Flilsumthing","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cfcs%2Flilsumthing/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cfcs%2Flilsumthing/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cfcs%2Flilsumthing/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cfcs","download_url":"https://codeload.github.com/cfcs/lilsumthing/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cfcs%2Flilsumthing/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266076350,"owners_count":23872741,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["abstract-syntax-tree","arithmetic","arithmetic-computation","ast","constant-folding","constant-time","gauss","python","summation"],"created_at":"2025-01-17T16:32:39.269Z","updated_at":"2025-07-20T06:32:14.215Z","avatar_url":"https://github.com/cfcs.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# lilsumthing\n\nThis repo contains a little experiment with the Python [`ast` module](https://docs.python.org/3/library/ast.html).\n\nI wanted to play with AST rewriting, and based on my previous [notes about Gauss summations](https://github.com/cfcs/misc/blob/master/gauss-sum.md) I thought it would be fun to try to write something that could rewrite simple for-loops that calculated sums, replacing them with their closed-form represenation.\n\nIt presently handles multiplications and additions, subtractions, and simple exponentiations (`n^0` through `n^11`), with constant folding, and it tries to refrain from suggesting incorrect patches, but it is probably not foolproof. If you manage to fool it, please open an issue and we'll add a test. :-) In general, any and all suggestions and patches are welcome here.\n\nModulo/division, and other arithmetic operations would be nice to add.\n\n## Examples\n\n### Usage\n```\nusage: lilsumthing [-h] [-v] FILE [FILE ...]\n\nTry to rewrite for-loop based summations to use closed-form expressions\n\npositional arguments:\n  FILE           python module files to examine\n\noptional arguments:\n  -h, --help     show this help message and exit\n  -v, --verbose\n```\n\n### Example 1\n\n```python\ndef sum1(n):\n    # 608850 * n \u003c=\u003e 4950 * 123 * n\n    x = 0\n    for i in range(100):\n        x += i * 123 * n\n    return x\n\ndef sum1_gen(n):\n    return sum((i * 123 * n for i in range(100)))\n```\n```bash\n$ python3 lilsumthing.py example1.py\n```\n```diff\n def sum1(n):\n     x = 0\n-    for i in range(100):\n-        x += i * 123 * n\n+    x = n * 608850\n     return x\n \n def sum1_gen(n):\n-    return sum((i * 123 * n for i in range(100)))\n+    return n * 608850\n```\n\nThe first example also highlights a glaring problem, namely that since we are using the `ast` module we are operating on an abstract syntax tree devoid of original comments, whitespace, etc, so we cannot produce a faithful, guaranteed-to-apply patch. Maybe in the future we could use [`libcst`](https://github.com/Instagram/LibCST) instead of the built-in `ast` module.\n\n### Example 2\n\n```python\ndef sum2(n):\n    x = 0\n    for i in range(100):\n        x += i * i * n\n    return x\n```\n```bash\n$ python lilsumthing.py -v example2.py\n# note -v for debug output:\n```\n```diff\n     identified potential counter x\n     ast.For loop: for i in range(100):\n    x += i * i * n\n           mult [i] [i]\n           prod [[i; i]]\n         mult [[i; i]] [n]\n         prod [[n; i; i]]\n       postprocess_augassign x += i * i * n\n       [[n; i; i]]\n       [[i; i; n; 1]]\n       [[n; 1; 328350]]\n       [[n; 328350]; [0]]\n       [n * 328350; 0]\n       x += i * i * n\n       ==\u003e x = n * 328350\n     ENDFOR\n\n def sum2(n):\n     x = 0\n-    for i in range(100):\n-        x += i * i * n\n+    x = n * 328350\n     return x\n```\n\n### Example 3\n```python\ndef sum3(n):\n    S = 0\n    for i in range(1,50000000):\n        S += i*(3+i*n+4)*i + (3+n*3*i)*5 + i*(2+n+i)*5\n    return S\n```\n```bash\n$ python3 lilsumthing.py example3.py\n```\n```diff\n def sum3(n):\n     S = 0\n-    for i in range(1, 50000000):\n-        S += i * (3 + i * n + 4) * i + (3 + n * 3 * i) * 5 + i * (2 + n + i) * 5\n+    S = 499999997500000599999985 + n * 1562499937500025624999500000000\n     return S\n```\n\n### Example 4\n```diff\n def sum4(n):\n     S = 0\n-    for i in range(1000000):\n-        S += ((-i)**2) * -2 * -3 *(-i-2)\n+    S = -1500000999995500002000000\n     return S\n```\n### Example 5\n```diff\n def sum5(n):\n     S = 0\n-    for i in range(1000):\n-        S += n * (n*5 - 2 * i * n) ** 3 - n * (i * 7 * n - 10*n) ** 2\n+    S = n * n * n * n * -1976106790000 + n * n * n * -16239011500\n     return S\n```\n\n### Example 6\n\nBesides exercising the hardcoded limit of `i**11`, this example demonstrates that I should really fold `n * n * ...` into `n**7` :-)\n```diff\n def sum6(n):\n     S = 0\n-    for i in range(10000000):\n-        S += (2 + i * n) ** 9 * (n - i + 3) ** 2\n+    S = n * n * 92159907840027647998156799995904000000 + n * n * n * 895999086080281599978879999667200045312000000 + n * n * n * n * 5759993952001921919852159993248000777600000128000000 + n * n * n * n * n * 25199972640008903999375039928600007392000067199989056000000 + n * n * n * n * n * n * 74666582666694186665322666211946708666667823999865599998912000000 + n * n * n * n * n * n * n * 143999832000053400000239998191200141120009259999135999980640001248000000 + n * n * n * n * n * n * n * n * 163636165636418636372636359256363879963677643633303636214636372456363852000000 + n * n * n * n * n * n * n * n * n * 83333228787890954565954539692045514545559278782238787310037905787880434545414000000 + n * n * n * n * n * n * n * n * n * n * -18181802181820848485298484708484852684849524848454848479848484938484856000000 + n * n * n * n * n * n * n * n * n * n * n * 999999500000074999999999999300000000000004999999999999985000000000000 + n * 5759994240001734399909120000000 + 170666487466728960000000\n     return S\n```\n\n## Math\n\n### Sums of `c` for constant `c`\n\nFor constants `c`, that do not depend on the loop variable, it suffices to multiply by the length/cardinality of the range we are iterating over.\n\n\u003cdetails\u003e\n\nExample:\n```python\nS = 0\nfor i in range(4)\n  S += 5\n```\nHere we are adding 5 for each element `i` in`[0,1,2,3]`. We can rewrite that as:\n```python\nS = 0\nS += len(range(4)) * 5 # == len([0,1,2,3]) * 5 # = 4 * 5 = 20\n```\n\n\u003c/details\u003e\n\n### Sums of `i` for loopvar `i`\n\nWikipedia has a [neat article about Triangular numbers](https://en.wikipedia.org/wiki/Triangular_number#Formula).\n\nThe TL;DR version is that it suffices that for `range(n)` we can substitute `(n-1)*n//2`.\nThe `//2` division doesn't truncate because either `n` or `n-1` will be an even number (*\"congruent to 0 mod 2\"*), ie a multiple of two, so we can safely divide by two.\n\n### Sums of `i^c` for constant `c`, loopvar `i`\n\n`i^0` is replaced with `1`.\n\n`i^1` uses the *Triangular number* formula described in the section above.\n\n\u003cdetails\u003e\n\nFor higher values of `c`, most solutions involve computing either [Bernoulli numbers](https://en.wikipedia.org/wiki/Bernoulli_number) or [Stirling partition numbers](https://en.wikipedia.org/wiki/Stirling_numbers_of_the_second_kind), to be plotted into [Faulhaber's formula](https://en.wikipedia.org/wiki/Faulhaber%27s_formula).\n\nThis gets pretty complicated, and slow, so for now, we hardcode the formulas for lower values of `c`, and fail to do anything sensible about e.g. `i^100`.\n\n1. https://github.com/Spooghetti420/Faulhaber/blob/main/calculator.py\n2. https://gist.github.com/goulu/5bbf24a3e2e25070904b79f49020448f\n3. https://stackoverflow.com/questions/22726715/efficient-implementation-fo-faulhabers-formula\n4. also of interest, this impl using Stirling numbers instead of Bernoulli numbers: https://rosettacode.org/wiki/Faulhaber%27s_formula#Python\n- see David Harvey's algorithm (according to wikipedia used in SageMath): https://arxiv.org/pdf/0807.1347.pdf\n5. and lastly this sounds promising https://arxiv.org/abs/1103.1585 but it reads pretty dense\n6. https://mathpages.com/home/kmath279/kmath279.htm here are some simple examples too\n\n\u003c/details\u003e\n\n### Sums of `c^i` for constant `c`, loopvar `i`\n\nThis one is sadly not implemented yet, but it would make a great addition.\n\n[sum of terms in geometric series with common factor `c`:](https://en.wikipedia.org/wiki/Geometric_series#Sum)\n\u003cdetails\u003e\n\n```python\n\u003e\u003e\u003e c = 10\n\u003e\u003e\u003e n = 4\n\u003e\u003e\u003e sum([c**i for i in range(1,n+1)])\n11110\n# n\n# ⅀ c**k  \u003c=\u003e ((c**i)-c**(n+1)) / (1-c)\n# k=i\n\u003e\u003e\u003e (c**1 - c**(n+1))//(1-c)\n11110\n```\n\n\u003c/details\u003e\n\nSource: https://mathworld.wolfram.com/PowerSum.html\n\u003cdetails\u003e\n\n```\nn\n⅀ k * c**k = (c-(n+1)* c**(n+1) + n * c**(n+2)) // (c-1)**2\nk=0\n```\n\n\u003c/details\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcfcs%2Flilsumthing","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcfcs%2Flilsumthing","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcfcs%2Flilsumthing/lists"}