homeotopy 0.1.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.
- homeotopy-0.1.0/LICENSE +21 -0
- homeotopy-0.1.0/PKG-INFO +39 -0
- homeotopy-0.1.0/README.md +24 -0
- homeotopy-0.1.0/homeotopy/__init__.py +39 -0
- homeotopy-0.1.0/homeotopy/_ball.py +45 -0
- homeotopy-0.1.0/homeotopy/_cube.py +28 -0
- homeotopy-0.1.0/homeotopy/_homeomorphism.py +95 -0
- homeotopy-0.1.0/homeotopy/_plane.py +34 -0
- homeotopy-0.1.0/homeotopy/_simplex.py +62 -0
- homeotopy-0.1.0/homeotopy/_sphere.py +49 -0
- homeotopy-0.1.0/pyproject.toml +23 -0
homeotopy-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Erik Brinkman
|
|
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.
|
homeotopy-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: homeotopy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A library for computing homeomorphisms between some common stnandard topologies
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: Erik Brinkman
|
|
7
|
+
Author-email: erik.brinkman@gmail.com
|
|
8
|
+
Requires-Python: >=3.12,<4.0
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Requires-Dist: numba (>=0.61.0,<0.62.0)
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# Homotopy
|
|
16
|
+
|
|
17
|
+
A python library for computing homeomorphisms between some common continuous
|
|
18
|
+
spaces.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
pip install homeotopy
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
```py
|
|
29
|
+
import homeotopy
|
|
30
|
+
|
|
31
|
+
points = ...
|
|
32
|
+
# create a mapping from the simplex to the surface of the sphere
|
|
33
|
+
mapping = homeotopy.homeomorphism(homeotopy.simplex(), homeotopy.sphere())
|
|
34
|
+
sphere_points = mapping(points)
|
|
35
|
+
|
|
36
|
+
rev_mapping = reversed(mapping)
|
|
37
|
+
duplicate_points = rev_mapping(sphere_points)
|
|
38
|
+
```
|
|
39
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Homotopy
|
|
2
|
+
|
|
3
|
+
A python library for computing homeomorphisms between some common continuous
|
|
4
|
+
spaces.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
pip install homeotopy
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
```py
|
|
15
|
+
import homeotopy
|
|
16
|
+
|
|
17
|
+
points = ...
|
|
18
|
+
# create a mapping from the simplex to the surface of the sphere
|
|
19
|
+
mapping = homeotopy.homeomorphism(homeotopy.simplex(), homeotopy.sphere())
|
|
20
|
+
sphere_points = mapping(points)
|
|
21
|
+
|
|
22
|
+
rev_mapping = reversed(mapping)
|
|
23
|
+
duplicate_points = rev_mapping(sphere_points)
|
|
24
|
+
```
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""A library for creating some standard homeomorphisms.
|
|
2
|
+
|
|
3
|
+
This library is based around a set of `Topologies`. Calling `homeomorphism` with
|
|
4
|
+
two Topologies creates a homeomorphism from one topology to the other.
|
|
5
|
+
Topoligies should specify their domains, but where unspecified, the topoligies
|
|
6
|
+
try to conform to a reasonable standard domain. The homeomorphism should work
|
|
7
|
+
for closed set elements too, but thos elements may not be bijective.
|
|
8
|
+
|
|
9
|
+
Remarks
|
|
10
|
+
-------
|
|
11
|
+
It's probably important to note that floating point numbers are not real
|
|
12
|
+
numbers, and so none of these are really bijective at all.
|
|
13
|
+
|
|
14
|
+
Also note that this library does not define homeotopies. It's just named this
|
|
15
|
+
have to "py" in the name.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from ._ball import Ball, ball
|
|
19
|
+
from ._cube import Cube, cube
|
|
20
|
+
from ._homeomorphism import Homeomorphism, Topology, homeomorphism
|
|
21
|
+
from ._plane import Plane, plane
|
|
22
|
+
from ._simplex import Simplex, simplex
|
|
23
|
+
from ._sphere import Sphere, sphere
|
|
24
|
+
|
|
25
|
+
__all__ = (
|
|
26
|
+
"Homeomorphism",
|
|
27
|
+
"Topology",
|
|
28
|
+
"homeomorphism",
|
|
29
|
+
"Ball",
|
|
30
|
+
"ball",
|
|
31
|
+
"Cube",
|
|
32
|
+
"cube",
|
|
33
|
+
"Plane",
|
|
34
|
+
"plane",
|
|
35
|
+
"Sphere",
|
|
36
|
+
"sphere",
|
|
37
|
+
"Simplex",
|
|
38
|
+
"simplex",
|
|
39
|
+
)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from numpy.typing import NDArray
|
|
6
|
+
|
|
7
|
+
from ._homeomorphism import Topology
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True, slots=True)
|
|
11
|
+
class Ball(Topology):
|
|
12
|
+
"""The topology of the p-norm ball.
|
|
13
|
+
|
|
14
|
+
This represents all points in R^n s.t. ||x||_p < 1, although it should also
|
|
15
|
+
work fo the boundary.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
p: float
|
|
19
|
+
|
|
20
|
+
def __post_init__(self):
|
|
21
|
+
if self.p <= 0:
|
|
22
|
+
raise ValueError(f"p must be greater than or equal to 0: {self.p:g}")
|
|
23
|
+
|
|
24
|
+
def to_inf_ball(self, points: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
25
|
+
if self.p == math.inf:
|
|
26
|
+
return points
|
|
27
|
+
else:
|
|
28
|
+
tiny = np.finfo(points.dtype).smallest_normal
|
|
29
|
+
source = np.linalg.norm(points, self.p, -1)
|
|
30
|
+
target = np.abs(points).max(-1) + tiny
|
|
31
|
+
return np.clip(points * (source / target)[..., None], -1, 1)
|
|
32
|
+
|
|
33
|
+
def from_inf_ball(self, points: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
34
|
+
if self.p == math.inf:
|
|
35
|
+
return points
|
|
36
|
+
else:
|
|
37
|
+
tiny = np.finfo(points.dtype).smallest_normal
|
|
38
|
+
source = np.abs(points).max(-1)
|
|
39
|
+
target = np.linalg.norm(points, self.p, -1) + tiny
|
|
40
|
+
return points * (source / target)[..., None]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def ball(p: float = 2.0) -> Ball:
|
|
44
|
+
"""Create a topology of the interior of the p-norm unit ball."""
|
|
45
|
+
return Ball(p)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from functools import cache
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from numpy.typing import NDArray
|
|
6
|
+
|
|
7
|
+
from ._homeomorphism import Topology
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True, slots=True)
|
|
11
|
+
class Cube(Topology):
|
|
12
|
+
"""The topology of the unit hyper cube.
|
|
13
|
+
|
|
14
|
+
This represents all points in R^n s.t 0 < x_i < 1, although the boundary
|
|
15
|
+
should also work.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def to_inf_ball(self, points: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
19
|
+
return np.clip(points * 2 - 1, -1, 1)
|
|
20
|
+
|
|
21
|
+
def from_inf_ball(self, points: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
22
|
+
return np.clip((points + 1) / 2, 0, 1)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@cache
|
|
26
|
+
def cube() -> Cube:
|
|
27
|
+
"""Create a topology of the unit cube."""
|
|
28
|
+
return Cube()
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
from numpy.typing import NDArray
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Topology(ABC):
|
|
11
|
+
"""An abstract topological space.
|
|
12
|
+
|
|
13
|
+
For this library to work, each Topologiy should define a homeomorphism from
|
|
14
|
+
it to the inf-norm ball.
|
|
15
|
+
|
|
16
|
+
Remarks
|
|
17
|
+
-------
|
|
18
|
+
`to_inf_ball` and `from_inf_ball` are not meant to be called in isolation,
|
|
19
|
+
but rather used in combination with `homeomorphism`.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def to_inf_ball(self, points: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
24
|
+
"""Map a set of points in this topology to the inf-ball.
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
points : (..., d_in)
|
|
29
|
+
A set of points in the input topological space
|
|
30
|
+
|
|
31
|
+
Returns
|
|
32
|
+
-------
|
|
33
|
+
points : (..., d_out)
|
|
34
|
+
A set of points in the inf-norm ball, e.g. -1 < x_i < 1 for points
|
|
35
|
+
in the open topology, but points can be mapped to the border for
|
|
36
|
+
border points in the source topology.
|
|
37
|
+
"""
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def from_inf_ball(self, points: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
42
|
+
"""Map a set of points from the inf-ball to this topology.
|
|
43
|
+
|
|
44
|
+
Parameters
|
|
45
|
+
----------
|
|
46
|
+
points : (..., d_in)
|
|
47
|
+
A set of points in the inf-norm ball, e.g. -1 < x_i < 1 for points
|
|
48
|
+
in the open topology, but points on the boarder should be handled as
|
|
49
|
+
well.
|
|
50
|
+
|
|
51
|
+
Returns
|
|
52
|
+
-------
|
|
53
|
+
points : (..., d_out)
|
|
54
|
+
A set of points in the topological space.
|
|
55
|
+
"""
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass(frozen=True, slots=True)
|
|
60
|
+
class Homeomorphism:
|
|
61
|
+
"""A homeomorphism from source to target.
|
|
62
|
+
|
|
63
|
+
Homeomorphisms can be called on points in the source domain to map them to
|
|
64
|
+
points in the target domain. They can also be `reversed` to create the
|
|
65
|
+
inverse mapping.
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
Example
|
|
69
|
+
-------
|
|
70
|
+
|
|
71
|
+
from homeotopy import homeomorphism, ball, simplex
|
|
72
|
+
import numpy as np
|
|
73
|
+
|
|
74
|
+
forward = homeomorphism(ball(1), simplex())
|
|
75
|
+
backward = reversed(forward)
|
|
76
|
+
|
|
77
|
+
ball_points = ...
|
|
78
|
+
simplex_points = forwad(ball_points)
|
|
79
|
+
backward(simplex_points)
|
|
80
|
+
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
source: Topology
|
|
84
|
+
target: Topology
|
|
85
|
+
|
|
86
|
+
def __reversed__(self) -> Homeomorphism:
|
|
87
|
+
return Homeomorphism(self.target, self.source)
|
|
88
|
+
|
|
89
|
+
def __call__(self, points: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
90
|
+
return self.target.from_inf_ball(self.source.to_inf_ball(points))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def homeomorphism(source: Topology, target: Topology) -> Homeomorphism:
|
|
94
|
+
"""Create a Homeomorphism from a source and a target topology."""
|
|
95
|
+
return Homeomorphism(source, target)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from functools import cache
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from numpy.typing import NDArray
|
|
6
|
+
|
|
7
|
+
from ._homeomorphism import Topology
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True, slots=True)
|
|
11
|
+
class Plane(Topology):
|
|
12
|
+
"""The topology of the euclidian plane.
|
|
13
|
+
|
|
14
|
+
This represents all points in R^n, but boundary points map to inf.
|
|
15
|
+
|
|
16
|
+
Remarks
|
|
17
|
+
-------
|
|
18
|
+
While translations will keep all points valid, this will try to keep points
|
|
19
|
+
at the "center" of the space mapped to (0, 0, ..., 0).
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def to_inf_ball(self, points: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
23
|
+
return np.tanh(points)
|
|
24
|
+
|
|
25
|
+
def from_inf_ball(self, points: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
26
|
+
# -1 and 1 raise this warning but produce the correct result, we clip in case things are a little outside
|
|
27
|
+
with np.errstate(divide="ignore"):
|
|
28
|
+
return np.arctanh(np.clip(points, -1, 1))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@cache
|
|
32
|
+
def plane() -> Plane:
|
|
33
|
+
"""Create a topology of the euclidian plane."""
|
|
34
|
+
return Plane()
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from functools import cache
|
|
3
|
+
|
|
4
|
+
import numba as nb
|
|
5
|
+
import numpy as np
|
|
6
|
+
from numpy.typing import NDArray
|
|
7
|
+
|
|
8
|
+
from ._homeomorphism import Topology
|
|
9
|
+
|
|
10
|
+
# NOTE we use this to avoid divide by zero errors
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@nb.jit(nb.float64[:, ::1](nb.int64), parallel=True, cache=True, nogil=True)
|
|
14
|
+
def basis(dim: int) -> NDArray[np.float64]:
|
|
15
|
+
"""Create a basis for rotating the simplex onto the origin of dim - 1"""
|
|
16
|
+
res = np.empty((dim, dim))
|
|
17
|
+
for i in nb.prange(0, dim - 1):
|
|
18
|
+
num = i + 1
|
|
19
|
+
frac = 1 / (num + 1)
|
|
20
|
+
res[i, :num] = -((frac / num) ** 0.5)
|
|
21
|
+
res[i, num] = (1 - frac) ** 0.5
|
|
22
|
+
res[i, num + 1 :] = 0
|
|
23
|
+
res[-1] = (1 / dim) ** 0.5
|
|
24
|
+
return res
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True, slots=True)
|
|
28
|
+
class Simplex(Topology):
|
|
29
|
+
"""The topology of the simplex
|
|
30
|
+
|
|
31
|
+
This represents all points in R^n s.t. 0 < x_i and Σx_i = 1.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def to_inf_ball(self, points: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
35
|
+
tiny = np.finfo(points.dtype).smallest_normal
|
|
36
|
+
d = points.shape[-1]
|
|
37
|
+
|
|
38
|
+
simp_direc = points - np.full(d, 1 / d)
|
|
39
|
+
isimp_a = d * np.maximum(-simp_direc, simp_direc / (d - 1)).max(-1)
|
|
40
|
+
|
|
41
|
+
cube_direc = simp_direc @ basis(d)[: d - 1].T
|
|
42
|
+
icube_a = np.max(np.abs(cube_direc), -1) + tiny
|
|
43
|
+
|
|
44
|
+
return np.clip(cube_direc * (isimp_a / icube_a)[..., None], -1, 1)
|
|
45
|
+
|
|
46
|
+
def from_inf_ball(self, points: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
47
|
+
tiny = np.finfo(points.dtype).smallest_normal
|
|
48
|
+
d = points.shape[-1]
|
|
49
|
+
|
|
50
|
+
icube_a = np.max(np.abs(points), -1)
|
|
51
|
+
|
|
52
|
+
simp_direc = np.insert(points, d, 0, -1) @ basis(d + 1)
|
|
53
|
+
isimp_a = (d + 1) * np.maximum(-simp_direc, simp_direc / d).max(-1) + tiny
|
|
54
|
+
|
|
55
|
+
raw = simp_direc * (icube_a / isimp_a)[..., None] + np.full(d + 1, 1 / (d + 1))
|
|
56
|
+
return np.clip(raw, 0, 1)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@cache
|
|
60
|
+
def simplex() -> Simplex:
|
|
61
|
+
"""Create the topology of the simplex."""
|
|
62
|
+
return Simplex()
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from functools import cache
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from numpy.typing import NDArray
|
|
6
|
+
|
|
7
|
+
from ._homeomorphism import Topology
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True, slots=True)
|
|
11
|
+
class Sphere(Topology):
|
|
12
|
+
"""The topology of the unit sphere.
|
|
13
|
+
|
|
14
|
+
This represents all points in R^n s.t. ||x||_2 = 1, except for the point
|
|
15
|
+
(1, 0, ..., 0). That point is considered the boundary of the space, and will
|
|
16
|
+
be mapped the largest closed point in some other topologies.
|
|
17
|
+
|
|
18
|
+
Remarks
|
|
19
|
+
-------
|
|
20
|
+
If you had points on the surface of another p-ball, you could use the ball
|
|
21
|
+
homeomorphism to first map them onto the surface of the 2-ball, and then
|
|
22
|
+
apply this homeomorphism.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
# NOTE for both of these we need to special case (1, 0, ..., 0) to (1, 1, ..., 1) and vice versa
|
|
26
|
+
def to_inf_ball(self, points: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
27
|
+
tiny = np.finfo(points.dtype).smallest_normal
|
|
28
|
+
scale = 1 - points[..., :1]
|
|
29
|
+
|
|
30
|
+
normal = np.clip(np.tanh(points[..., 1:] / np.maximum(scale, tiny)), -1, 1)
|
|
31
|
+
return np.where(scale <= 0, 1, normal)
|
|
32
|
+
|
|
33
|
+
def from_inf_ball(self, points: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
34
|
+
big = np.finfo(points.dtype).max
|
|
35
|
+
with np.errstate(divide="ignore"):
|
|
36
|
+
plane = np.arctanh(points)
|
|
37
|
+
|
|
38
|
+
s2 = (plane[..., None, :] @ plane[..., None])[..., 0]
|
|
39
|
+
s2p = np.minimum(s2 + 1, big)
|
|
40
|
+
|
|
41
|
+
x0 = np.where(s2 == np.inf, 1, (s2 - 1) / s2p)
|
|
42
|
+
xns = np.where(s2 == np.inf, 0, 2 * plane / s2p)
|
|
43
|
+
return np.concatenate([x0, xns], -1)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@cache
|
|
47
|
+
def sphere() -> Sphere:
|
|
48
|
+
"""Create a topology fot the unit sphere."""
|
|
49
|
+
return Sphere()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "homeotopy"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "A library for computing homeomorphisms between some common stnandard topologies"
|
|
5
|
+
authors = ["Erik Brinkman <erik.brinkman@gmail.com>"]
|
|
6
|
+
license = "MIT"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
|
|
9
|
+
[tool.poetry.dependencies]
|
|
10
|
+
python = "^3.12"
|
|
11
|
+
numba = "^0.61.0"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
[tool.poetry.group.dev.dependencies]
|
|
15
|
+
pytest = "^8.3.4"
|
|
16
|
+
ruff = "^0.9.2"
|
|
17
|
+
ipykernel = "^6.29.5"
|
|
18
|
+
sphinx = "^8.1.3"
|
|
19
|
+
myst-parser = "^4.0.0"
|
|
20
|
+
|
|
21
|
+
[build-system]
|
|
22
|
+
requires = ["poetry-core"]
|
|
23
|
+
build-backend = "poetry.core.masonry.api"
|