invrs-opt 0.4.0__py3-none-any.whl → 0.5.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 CHANGED
@@ -3,7 +3,7 @@
3
3
  Copyright (c) 2023 The INVRS-IO authors.
4
4
  """
5
5
 
6
- __version__ = "v0.4.0"
6
+ __version__ = "v0.5.0"
7
7
  __author__ = "Martin F. Schubert <mfschubert@gmail.com>"
8
8
 
9
9
  from invrs_opt.lbfgsb.lbfgsb import density_lbfgsb as density_lbfgsb
@@ -29,16 +29,16 @@ LbfgsbState = Tuple[PyTree, PyTree, JaxLbfgsbDict]
29
29
  # Task message prefixes for the underlying L-BFGS-B implementation.
30
30
  TASK_START = b"START"
31
31
  TASK_FG = b"FG"
32
+ TASK_CONVERGED = b"CONVERGENCE"
32
33
 
33
- # Parameters which configure the state update step.
34
34
  UPDATE_IPRINT = -1
35
- UPDATE_PGTOL = 0.0
36
- UPDATE_FACTR = 0.0
37
35
 
38
36
  # Maximum value for the `maxcor` parameter in the L-BFGS-B scheme.
39
37
  MAXCOR_MAX_VALUE = 100
40
38
  MAXCOR_DEFAULT = 20
41
39
  LINE_SEARCH_MAX_STEPS_DEFAULT = 100
40
+ FTOL_DEFAULT = 0.0
41
+ GTOL_DEFAULT = 0.0
42
42
 
43
43
  # Maps bound scenarios to integers.
44
44
  BOUNDS_MAP: Dict[Tuple[bool, bool], int] = {
@@ -54,6 +54,8 @@ FORTRAN_INT = scipy_lbfgsb.types.intvar.dtype
54
54
  def lbfgsb(
55
55
  maxcor: int = MAXCOR_DEFAULT,
56
56
  line_search_max_steps: int = LINE_SEARCH_MAX_STEPS_DEFAULT,
57
+ ftol: float = FTOL_DEFAULT,
58
+ gtol: float = GTOL_DEFAULT,
57
59
  ) -> base.Optimizer:
58
60
  """Return an optimizer implementing the standard L-BFGS-B algorithm.
59
61
 
@@ -85,10 +87,17 @@ def lbfgsb(
85
87
  While the algorithm can work with pytrees of jax arrays, numpy arrays can
86
88
  also be used. Thus, e.g. the optimizer can directly be used with autograd.
87
89
 
90
+ When the optimization has converged (according to `ftol` or `gtol` criteria), the
91
+ optimizer simply returns the parameters which obtained the converged result. The
92
+ convergence can be queried by `is_converged(state)`.
93
+
88
94
  Args:
89
95
  maxcor: The maximum number of variable metric corrections used to define
90
96
  the limited memory matrix, in the L-BFGS-B scheme.
91
97
  line_search_max_steps: The maximum number of steps in the line search.
98
+ ftol: Tolerance for stopping criteria based on function values. See scipy
99
+ documentation for details.
100
+ gtol: Tolerance for stopping criteria based on gradient.
92
101
 
93
102
  Returns:
94
103
  The `base.Optimizer`.
@@ -96,6 +105,8 @@ def lbfgsb(
96
105
  return transformed_lbfgsb(
97
106
  maxcor=maxcor,
98
107
  line_search_max_steps=line_search_max_steps,
108
+ ftol=ftol,
109
+ gtol=gtol,
99
110
  transform_fn=lambda x: x,
100
111
  initialize_latent_fn=lambda x: x,
101
112
  )
@@ -105,6 +116,8 @@ def density_lbfgsb(
105
116
  beta: float,
106
117
  maxcor: int = MAXCOR_DEFAULT,
107
118
  line_search_max_steps: int = LINE_SEARCH_MAX_STEPS_DEFAULT,
119
+ ftol: float = FTOL_DEFAULT,
120
+ gtol: float = GTOL_DEFAULT,
108
121
  ) -> base.Optimizer:
109
122
  """Return an L-BFGS-B optimizer with additional transforms for density arrays.
110
123
 
@@ -117,11 +130,18 @@ def density_lbfgsb(
117
130
  and spacing parameters of the `DensityArray2D`. Where the bounds differ, the
118
131
  density is scaled before the transform is applied, and then unscaled afterwards.
119
132
 
133
+ When the optimization has converged (according to `ftol` or `gtol` criteria), the
134
+ optimizer simply returns the parameters which obtained the converged result. The
135
+ convergence can be queried by `is_converged(state)`.
136
+
120
137
  Args:
121
138
  beta: Determines the steepness of the thresholding.
122
139
  maxcor: The maximum number of variable metric corrections used to define
123
140
  the limited memory matrix, in the L-BFGS-B scheme.
124
141
  line_search_max_steps: The maximum number of steps in the line search.
142
+ ftol: Tolerance for stopping criteria based on function values. See scipy
143
+ documentation for details.
144
+ gtol: Tolerance for stopping criteria based on gradient.
125
145
 
126
146
  Returns:
127
147
  The `base.Optimizer`.
@@ -164,6 +184,8 @@ def density_lbfgsb(
164
184
  return transformed_lbfgsb(
165
185
  maxcor=maxcor,
166
186
  line_search_max_steps=line_search_max_steps,
187
+ ftol=ftol,
188
+ gtol=gtol,
167
189
  transform_fn=transform_fn,
168
190
  initialize_latent_fn=initialize_latent_fn,
169
191
  )
@@ -172,6 +194,8 @@ def density_lbfgsb(
172
194
  def transformed_lbfgsb(
173
195
  maxcor: int,
174
196
  line_search_max_steps: int,
197
+ ftol: float,
198
+ gtol: float,
175
199
  transform_fn: Callable[[PyTree], PyTree],
176
200
  initialize_latent_fn: Callable[[PyTree], PyTree],
177
201
  ) -> base.Optimizer:
@@ -182,10 +206,17 @@ def transformed_lbfgsb(
182
206
  `transform_fn`. In the simple case where this is just `lambda x: x` (i.e.
183
207
  the identity), this is equivalent to the standard L-BFGS-B algorithm.
184
208
 
209
+ When the optimization has converged (according to `ftol` or `gtol` criteria), the
210
+ optimizer simply returns the parameters which obtained the converged result. The
211
+ convergence can be queried by `is_converged(state)`.
212
+
185
213
  Args:
186
214
  maxcor: The maximum number of variable metric corrections used to define
187
215
  the limited memory matrix, in the L-BFGS-B scheme.
188
216
  line_search_max_steps: The maximum number of steps in the line search.
217
+ ftol: Tolerance for stopping criteria based on function values. See scipy
218
+ documentation for details.
219
+ gtol: Tolerance for stopping criteria based on gradient.
189
220
  transform_fn: Function which transforms the internal latent parameters to
190
221
  the parameters returned by the optimizer.
191
222
  initialize_latent_fn: Function which computes the initial latent parameters
@@ -218,6 +249,8 @@ def transformed_lbfgsb(
218
249
  upper_bound=_bound_for_params(upper_bound, params),
219
250
  maxcor=maxcor,
220
251
  line_search_max_steps=line_search_max_steps,
252
+ ftol=ftol,
253
+ gtol=gtol,
221
254
  )
222
255
  latent_params = _to_pytree(scipy_lbfgsb_state.x, params)
223
256
  return latent_params, scipy_lbfgsb_state.to_jax()
@@ -288,6 +321,11 @@ def transformed_lbfgsb(
288
321
  )
289
322
 
290
323
 
324
+ def is_converged(state: LbfgsbState) -> jnp.ndarray:
325
+ """Returns `True` if the optimization has converged."""
326
+ return state[2]["converged"]
327
+
328
+
291
329
  # ------------------------------------------------------------------------------
292
330
  # Helper functions.
293
331
  # ------------------------------------------------------------------------------
@@ -392,8 +430,11 @@ def _example_state(params: PyTree, maxcor: int) -> PyTree:
392
430
  float_params = tree_util.tree_map(lambda x: jnp.asarray(x, dtype=float), params)
393
431
  example_jax_lbfgsb_state = dict(
394
432
  x=jnp.zeros(n, dtype=float),
433
+ converged=jnp.asarray(False),
395
434
  _maxcor=jnp.zeros((), dtype=int),
396
435
  _line_search_max_steps=jnp.zeros((), dtype=int),
436
+ _ftol=jnp.zeros((), dtype=float),
437
+ _gtol=jnp.zeros((), dtype=float),
397
438
  _wa=jnp.ones(_wa_size(n=n, maxcor=maxcor), dtype=float),
398
439
  _iwa=jnp.ones(n * 3, dtype=jnp.int32), # Fortran int
399
440
  _task=jnp.zeros(59, dtype=int),
@@ -443,10 +484,13 @@ class ScipyLbfgsbState:
443
484
  """
444
485
 
445
486
  x: NDArray
487
+ converged: NDArray
446
488
  # Private attributes correspond to internal variables in the `scipy.optimize.
447
489
  # lbfgsb._minimize_lbfgsb` function.
448
490
  _maxcor: int
449
491
  _line_search_max_steps: int
492
+ _ftol: NDArray
493
+ _gtol: NDArray
450
494
  _wa: NDArray
451
495
  _iwa: NDArray
452
496
  _task: NDArray
@@ -476,8 +520,11 @@ class ScipyLbfgsbState:
476
520
  """Generates a dictionary of jax arrays defining the state."""
477
521
  return dict(
478
522
  x=jnp.asarray(self.x),
523
+ converged=jnp.asarray(self.converged),
479
524
  _maxcor=jnp.asarray(self._maxcor),
480
525
  _line_search_max_steps=jnp.asarray(self._line_search_max_steps),
526
+ _ftol=jnp.asarray(self._ftol),
527
+ _gtol=jnp.asarray(self._gtol),
481
528
  _wa=jnp.asarray(self._wa),
482
529
  _iwa=jnp.asarray(self._iwa),
483
530
  _task=_array_from_s60_str(self._task),
@@ -496,8 +543,11 @@ class ScipyLbfgsbState:
496
543
  state_dict = copy.deepcopy(state_dict)
497
544
  return ScipyLbfgsbState(
498
545
  x=onp.asarray(state_dict["x"], dtype=onp.float64),
546
+ converged=onp.asarray(state_dict["converged"], dtype=bool),
499
547
  _maxcor=int(state_dict["_maxcor"]),
500
548
  _line_search_max_steps=int(state_dict["_line_search_max_steps"]),
549
+ _ftol=onp.asarray(state_dict["_ftol"], dtype=onp.float64),
550
+ _gtol=onp.asarray(state_dict["_gtol"], dtype=onp.float64),
501
551
  _wa=onp.asarray(state_dict["_wa"], onp.float64),
502
552
  _iwa=onp.asarray(state_dict["_iwa"], dtype=FORTRAN_INT),
503
553
  _task=_s60_str_from_array(state_dict["_task"]),
@@ -518,6 +568,8 @@ class ScipyLbfgsbState:
518
568
  upper_bound: ElementwiseBound,
519
569
  maxcor: int,
520
570
  line_search_max_steps: int,
571
+ ftol: float,
572
+ gtol: float,
521
573
  ) -> "ScipyLbfgsbState":
522
574
  """Initializes the `ScipyLbfgsbState` for `x0`.
523
575
 
@@ -528,6 +580,9 @@ class ScipyLbfgsbState:
528
580
  maxcor: The maximum number of variable metric corrections used to define
529
581
  the limited memory matrix, in the L-BFGS-B scheme.
530
582
  line_search_max_steps: The maximum number of steps in the line search.
583
+ ftol: Tolerance for stopping criteria based on function values. See scipy
584
+ documentation for details.
585
+ gtol: Tolerance for stopping criteria based on gradient.
531
586
 
532
587
  Returns:
533
588
  The `ScipyLbfgsbState`.
@@ -558,8 +613,11 @@ class ScipyLbfgsbState:
558
613
  wa_size = _wa_size(n=n, maxcor=maxcor)
559
614
  state = ScipyLbfgsbState(
560
615
  x=onp.array(x0, onp.float64),
616
+ converged=onp.asarray(False),
561
617
  _maxcor=maxcor,
562
618
  _line_search_max_steps=line_search_max_steps,
619
+ _ftol=onp.asarray(ftol, onp.float64),
620
+ _gtol=onp.asarray(gtol, onp.float64),
563
621
  _wa=onp.zeros(wa_size, onp.float64),
564
622
  _iwa=onp.zeros(3 * n, FORTRAN_INT),
565
623
  _task=task,
@@ -582,12 +640,14 @@ class ScipyLbfgsbState:
582
640
  grad: NDArray,
583
641
  value: NDArray,
584
642
  ) -> None:
585
- """Performs an in-place update of the `ScipyLbfgsbState`.
643
+ """Performs an in-place update of the `ScipyLbfgsbState` if not converged.
586
644
 
587
645
  Args:
588
646
  grad: The function gradient for the current `x`.
589
647
  value: The scalar function value for the current `x`.
590
648
  """
649
+ if self.converged:
650
+ return
591
651
  if grad.shape != self.x.shape:
592
652
  raise ValueError(
593
653
  f"`grad` must have the same shape as attribute `x`, but got shapes "
@@ -608,8 +668,8 @@ class ScipyLbfgsbState:
608
668
  nbd=self._bound_type,
609
669
  f=value,
610
670
  g=grad,
611
- factr=UPDATE_FACTR,
612
- pgtol=UPDATE_PGTOL,
671
+ factr=self._ftol / onp.finfo(float).eps,
672
+ pgtol=self._gtol,
613
673
  wa=self._wa,
614
674
  iwa=self._iwa,
615
675
  task=self._task,
@@ -621,6 +681,8 @@ class ScipyLbfgsbState:
621
681
  maxls=self._line_search_max_steps,
622
682
  )
623
683
  task_str = self._task.tobytes()
684
+ if task_str.startswith(TASK_CONVERGED):
685
+ self.converged = onp.asarray(True)
624
686
  if task_str.startswith(TASK_FG):
625
687
  break
626
688
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: invrs_opt
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Algorithms for inverse design
5
5
  Author-email: "Martin F. Schubert" <mfschubert@gmail.com>
6
6
  Maintainer-email: "Martin F. Schubert" <mfschubert@gmail.com>
@@ -49,7 +49,7 @@ Requires-Dist: pytest-cov ; extra == 'tests'
49
49
  Requires-Dist: pytest-subtests ; extra == 'tests'
50
50
 
51
51
  # invrs-opt - Optimization algorithms for inverse design
52
- `v0.4.0`
52
+ `v0.5.0`
53
53
 
54
54
  ## Overview
55
55
 
@@ -1,14 +1,14 @@
1
- invrs_opt/__init__.py,sha256=sKLkaXTzj4zJf3pOcXI1uiHRM15tEEr2BhSL4RWQEns,309
1
+ invrs_opt/__init__.py,sha256=SK2nHKhwT60br9D6W8gfJ5Kj11BR3fIdGAEVAJBgH1I,309
2
2
  invrs_opt/base.py,sha256=dSX9QkMPzI8ROxy2cFNmMwk_89eQbk0rw94CzvLPQoY,907
3
3
  invrs_opt/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  invrs_opt/experimental/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  invrs_opt/experimental/client.py,sha256=td5o_YqqbcSypDrWCVrHGSoF8UxEdOLtKU0z9Dth9lA,4842
6
6
  invrs_opt/experimental/labels.py,sha256=dQDAMPyFMV6mXnMy295z8Ap205DRdVzysXny_Be8FmY,562
7
7
  invrs_opt/lbfgsb/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- invrs_opt/lbfgsb/lbfgsb.py,sha256=YEBM7XcKj65QpEwO5Y8Mgmjud-h8k-1lF6UsEFgu6sM,25130
8
+ invrs_opt/lbfgsb/lbfgsb.py,sha256=59OpEYmGH3ofDv6m8KQQob5mQJei2vTcm1COh7SAbgA,27896
9
9
  invrs_opt/lbfgsb/transform.py,sha256=a_Saj9Wq4lvqCJBrg5L2Z9DZ2NVs1xqrHLqha90a9Ws,5971
10
- invrs_opt-0.4.0.dist-info/LICENSE,sha256=ty6jHPvpyjHy6dbhnu6aDSY05bbl2jQTjnq9u1sBCfM,1078
11
- invrs_opt-0.4.0.dist-info/METADATA,sha256=abuCz0t6ZBbkxwL23I-Ef1m5IKMVJveOjvEl25SmfEw,3326
12
- invrs_opt-0.4.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
13
- invrs_opt-0.4.0.dist-info/top_level.txt,sha256=hOziS2uJ_NgwaW9yhtOfeuYnm1X7vobPBcp_6eVWKfM,10
14
- invrs_opt-0.4.0.dist-info/RECORD,,
10
+ invrs_opt-0.5.0.dist-info/LICENSE,sha256=ty6jHPvpyjHy6dbhnu6aDSY05bbl2jQTjnq9u1sBCfM,1078
11
+ invrs_opt-0.5.0.dist-info/METADATA,sha256=kV8T1uAmMnr0IDaNjq1SK8btKa_XlyHjapKOfti_MbY,3326
12
+ invrs_opt-0.5.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
13
+ invrs_opt-0.5.0.dist-info/top_level.txt,sha256=hOziS2uJ_NgwaW9yhtOfeuYnm1X7vobPBcp_6eVWKfM,10
14
+ invrs_opt-0.5.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.42.0)
2
+ Generator: bdist_wheel (0.43.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5