Skip to content

Commit af01498

Browse files
sfarrenschaithyagrchaithyagr
authored
Version 1.5.1 patch release (#114)
* Add support for tensorflow backend which allows for differentiability (#112) * Added support for tensorflow * Updates to get tests passing * Or --> And * Moving modopt to allow working with tensorflow * Fix issues with wos * Fix all flakes finally! * Update modopt/base/backend.py Co-authored-by: Samuel Farrens <samuel.farrens@gmail.com> * Update modopt/base/backend.py Co-authored-by: Samuel Farrens <samuel.farrens@gmail.com> * Minute updates to codes * Add dynamic module * Fix docu * Fix PEP Co-authored-by: chaithyagr <chaithyagr@gitlab.com> Co-authored-by: Samuel Farrens <samuel.farrens@gmail.com> * Fix 115 (#116) * Fix issues * Add right tests * Fix PEP Co-authored-by: chaithyagr <chaithyagr@gitlab.com> * Minor bug fix, remove elif (#124) Co-authored-by: chaithyagr <chaithyagr@gitlab.com> * Add tests for modopt.base.backend and fix minute bug uncovered (#126) * Minor bug fix, remove elif * Add tests for backend * Fix tests * Add tests * Remove cupy * PEP fixes * Fix PEP * Fix PEP and update * Final PEP * Update setup.cfg Co-authored-by: Samuel Farrens <samuel.farrens@gmail.com> * Update test_base.py Co-authored-by: chaithyagr <chaithyagr@gitlab.com> Co-authored-by: Samuel Farrens <samuel.farrens@gmail.com> * Release cleanup (#128) * updated GPU dependencies * added logo to manifest * updated package version and release date Co-authored-by: Chaithya G R <chaithyagr@gmail.com> Co-authored-by: chaithyagr <chaithyagr@gitlab.com>
1 parent 616c9e7 commit af01498

12 files changed

Lines changed: 237 additions & 82 deletions

File tree

.github/workflows/ci-build.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ jobs:
5353
python -m pip install -r develop.txt
5454
python -m pip install -r docs/requirements.txt
5555
python -m pip install astropy scikit-image scikit-learn
56+
python -m pip install tensorflow>=2.4.1
5657
python -m pip install twine
5758
python -m pip install .
5859

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ include develop.txt
33
include docs/requirements.txt
44
include README.rst
55
include LICENSE.txt
6+
include docs/source/modopt_logo.png

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ Note that none of these are required for running on a CPU.
5858

5959
* [CuPy](https://cupy.dev/)
6060
* [Torch](https://pytorch.org/)
61+
* [TensorFlow](https://www.tensorflow.org/)
6162

6263
## Citation
6364

docs/source/dependencies.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ For GPU compliance the following packages can also be installed:
8282

8383
* |link-to-cupy|
8484
* |link-to-torch|
85+
* |link-to-tf|
8586

8687
.. |link-to-cupy| raw:: html
8788

@@ -93,6 +94,11 @@ For GPU compliance the following packages can also be installed:
9394
<a href="https://pytorch.org/"
9495
target="_blank">Torch</a>
9596

97+
.. |link-to-tf| raw:: html
98+
99+
<a href="https://www.tensorflow.org/"
100+
target="_blank">TensorFlow</a>
101+
96102
.. note::
97103

98104
Note that none of these are required for running on a CPU.

docs/source/index.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ ModOpt Documentation
1212
.. include:: toc.rst
1313

1414
:Author: Samuel Farrens `(samuel.farrens@cea.fr) <samuel.farrens@cea.fr>`_
15-
:Version: 1.5.0
16-
:Release Date: 31/03/2021
15+
:Version: 1.5.1
16+
:Release Date: 22/04/2021
1717
:Repository: |link-to-repo|
1818

1919
.. |link-to-repo| raw:: html

modopt/base/backend.py

Lines changed: 76 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,35 +8,73 @@
88
99
"""
1010

11-
import warnings
1211
from importlib import util
1312

1413
import numpy as np
1514

15+
from modopt.interface.errors import warn
16+
1617
try:
1718
import torch
19+
from torch.utils.dlpack import from_dlpack as torch_from_dlpack
20+
from torch.utils.dlpack import to_dlpack as torch_to_dlpack
21+
1822
except ImportError: # pragma: no cover
1923
import_torch = False
2024
else:
2125
import_torch = True
2226

2327
# Handle the compatibility with variable
24-
gpu_compatibility = {
25-
'cupy': False,
26-
'cupy-cudnn': False,
28+
LIBRARIES = {
29+
'cupy': None,
30+
'tensorflow': None,
31+
'numpy': np,
2732
}
2833

2934
if util.find_spec('cupy') is not None:
3035
try:
3136
import cupy as cp
32-
gpu_compatibility['cupy'] = True
37+
LIBRARIES['cupy'] = cp
38+
except ImportError:
39+
pass
3340

34-
if util.find_spec('cupy.cuda.cudnn') is not None:
35-
gpu_compatibility['cupy-cudnn'] = True
41+
if util.find_spec('tensorflow') is not None:
42+
try:
43+
from tensorflow.experimental import numpy as tnp
44+
LIBRARIES['tensorflow'] = tnp
3645
except ImportError:
3746
pass
3847

3948

49+
def get_backend(backend):
50+
"""Get backend.
51+
52+
Returns the backend module for input specified by string
53+
54+
Parameters
55+
----------
56+
backend: str
57+
String holding the backend name. One of `tensorflow`,
58+
`numpy` or `cupy`.
59+
60+
Returns
61+
-------
62+
tuple
63+
Returns the module for carrying out calculations and the actual backend
64+
that was reverted towards. If the right libraries are not installed,
65+
the function warns and reverts to `numpy` backend
66+
"""
67+
if backend not in LIBRARIES.keys() or LIBRARIES[backend] is None:
68+
msg = (
69+
'{0} backend not possible, please ensure that '
70+
+ 'the optional libraries are installed.\n'
71+
+ 'Reverting to numpy'
72+
)
73+
warn(msg.format(backend))
74+
backend = 'numpy'
75+
return LIBRARIES[backend], backend
76+
77+
4078
def get_array_module(input_data):
4179
"""Get Array Module.
4280
@@ -54,48 +92,47 @@ def get_array_module(input_data):
5492
The numpy or cupy module
5593
5694
"""
57-
if gpu_compatibility['cupy']:
58-
return cp.get_array_module(input_data)
59-
95+
if LIBRARIES['tensorflow'] is not None:
96+
if isinstance(input_data, LIBRARIES['tensorflow'].ndarray):
97+
return LIBRARIES['tensorflow']
98+
if LIBRARIES['cupy'] is not None:
99+
if isinstance(input_data, LIBRARIES['cupy'].ndarray):
100+
return LIBRARIES['cupy']
60101
return np
61102

62103

63-
def move_to_device(input_data):
104+
def change_backend(input_data, backend='cupy'):
64105
"""Move data to device.
65106
66-
This method moves data from CPU to GPU if we have the
67-
compatibility to do so. It returns the same data if
68-
it is already on GPU.
107+
This method changes the backend of an array
108+
This can be used to copy data to GPU or to CPU
69109
70110
Parameters
71111
----------
72112
input_data : numpy.ndarray or cupy.ndarray
73113
Input data array to be moved
114+
backend: str, optional
115+
The backend to use, one among `tensorflow`, `cupy` and
116+
`numpy`. Default is `cupy`.
74117
75118
Returns
76119
-------
77-
cupy.ndarray
78-
The CuPy array residing on GPU
120+
backend.ndarray
121+
An ndarray of specified backend
79122
80123
"""
81124
xp = get_array_module(input_data)
82-
83-
if xp == cp:
125+
txp, target_backend = get_backend(backend)
126+
if xp == txp:
84127
return input_data
85-
86-
if gpu_compatibility['cupy']:
87-
return cp.array(input_data)
88-
89-
warnings.warn('Cupy is not installed, cannot move data to GPU')
90-
91-
return input_data
128+
return txp.array(input_data)
92129

93130

94131
def move_to_cpu(input_data):
95132
"""Move data to CPU.
96133
97-
This method moves data from GPU to CPU.It returns the same data if it is
98-
already on CPU.
134+
This method moves data from GPU to CPU.
135+
It returns the same data if it is already on CPU.
99136
100137
Parameters
101138
----------
@@ -107,13 +144,20 @@ def move_to_cpu(input_data):
107144
numpy.ndarray
108145
The NumPy array residing on CPU
109146
147+
Raises
148+
------
149+
ValueError
150+
if the input does not correspond to any array
110151
"""
111152
xp = get_array_module(input_data)
112153

113-
if xp == np:
154+
if xp == LIBRARIES['numpy']:
114155
return input_data
115-
116-
return input_data.get()
156+
elif xp == LIBRARIES['cupy']:
157+
return input_data.get()
158+
elif xp == LIBRARIES['tensorflow']:
159+
return input_data.data.numpy()
160+
raise ValueError('Cannot identify the array type.')
117161

118162

119163
def convert_to_tensor(input_data):
@@ -150,7 +194,7 @@ def convert_to_tensor(input_data):
150194
if xp == np:
151195
return torch.Tensor(input_data)
152196

153-
return torch.utils.dlpack.from_dlpack(input_data.toDlpack()).float()
197+
return torch_from_dlpack(input_data.toDlpack()).float()
154198

155199

156200
def convert_to_cupy_array(input_data):
@@ -182,6 +226,6 @@ def convert_to_cupy_array(input_data):
182226
)
183227

184228
if input_data.is_cuda:
185-
return cp.fromDlpack(torch.utils.dlpack.to_dlpack(input_data))
229+
return cp.fromDlpack(torch_to_dlpack(input_data))
186230

187231
return input_data.detach().numpy()

modopt/math/matrix.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,7 @@
1212

1313
import numpy as np
1414

15-
from modopt.base.backend import get_array_module
16-
17-
try:
18-
import cupy as cp
19-
except ImportError: # pragma: no cover
20-
pass
15+
from modopt.base.backend import get_array_module, get_backend
2116

2217

2318
def gram_schmidt(matrix, return_opt='orthonormal'):
@@ -303,18 +298,17 @@ def __init__(
303298
data_shape,
304299
data_type=float,
305300
auto_run=True,
306-
use_gpu=False,
301+
compute_backend='numpy',
307302
verbose=False,
308303
):
309304

310305
self._operator = operator
311306
self._data_shape = data_shape
312307
self._data_type = data_type
313308
self._verbose = verbose
314-
if use_gpu:
315-
self.xp = cp
316-
else:
317-
self.xp = np
309+
xp, compute_backend = get_backend(compute_backend)
310+
self.xp = xp
311+
self.compute_backend = compute_backend
318312
if auto_run:
319313
self.get_spec_rad()
320314

modopt/opt/algorithms.py

Lines changed: 19 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,6 @@
5454
from modopt.opt.cost import costObj
5555
from modopt.opt.linear import Identity
5656

57-
try:
58-
import cupy as cp
59-
except ImportError: # pragma: no cover
60-
pass
61-
6257

6358
class SetUp(Observable):
6459
r"""Algorithm Set-Up.
@@ -92,7 +87,7 @@ def __init__(
9287
verbose=False,
9388
progress=True,
9489
step_size=None,
95-
use_gpu=False,
90+
compute_backend='numpy',
9691
**dummy_kwargs,
9792
):
9893

@@ -123,20 +118,9 @@ def __init__(
123118
)
124119
self.add_observer('cv_metrics', observer)
125120

126-
# Check for GPU
127-
if use_gpu:
128-
if backend.gpu_compatibility['cupy']:
129-
self.xp = cp
130-
else:
131-
warn(
132-
'CuPy is not installed, cannot run on GPU!'
133-
+ 'Running optimization on CPU.',
134-
)
135-
self.xp = np
136-
use_gpu = False
137-
else:
138-
self.xp = np
139-
self.use_gpu = use_gpu
121+
xp, compute_backend = backend.get_backend(compute_backend)
122+
self.xp = xp
123+
self.compute_backend = compute_backend
140124

141125
@property
142126
def metrics(self):
@@ -148,7 +132,9 @@ def metrics(self, metrics):
148132

149133
if isinstance(metrics, type(None)):
150134
self._metrics = {}
151-
elif not isinstance(metrics, dict):
135+
elif isinstance(metrics, dict):
136+
self._metrics = metrics
137+
else:
152138
raise TypeError(
153139
'Metrics must be a dictionary, not {0}.'.format(type(metrics)),
154140
)
@@ -184,10 +170,10 @@ def copy_data(self, input_data):
184170
Copy of input data
185171
186172
"""
187-
if self.use_gpu:
188-
return backend.move_to_device(input_data)
189-
190-
return self.xp.copy(input_data)
173+
return self.xp.copy(backend.change_backend(
174+
input_data,
175+
self.compute_backend,
176+
))
191177

192178
def _check_input_data(self, input_data):
193179
"""Check input data type.
@@ -205,8 +191,10 @@ def _check_input_data(self, input_data):
205191
For invalid input type
206192
207193
"""
208-
if not isinstance(input_data, self.xp.ndarray):
209-
raise TypeError('Input data must be a numpy array.')
194+
if not (isinstance(input_data, (self.xp.ndarray, np.ndarray))):
195+
raise TypeError(
196+
'Input data must be a numpy array or backend array',
197+
)
210198

211199
def _check_param(self, param_val):
212200
"""Check algorithm parameters.
@@ -779,8 +767,8 @@ def _update(self):
779767
self._z_new = self._x_new
780768

781769
# Update old values for next iteration.
782-
self.xp.copyto(self._x_old, self._x_new)
783-
self.xp.copyto(self._z_old, self._z_new)
770+
self._x_old = self.xp.copy(self._x_new)
771+
self._z_old = self.xp.copy(self._z_new)
784772

785773
# Update parameter values for next iteration.
786774
self._update_param()
@@ -789,7 +777,7 @@ def _update(self):
789777
if self._cost_func:
790778
self.converge = (
791779
self.any_convergence_flag()
792-
or self._cost_func.get_cost(self._x_new),
780+
or self._cost_func.get_cost(self._x_new)
793781
)
794782

795783
def iterate(self, max_iter=150):
@@ -1548,7 +1536,7 @@ def _update(self):
15481536
if self._cost_func:
15491537
self.converge = (
15501538
self.any_convergence_flag()
1551-
or self._cost_func.get_cost(self._x_new),
1539+
or self._cost_func.get_cost(self._x_new)
15521540
)
15531541

15541542
def iterate(self, max_iter=150):

0 commit comments

Comments
 (0)