invrs-opt 0.10.7__py3-none-any.whl → 0.12.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- invrs_opt/__init__.py +2 -2
- invrs_opt/experimental/client.py +1 -1
- invrs_opt/experimental/labels.py +1 -1
- invrs_opt/optimizers/base.py +1 -1
- invrs_opt/optimizers/lbfgsb.py +41 -19
- invrs_opt/optimizers/wrapped_optax.py +42 -17
- invrs_opt/parameterization/base.py +1 -1
- invrs_opt/parameterization/filter_project.py +1 -1
- invrs_opt/parameterization/gaussian_levelset.py +1 -1
- invrs_opt/parameterization/pixel.py +1 -1
- invrs_opt/parameterization/transforms.py +1 -1
- invrs_opt-0.12.0.dist-info/METADATA +78 -0
- invrs_opt-0.12.0.dist-info/RECORD +20 -0
- invrs_opt-0.12.0.dist-info/licenses/LICENSE +21 -0
- invrs_opt-0.10.7.dist-info/METADATA +0 -561
- invrs_opt-0.10.7.dist-info/RECORD +0 -20
- invrs_opt-0.10.7.dist-info/licenses/LICENSE +0 -504
- {invrs_opt-0.10.7.dist-info → invrs_opt-0.12.0.dist-info}/WHEEL +0 -0
- {invrs_opt-0.10.7.dist-info → invrs_opt-0.12.0.dist-info}/top_level.txt +0 -0
invrs_opt/__init__.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"""invrs_opt - Optimization algorithms for inverse design.
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2025 invrs.io LLC
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
__version__ = "v0.
|
|
6
|
+
__version__ = "v0.12.0"
|
|
7
7
|
__author__ = "Martin F. Schubert <mfschubert@gmail.com>"
|
|
8
8
|
|
|
9
9
|
from invrs_opt import parameterization as parameterization
|
invrs_opt/experimental/client.py
CHANGED
invrs_opt/experimental/labels.py
CHANGED
invrs_opt/optimizers/base.py
CHANGED
invrs_opt/optimizers/lbfgsb.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Defines a jax-style wrapper for scipy's L-BFGS-B algorithm.
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2025 invrs.io LLC
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import dataclasses
|
|
@@ -16,7 +16,7 @@ from jax import flatten_util, tree_util
|
|
|
16
16
|
from scipy.optimize._lbfgsb_py import ( # type: ignore[import-untyped]
|
|
17
17
|
_lbfgsb as scipy_lbfgsb,
|
|
18
18
|
)
|
|
19
|
-
from totypes import types
|
|
19
|
+
from totypes import json_utils, types
|
|
20
20
|
|
|
21
21
|
from invrs_opt.optimizers import base
|
|
22
22
|
from invrs_opt.parameterization import (
|
|
@@ -31,7 +31,26 @@ PyTree = Any
|
|
|
31
31
|
ElementwiseBound = Union[NDArray, Sequence[Optional[float]]]
|
|
32
32
|
NumpyLbfgsbDict = Dict[str, NDArray]
|
|
33
33
|
JaxLbfgsbDict = Dict[str, jnp.ndarray]
|
|
34
|
-
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclasses.dataclass
|
|
37
|
+
class LbfgsbState:
|
|
38
|
+
"""Stores the state of the L-BFGS-B optimizer."""
|
|
39
|
+
|
|
40
|
+
step: int
|
|
41
|
+
params: PyTree
|
|
42
|
+
latent_params: PyTree
|
|
43
|
+
opt_state: JaxLbfgsbDict
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
tree_util.register_dataclass(
|
|
47
|
+
LbfgsbState,
|
|
48
|
+
data_fields=["step", "params", "latent_params", "opt_state"],
|
|
49
|
+
meta_fields=[],
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
json_utils.register_custom_type(LbfgsbState)
|
|
35
54
|
|
|
36
55
|
|
|
37
56
|
# Task message prefixes for the underlying L-BFGS-B implementation.
|
|
@@ -327,17 +346,16 @@ def parameterized_lbfgsb(
|
|
|
327
346
|
latents,
|
|
328
347
|
)
|
|
329
348
|
latent_params = param_base.combine_density_metadata(metadata, latents)
|
|
330
|
-
return (
|
|
331
|
-
0,
|
|
332
|
-
_params_from_latent_params(latent_params),
|
|
333
|
-
latent_params,
|
|
334
|
-
jax_lbfgsb_state,
|
|
349
|
+
return LbfgsbState(
|
|
350
|
+
step=0,
|
|
351
|
+
params=_params_from_latent_params(latent_params),
|
|
352
|
+
latent_params=latent_params,
|
|
353
|
+
opt_state=jax_lbfgsb_state,
|
|
335
354
|
)
|
|
336
355
|
|
|
337
356
|
def params_fn(state: LbfgsbState) -> PyTree:
|
|
338
357
|
"""Returns the parameters for the given `state`."""
|
|
339
|
-
|
|
340
|
-
return params
|
|
358
|
+
return state.params
|
|
341
359
|
|
|
342
360
|
def update_fn(
|
|
343
361
|
*,
|
|
@@ -366,8 +384,7 @@ def parameterized_lbfgsb(
|
|
|
366
384
|
flat_latent_updates = updated_flat_latent_params - flat_latent_params
|
|
367
385
|
return flat_latent_updates, scipy_lbfgsb_state.to_dict()
|
|
368
386
|
|
|
369
|
-
|
|
370
|
-
metadata, latents = param_base.partition_density_metadata(latent_params)
|
|
387
|
+
metadata, latents = param_base.partition_density_metadata(state.latent_params)
|
|
371
388
|
|
|
372
389
|
def _params_from_latents(latents: PyTree) -> PyTree:
|
|
373
390
|
latent_params = param_base.combine_density_metadata(metadata, latents)
|
|
@@ -404,23 +421,28 @@ def parameterized_lbfgsb(
|
|
|
404
421
|
latents_grad
|
|
405
422
|
) # type: ignore[no-untyped-call]
|
|
406
423
|
|
|
407
|
-
flat_latent_updates,
|
|
424
|
+
flat_latent_updates, opt_state = callback_sequential(
|
|
408
425
|
_update_pure,
|
|
409
|
-
(flat_latents_grad,
|
|
426
|
+
(flat_latents_grad, state.opt_state),
|
|
410
427
|
flat_latents_grad,
|
|
411
428
|
value,
|
|
412
|
-
|
|
429
|
+
state.opt_state,
|
|
413
430
|
)
|
|
414
431
|
latent_updates = unflatten_fn(flat_latent_updates)
|
|
415
432
|
latent_params = _apply_updates(
|
|
416
|
-
params=latent_params,
|
|
433
|
+
params=state.latent_params,
|
|
417
434
|
updates=param_base.combine_density_metadata(metadata, latent_updates),
|
|
418
435
|
value=value,
|
|
419
|
-
step=step,
|
|
436
|
+
step=state.step,
|
|
420
437
|
)
|
|
421
438
|
latent_params = _clip(latent_params)
|
|
422
439
|
params = _params_from_latent_params(latent_params)
|
|
423
|
-
return
|
|
440
|
+
return LbfgsbState(
|
|
441
|
+
step=state.step + 1,
|
|
442
|
+
params=params,
|
|
443
|
+
latent_params=latent_params,
|
|
444
|
+
opt_state=opt_state,
|
|
445
|
+
)
|
|
424
446
|
|
|
425
447
|
# -------------------------------------------------------------------------
|
|
426
448
|
# Functions related to the density parameterization.
|
|
@@ -501,7 +523,7 @@ def parameterized_lbfgsb(
|
|
|
501
523
|
|
|
502
524
|
def is_converged(state: LbfgsbState) -> jnp.ndarray:
|
|
503
525
|
"""Returns `True` if the optimization has converged."""
|
|
504
|
-
return state[
|
|
526
|
+
return state.opt_state["converged"]
|
|
505
527
|
|
|
506
528
|
|
|
507
529
|
# ------------------------------------------------------------------------------
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
"""Defines a wrapper for optax optimizers.
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2025 invrs.io LLC
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
import dataclasses
|
|
7
|
+
from typing import Any, Optional
|
|
7
8
|
|
|
8
9
|
import jax
|
|
9
10
|
import jax.numpy as jnp
|
|
10
11
|
import optax # type: ignore[import-untyped]
|
|
11
12
|
from jax import tree_util
|
|
12
|
-
from totypes import types
|
|
13
|
+
from totypes import json_utils, types
|
|
13
14
|
|
|
14
15
|
from invrs_opt.optimizers import base
|
|
15
16
|
from invrs_opt.parameterization import (
|
|
@@ -20,7 +21,26 @@ from invrs_opt.parameterization import (
|
|
|
20
21
|
)
|
|
21
22
|
|
|
22
23
|
PyTree = Any
|
|
23
|
-
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclasses.dataclass
|
|
27
|
+
class WrappedOptaxState:
|
|
28
|
+
"""Stores the state of a wrapped optax optimizer."""
|
|
29
|
+
|
|
30
|
+
step: int
|
|
31
|
+
params: PyTree
|
|
32
|
+
latent_params: PyTree
|
|
33
|
+
opt_state: PyTree
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
tree_util.register_dataclass(
|
|
37
|
+
WrappedOptaxState,
|
|
38
|
+
data_fields=["step", "params", "latent_params", "opt_state"],
|
|
39
|
+
meta_fields=[],
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
json_utils.register_custom_type(WrappedOptaxState)
|
|
24
44
|
|
|
25
45
|
|
|
26
46
|
def wrapped_optax(opt: optax.GradientTransformation) -> base.Optimizer:
|
|
@@ -182,17 +202,16 @@ def parameterized_wrapped_optax(
|
|
|
182
202
|
"""Initializes the optimization state."""
|
|
183
203
|
latent_params = _init_latents(params)
|
|
184
204
|
_, latents = param_base.partition_density_metadata(latent_params)
|
|
185
|
-
return (
|
|
186
|
-
0,
|
|
187
|
-
_params_from_latent_params(latent_params),
|
|
188
|
-
latent_params,
|
|
189
|
-
opt.init(latents),
|
|
205
|
+
return WrappedOptaxState(
|
|
206
|
+
step=0,
|
|
207
|
+
params=_params_from_latent_params(latent_params),
|
|
208
|
+
latent_params=latent_params,
|
|
209
|
+
opt_state=opt.init(latents),
|
|
190
210
|
)
|
|
191
211
|
|
|
192
212
|
def params_fn(state: WrappedOptaxState) -> PyTree:
|
|
193
213
|
"""Returns the parameters for the given `state`."""
|
|
194
|
-
|
|
195
|
-
return params
|
|
214
|
+
return state.params
|
|
196
215
|
|
|
197
216
|
def update_fn(
|
|
198
217
|
*,
|
|
@@ -204,8 +223,7 @@ def parameterized_wrapped_optax(
|
|
|
204
223
|
"""Updates the state."""
|
|
205
224
|
del params
|
|
206
225
|
|
|
207
|
-
|
|
208
|
-
metadata, latents = param_base.partition_density_metadata(latent_params)
|
|
226
|
+
metadata, latents = param_base.partition_density_metadata(state.latent_params)
|
|
209
227
|
|
|
210
228
|
def _params_from_latents(latents: PyTree) -> PyTree:
|
|
211
229
|
latent_params = param_base.combine_density_metadata(metadata, latents)
|
|
@@ -232,16 +250,23 @@ def parameterized_wrapped_optax(
|
|
|
232
250
|
lambda a, b: a + b, latents_grad, constraint_loss_grad
|
|
233
251
|
)
|
|
234
252
|
|
|
235
|
-
latent_updates, opt_state = opt.update(
|
|
253
|
+
latent_updates, opt_state = opt.update(
|
|
254
|
+
latents_grad, state.opt_state, params=latents
|
|
255
|
+
)
|
|
236
256
|
latent_params = _apply_updates(
|
|
237
|
-
params=latent_params,
|
|
257
|
+
params=state.latent_params,
|
|
238
258
|
updates=param_base.combine_density_metadata(metadata, latent_updates),
|
|
239
259
|
value=value,
|
|
240
|
-
step=step,
|
|
260
|
+
step=state.step,
|
|
241
261
|
)
|
|
242
262
|
latent_params = _clip(latent_params)
|
|
243
263
|
params = _params_from_latent_params(latent_params)
|
|
244
|
-
return (
|
|
264
|
+
return WrappedOptaxState(
|
|
265
|
+
step=state.step + 1,
|
|
266
|
+
params=params,
|
|
267
|
+
latent_params=latent_params,
|
|
268
|
+
opt_state=opt_state,
|
|
269
|
+
)
|
|
245
270
|
|
|
246
271
|
# -------------------------------------------------------------------------
|
|
247
272
|
# Functions related to the density parameterization.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: invrs_opt
|
|
3
|
+
Version: 0.12.0
|
|
4
|
+
Summary: Algorithms for inverse design
|
|
5
|
+
Author-email: "Martin F. Schubert" <mfschubert@gmail.com>
|
|
6
|
+
Maintainer-email: "Martin F. Schubert" <mfschubert@gmail.com>
|
|
7
|
+
License: MIT License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2025 invrs.io LLC
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
|
|
29
|
+
Keywords: topology,optimization,jax,inverse design
|
|
30
|
+
Requires-Python: >=3.7
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
License-File: LICENSE
|
|
33
|
+
Requires-Dist: jax>=0.4.27
|
|
34
|
+
Requires-Dist: jaxlib
|
|
35
|
+
Requires-Dist: numpy
|
|
36
|
+
Requires-Dist: requests
|
|
37
|
+
Requires-Dist: optax
|
|
38
|
+
Requires-Dist: scipy>=1.15.0
|
|
39
|
+
Requires-Dist: totypes
|
|
40
|
+
Requires-Dist: types-requests
|
|
41
|
+
Provides-Extra: tests
|
|
42
|
+
Requires-Dist: parameterized; extra == "tests"
|
|
43
|
+
Requires-Dist: pytest; extra == "tests"
|
|
44
|
+
Requires-Dist: pytest-cov; extra == "tests"
|
|
45
|
+
Requires-Dist: pytest-subtests; extra == "tests"
|
|
46
|
+
Provides-Extra: dev
|
|
47
|
+
Requires-Dist: bump-my-version; extra == "dev"
|
|
48
|
+
Requires-Dist: darglint; extra == "dev"
|
|
49
|
+
Requires-Dist: mypy; extra == "dev"
|
|
50
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
51
|
+
Dynamic: license-file
|
|
52
|
+
|
|
53
|
+
# invrs-opt - Optimization algorithms for inverse design
|
|
54
|
+

|
|
55
|
+

|
|
56
|
+
|
|
57
|
+
## Overview
|
|
58
|
+
|
|
59
|
+
The `invrs-opt` package defines an optimizer API intended for topology optimization, inverse design, or AI-guided design. It (currently) implements the L-BFGS-B optimization algorithm along with some variants. The API is intended to be general so that new algorithms can be accommodated, and is inspired by the functional optimizer approach used in jax. Example usage is as follows:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
initial_params = ...
|
|
63
|
+
|
|
64
|
+
optimizer = invrs_opt.lbfgsb()
|
|
65
|
+
state = optimizer.init(initial_params)
|
|
66
|
+
|
|
67
|
+
for _ in range(steps):
|
|
68
|
+
params = optimizer.params(state)
|
|
69
|
+
value, grad = jax.value_and_grad(loss_fn)(params)
|
|
70
|
+
state = optimizer.update(grad=grad, value=value, params=params, state=state)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Optimizers in `invrs-opt` are compatible with custom types defined in the [totypes](https://github.com/invrs-io/totypes) package. The basic `lbfgsb` optimizer enforces bounds for custom types, while the `density_lbfgsb` optimizer implements a filter-and-threshold operation for `DensityArray2D` types to ensure that solutions have the correct length scale.
|
|
74
|
+
|
|
75
|
+
## Install
|
|
76
|
+
```
|
|
77
|
+
pip install invrs_opt
|
|
78
|
+
```
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
invrs_opt/__init__.py,sha256=Na1UiJf6o2RiEZH_h5BJw8Po5olJhZNs1hM04hyn--I,577
|
|
2
|
+
invrs_opt/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
invrs_opt/experimental/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
invrs_opt/experimental/client.py,sha256=skok5ezZj1twa_bxDbhNgXgJ6inawXwGmHKaSPk5V6k,4900
|
|
5
|
+
invrs_opt/experimental/labels.py,sha256=YgkQTq62AFxkDdLS8iQnUUoYjxyC01XXdwBa_PY9O8E,553
|
|
6
|
+
invrs_opt/optimizers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
invrs_opt/optimizers/base.py,sha256=dIEAy6HL7QCqzLXSfaLLJh_d283__yqH6EVpKklCxmM,1297
|
|
8
|
+
invrs_opt/optimizers/lbfgsb.py,sha256=jQmthq8fjX41nl1Xk8X_0Of-ae2ZiX4vtiYkmuvmmE0,37050
|
|
9
|
+
invrs_opt/optimizers/wrapped_optax.py,sha256=KEZYa8OhX3UtQzIgfDBRDIiH15fztxXskxnRpx95vec,14152
|
|
10
|
+
invrs_opt/parameterization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
invrs_opt/parameterization/base.py,sha256=hUrdQFcT0Mz85gveDdav2mUuMW7hU1ZJzVb8YMRJwCA,6530
|
|
12
|
+
invrs_opt/parameterization/filter_project.py,sha256=UXfrpdO-NPYYnVSXSj07OQb6CSYC3U2JcH_7eS5kH3A,4581
|
|
13
|
+
invrs_opt/parameterization/gaussian_levelset.py,sha256=bjFjrJPDk0tmhO-sCQ326L4I6jROZX7Z04PVafmAS9Q,24817
|
|
14
|
+
invrs_opt/parameterization/pixel.py,sha256=sG7pcupr9EPEK106dJSMKSgca_-av8JSUxFjtoO7QEM,1946
|
|
15
|
+
invrs_opt/parameterization/transforms.py,sha256=nr4CCKl2Wm4OixLlLKH0oz8tkX1T3n0VYsam8-UjloE,9432
|
|
16
|
+
invrs_opt-0.12.0.dist-info/licenses/LICENSE,sha256=3G_iFLh34YDwPMkRhyw_EZc7FCn0i3PV6Zc9BzcXdsI,1069
|
|
17
|
+
invrs_opt-0.12.0.dist-info/METADATA,sha256=9cGIsXH3wfYECFWxe2nVFChTp8M2GNQaDTnS5JuZ0e8,3517
|
|
18
|
+
invrs_opt-0.12.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
19
|
+
invrs_opt-0.12.0.dist-info/top_level.txt,sha256=hOziS2uJ_NgwaW9yhtOfeuYnm1X7vobPBcp_6eVWKfM,10
|
|
20
|
+
invrs_opt-0.12.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 invrs.io LLC
|
|
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.
|