hilbertplot 0.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,285 @@
1
+ Metadata-Version: 2.4
2
+ Name: hilbertplot
3
+ Version: 0.0.0
4
+ Summary: The complete set of 40 two-dimensional Hilbert curves, and data plots on them.
5
+ Project-URL: Homepage, https://github.com/El3ssar/hilbertplot
6
+ Project-URL: Source, https://github.com/El3ssar/hilbertplot
7
+ Project-URL: Issues, https://github.com/El3ssar/hilbertplot/issues
8
+ Author-email: Daniel Estevez <kemossabee@gmail.com>
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: dimensionality-reduction,hilbert-curve,locality,moore-curve,space-filling-curve,visualization
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
17
+ Classifier: Topic :: Scientific/Engineering :: Visualization
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: numpy>=1.21
21
+ Provides-Extra: dev
22
+ Requires-Dist: matplotlib>=3.5; extra == 'dev'
23
+ Requires-Dist: pytest>=7.0; extra == 'dev'
24
+ Requires-Dist: ruff==0.15.13; extra == 'dev'
25
+ Provides-Extra: plot
26
+ Requires-Dist: matplotlib>=3.5; extra == 'plot'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # hilbertplot
30
+
31
+ The **complete set of forty two-dimensional Hilbert curves** — and a fast way to plot
32
+ long 1-D data on any of them.
33
+
34
+ Most software knows exactly one Hilbert curve. In fact there are **forty** distinct
35
+ space-filling curves of Hilbert type in two dimensions (up to rotation, reflection and
36
+ reversion), as proved by
37
+
38
+ > E. Estevez-Rams, D. Estevez-Moya, Y. Martínez-Camejo, D. Gómez-Gómez and
39
+ > B. Aragón-Fernández, *"Hilbert curves in two dimensions"*,
40
+ > Revista Cubana de Física **34**, 9 (2017).
41
+
42
+ | group | count | kernel(s) | members |
43
+ |-------|-------|-----------|---------|
44
+ | **homogeneous · proper** | 6 | `Hilbert` | Hilbert, Moore, Liu1–4 |
45
+ | **homogeneous · improper** | 6 | `Liu4` | Improper1–6 |
46
+ | **inhomogeneous** | 28 | mixed | e.g. `Liu2+Liu4 #1`, `Hilbert+Liu3 #1` |
47
+
48
+ *Homogeneous* curves build every quadrant from a **single** kernel curve type;
49
+ *inhomogeneous* curves mix **two or more** — and each inhomogeneous curve is named for
50
+ the recipe it uses.
51
+
52
+ ## Install
53
+
54
+ ```bash
55
+ pip install hilbertplot # core (numpy only)
56
+ pip install "hilbertplot[plot]" # + matplotlib for the plotting / visualisation features
57
+ ```
58
+
59
+ or, with [uv](https://docs.astral.sh/uv/):
60
+
61
+ ```bash
62
+ uv add "hilbertplot[plot]"
63
+ ```
64
+
65
+ Development (editable, with tests):
66
+
67
+ ```bash
68
+ git clone https://github.com/El3ssar/hilbertplot
69
+ cd hilbertplot
70
+ uv venv && uv pip install -e ".[dev]"
71
+ uv run pytest
72
+ ```
73
+
74
+ ## Curves — you think "curve *i* of order *j*"
75
+
76
+ ```python
77
+ import hilbertplot
78
+
79
+ c = hilbertplot.curve(0) # by index 0–39, name ("Moore"), or symbol ("12H")
80
+ c.show(6) # draw order 6 in a window
81
+ c.save(6, "hilbert.png") # ...or to a file
82
+ c.points(6) # (4096, 2) integer lattice points, in visit order
83
+ c.word(6) # the u/d/l/r stroke string
84
+ c # repr is just the name: Hilbert
85
+ ```
86
+
87
+ Rendering adapts to the order so it stays fast at every scale (`mode="auto"`): low orders
88
+ draw the colour-graded polyline; from order 8 up, where strokes are sub-pixel, it paints a
89
+ visit-order **fill heatmap** (a million points in a fraction of a second). Force it with
90
+ `mode="line"` / `mode="fill"`, or pass `color="steelblue"` for a solid line.
91
+
92
+ ## Grouping
93
+
94
+ The group functions return a list that **prints as a table**:
95
+
96
+ ```python
97
+ >>> print(hilbertplot.proper())
98
+ # name kind closed
99
+ - ------- ------ ------
100
+ 0 Hilbert proper
101
+ 1 Moore proper yes
102
+ 2 Liu1 proper yes
103
+ 3 Liu2 proper
104
+ 4 Liu3 proper
105
+ 5 Liu4 proper
106
+ ```
107
+
108
+ ```python
109
+ hilbertplot.homogeneous() hilbertplot.inhomogeneous() # 12 + 28
110
+ hilbertplot.proper() hilbertplot.improper() # 6 + 6
111
+ hilbertplot.by_kernels(3, 5) # the six Liu2+Liu4 curves
112
+ hilbertplot.closed() # Moore, Liu1, Improper1, Improper4
113
+ hilbertplot.generalizing() # the 8 that tile any n×n square
114
+ ```
115
+
116
+ Any single curve tells you where it sits:
117
+
118
+ ```python
119
+ c = hilbertplot.curve("Liu2+Liu4 #1")
120
+ c.kind # 'inhomogeneous'
121
+ c.is_homogeneous # False
122
+ c.kernels # frozenset({3, 5})
123
+ c.is_closed # False
124
+ ```
125
+
126
+ ## Visualising data — Hilbert plots
127
+
128
+ Lay a long 1-D vector onto a 2-D image by walking a curve and dropping `data[i]` on the
129
+ `i`-th cell it visits (Estevez-Rams et al., *Comput. Phys. Commun.* **197** (2015) 118).
130
+ `plot_data` (or the `hilbert_plot` shortcut) returns a **`HilbertPlot`** that holds the
131
+ data and draws *nothing* until you ask:
132
+
133
+ ```python
134
+ import numpy as np, hilbertplot
135
+
136
+ plot = hilbertplot.hilbert_plot(0, values) # or: hilbertplot.curve(0).plot_data(values)
137
+ plot.show("viridis") # a matplotlib colormap by name...
138
+ plot.show(["red", "green", "tab:blue"]) # ...or colours to interpolate across
139
+ plot.save("out.png", "gray") # straight to a file
140
+ img = plot.image() # just the 2-D array (for a Fourier map, etc.)
141
+ ```
142
+
143
+ Pass a **list of colours** and the library builds a smooth colormap through them and
144
+ spreads it over the data's min→max — so `1..100` with `["red","green","tab:blue"]` sweeps
145
+ red → green → blue along the curve. A single string is a built-in matplotlib colormap.
146
+
147
+ **Granularity** (block-mean coarsening that makes periodic structure pop) and the **fit**
148
+ (how the grid is sized) are chosen per render, so one `HilbertPlot` gives every view:
149
+
150
+ ```python
151
+ plot.show(granularity=4) # coarsen: blocks of 4 -> their mean
152
+ plot.show() # fit="auto": tight ceil(√N) square for the 8
153
+ # generalizing curves, else pad to 2^k
154
+ plot.show(fit="square") # force the tight square (generalizing curves only)
155
+ plot.show(fit="pad") # smallest enclosing 2^k grid, extra cells blank
156
+ plot.show(fit="pad", fill=0.0) # ...pad with a value that won't skew the colours
157
+ plot.show(fit="truncate") # largest 2^k grid that fits inside; drop the tail
158
+ plot.show(order=6) # force a 2^order grid
159
+ ```
160
+
161
+ `fit` answers "what if my data length isn't a power of four?" For the eight
162
+ [generalizing curves](#arbitrary-square-sizes) the default is a **tight `⌈√N⌉` square**
163
+ (so 10 000 values → `100×100`, not `128×128`); for the other 32 it falls back to **pad**
164
+ (smallest enclosing `2^k`) or **truncate**. See `examples/plot_data.py`.
165
+
166
+ ### Arbitrary square sizes
167
+
168
+ Eight of the forty curves — `hilbertplot.generalizing()` — can be laid on **any** `n×n` square,
169
+ not just `2^k` (proved in `research/quasisquares/`; the other 32 are provably obstructed):
170
+
171
+ ```python
172
+ hilbertplot.curve(0).grid(100) # Hilbert on a 100×100 grid -> (10000, 2)
173
+ hilbertplot.curve(0).generalizes # True
174
+ hilbertplot.curve(9).grid(100) # ValueError: Improper4 can't tile arbitrary squares
175
+ ```
176
+
177
+ `grid(2**k)` equals `points(k)` exactly, so the two are interchangeable.
178
+
179
+ ### Where locality breaks — the difference map
180
+
181
+ Toggle a **difference-map** overlay to mark, in colour, where the curve breaks locality —
182
+ cells that are neighbours on the plane but far apart along the curve (Pérez-Demydenko et
183
+ al., *Appl. Math. Comput.* **234** (2014) 531). The threshold is tunable: **higher marks
184
+ fewer, worse sites**, lower marks more.
185
+
186
+ ```python
187
+ plot.show() # data only
188
+ plot.show(difference=True) # + strong-locality barrier
189
+ plot.show(difference=True, difference_threshold=0.5) # mark more of it
190
+ plot.show(difference=True, difference_color="red")
191
+
192
+ hilbertplot.curve(0).difference_map(6) # the raw field, independent of any data
193
+ ```
194
+
195
+ The barrier is the locus of cells above `mean + threshold·std` of the difference field;
196
+ there is always one between quadrants 1 and 4.
197
+
198
+ ### Fourier map — hidden patterns
199
+
200
+ Toggle `fourier=True` to render the **2-D Fourier map** (centred power spectrum) of the
201
+ Hilbert plot instead of the data. Periodic and self-similar structure that's invisible in
202
+ the raw plot shows up as bright peaks and symmetric patterns (Estevez-Rams et al., 2015):
203
+
204
+ ```python
205
+ plot.show(fourier=True) # the 2-D Fourier map
206
+ plot.show(fourier=True, window=True) # Hann window first (kills edge artifacts)
207
+ spec = plot.fourier() # the raw spectrum array
208
+
209
+ # paper-style side by side
210
+ fig, (a, b) = plt.subplots(1, 2)
211
+ plot.draw("gray", ax=a); plot.draw("magma", ax=b, fourier=True)
212
+ ```
213
+
214
+ A Thue–Morse sequence gives a self-similar 4-fold-symmetric map; a periodic signal buried
215
+ in noise (which looks like static as a Hilbert plot) reveals a clean bright cluster.
216
+
217
+ ## Round trip — 2-D back to 1-D
218
+
219
+ `unroll` reads a square array back into a 1-D vector in curve order — the inverse of the
220
+ data mapping, and a locality-preserving way to flatten an image. The side may be any `n`
221
+ for a generalizing curve, else a power of two:
222
+
223
+ ```python
224
+ img = hilbertplot.hilbert_plot(3, data).image() # 1-D -> 2-D
225
+ back = hilbertplot.curve(3).unroll(img) # 2-D -> 1-D (back == data)
226
+ ```
227
+
228
+ ## Performance
229
+
230
+ Producing the **whole** curve at order `n` is `O(4ⁿ)` — the curve *is* `4ⁿ` points. The
231
+ generator runs on numpy stroke-code arrays (a morphism is a 4-entry lookup, reversion an
232
+ array reversal, quadrant assembly a `concatenate`) with no Python loop over strokes:
233
+
234
+ | order | points | `points()` | `save()` |
235
+ |------:|-------:|-----------:|---------:|
236
+ | 8 | 65 536 | ~4 ms | ~80 ms |
237
+ | 10 | 1 048 576 | ~40 ms | ~180 ms |
238
+ | 12 | 16 777 216 | ~0.7 s | ~2.4 s |
239
+
240
+ ## Design
241
+
242
+ The public API is small — `curve`, `catalog`, the group filters (`homogeneous`,
243
+ `inhomogeneous`, `proper`, `improper`, `closed`, `generalizing`, `by_kernels`), `gallery`
244
+ and `hilbert_plot`. `curve(i)` hands you a `Curve`; `plot_data` / `hilbert_plot` hand you a
245
+ `HilbertPlot`. Everything else is internal, organised into three subpackages:
246
+
247
+ ```
248
+ hilbertplot/
249
+ ├── _core/ the curve maths (numpy + nothing else)
250
+ │ ├── symmetry.py the square's symmetry group D4, morphisms, reversion
251
+ │ ├── catalog.py the declarative table of all 40 curves (single source of truth)
252
+ │ ├── generation.py the tag-system generator (numpy stroke codes, memoised)
253
+ │ ├── geometry.py codes → points, space-filling validation, canonicalisation
254
+ │ ├── arbitrary.py arbitrary n×n squares (AEP) for the 8 generalizing curves
255
+ │ └── curve.py the Curve object, CurveList, and catalogue lookups
256
+ ├── _data/ 1-D ↔ 2-D mapping, granularity, pad/truncate fitting, difference map
257
+ │ └── mapping.py
258
+ └── _viz/ matplotlib rendering (imported lazily)
259
+ ├── render.py curve rendering + gallery
260
+ └── hilbert_plot.py the HilbertPlot object
261
+ ```
262
+
263
+ Correctness is enforced by tests that check **all forty** curves are space-filling at
264
+ several orders and are **pairwise distinct** under the sixteen-element
265
+ symmetry-plus-reversion group — which, by the paper's completeness theorem, means the set
266
+ is exactly right. Curve 0 is additionally cross-checked against the standard `d2xy`
267
+ algorithm, and the homogeneous curves against Table 3 of the paper.
268
+
269
+ ```bash
270
+ uv run pytest # 217 tests
271
+ uv run pytest --doctest-modules src/hilbertplot # + the docstring examples
272
+ ```
273
+
274
+ ## Roadmap
275
+
276
+ * **done:** 1-D → 2-D on any curve, granularity, `unroll` back to 1-D, difference-map
277
+ overlays, the **2-D Fourier map**, and **arbitrary `n×n` squares for the eight
278
+ generalizing curves** (full classification + impossibility proofs in
279
+ `research/quasisquares/`);
280
+ * arbitrary **rectangles** `w×h` (the classification for these is open — next phase);
281
+ * faster single-point mapping — `index → (x, y)` in `O(n)` without building the whole curve.
282
+
283
+ ## License
284
+
285
+ MIT.
@@ -0,0 +1,18 @@
1
+ hilbertplot/__init__.py,sha256=-9wB_uknZIkw8UUNY6sHFcWO7J8v1AydiVvDE1-q6bQ,2072
2
+ hilbertplot/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ hilbertplot/_core/__init__.py,sha256=MgPnyufkBj38ptK450ohQ--ksjM2ndQErzASxcWNKsA,56
4
+ hilbertplot/_core/arbitrary.py,sha256=gbhRuz6jQg2MvhItcAgKhD2626mzz56QsynJmoRbbQc,5234
5
+ hilbertplot/_core/catalog.py,sha256=LSF27fC7HGul8ZMCk7mw0Bh3d6YqmwoOfgZtkK7VZzk,14601
6
+ hilbertplot/_core/curve.py,sha256=Kj4N-jr-QolndX7rT9X4eD9Uhgbdhse3gY1Vh-FGsTI,18372
7
+ hilbertplot/_core/generation.py,sha256=AG5OV1qjU2wSL6R7bG5OrPfFkQoV4fraHd-E-JsvhKo,3722
8
+ hilbertplot/_core/geometry.py,sha256=NNeVQfXGYFrYrpNTLVjD9eU8eg0AMOnxTgfnewOBnOk,4987
9
+ hilbertplot/_core/symmetry.py,sha256=x6ieGZDhx_dgI3ffi6gwGZcewG2qhAGqOoKyYOkzhpg,6580
10
+ hilbertplot/_data/__init__.py,sha256=MgPnyufkBj38ptK450ohQ--ksjM2ndQErzASxcWNKsA,56
11
+ hilbertplot/_data/mapping.py,sha256=KGGVuMepcImUqwPYl_wawF5UPULp1bJ4Iovh0LQnoLo,8821
12
+ hilbertplot/_viz/__init__.py,sha256=MgPnyufkBj38ptK450ohQ--ksjM2ndQErzASxcWNKsA,56
13
+ hilbertplot/_viz/hilbert_plot.py,sha256=Gzms6JJpBWALsvzDTU5KtjOrrOEDvamq_HB-iSNsKuQ,15284
14
+ hilbertplot/_viz/render.py,sha256=rDv1MMFcxdG1xmT-pELAYylLPHXrG4OIx-GebqC2W4Q,6420
15
+ hilbertplot-0.0.0.dist-info/METADATA,sha256=Ll3xbNy8zaJGgTtFJ5FNub9EFZtInq5JQgxUPpRYNZw,12026
16
+ hilbertplot-0.0.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
17
+ hilbertplot-0.0.0.dist-info/licenses/LICENSE,sha256=tZaSFJxkDbGoVJ93T4O4aH-vZKq0TW_8A7JtP7aX7Cg,1071
18
+ hilbertplot-0.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Daniel Estevez
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.