complexplorer 2.0.0__tar.gz → 3.0.0__tar.gz
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.
- complexplorer-3.0.0/PKG-INFO +222 -0
- complexplorer-3.0.0/README.md +167 -0
- complexplorer-3.0.0/complexplorer/__init__.py +119 -0
- {complexplorer-2.0.0 → complexplorer-3.0.0}/complexplorer/_version.py +1 -1
- complexplorer-3.0.0/complexplorer/api.py +111 -0
- complexplorer-3.0.0/complexplorer/cli/__init__.py +6 -0
- complexplorer-3.0.0/complexplorer/cli/main.py +239 -0
- complexplorer-3.0.0/complexplorer/core/__init__.py +73 -0
- {complexplorer-2.0.0 → complexplorer-3.0.0}/complexplorer/core/color_utils.py +114 -93
- {complexplorer-2.0.0 → complexplorer-3.0.0}/complexplorer/core/colormap.py +595 -359
- {complexplorer-2.0.0 → complexplorer-3.0.0}/complexplorer/core/domain.py +148 -234
- complexplorer-3.0.0/complexplorer/core/expression.py +137 -0
- complexplorer-3.0.0/complexplorer/core/field.py +203 -0
- {complexplorer-2.0.0 → complexplorer-3.0.0}/complexplorer/core/functions.py +76 -101
- complexplorer-3.0.0/complexplorer/core/presets.py +545 -0
- {complexplorer-2.0.0 → complexplorer-3.0.0}/complexplorer/core/scaling.py +151 -96
- complexplorer-3.0.0/complexplorer/ee/__init__.py +29 -0
- complexplorer-3.0.0/complexplorer/ee/transfer_function.py +284 -0
- complexplorer-3.0.0/complexplorer/exceptions.py +34 -0
- complexplorer-3.0.0/complexplorer/export/__init__.py +6 -0
- complexplorer-3.0.0/complexplorer/export/stl/__init__.py +19 -0
- {complexplorer-2.0.0 → complexplorer-3.0.0}/complexplorer/export/stl/mesh_repair.py +37 -78
- complexplorer-3.0.0/complexplorer/export/stl/ornament_generator.py +280 -0
- {complexplorer-2.0.0 → complexplorer-3.0.0}/complexplorer/export/stl/utils.py +84 -97
- complexplorer-3.0.0/complexplorer/gallery.py +142 -0
- complexplorer-3.0.0/complexplorer/mesh/__init__.py +17 -0
- complexplorer-3.0.0/complexplorer/mesh/builders.py +141 -0
- complexplorer-3.0.0/complexplorer/mesh/riemann_surface.py +188 -0
- complexplorer-3.0.0/complexplorer/mesh/surface.py +153 -0
- complexplorer-3.0.0/complexplorer/plotting/__init__.py +1 -0
- complexplorer-3.0.0/complexplorer/plotting/matplotlib/__init__.py +5 -0
- complexplorer-3.0.0/complexplorer/plotting/matplotlib/plot_2d.py +420 -0
- complexplorer-3.0.0/complexplorer/plotting/pyvista/__init__.py +12 -0
- complexplorer-3.0.0/complexplorer/plotting/pyvista/plot_3d.py +380 -0
- complexplorer-3.0.0/complexplorer/plotting/pyvista/riemann.py +285 -0
- complexplorer-3.0.0/complexplorer/plotting/pyvista/riemann_surface.py +117 -0
- complexplorer-3.0.0/complexplorer/plotting/pyvista/utils.py +196 -0
- complexplorer-3.0.0/complexplorer/py.typed +0 -0
- complexplorer-3.0.0/complexplorer/typing.py +110 -0
- complexplorer-3.0.0/complexplorer/utils/__init__.py +5 -0
- {complexplorer-2.0.0 → complexplorer-3.0.0}/complexplorer/utils/backend.py +29 -26
- complexplorer-3.0.0/complexplorer/utils/mesh_distortion.py +120 -0
- complexplorer-3.0.0/complexplorer/utils/validation.py +54 -0
- complexplorer-3.0.0/complexplorer.egg-info/PKG-INFO +222 -0
- {complexplorer-2.0.0 → complexplorer-3.0.0}/complexplorer.egg-info/SOURCES.txt +16 -7
- complexplorer-3.0.0/complexplorer.egg-info/entry_points.txt +2 -0
- complexplorer-3.0.0/complexplorer.egg-info/requires.txt +34 -0
- complexplorer-3.0.0/pyproject.toml +157 -0
- complexplorer-2.0.0/PKG-INFO +0 -362
- complexplorer-2.0.0/README.md +0 -327
- complexplorer-2.0.0/complexplorer/__init__.py +0 -168
- complexplorer-2.0.0/complexplorer/api.py +0 -210
- complexplorer-2.0.0/complexplorer/core/__init__.py +0 -59
- complexplorer-2.0.0/complexplorer/core/constants.py +0 -161
- complexplorer-2.0.0/complexplorer/exceptions.py +0 -259
- complexplorer-2.0.0/complexplorer/export/__init__.py +0 -5
- complexplorer-2.0.0/complexplorer/export/stl/__init__.py +0 -20
- complexplorer-2.0.0/complexplorer/export/stl/ornament_generator.py +0 -395
- complexplorer-2.0.0/complexplorer/plotting/__init__.py +0 -28
- complexplorer-2.0.0/complexplorer/plotting/matplotlib/__init__.py +0 -6
- complexplorer-2.0.0/complexplorer/plotting/matplotlib/plot_2d.py +0 -478
- complexplorer-2.0.0/complexplorer/plotting/matplotlib/plot_3d.py +0 -509
- complexplorer-2.0.0/complexplorer/plotting/pyvista/__init__.py +0 -7
- complexplorer-2.0.0/complexplorer/plotting/pyvista/plot_3d.py +0 -472
- complexplorer-2.0.0/complexplorer/plotting/pyvista/riemann.py +0 -339
- complexplorer-2.0.0/complexplorer/plotting/pyvista/utils.py +0 -170
- complexplorer-2.0.0/complexplorer/plotting/validation.py +0 -247
- complexplorer-2.0.0/complexplorer/special.py +0 -421
- complexplorer-2.0.0/complexplorer/utils/__init__.py +0 -6
- complexplorer-2.0.0/complexplorer/utils/color.py +0 -177
- complexplorer-2.0.0/complexplorer/utils/logging.py +0 -225
- complexplorer-2.0.0/complexplorer/utils/mesh.py +0 -234
- complexplorer-2.0.0/complexplorer/utils/mesh_distortion.py +0 -196
- complexplorer-2.0.0/complexplorer/utils/validation.py +0 -345
- complexplorer-2.0.0/complexplorer.egg-info/PKG-INFO +0 -362
- complexplorer-2.0.0/complexplorer.egg-info/requires.txt +0 -17
- complexplorer-2.0.0/pyproject.toml +0 -57
- {complexplorer-2.0.0 → complexplorer-3.0.0}/LICENSE +0 -0
- {complexplorer-2.0.0 → complexplorer-3.0.0}/LICENSE.art +0 -0
- {complexplorer-2.0.0 → complexplorer-3.0.0}/complexplorer.egg-info/dependency_links.txt +0 -0
- {complexplorer-2.0.0 → complexplorer-3.0.0}/complexplorer.egg-info/top_level.txt +0 -0
- {complexplorer-2.0.0 → complexplorer-3.0.0}/setup.cfg +0 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: complexplorer
|
|
3
|
+
Version: 3.0.0
|
|
4
|
+
Summary: Complex function visualizations inspired by Elias Wegert's 'Visual Complex Functions - An Introduction with Phase Portraits'
|
|
5
|
+
Author-email: Igor Kuvychko <igor.kuvychko@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/kuvychko/complexplorer
|
|
8
|
+
Project-URL: Documentation, https://kuvychko.github.io/complexplorer/
|
|
9
|
+
Project-URL: Source, https://github.com/kuvychko/complexplorer
|
|
10
|
+
Project-URL: Changelog, https://github.com/kuvychko/complexplorer/blob/main/CHANGELOG.md
|
|
11
|
+
Project-URL: Release notes, https://github.com/kuvychko/complexplorer/releases
|
|
12
|
+
Project-URL: Bug Tracker, https://github.com/kuvychko/complexplorer/issues
|
|
13
|
+
Keywords: complex analysis,visualization,phase portrait,domain coloring,riemann sphere,riemann surface,stl,3d printing,pyvista
|
|
14
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
20
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
21
|
+
Classifier: Operating System :: OS Independent
|
|
22
|
+
Classifier: Framework :: Matplotlib
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
License-File: LICENSE.art
|
|
27
|
+
Requires-Dist: numpy>=1.26.0
|
|
28
|
+
Requires-Dist: matplotlib>=3.8.0
|
|
29
|
+
Requires-Dist: scipy>=1.11.0
|
|
30
|
+
Requires-Dist: asteval>=1.0
|
|
31
|
+
Requires-Dist: pyvista>=0.47
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest-cov>=3.0.0; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest-timeout>=2.1.0; extra == "dev"
|
|
36
|
+
Requires-Dist: ruff<0.17,>=0.16.7; extra == "dev"
|
|
37
|
+
Requires-Dist: complexplorer[examples]; extra == "dev"
|
|
38
|
+
Provides-Extra: examples
|
|
39
|
+
Requires-Dist: nbmake>=1.5; extra == "examples"
|
|
40
|
+
Requires-Dist: nbconvert>=7.0; extra == "examples"
|
|
41
|
+
Requires-Dist: ipykernel>=6.0; extra == "examples"
|
|
42
|
+
Requires-Dist: colorspacious>=1.1; extra == "examples"
|
|
43
|
+
Requires-Dist: imageio>=2.31; extra == "examples"
|
|
44
|
+
Provides-Extra: docs
|
|
45
|
+
Requires-Dist: mkdocs-material<10,>=9.5; extra == "docs"
|
|
46
|
+
Requires-Dist: mkdocstrings[python]<0.31,>=0.27; extra == "docs"
|
|
47
|
+
Requires-Dist: mkdocs-jupyter<0.26,>=0.25; extra == "docs"
|
|
48
|
+
Provides-Extra: pyvista
|
|
49
|
+
Provides-Extra: 3d
|
|
50
|
+
Provides-Extra: qt
|
|
51
|
+
Requires-Dist: PyQt6>=6.5.0; extra == "qt"
|
|
52
|
+
Provides-Extra: all
|
|
53
|
+
Requires-Dist: complexplorer[qt]; extra == "all"
|
|
54
|
+
Dynamic: license-file
|
|
55
|
+
|
|
56
|
+
# Complexplorer
|
|
57
|
+
|
|
58
|
+
[](https://badge.fury.io/py/complexplorer)
|
|
59
|
+
[](https://pypi.org/project/complexplorer/)
|
|
60
|
+
[](https://opensource.org/licenses/MIT)
|
|
61
|
+
|
|
62
|
+
Complexplorer turns a complex function into something you can look at — and, if you want, hold.
|
|
63
|
+
It draws phase portraits, lifts them into 3D analytic landscapes, wraps them onto the Riemann
|
|
64
|
+
sphere, unfolds multivalued functions onto their Riemann surfaces, and exports any of it as an
|
|
65
|
+
STL you can print.
|
|
66
|
+
|
|
67
|
+
<p align="center">
|
|
68
|
+
<img src="https://raw.githubusercontent.com/kuvychko/complexplorer/main/examples/gallery/view/_tour/hero_labels_below.png" width="100%"
|
|
69
|
+
alt="Six panels: domain coloring, analytic landscape, Riemann relief, Riemann surface, transfer functions, and a 3D-printed ornament">
|
|
70
|
+
</p>
|
|
71
|
+
|
|
72
|
+
## Install and draw something
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pip install complexplorer
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
import complexplorer as cp
|
|
80
|
+
|
|
81
|
+
cp.plot(
|
|
82
|
+
cp.Rectangle(4, 4),
|
|
83
|
+
lambda z: (z**2 - 1) / (z**2 + 1),
|
|
84
|
+
cmap=cp.Phase(phase_sectors=6, auto_scale_r=True),
|
|
85
|
+
legend=True,
|
|
86
|
+
)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
<p align="center">
|
|
90
|
+
<img src="https://raw.githubusercontent.com/kuvychko/complexplorer/main/examples/gallery/view/_tour/legend_portrait.png" width="60%"
|
|
91
|
+
alt="Phase portrait of (z^2-1)/(z^2+1) with a phase-wheel legend inset">
|
|
92
|
+
</p>
|
|
93
|
+
|
|
94
|
+
Hue is the phase of `f(z)` and the shaded cells are its contour bands, so zeros and poles read as
|
|
95
|
+
opposite winding directions. The inset legend is the same colormap applied to the identity map,
|
|
96
|
+
which is what makes the picture decodable.
|
|
97
|
+
|
|
98
|
+
The same function in 3D, with `|f(z)|` as height:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
cp.plot_landscape_pv(cp.Rectangle(4, 4), lambda z: (z**2 - 1) / (z**2 + 1))
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
→ [Installation and a first portrait](https://kuvychko.github.io/complexplorer/getting-started/first-portrait/)
|
|
105
|
+
|
|
106
|
+
## What's new in 3.0
|
|
107
|
+
|
|
108
|
+
- **PyVista is the sole 3D backend**, and a required dependency. The matplotlib 3D paths are
|
|
109
|
+
removed; `plot_landscape_pv`, `pair_plot_landscape_pv` and `riemann_pv` replace them.
|
|
110
|
+
- **Riemann surfaces**: multi-sheeted covers of `z^(1/n)`, `log`, and algebraic curves
|
|
111
|
+
`w² = P(z)`, with branch points emerging from the geometry.
|
|
112
|
+
- **Thirteen colormaps**, including the perceptual families built on OkLCh and cubehelix.
|
|
113
|
+
- **A function catalog** (`cp.catalog`) with exact zero/pole/branch-point answer keys, and a CLI
|
|
114
|
+
that renders and exports from the terminal.
|
|
115
|
+
- **Engineering mode** (`cp.ee`): transfer functions as first-class complex callables.
|
|
116
|
+
- **A typed public API** with a `py.typed` marker, and colormap configuration validated at
|
|
117
|
+
construction.
|
|
118
|
+
|
|
119
|
+
Upgrading? The [migration guide](https://kuvychko.github.io/complexplorer/migration-3.0/) maps
|
|
120
|
+
every removed and renamed name to its replacement.
|
|
121
|
+
|
|
122
|
+
## What it does
|
|
123
|
+
|
|
124
|
+
| | |
|
|
125
|
+
|---|---|
|
|
126
|
+
| **Phase portraits** | Classic and enhanced domain coloring, with a phase-wheel legend |
|
|
127
|
+
| **Analytic landscapes** | `\|f(z)\|` as height, phase as colour, rendered by PyVista |
|
|
128
|
+
| **Riemann sphere** | The compactified plane, so infinity is a place you can look at |
|
|
129
|
+
| **Riemann surfaces** | The multi-sheeted cover on which a multivalued function is single-valued |
|
|
130
|
+
| **Domains** | Rectangles, disks, annuli, and set operations on them |
|
|
131
|
+
| **Colormaps** | Thirteen, including perceptual families and greyscale pattern maps |
|
|
132
|
+
| **Modulus scaling** | Ten transfer functions from `\|f(z)\|` to height or radius |
|
|
133
|
+
| **Engineering mode** | `H(s)` / `H(z)` with portrait, pole-zero, Bode and Nyquist views |
|
|
134
|
+
| **STL export** | Modulus-scaled Riemann relief ornaments for 3D printing |
|
|
135
|
+
| **CLI** | `complexplorer render \| stl \| list \| gallery` |
|
|
136
|
+
|
|
137
|
+
## A few more lines
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
# One-liners with sensible defaults: "2d", "3d", "riemann"
|
|
141
|
+
cp.quick_plot(lambda z: 1 / z, mode="riemann")
|
|
142
|
+
|
|
143
|
+
# A curated function, with its exact zeros and poles recorded
|
|
144
|
+
preset = cp.catalog.get("pole_flower_10")
|
|
145
|
+
cp.quick_plot(preset.func, **cp.PlotPresets.publication_ready())
|
|
146
|
+
|
|
147
|
+
# Transfer functions are plain callables, so every renderer accepts them
|
|
148
|
+
H = cp.ee.TransferFunction([1], [1, 0.2, 1])
|
|
149
|
+
cp.ee.transfer_portrait(H, legend=True)
|
|
150
|
+
cp.plot_landscape_pv(cp.Rectangle(6, 6), H)
|
|
151
|
+
|
|
152
|
+
# A printable ornament
|
|
153
|
+
cp.create_ornament(lambda z: z / (z**10 - 1), "flower.stl", size_mm=80)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
From the terminal:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
complexplorer list
|
|
160
|
+
complexplorer render preset:pole_flower_10 -o flower.png
|
|
161
|
+
complexplorer stl "z / (z**10 - 1)" --size-mm 80 -o flower.stl
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## From mathematics to an object on your desk
|
|
165
|
+
|
|
166
|
+
<p align="center">
|
|
167
|
+
<img src="https://raw.githubusercontent.com/kuvychko/complexplorer/main/examples/gallery/view/_tour/physical_triptych.png" width="100%"
|
|
168
|
+
alt="Riemann relief render, untextured STL mesh, and the printed ornament">
|
|
169
|
+
</p>
|
|
170
|
+
|
|
171
|
+
The relief is the mathematics, the mesh is the geometry that survives losing the colour, and the
|
|
172
|
+
print is the object on a desk. Ten poles become ten spikes around the central zero.
|
|
173
|
+
|
|
174
|
+
→ [STL export and the physical workflow](https://kuvychko.github.io/complexplorer/guide/physical-workflow/)
|
|
175
|
+
|
|
176
|
+
## Documentation
|
|
177
|
+
|
|
178
|
+
The [documentation site](https://kuvychko.github.io/complexplorer/) has the visual tour, the
|
|
179
|
+
guides, and a generated API reference.
|
|
180
|
+
|
|
181
|
+
- [Reading a phase portrait](https://kuvychko.github.io/complexplorer/guide/reading-a-portrait/) —
|
|
182
|
+
what the colours encode, and which colormap to choose (with measured colour-vision-deficiency
|
|
183
|
+
behaviour)
|
|
184
|
+
- [3D and the Riemann sphere](https://kuvychko.github.io/complexplorer/guide/three-dimensions/) —
|
|
185
|
+
including headless rendering and reproducible cameras
|
|
186
|
+
- [Gallery](https://kuvychko.github.io/complexplorer/gallery/gallery.generated/) — every preset
|
|
187
|
+
and colormap, with the code that made it
|
|
188
|
+
- [API map](https://kuvychko.github.io/complexplorer/api/map/) — one entry point per task, and
|
|
189
|
+
what each returns
|
|
190
|
+
- [Migration guide](https://kuvychko.github.io/complexplorer/migration-3.0/) and
|
|
191
|
+
[changelog](https://github.com/kuvychko/complexplorer/blob/main/CHANGELOG.md)
|
|
192
|
+
|
|
193
|
+
## A note on backends
|
|
194
|
+
|
|
195
|
+
matplotlib draws the 2D portraits and the stereographic charts. PyVista draws everything 3D and
|
|
196
|
+
builds the export meshes, because 3D here is a mesh, camera, lighting and export problem, which is
|
|
197
|
+
what PyVista is for and what matplotlib's 3D engine is not. As of 3.0 PyVista is a required
|
|
198
|
+
dependency, so there is no capability flag to check. The reasoning, with the measured install
|
|
199
|
+
footprint and import cost, is in
|
|
200
|
+
[the backend policy](https://kuvychko.github.io/complexplorer/development/backend-policy/).
|
|
201
|
+
|
|
202
|
+
## Contributing
|
|
203
|
+
|
|
204
|
+
See [CONTRIBUTING.md](https://github.com/kuvychko/complexplorer/blob/main/CONTRIBUTING.md) for
|
|
205
|
+
setup, the checks, the spec-driven workflow, and the release runbook.
|
|
206
|
+
|
|
207
|
+
## Citing
|
|
208
|
+
|
|
209
|
+
Citation metadata is in
|
|
210
|
+
[CITATION.cff](https://github.com/kuvychko/complexplorer/blob/main/CITATION.cff); GitHub's
|
|
211
|
+
"Cite this repository" button generates BibTeX from it.
|
|
212
|
+
|
|
213
|
+
## Acknowledgements
|
|
214
|
+
|
|
215
|
+
Inspired by Elias Wegert's *Visual Complex Functions: An Introduction with Phase Portraits*
|
|
216
|
+
(Birkhäuser, 2012), which is the book to read if you want to understand what these pictures show.
|
|
217
|
+
|
|
218
|
+
## License
|
|
219
|
+
|
|
220
|
+
MIT for the code; see [LICENSE](https://github.com/kuvychko/complexplorer/blob/main/LICENSE).
|
|
221
|
+
The gallery images and ornament designs are under
|
|
222
|
+
[LICENSE.art](https://github.com/kuvychko/complexplorer/blob/main/LICENSE.art).
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Complexplorer
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/py/complexplorer)
|
|
4
|
+
[](https://pypi.org/project/complexplorer/)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
Complexplorer turns a complex function into something you can look at — and, if you want, hold.
|
|
8
|
+
It draws phase portraits, lifts them into 3D analytic landscapes, wraps them onto the Riemann
|
|
9
|
+
sphere, unfolds multivalued functions onto their Riemann surfaces, and exports any of it as an
|
|
10
|
+
STL you can print.
|
|
11
|
+
|
|
12
|
+
<p align="center">
|
|
13
|
+
<img src="https://raw.githubusercontent.com/kuvychko/complexplorer/main/examples/gallery/view/_tour/hero_labels_below.png" width="100%"
|
|
14
|
+
alt="Six panels: domain coloring, analytic landscape, Riemann relief, Riemann surface, transfer functions, and a 3D-printed ornament">
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
## Install and draw something
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install complexplorer
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
import complexplorer as cp
|
|
25
|
+
|
|
26
|
+
cp.plot(
|
|
27
|
+
cp.Rectangle(4, 4),
|
|
28
|
+
lambda z: (z**2 - 1) / (z**2 + 1),
|
|
29
|
+
cmap=cp.Phase(phase_sectors=6, auto_scale_r=True),
|
|
30
|
+
legend=True,
|
|
31
|
+
)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
<p align="center">
|
|
35
|
+
<img src="https://raw.githubusercontent.com/kuvychko/complexplorer/main/examples/gallery/view/_tour/legend_portrait.png" width="60%"
|
|
36
|
+
alt="Phase portrait of (z^2-1)/(z^2+1) with a phase-wheel legend inset">
|
|
37
|
+
</p>
|
|
38
|
+
|
|
39
|
+
Hue is the phase of `f(z)` and the shaded cells are its contour bands, so zeros and poles read as
|
|
40
|
+
opposite winding directions. The inset legend is the same colormap applied to the identity map,
|
|
41
|
+
which is what makes the picture decodable.
|
|
42
|
+
|
|
43
|
+
The same function in 3D, with `|f(z)|` as height:
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
cp.plot_landscape_pv(cp.Rectangle(4, 4), lambda z: (z**2 - 1) / (z**2 + 1))
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
→ [Installation and a first portrait](https://kuvychko.github.io/complexplorer/getting-started/first-portrait/)
|
|
50
|
+
|
|
51
|
+
## What's new in 3.0
|
|
52
|
+
|
|
53
|
+
- **PyVista is the sole 3D backend**, and a required dependency. The matplotlib 3D paths are
|
|
54
|
+
removed; `plot_landscape_pv`, `pair_plot_landscape_pv` and `riemann_pv` replace them.
|
|
55
|
+
- **Riemann surfaces**: multi-sheeted covers of `z^(1/n)`, `log`, and algebraic curves
|
|
56
|
+
`w² = P(z)`, with branch points emerging from the geometry.
|
|
57
|
+
- **Thirteen colormaps**, including the perceptual families built on OkLCh and cubehelix.
|
|
58
|
+
- **A function catalog** (`cp.catalog`) with exact zero/pole/branch-point answer keys, and a CLI
|
|
59
|
+
that renders and exports from the terminal.
|
|
60
|
+
- **Engineering mode** (`cp.ee`): transfer functions as first-class complex callables.
|
|
61
|
+
- **A typed public API** with a `py.typed` marker, and colormap configuration validated at
|
|
62
|
+
construction.
|
|
63
|
+
|
|
64
|
+
Upgrading? The [migration guide](https://kuvychko.github.io/complexplorer/migration-3.0/) maps
|
|
65
|
+
every removed and renamed name to its replacement.
|
|
66
|
+
|
|
67
|
+
## What it does
|
|
68
|
+
|
|
69
|
+
| | |
|
|
70
|
+
|---|---|
|
|
71
|
+
| **Phase portraits** | Classic and enhanced domain coloring, with a phase-wheel legend |
|
|
72
|
+
| **Analytic landscapes** | `\|f(z)\|` as height, phase as colour, rendered by PyVista |
|
|
73
|
+
| **Riemann sphere** | The compactified plane, so infinity is a place you can look at |
|
|
74
|
+
| **Riemann surfaces** | The multi-sheeted cover on which a multivalued function is single-valued |
|
|
75
|
+
| **Domains** | Rectangles, disks, annuli, and set operations on them |
|
|
76
|
+
| **Colormaps** | Thirteen, including perceptual families and greyscale pattern maps |
|
|
77
|
+
| **Modulus scaling** | Ten transfer functions from `\|f(z)\|` to height or radius |
|
|
78
|
+
| **Engineering mode** | `H(s)` / `H(z)` with portrait, pole-zero, Bode and Nyquist views |
|
|
79
|
+
| **STL export** | Modulus-scaled Riemann relief ornaments for 3D printing |
|
|
80
|
+
| **CLI** | `complexplorer render \| stl \| list \| gallery` |
|
|
81
|
+
|
|
82
|
+
## A few more lines
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
# One-liners with sensible defaults: "2d", "3d", "riemann"
|
|
86
|
+
cp.quick_plot(lambda z: 1 / z, mode="riemann")
|
|
87
|
+
|
|
88
|
+
# A curated function, with its exact zeros and poles recorded
|
|
89
|
+
preset = cp.catalog.get("pole_flower_10")
|
|
90
|
+
cp.quick_plot(preset.func, **cp.PlotPresets.publication_ready())
|
|
91
|
+
|
|
92
|
+
# Transfer functions are plain callables, so every renderer accepts them
|
|
93
|
+
H = cp.ee.TransferFunction([1], [1, 0.2, 1])
|
|
94
|
+
cp.ee.transfer_portrait(H, legend=True)
|
|
95
|
+
cp.plot_landscape_pv(cp.Rectangle(6, 6), H)
|
|
96
|
+
|
|
97
|
+
# A printable ornament
|
|
98
|
+
cp.create_ornament(lambda z: z / (z**10 - 1), "flower.stl", size_mm=80)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
From the terminal:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
complexplorer list
|
|
105
|
+
complexplorer render preset:pole_flower_10 -o flower.png
|
|
106
|
+
complexplorer stl "z / (z**10 - 1)" --size-mm 80 -o flower.stl
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## From mathematics to an object on your desk
|
|
110
|
+
|
|
111
|
+
<p align="center">
|
|
112
|
+
<img src="https://raw.githubusercontent.com/kuvychko/complexplorer/main/examples/gallery/view/_tour/physical_triptych.png" width="100%"
|
|
113
|
+
alt="Riemann relief render, untextured STL mesh, and the printed ornament">
|
|
114
|
+
</p>
|
|
115
|
+
|
|
116
|
+
The relief is the mathematics, the mesh is the geometry that survives losing the colour, and the
|
|
117
|
+
print is the object on a desk. Ten poles become ten spikes around the central zero.
|
|
118
|
+
|
|
119
|
+
→ [STL export and the physical workflow](https://kuvychko.github.io/complexplorer/guide/physical-workflow/)
|
|
120
|
+
|
|
121
|
+
## Documentation
|
|
122
|
+
|
|
123
|
+
The [documentation site](https://kuvychko.github.io/complexplorer/) has the visual tour, the
|
|
124
|
+
guides, and a generated API reference.
|
|
125
|
+
|
|
126
|
+
- [Reading a phase portrait](https://kuvychko.github.io/complexplorer/guide/reading-a-portrait/) —
|
|
127
|
+
what the colours encode, and which colormap to choose (with measured colour-vision-deficiency
|
|
128
|
+
behaviour)
|
|
129
|
+
- [3D and the Riemann sphere](https://kuvychko.github.io/complexplorer/guide/three-dimensions/) —
|
|
130
|
+
including headless rendering and reproducible cameras
|
|
131
|
+
- [Gallery](https://kuvychko.github.io/complexplorer/gallery/gallery.generated/) — every preset
|
|
132
|
+
and colormap, with the code that made it
|
|
133
|
+
- [API map](https://kuvychko.github.io/complexplorer/api/map/) — one entry point per task, and
|
|
134
|
+
what each returns
|
|
135
|
+
- [Migration guide](https://kuvychko.github.io/complexplorer/migration-3.0/) and
|
|
136
|
+
[changelog](https://github.com/kuvychko/complexplorer/blob/main/CHANGELOG.md)
|
|
137
|
+
|
|
138
|
+
## A note on backends
|
|
139
|
+
|
|
140
|
+
matplotlib draws the 2D portraits and the stereographic charts. PyVista draws everything 3D and
|
|
141
|
+
builds the export meshes, because 3D here is a mesh, camera, lighting and export problem, which is
|
|
142
|
+
what PyVista is for and what matplotlib's 3D engine is not. As of 3.0 PyVista is a required
|
|
143
|
+
dependency, so there is no capability flag to check. The reasoning, with the measured install
|
|
144
|
+
footprint and import cost, is in
|
|
145
|
+
[the backend policy](https://kuvychko.github.io/complexplorer/development/backend-policy/).
|
|
146
|
+
|
|
147
|
+
## Contributing
|
|
148
|
+
|
|
149
|
+
See [CONTRIBUTING.md](https://github.com/kuvychko/complexplorer/blob/main/CONTRIBUTING.md) for
|
|
150
|
+
setup, the checks, the spec-driven workflow, and the release runbook.
|
|
151
|
+
|
|
152
|
+
## Citing
|
|
153
|
+
|
|
154
|
+
Citation metadata is in
|
|
155
|
+
[CITATION.cff](https://github.com/kuvychko/complexplorer/blob/main/CITATION.cff); GitHub's
|
|
156
|
+
"Cite this repository" button generates BibTeX from it.
|
|
157
|
+
|
|
158
|
+
## Acknowledgements
|
|
159
|
+
|
|
160
|
+
Inspired by Elias Wegert's *Visual Complex Functions: An Introduction with Phase Portraits*
|
|
161
|
+
(Birkhäuser, 2012), which is the book to read if you want to understand what these pictures show.
|
|
162
|
+
|
|
163
|
+
## License
|
|
164
|
+
|
|
165
|
+
MIT for the code; see [LICENSE](https://github.com/kuvychko/complexplorer/blob/main/LICENSE).
|
|
166
|
+
The gallery images and ornament designs are under
|
|
167
|
+
[LICENSE.art](https://github.com/kuvychko/complexplorer/blob/main/LICENSE.art).
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Complexplorer: A Python library for visualizing complex functions.
|
|
3
|
+
|
|
4
|
+
This library provides tools to create beautiful visualizations of complex-valued
|
|
5
|
+
functions using various color mapping techniques and plot types.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# Engineering mode (namespaced: cp.ee.TransferFunction, cp.ee.bode_plot, ...)
|
|
9
|
+
from complexplorer import ee
|
|
10
|
+
from complexplorer._version import __version__
|
|
11
|
+
|
|
12
|
+
# High-level API
|
|
13
|
+
from complexplorer.api import PlotPresets, quick_plot
|
|
14
|
+
from complexplorer.core.colormap import (
|
|
15
|
+
AnalogousWedge,
|
|
16
|
+
Chessboard,
|
|
17
|
+
Colormap,
|
|
18
|
+
CubehelixPhase,
|
|
19
|
+
DivergingWarmCool,
|
|
20
|
+
EarthTopographic,
|
|
21
|
+
FourQuadrant,
|
|
22
|
+
InkPaper,
|
|
23
|
+
Isoluminant,
|
|
24
|
+
LogRings,
|
|
25
|
+
OklabPhase,
|
|
26
|
+
PerceptualPastel,
|
|
27
|
+
Phase,
|
|
28
|
+
PolarChessboard,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Core functionality
|
|
32
|
+
from complexplorer.core.domain import Annulus, CompositeDomain, Disk, Domain, Rectangle
|
|
33
|
+
from complexplorer.core.functions import (
|
|
34
|
+
inverse_stereographic,
|
|
35
|
+
phase,
|
|
36
|
+
sawtooth,
|
|
37
|
+
stereographic_projection,
|
|
38
|
+
)
|
|
39
|
+
from complexplorer.core.presets import FunctionPreset, catalog
|
|
40
|
+
from complexplorer.core.scaling import ModulusScaling, get_scaling_preset
|
|
41
|
+
from complexplorer.exceptions import ColormapError, ComplexplorerError, ValidationError
|
|
42
|
+
|
|
43
|
+
# PyVista is a required core dependency as of 3.0 (the sole 3D backend; also powers STL
|
|
44
|
+
# export), so these imports are unconditional.
|
|
45
|
+
from complexplorer.export.stl import OrnamentGenerator, create_ornament
|
|
46
|
+
from complexplorer.gallery import generate_gallery
|
|
47
|
+
|
|
48
|
+
# Plotting functions (matplotlib)
|
|
49
|
+
from complexplorer.plotting.matplotlib.plot_2d import (
|
|
50
|
+
pair_plot,
|
|
51
|
+
plot,
|
|
52
|
+
riemann_chart,
|
|
53
|
+
riemann_hemispheres,
|
|
54
|
+
)
|
|
55
|
+
from complexplorer.plotting.pyvista.plot_3d import pair_plot_landscape_pv, plot_landscape_pv
|
|
56
|
+
from complexplorer.plotting.pyvista.riemann import riemann_pv
|
|
57
|
+
from complexplorer.plotting.pyvista.riemann_surface import riemann_surface_pv
|
|
58
|
+
|
|
59
|
+
# Utility functions
|
|
60
|
+
|
|
61
|
+
__all__ = [
|
|
62
|
+
# Version
|
|
63
|
+
"__version__",
|
|
64
|
+
# Exceptions
|
|
65
|
+
"ComplexplorerError",
|
|
66
|
+
"ValidationError",
|
|
67
|
+
"ColormapError",
|
|
68
|
+
# Core classes
|
|
69
|
+
"Domain",
|
|
70
|
+
"Rectangle",
|
|
71
|
+
"Disk",
|
|
72
|
+
"Annulus",
|
|
73
|
+
"CompositeDomain",
|
|
74
|
+
"Colormap",
|
|
75
|
+
"Phase",
|
|
76
|
+
"Chessboard",
|
|
77
|
+
"PolarChessboard",
|
|
78
|
+
"LogRings",
|
|
79
|
+
"OklabPhase",
|
|
80
|
+
"PerceptualPastel",
|
|
81
|
+
"AnalogousWedge",
|
|
82
|
+
"DivergingWarmCool",
|
|
83
|
+
"Isoluminant",
|
|
84
|
+
"CubehelixPhase",
|
|
85
|
+
"InkPaper",
|
|
86
|
+
"EarthTopographic",
|
|
87
|
+
"FourQuadrant",
|
|
88
|
+
"ModulusScaling",
|
|
89
|
+
"get_scaling_preset",
|
|
90
|
+
# Function preset registry: catalog supplies functions, PlotPresets configures renders
|
|
91
|
+
"catalog",
|
|
92
|
+
"FunctionPreset",
|
|
93
|
+
# Gallery generator
|
|
94
|
+
"generate_gallery",
|
|
95
|
+
# Core functions
|
|
96
|
+
"phase",
|
|
97
|
+
"sawtooth",
|
|
98
|
+
"stereographic_projection",
|
|
99
|
+
"inverse_stereographic",
|
|
100
|
+
# Plotting functions
|
|
101
|
+
"plot",
|
|
102
|
+
"pair_plot",
|
|
103
|
+
"riemann_chart",
|
|
104
|
+
"riemann_hemispheres",
|
|
105
|
+
# Utilities
|
|
106
|
+
# High-level API
|
|
107
|
+
"quick_plot",
|
|
108
|
+
"PlotPresets",
|
|
109
|
+
# Engineering mode (namespaced subpackage)
|
|
110
|
+
"ee",
|
|
111
|
+
# STL export (PyVista-backed)
|
|
112
|
+
"OrnamentGenerator",
|
|
113
|
+
"create_ornament",
|
|
114
|
+
# PyVista 3D plotting
|
|
115
|
+
"plot_landscape_pv",
|
|
116
|
+
"pair_plot_landscape_pv",
|
|
117
|
+
"riemann_pv",
|
|
118
|
+
"riemann_surface_pv",
|
|
119
|
+
]
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""High-level API for common complexplorer workflows.
|
|
2
|
+
|
|
3
|
+
This module provides convenient functions for typical use cases.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
7
|
+
|
|
8
|
+
from complexplorer.core.colormap import Phase
|
|
9
|
+
from complexplorer.core.domain import Domain, Rectangle
|
|
10
|
+
from complexplorer.exceptions import ValidationError
|
|
11
|
+
|
|
12
|
+
# Import plotting functions. 2D is matplotlib; 3D/Riemann are PyVista (a required core
|
|
13
|
+
# dependency as of 3.0 — there is no matplotlib 3D backend).
|
|
14
|
+
from complexplorer.plotting.matplotlib.plot_2d import plot as plot_2d
|
|
15
|
+
from complexplorer.typing import ComplexFunction
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING: # import cost stays out of the runtime path
|
|
18
|
+
import pyvista as pv
|
|
19
|
+
from matplotlib.axes import Axes
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def quick_plot(
|
|
23
|
+
func: ComplexFunction, domain: Domain | None = None, mode: str = "2d", **kwargs
|
|
24
|
+
) -> "Axes | pv.Plotter | None":
|
|
25
|
+
"""Quick visualization of a complex function.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
func : callable
|
|
30
|
+
Complex function to visualize
|
|
31
|
+
domain : Domain, optional
|
|
32
|
+
Domain to plot. Defaults to Rectangle(4, 4)
|
|
33
|
+
mode : str
|
|
34
|
+
Plot mode: '2d', '3d', 'riemann'
|
|
35
|
+
**kwargs
|
|
36
|
+
Additional arguments passed to plotting function
|
|
37
|
+
|
|
38
|
+
Returns
|
|
39
|
+
-------
|
|
40
|
+
Axes or Plotter object depending on mode
|
|
41
|
+
"""
|
|
42
|
+
# Remember whether the caller supplied a domain: in Riemann mode a domain masks the
|
|
43
|
+
# sphere, so the Rectangle(4, 4) default must NOT be forwarded (default = full sphere).
|
|
44
|
+
domain_supplied = domain is not None
|
|
45
|
+
if domain is None:
|
|
46
|
+
domain = Rectangle(4, 4)
|
|
47
|
+
|
|
48
|
+
if "cmap" not in kwargs:
|
|
49
|
+
kwargs["cmap"] = Phase(phase_sectors=6, auto_scale_r=True)
|
|
50
|
+
|
|
51
|
+
# Pop the backend selector so it never leaks into the renderer. 2D is matplotlib;
|
|
52
|
+
# 3D/Riemann are PyVista-only (the matplotlib 3D backend was removed in 3.0).
|
|
53
|
+
backend = kwargs.pop("backend", None)
|
|
54
|
+
|
|
55
|
+
if mode == "2d":
|
|
56
|
+
return plot_2d(domain, func, **kwargs)
|
|
57
|
+
if mode in ("3d", "riemann"):
|
|
58
|
+
if backend == "matplotlib":
|
|
59
|
+
raise ValidationError(
|
|
60
|
+
"The matplotlib 3D backend was removed in 3.0; 3D/Riemann use PyVista."
|
|
61
|
+
)
|
|
62
|
+
if mode == "3d":
|
|
63
|
+
from complexplorer.plotting.pyvista.plot_3d import plot_landscape_pv
|
|
64
|
+
|
|
65
|
+
return plot_landscape_pv(domain, func, **kwargs)
|
|
66
|
+
from complexplorer.plotting.pyvista.riemann import riemann_pv
|
|
67
|
+
|
|
68
|
+
if domain_supplied:
|
|
69
|
+
kwargs["domain"] = domain
|
|
70
|
+
return riemann_pv(func, **kwargs)
|
|
71
|
+
raise ValidationError(f"Unknown mode: {mode}")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# Preset configurations for common use cases
|
|
75
|
+
class PlotPresets:
|
|
76
|
+
"""Named plot-configuration presets (colormap + resolution bundles).
|
|
77
|
+
|
|
78
|
+
Each preset returns a plain dict of keyword arguments to spread into a
|
|
79
|
+
plotting entry point, e.g. ``quick_plot(f, **PlotPresets.publication_ready())``.
|
|
80
|
+
|
|
81
|
+
``PlotPresets`` configures a render; ``catalog`` supplies a function. The registry
|
|
82
|
+
``complexplorer.catalog`` holds curated *functions* (expression, domain/colormap/scaling
|
|
83
|
+
specs, singularity answer keys); these are the settings you draw one with.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
@staticmethod
|
|
87
|
+
def publication_ready() -> dict[str, Any]:
|
|
88
|
+
"""Settings for publication-quality figures."""
|
|
89
|
+
return {
|
|
90
|
+
"cmap": Phase(phase_sectors=12, auto_scale_r=True, scale_radius=0.8),
|
|
91
|
+
"resolution": 800,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@staticmethod
|
|
95
|
+
def interactive() -> dict[str, Any]:
|
|
96
|
+
"""Settings for interactive exploration."""
|
|
97
|
+
return {"cmap": Phase(phase_sectors=6, auto_scale_r=True), "resolution": 400}
|
|
98
|
+
|
|
99
|
+
@staticmethod
|
|
100
|
+
def high_contrast() -> dict[str, Any]:
|
|
101
|
+
"""Settings for high contrast visualization."""
|
|
102
|
+
return {
|
|
103
|
+
"cmap": Phase(phase_sectors=16, auto_scale_r=True, scale_radius=0.5),
|
|
104
|
+
"resolution": 600,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
__all__ = [
|
|
109
|
+
"quick_plot",
|
|
110
|
+
"PlotPresets",
|
|
111
|
+
]
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"""Command-line interface for Complexplorer.
|
|
2
|
+
|
|
3
|
+
The console entry point is ``complexplorer.cli.main:main``. Import the function as
|
|
4
|
+
``from complexplorer.cli.main import main`` (not re-exported here, to avoid shadowing the
|
|
5
|
+
``main`` submodule with the ``main`` function).
|
|
6
|
+
"""
|