sdot 2024.11.23.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.
- sdot-2024.11.23.0/PKG-INFO +79 -0
- sdot-2024.11.23.0/README.md +1 -0
- sdot-2024.11.23.0/pyproject.toml +22 -0
- sdot-2024.11.23.0/sdot/Cell.py +272 -0
- sdot-2024.11.23.0/sdot/ConvexFunction.py +199 -0
- sdot-2024.11.23.0/sdot/D2GTransportMap.py +5 -0
- sdot-2024.11.23.0/sdot/Expr.py +142 -0
- sdot-2024.11.23.0/sdot/G2DTransportMap.py +6 -0
- sdot-2024.11.23.0/sdot/PoomVec.py +92 -0
- sdot-2024.11.23.0/sdot/PowerDiagram.py +376 -0
- sdot-2024.11.23.0/sdot/PowerDiagramSummary.py +63 -0
- sdot-2024.11.23.0/sdot/SdotSolver.py +297 -0
- sdot-2024.11.23.0/sdot/TransformationMatrix.py +46 -0
- sdot-2024.11.23.0/sdot/VtkOutput.py +18 -0
- sdot-2024.11.23.0/sdot/__init__.py +18 -0
- sdot-2024.11.23.0/sdot/bindings/RefMap.h +101 -0
- sdot-2024.11.23.0/sdot/bindings/TF_objects.SConstruct +8 -0
- sdot-2024.11.23.0/sdot/bindings/TF_objects_bindings.cpp +16 -0
- sdot-2024.11.23.0/sdot/bindings/__init__.py +0 -0
- sdot-2024.11.23.0/sdot/bindings/construct.py +158 -0
- sdot-2024.11.23.0/sdot/bindings/ext +1 -0
- sdot-2024.11.23.0/sdot/bindings/generated/1jFP64200/integration_1jFP64200.cpp +0 -0
- sdot-2024.11.23.0/sdot/bindings/generated/1l/integration_1l.SConstruct +4 -0
- sdot-2024.11.23.0/sdot/bindings/generated/1l/integration_1l.cpp +51 -0
- sdot-2024.11.23.0/sdot/bindings/generic_objects.SConstruct +8 -0
- sdot-2024.11.23.0/sdot/bindings/generic_objects_bindings.cpp +84 -0
- sdot-2024.11.23.0/sdot/bindings/img_interpolation.SConstruct +8 -0
- sdot-2024.11.23.0/sdot/bindings/img_interpolation_bindings.cpp +35 -0
- sdot-2024.11.23.0/sdot/bindings/integration.SConstruct +14 -0
- sdot-2024.11.23.0/sdot/bindings/integration_bindings.cpp +48 -0
- sdot-2024.11.23.0/sdot/bindings/integration_module.py +105 -0
- sdot-2024.11.23.0/sdot/bindings/loader.py +156 -0
- sdot-2024.11.23.0/sdot/bindings/poom_vec_nd_0.SConstruct +8 -0
- sdot-2024.11.23.0/sdot/bindings/poom_vec_nd_0_bindings.cpp +46 -0
- sdot-2024.11.23.0/sdot/bindings/poom_vec_nd_1.SConstruct +8 -0
- sdot-2024.11.23.0/sdot/bindings/poom_vec_nd_1_bindings.cpp +49 -0
- sdot-2024.11.23.0/sdot/bindings/sdot.SConstruct +6 -0
- sdot-2024.11.23.0/sdot/bindings/sdot_bindings.cpp +345 -0
- sdot-2024.11.23.0/sdot/bindings/src +1 -0
- sdot-2024.11.23.0/sdot/bindings/variants/integration_module/1l/integration_module.cpp +1 -0
- sdot-2024.11.23.0/sdot/distributions/Distribution.py +21 -0
- sdot-2024.11.23.0/sdot/distributions/SumOfDiracs.py +34 -0
- sdot-2024.11.23.0/sdot/distributions/SymbolicFunction.py +17 -0
- sdot-2024.11.23.0/sdot/distributions/UnitBox.py +41 -0
- sdot-2024.11.23.0/sdot/distributions/normalized_distribution.py +23 -0
- sdot-2024.11.23.0/sdot/optimal_transport_plan.py +9 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: sdot
|
|
3
|
+
Version: 2024.11.23.0
|
|
4
|
+
Summary: Tools to construct/handle PowerDiagram, notably for semi-discrete optimal transport
|
|
5
|
+
Keywords: optimal transport,power diagram,Laguerre diagram
|
|
6
|
+
Author-email: Hugo Leclerc <hugo.leclerc@universite-paris-saclay.fr>
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: munch
|
|
9
|
+
Requires-Dist: pybind11
|
|
10
|
+
Requires-Dist: archspec
|
|
11
|
+
Requires-Dist: scons
|
|
12
|
+
Requires-Dist: pytest
|
|
13
|
+
Requires-Dist: numpy
|
|
14
|
+
Requires-Dist: matplotlib
|
|
15
|
+
Requires-Dist: dload
|
|
16
|
+
Requires-Dist: gitpython
|
|
17
|
+
|
|
18
|
+
SDOT
|
|
19
|
+
====
|
|
20
|
+
|
|
21
|
+
This package contains tool to handle
|
|
22
|
+
* semi-discrete transport plans (between discrete and generic densities),
|
|
23
|
+
* polyhedral convex functions (represented as a max of affine functions),
|
|
24
|
+
* and power diagrams (that can be seen as a generalization of Voronoi diagrams).
|
|
25
|
+
|
|
26
|
+
It works in any number of dimensions, and is highly optimized, in terms of execution speed and memory usage.
|
|
27
|
+
|
|
28
|
+
Historically, this package is a re-design of the `pysdot` package, which was written to handle a wide number of semi-discrete transport applications (partial transport, Moreau-Yosida regularization, etc...) but only worked in 2D or 3D. Additionaly, and more specifically, we wanted to make operations on power diagram much more comprehensive and generic.
|
|
29
|
+
|
|
30
|
+
Currently, there are bindings C++ and Python, but more languages are in the pipeline (feel free to ask if your favorite language is not represented :) ).
|
|
31
|
+
|
|
32
|
+
Installation
|
|
33
|
+
------------
|
|
34
|
+
|
|
35
|
+
### Pip
|
|
36
|
+
|
|
37
|
+
For python, `pip install sdot` should do the job, including some precompiled libraries for the most common cases (2D/3D, float64, ...). If your cases are not included in the distribution, the required dynamic libraries will be automatically compiled on first use. In this situation, you will need to have a C++ compiler installed on your machine. As scons is used to find and call the compiler, all you need to do is install one compiler that is compatible with this builder (for instance g++, clang, xcode, vscode, ...).
|
|
38
|
+
|
|
39
|
+
For a compiler : under Debian like, `sudo apt install g++`. Under Mac os, `xcode-select --install`. Under Windows, you can follow [this link](https://code.visualstudio.com/docs/cpp/config-mingw#_prerequisites).
|
|
40
|
+
|
|
41
|
+
### Sources
|
|
42
|
+
|
|
43
|
+
To get that latest version, `sdot` can also be installed from the git repository.
|
|
44
|
+
|
|
45
|
+
For the python modules:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
git clone https://github.com/sdot-team/sdot.git
|
|
49
|
+
# maybe after a micromamba activate ...
|
|
50
|
+
cd sdot/src/python
|
|
51
|
+
pip install flit
|
|
52
|
+
flit install -s # -s makes symbolic links to the sources
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Modules
|
|
56
|
+
-------
|
|
57
|
+
|
|
58
|
+
* [Optimal transport operations in python](doc/optimal_transport_py.md).
|
|
59
|
+
* [Power or voronoi diagrams and cells in python](doc/power_diagram_py.md).
|
|
60
|
+
* [Polyhedral convex function in python](doc/polyhedral_convex_py.md).
|
|
61
|
+
|
|
62
|
+
A word on performance
|
|
63
|
+
---------------------
|
|
64
|
+
|
|
65
|
+
The most common tools to handle voronoi and power diagrams start from delaunay (regular) triangulations. Building this triangulation is generaly the most time-consuming part of this approach, notably because one have to deal with the problems that come with digital precision...
|
|
66
|
+
|
|
67
|
+
Nevertheless, for most applications of the sdot package, we only need the *integrals* of the cells and the boundaries, meaning that most of the problems with digital precision naturally vanish at the end. We also realized that it was much more convenient for the user to work directly with the cells.
|
|
68
|
+
|
|
69
|
+
Bearing in mind that exact connectivity may of course be required for some applications (the 'dual' geometry becoming the triangulation in our case :) ), we therefore decided to give a try to algorithms with a focus on the cells, that are computed individually in a fully parallel fashion. We tried to stay on the user specified scalar types (e.g. `float64`) as long as possible. Finally, we designed adapted spatial acceleration structures to stay within O(n log(n)) execution speed with the smallest possible constant.
|
|
70
|
+
|
|
71
|
+
Internally, it is written in C++/Cuda with SIMD/SIMT instructions. It support large vectors, for out-of-core and multi-machine computations. More on [this page](doc/performance.md).
|
|
72
|
+
|
|
73
|
+
On going work
|
|
74
|
+
-------------
|
|
75
|
+
|
|
76
|
+
* pytorch compatible operations
|
|
77
|
+
* pre-guess of the weight to avoid void cells at the beginning
|
|
78
|
+
* non-linear solvers to avoid bad newton directions
|
|
79
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/home/leclerc/sdot/README.md
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["flit_core >=3.2,<4","pytest"]
|
|
3
|
+
build-backend = "flit_core.buildapi"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
description = "Tools to construct/handle PowerDiagram, notably for semi-discrete optimal transport"
|
|
7
|
+
authors = [ { name = "Hugo Leclerc", email = "hugo.leclerc@universite-paris-saclay.fr" }, ]
|
|
8
|
+
keywords = ["optimal transport","power diagram","Laguerre diagram"]
|
|
9
|
+
dependencies = [ "munch", "pybind11", "archspec", "scons", "pytest", "numpy", "matplotlib", "dload", "gitpython" ]
|
|
10
|
+
version = "2024.11.23.0"
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
name = "sdot"
|
|
13
|
+
|
|
14
|
+
# Documentation = "https://readthedocs.org"
|
|
15
|
+
# Repository = "https://github.com/me/spam.git"
|
|
16
|
+
|
|
17
|
+
# [tool.hatch.build.targets.wheel]
|
|
18
|
+
# artifacts = [
|
|
19
|
+
# "*.so",
|
|
20
|
+
# "*.dll",
|
|
21
|
+
# "*.dylib",
|
|
22
|
+
# ]
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
from .bindings.integration_module import integration_module
|
|
2
|
+
from .bindings.loader import module_for
|
|
3
|
+
from .Expr import Expr
|
|
4
|
+
|
|
5
|
+
from types import ModuleType
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
class Cell:
|
|
9
|
+
""" wrapper around cpp sdot::Cell class """
|
|
10
|
+
|
|
11
|
+
def __init__( self, ndim = None, dtype = None, _cell = None, _binding_module = None ):
|
|
12
|
+
""" _cell is the sdot::Cell class instance. If not specified, __init__ makes a new one using `ndim` and `dtype`.
|
|
13
|
+
By default, dtype = np.double, ndim = 2
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
if _cell:
|
|
17
|
+
assert _binding_module is not None
|
|
18
|
+
self._binding_module = _binding_module
|
|
19
|
+
self._cell = _cell
|
|
20
|
+
return
|
|
21
|
+
|
|
22
|
+
if ndim is None:
|
|
23
|
+
ndim = 2
|
|
24
|
+
|
|
25
|
+
if dtype is None:
|
|
26
|
+
dtype = "FP64"
|
|
27
|
+
|
|
28
|
+
self._binding_module = module_for( 'sdot', use_arch = True, scalar_type = dtype, nb_dims = ndim )
|
|
29
|
+
self._cell = self._binding_module.Cell()
|
|
30
|
+
|
|
31
|
+
def __repr__( self ):
|
|
32
|
+
return self._cell.__repr__()
|
|
33
|
+
|
|
34
|
+
def integral( self, funcs = '1', underlying_measure_override = None, underlying_radial_function_override = None ):
|
|
35
|
+
""" integral of a symbolic function or a list of symbolic functions """
|
|
36
|
+
if not isinstance( funcs, list ):
|
|
37
|
+
return self.integral( [ funcs ], underlying_measure_override, underlying_radial_function_override )[ 0 ]
|
|
38
|
+
|
|
39
|
+
# module généré
|
|
40
|
+
module, rt_data = integration_module( funcs, self._binding_module.dtype(), self._binding_module.ndim() )
|
|
41
|
+
return module.cell_integral( self._cell, rt_data )
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def true_dimensionality( self ):
|
|
45
|
+
""" Size of the base.
|
|
46
|
+
|
|
47
|
+
at the beginning, all the cell starts with true_dimensionality == 0. A first cut will lead to true_dimensionality == 1.
|
|
48
|
+
If not in the same direction, true_dimensionality will be incremented, ...
|
|
49
|
+
If true_dimensionality < ndims, coordinates are expressed in self.base
|
|
50
|
+
"""
|
|
51
|
+
return self._cell.true_dimensionality()
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def vertex_coords_td( self ):
|
|
55
|
+
""" get list of coordinates for each vertex (stored in a numpy array) in the current base """
|
|
56
|
+
return self._cell.vertex_coords( True )
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def vertex_coords( self ):
|
|
60
|
+
""" get list of coordinates for each vertex (stored in a numpy array) """
|
|
61
|
+
return self._cell.vertex_coords( False )
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def vertex_refs_td( self ):
|
|
65
|
+
""" get list of cut indices for each vertex (stored in a numpy array) in the current base """
|
|
66
|
+
return self._cell.vertex_refs( True )
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def vertex_refs( self ):
|
|
70
|
+
""" get list of cut indices for each vertex (stored in a numpy array) in the current base """
|
|
71
|
+
return self._cell.vertex_refs( False )
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def nb_active_cuts( self ):
|
|
75
|
+
""" beware: if remove_inactive_cuts() has not been called, this procedure will have first to compute the active cuts (may take some time) """
|
|
76
|
+
return self._cell.nb_active_cuts()
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def nb_stored_cuts( self ):
|
|
80
|
+
""" may be different from nb_active_cuts if remove_inactive_cuts() has not been called """
|
|
81
|
+
return self._cell.nb_stored_cuts()
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def nb_vertices_td( self ):
|
|
85
|
+
""" nb vertices in current "true" dimensionality """
|
|
86
|
+
return self._cell.nb_vertices( True )
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def nb_vertices( self ):
|
|
90
|
+
return self._cell.nb_vertices( False )
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def bounded( self ):
|
|
94
|
+
return self._cell.bounded()
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def empty( self ):
|
|
98
|
+
return self._cell.empty()
|
|
99
|
+
|
|
100
|
+
# @property
|
|
101
|
+
# def closed_faces( self ):
|
|
102
|
+
# return self._cell.closed_faces()
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def base( self ):
|
|
106
|
+
""" internal base, used to get coordinates and directions if true_dimensionnality < nb_dims.
|
|
107
|
+
It true_dimensionality == nb_dims, it return an identity matrix
|
|
108
|
+
"""
|
|
109
|
+
return self._cell.base()
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def ndim( self ):
|
|
113
|
+
return self._cell.ndim()
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def cuts( self ):
|
|
117
|
+
return self._cell.cuts()
|
|
118
|
+
|
|
119
|
+
def cut_boundary( self, dir, val, index = 0 ):
|
|
120
|
+
return self._cell.cut_boundary( np.ascontiguousarray( dir ), val, int( index ) )
|
|
121
|
+
|
|
122
|
+
def cut( self, dir, val, index = 0 ):
|
|
123
|
+
return self._cell.cut( np.ascontiguousarray( dir ), val, int( index ) )
|
|
124
|
+
|
|
125
|
+
def display_vtk( self, vtk_output ):
|
|
126
|
+
return self._cell.display_vtk( vtk_output )
|
|
127
|
+
|
|
128
|
+
# def for_each_face( self, on_face ):
|
|
129
|
+
# """
|
|
130
|
+
# Call args:
|
|
131
|
+
# on_face( cut_refs, vertex_indices, ray_refs_list ),
|
|
132
|
+
# """
|
|
133
|
+
# return self._cell.for_each_face( on_face )
|
|
134
|
+
|
|
135
|
+
def for_each_edge( self, on_edge ):
|
|
136
|
+
"""
|
|
137
|
+
on_edge( edge_refs, vertex_indices )
|
|
138
|
+
* edge_refs = [ num_cut_0, ... ]
|
|
139
|
+
* vertex_indices =>
|
|
140
|
+
- size = 2 for a regular edge
|
|
141
|
+
- size = 1 for a ray
|
|
142
|
+
|
|
143
|
+
"""
|
|
144
|
+
return self._cell.for_each_edge( on_edge, False )
|
|
145
|
+
|
|
146
|
+
def for_each_edge_td( self, on_edge ):
|
|
147
|
+
"""
|
|
148
|
+
on_edge( edge_refs, vertex_indices )
|
|
149
|
+
* edge_refs = [ num_cut_0, ... ]
|
|
150
|
+
* vertex_indices =>
|
|
151
|
+
- size = 2 for a regular edge
|
|
152
|
+
- size = 1 for a ray
|
|
153
|
+
|
|
154
|
+
"""
|
|
155
|
+
return self._cell.for_each_edge( on_edge, True )
|
|
156
|
+
|
|
157
|
+
def ray_dir( self, ray_refs, base_vertex ):
|
|
158
|
+
return self._cell.ray_dir( ray_refs, base_vertex )
|
|
159
|
+
|
|
160
|
+
def plot_pyplot( self, fig, color = 'black', linestyle = '-', linewidth = 2, low_dim_linewidth_coeff = 0.25, ray_length = 0.2, ray_color = None, ray_linestyle = '--', free_color = None, free_linestyle = ':' ):
|
|
161
|
+
"""
|
|
162
|
+
ray_length is the length used to display infinite edges.
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
# check fig properties
|
|
166
|
+
if isinstance( fig, ModuleType ):
|
|
167
|
+
fig.axis( 'equal' )
|
|
168
|
+
|
|
169
|
+
if self.ndim == 3:
|
|
170
|
+
fig = fig.figure()
|
|
171
|
+
fig = fig.add_subplot( projection = '3d' )
|
|
172
|
+
|
|
173
|
+
#
|
|
174
|
+
coords = self.vertex_coords_td @ self.base
|
|
175
|
+
|
|
176
|
+
# find a complementary base
|
|
177
|
+
cbase = list( np.eye( self.ndim ) )
|
|
178
|
+
for r in range( self.ndim ):
|
|
179
|
+
for c in self.base:
|
|
180
|
+
cbase[ r ] -= np.dot( cbase[ r ], c ) * c
|
|
181
|
+
cbase.sort( key = np.linalg.norm )
|
|
182
|
+
cbase = cbase[ self.true_dimensionality : ]
|
|
183
|
+
|
|
184
|
+
# helper to plot
|
|
185
|
+
def pl( array, gtype ):
|
|
186
|
+
array = np.array( array )
|
|
187
|
+
xyz = [ array[ :, d ] for d in range( min( self.ndim, 3 ) ) ]
|
|
188
|
+
kwa = { 'linewidth': linewidth }
|
|
189
|
+
if self.true_dimensionality < self.ndim and gtype != 'free':
|
|
190
|
+
kwa[ 'linewidth' ] *= low_dim_linewidth_coeff
|
|
191
|
+
|
|
192
|
+
if gtype == 'free':
|
|
193
|
+
return fig.plot( *xyz, **kwa, linestyle = free_linestyle, color = free_color or color )
|
|
194
|
+
if gtype == 'ray':
|
|
195
|
+
return fig.plot( *xyz, **kwa, linestyle = ray_linestyle, color = ray_color or color )
|
|
196
|
+
if gtype == 'edge':
|
|
197
|
+
return fig.plot( *xyz, **kwa, linestyle = linestyle, color = color )
|
|
198
|
+
|
|
199
|
+
# for each vertex, plot free items, get the edges and the rays
|
|
200
|
+
for c in coords:
|
|
201
|
+
for cvec in cbase:
|
|
202
|
+
pl( [ c - ray_length * cvec, c + ray_length * cvec ], 'free' )
|
|
203
|
+
|
|
204
|
+
# plot edge
|
|
205
|
+
def edge_func( refs, vertices ):
|
|
206
|
+
if len( refs ) == 0:
|
|
207
|
+
return
|
|
208
|
+
|
|
209
|
+
# ray
|
|
210
|
+
if len( vertices ) == 1:
|
|
211
|
+
rd = self.ray_dir( refs, vertices[ 0 ] ) @ self.base
|
|
212
|
+
rd /= np.linalg.norm( rd )
|
|
213
|
+
bc = coords[ vertices[ 0 ] ]
|
|
214
|
+
pl( [ bc, bc + ray_length * rd ], 'ray' )
|
|
215
|
+
return
|
|
216
|
+
|
|
217
|
+
# edge
|
|
218
|
+
if len( vertices ) == 2:
|
|
219
|
+
pl( [ coords[ vertices[ 0 ] ], coords[ vertices[ 1 ] ] ], 'edge' )
|
|
220
|
+
return
|
|
221
|
+
|
|
222
|
+
raise RuntimeError( "not an expected size for vertices" )
|
|
223
|
+
|
|
224
|
+
self.for_each_edge_td( edge_func )
|
|
225
|
+
|
|
226
|
+
def plot( self, fig = None, **kwargs ):
|
|
227
|
+
"""
|
|
228
|
+
"""
|
|
229
|
+
if fig is None:
|
|
230
|
+
import matplotlib.pyplot as plt
|
|
231
|
+
self.plot( plt )
|
|
232
|
+
plt.show()
|
|
233
|
+
return
|
|
234
|
+
|
|
235
|
+
self.plot_pyplot( fig, **kwargs )
|
|
236
|
+
|
|
237
|
+
# if self.true_dimensionality == 2:
|
|
238
|
+
|
|
239
|
+
# def disp_ray( ray_refs, vertex_index, rev ):
|
|
240
|
+
# print( "ray" )
|
|
241
|
+
# dir = self.ray_dir( ray_refs, vertex_index ) @ self.base.T
|
|
242
|
+
# b_c = coords[ vertex_index ]
|
|
243
|
+
# d_c = b_c + ray_length * dir / np.linalg.norm( dir )
|
|
244
|
+
# x = [ b_c[ 0 ], d_c[ 0 ] ]
|
|
245
|
+
# y = [ b_c[ 1 ], d_c[ 1 ] ]
|
|
246
|
+
# if rev:
|
|
247
|
+
# x.reverse()
|
|
248
|
+
# y.reverse()
|
|
249
|
+
# fig.plot( x, y, ray_linestyle or linestyle, color = ray_color or color )
|
|
250
|
+
|
|
251
|
+
# def on_face( edge_refs, vertex_indices, ray_refs ):
|
|
252
|
+
# print( "cut_refs" )
|
|
253
|
+
|
|
254
|
+
# if len( ray_refs ) >= 1:
|
|
255
|
+
# disp_ray( ray_refs[ 0 ], vertex_indices[ 0 ], 1 )
|
|
256
|
+
|
|
257
|
+
# if len( vertex_indices ) >= 2:
|
|
258
|
+
# x = [ coords[ n, 0 ] for n in vertex_indices ]
|
|
259
|
+
# y = [ coords[ n, 1 ] for n in vertex_indices ]
|
|
260
|
+
# if len( ray_refs ) == 0: # closed
|
|
261
|
+
# x.append( x[ 0 ] )
|
|
262
|
+
# y.append( y[ 0 ] )
|
|
263
|
+
# fig.plot( x, y, linestyle, color = color )
|
|
264
|
+
|
|
265
|
+
# if len( ray_refs ) >= 2:
|
|
266
|
+
# disp_ray( ray_refs[ 1 ], vertex_indices[ -1 ], 0 )
|
|
267
|
+
|
|
268
|
+
# self.for_each_face( on_face )
|
|
269
|
+
# return
|
|
270
|
+
|
|
271
|
+
# raise RuntimeError( f"TODO: plot for dim == { self.ndim }" )
|
|
272
|
+
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
from .PowerDiagram import PowerDiagram, rt_int
|
|
2
|
+
import numpy as np
|
|
3
|
+
import vfs
|
|
4
|
+
|
|
5
|
+
class ConvexFunction:
|
|
6
|
+
"""
|
|
7
|
+
Function =
|
|
8
|
+
max(
|
|
9
|
+
-inf,
|
|
10
|
+
scalar_product( m_dirs[ 0 ], y ) - m_offs[ 0 ],
|
|
11
|
+
scalar_product( m_dirs[ 1 ], y ) - m_offs[ 1 ],
|
|
12
|
+
...
|
|
13
|
+
)
|
|
14
|
+
for y such that
|
|
15
|
+
scalar_product( b_dirs[ 0 ], y ) - b_offs[ 0 ] <= 0,
|
|
16
|
+
scalar_product( b_dirs[ 1 ], y ) - b_offs[ 1 ] <= 0,
|
|
17
|
+
...
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__( self, m_dirs, m_offs, b_dirs = None, b_offs = None, dtype = np.double ) -> None:
|
|
21
|
+
# m_...
|
|
22
|
+
self.m_dirs = np.asarray( m_dirs, dtype = dtype )
|
|
23
|
+
self.m_offs = np.asarray( m_offs, dtype = dtype )
|
|
24
|
+
|
|
25
|
+
assert( self.m_dirs.ndim == 2 )
|
|
26
|
+
assert( self.m_offs.ndim == 1 )
|
|
27
|
+
assert( self.m_offs.shape[ 0 ] == self.m_dirs.shape[ 0 ] )
|
|
28
|
+
|
|
29
|
+
# b_...
|
|
30
|
+
if b_dirs is None or not len( b_dirs ):
|
|
31
|
+
assert( b_offs is None or not len( b_offs ) )
|
|
32
|
+
b_dirs = np.zeros( [ 0, self.m_dirs.shape[ 1 ] ], dtype = dtype )
|
|
33
|
+
b_offs = np.zeros( [ 0 ], dtype = dtype )
|
|
34
|
+
self.b_dirs = np.asarray( b_dirs, dtype = dtype )
|
|
35
|
+
self.b_offs = np.asarray( b_offs, dtype = dtype )
|
|
36
|
+
|
|
37
|
+
assert( self.b_dirs.ndim == 2 )
|
|
38
|
+
assert( self.b_offs.ndim == 1 )
|
|
39
|
+
assert( self.b_offs.shape[ 0 ] == self.b_dirs.shape[ 0 ] )
|
|
40
|
+
assert( self.b_dirs.shape[ 1 ] == self.m_dirs.shape[ 1 ] )
|
|
41
|
+
|
|
42
|
+
# attributes by deduction
|
|
43
|
+
self.nb_dims = self.m_dirs.shape[ 1 ]
|
|
44
|
+
|
|
45
|
+
def make_approx_from_values_and_derivatives( sample_coords, f_val, f_der, b_dirs = None, b_offs = None ):
|
|
46
|
+
m_dirs = []
|
|
47
|
+
m_offs = []
|
|
48
|
+
for num_point in range( sample_coords.shape[ 1 ] ):
|
|
49
|
+
point = sample_coords[ :, num_point ]
|
|
50
|
+
val = f_val( point )
|
|
51
|
+
der = f_der( point )
|
|
52
|
+
|
|
53
|
+
m_dirs.append( der )
|
|
54
|
+
m_offs.append( np.dot( der, point ) - val )
|
|
55
|
+
return ConvexFunction( m_dirs, m_offs, b_dirs, b_offs )
|
|
56
|
+
|
|
57
|
+
def to_power_diagram( self ):
|
|
58
|
+
weights = np.sum( self.m_dirs * self.m_dirs, axis = 1 ) - 2 * self.m_offs
|
|
59
|
+
return PowerDiagram( self.m_dirs, weights, self.b_dirs, self.b_offs )
|
|
60
|
+
|
|
61
|
+
def write_vtk( self, filename, fit_boundary = 1.0 ):
|
|
62
|
+
pd = self.to_power_diagram()
|
|
63
|
+
pd.write_vtk( filename, fit_boundary )
|
|
64
|
+
|
|
65
|
+
def summary( self ):
|
|
66
|
+
from sympy import Symbol, tensorproduct
|
|
67
|
+
|
|
68
|
+
y = [ Symbol( f'y_{ d }' ) for d in range( self.nb_dims ) ]
|
|
69
|
+
|
|
70
|
+
repr_max = lambda i: str( sum( tensorproduct( self.m_dirs[ i, : ], y ) ) - self.m_offs[ i ] )
|
|
71
|
+
repr_bnd = lambda i: str( sum( tensorproduct( self.b_dirs[ i, : ], y ) ) <= self.b_offs[ i ] )
|
|
72
|
+
|
|
73
|
+
lst_max = [ repr_max( i ) for i in range( self.m_dirs.shape[ 0 ] ) ]
|
|
74
|
+
lst_bnd = [ repr_bnd( i ) for i in range( self.b_dirs.shape[ 0 ] ) ]
|
|
75
|
+
lst_max.sort()
|
|
76
|
+
lst_bnd.sort()
|
|
77
|
+
|
|
78
|
+
res = f"max({ ', '.join( lst_max ) })"
|
|
79
|
+
if len( lst_bnd ):
|
|
80
|
+
res += f" for { ', '.join( lst_bnd ) }"
|
|
81
|
+
return res
|
|
82
|
+
|
|
83
|
+
def legendre_transform( self ):
|
|
84
|
+
# call the legendre_transform func
|
|
85
|
+
func = vfs.function( 'legendre_transform', [ f'inc_file:sdot/legendre_transform/legendre_transform.h' ] )
|
|
86
|
+
new_m_dirs, new_m_offs, new_b_dirs, new_b_offs = func( self.m_dirs, self.m_offs, self.b_dirs, self.b_offs, rt_int( self.nb_dims ) )
|
|
87
|
+
new_m_dirs = np.reshape( new_m_dirs, [ -1, self.nb_dims ] )
|
|
88
|
+
new_b_dirs = np.reshape( new_b_dirs, [ -1, self.nb_dims ] )
|
|
89
|
+
|
|
90
|
+
# make a new ConvexFunction
|
|
91
|
+
return ConvexFunction( new_m_dirs, new_m_offs, new_b_dirs, new_b_offs )
|
|
92
|
+
|
|
93
|
+
def __repr__( self ):
|
|
94
|
+
return self.summary()
|
|
95
|
+
|
|
96
|
+
def is_inside_boundaries( self, x_k ):
|
|
97
|
+
if isinstance( x_k, ( int, float ) ):
|
|
98
|
+
x_k = [ x_k ]
|
|
99
|
+
x_k = np.asarray( x_k )
|
|
100
|
+
|
|
101
|
+
return self.b_dirs.shape[ 0 ] == 0 or ( self.b_dirs @ x_k - self.b_offs ).max() <= 0
|
|
102
|
+
|
|
103
|
+
def __call__( self, x_k ):
|
|
104
|
+
return self.val( x_k )
|
|
105
|
+
|
|
106
|
+
def val( self, x_k ):
|
|
107
|
+
if isinstance( x_k, ( int, float ) ):
|
|
108
|
+
x_k = [ x_k ]
|
|
109
|
+
x_k = np.asarray( x_k )
|
|
110
|
+
|
|
111
|
+
if not self.is_inside_boundaries( x_k ):
|
|
112
|
+
return float('inf')
|
|
113
|
+
|
|
114
|
+
return ( self.m_dirs @ x_k - self.m_offs ).max()
|
|
115
|
+
|
|
116
|
+
def grad( self, x_k ):
|
|
117
|
+
if isinstance( x_k, ( int, float ) ):
|
|
118
|
+
x_k = [ x_k ]
|
|
119
|
+
x_k = np.asarray( x_k )
|
|
120
|
+
|
|
121
|
+
if not self.is_inside_boundaries( x_k ):
|
|
122
|
+
return float('inf')
|
|
123
|
+
|
|
124
|
+
k = ( self.m_dirs @ x_k - self.m_offs ).argmax()
|
|
125
|
+
return self.m_dirs[ k ]
|
|
126
|
+
|
|
127
|
+
def __add__( self, u2 ):
|
|
128
|
+
u1 = self
|
|
129
|
+
if isinstance( u2, ConvexFunction ): # addition of two convex functions
|
|
130
|
+
if u1.nb_dims == u2.nb_dims:
|
|
131
|
+
nb_dims = u1.nb_dims
|
|
132
|
+
else:
|
|
133
|
+
print('Dimensions do not match.')
|
|
134
|
+
|
|
135
|
+
new_m_dirs = ( u1.m_dirs[ :, None, : ] + u2.m_dirs[ None, :, : ] ).reshape( ( -1, nb_dims ) )
|
|
136
|
+
new_m_offs = ( u1.m_dirs[ :, None ] + u2.m_dirs[ None, : ] ).flatten()
|
|
137
|
+
new_b_dirs = np.block( [ [ u1.b_dirs ], [ u2.b_dirs ] ] )
|
|
138
|
+
new_b_offs = np.concatenate( [ u1.b_offs, u2.b_offs ] )
|
|
139
|
+
|
|
140
|
+
return ConvexFunction( new_m_dirs, new_m_offs, new_b_dirs, new_b_offs )
|
|
141
|
+
|
|
142
|
+
elif isinstance( u2, ( int, float ) ): # addition of a polyhedral function and a scalar
|
|
143
|
+
return ConvexFunction( u1.m_dirs, u1.m_offs - u2, u1.b_dirs, u1.b_offs )
|
|
144
|
+
|
|
145
|
+
else:
|
|
146
|
+
raise NotImplementedError( "Can't add Polyhedral and {}".format( type( u2 ) ) )
|
|
147
|
+
|
|
148
|
+
def __radd__( self, scal ):
|
|
149
|
+
return self.__add__( scal )
|
|
150
|
+
|
|
151
|
+
def __sub__( self, scal ):
|
|
152
|
+
if isinstance( scal,( int, float ) ):
|
|
153
|
+
return ConvexFunction( self.m_dirs, self.m_offs + scal, self.b_dirs, self.b_offs )
|
|
154
|
+
else:
|
|
155
|
+
raise NotImplementedError( "Can't substract ConvexFunction from {}".format( type( scal ) ) )
|
|
156
|
+
|
|
157
|
+
def __mul__( self, scal ):
|
|
158
|
+
if isinstance( scal, ( int, float ) ):
|
|
159
|
+
assert( scal >= 0 )
|
|
160
|
+
return ConvexFunction( scal * self.m_dirs, scal * self.m_offs, self.b_dirs, self.b_offs )
|
|
161
|
+
else:
|
|
162
|
+
raise NotImplementedError( "Can't multiply ConvexFunction and {}".format( type( scal ) ) )
|
|
163
|
+
|
|
164
|
+
def __rmul__( self, scal ):
|
|
165
|
+
return self.__mul__( scal )
|
|
166
|
+
|
|
167
|
+
def __truediv__( self, scal ):
|
|
168
|
+
if isinstance( scal, ( int, float ) ):
|
|
169
|
+
assert( scal > 0 )
|
|
170
|
+
return ConvexFunction( self.m_dirs / scal, self.m_offs / scal, self.b_dirs, self.b_offs )
|
|
171
|
+
else:
|
|
172
|
+
raise NotImplementedError( "Can't divide {} by ConvexFunction.".format( type( scal ) ) )
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def __or__(self,u2):
|
|
176
|
+
u1 = self
|
|
177
|
+
if isinstance( u2, ConvexFunction ): # maximum of two PHC functions
|
|
178
|
+
new_m_dirs = np.block( [ [ u1.m_dirs ], [ u2.m_dirs ] ] )
|
|
179
|
+
new_m_offs = np.concatenate( [ u1.m_offs, u2.m_offs ] )
|
|
180
|
+
new_b_dirs = np.block( [ [ u1.b_dirs ], [ u2.b_dirs ] ] )
|
|
181
|
+
new_b_offs = np.concatenate( [ u1.b_offs,u2.b_offs ] )
|
|
182
|
+
return ConvexFunction( new_m_dirs, new_m_offs, new_b_dirs, new_b_offs )
|
|
183
|
+
|
|
184
|
+
elif isinstance(u2,(int,float)): # addition of a polyhedral function and a scalar
|
|
185
|
+
return u1.__or__( ConvexFunction( np.zero( [ 1, u1.nb_dims ] ), [ -u2 ] ) )
|
|
186
|
+
|
|
187
|
+
else:
|
|
188
|
+
raise NotImplementedError( "Can't take the maximum of ConvexFunction and {}".format( type( u2 ) ) )
|
|
189
|
+
|
|
190
|
+
def __ror__( self, scal ):
|
|
191
|
+
return self.__or__( scal )
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def __xor__( self, u2 ): # inf-convolution of two PHC functions
|
|
195
|
+
if isinstance( u2, ConvexFunction ):
|
|
196
|
+
return ( self.legendre_transform() + u2.legendre_transform() ).legendre_transform()
|
|
197
|
+
|
|
198
|
+
else:
|
|
199
|
+
raise NotImplementedError("Can't take the inf-convolution of ConvexFunction and {}".format(type(u2)))
|