bs-python-utils 0.0.1__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.
@@ -0,0 +1,363 @@
1
+ """
2
+ Contains various utilities programs.
3
+ """
4
+ import sys
5
+ import traceback
6
+ from functools import wraps
7
+ from io import TextIOBase
8
+ from math import exp, factorial, log, sqrt
9
+ from pathlib import Path
10
+ from typing import Any, Callable, Iterable, cast
11
+
12
+ TwoFloats = tuple[float, float]
13
+ ThreeFloats = tuple[float, float, float]
14
+
15
+
16
+ def printargs(func: Callable) -> Callable:
17
+ """
18
+ Decorator that reports the arguments of the function
19
+
20
+ Args:
21
+ func: the decorated function
22
+ """
23
+
24
+ @wraps(func)
25
+ def wrapper(*args, **kwargs):
26
+ print(
27
+ f"Function {func.__name__} called with args = {args} and kwargs = {kwargs}"
28
+ )
29
+ return func(*args, **kwargs)
30
+
31
+ return wrapper
32
+
33
+
34
+ def bs_name_func(back: int = 2) -> str:
35
+ """
36
+ get the name of the current function, or further back in stack
37
+
38
+ Args:
39
+ back: 2 is current function, 3 the function that called it etc
40
+
41
+ Returns:
42
+ the name of the function
43
+ """
44
+ stack = traceback.extract_stack()
45
+ *_, func_name, _ = stack[-back]
46
+ return cast(str, func_name)
47
+
48
+
49
+ def bs_error_abort(msg: str = "error, aborting") -> None:
50
+ """
51
+ report error and abort
52
+
53
+ Args:
54
+ msg: a message
55
+
56
+ Returns:
57
+ prints the message and exits with code 1
58
+ """
59
+ print_stars(f"{bs_name_func(3)}: {msg}")
60
+ sys.exit(1)
61
+
62
+
63
+ def bs_switch(
64
+ match: str, dico: dict, strict: bool = True, default: Any = "no match"
65
+ ) -> Any:
66
+ """
67
+ a switch statement that allows for partial matches if strict is False
68
+
69
+ Args:
70
+ match: what we look for in the keys
71
+ dico: a dictionary with string keys
72
+ strict: if `False`, we accept a partial match
73
+ default: what we return if no match is found
74
+
75
+ Returns:
76
+ the value for the match, or `default`
77
+
78
+ Example:
79
+ calc_dict = {
80
+ "plus": lambda x, y: x + y,
81
+ "minus": lambda x, y: x - y
82
+ }
83
+
84
+ plus = bs_switch('plus', calc_dict, default="unintended function")
85
+ minus = bs_switch('min', calc_dict, strict=False, default="unintended function")
86
+
87
+ plus(6, 4)
88
+ >> 10
89
+ minus(6, 4)
90
+ >> 2
91
+
92
+ bs_switch('plu', calc_dict)
93
+ >> "no match""
94
+
95
+ """
96
+ if strict:
97
+ for key in dico:
98
+ if match == key:
99
+ return dico.get(key)
100
+ else:
101
+ for key in dico:
102
+ if match in key:
103
+ return dico.get(key)
104
+ return default
105
+
106
+
107
+ def find_first(iterable: Iterable, condition: Callable = lambda x: True) -> Any:
108
+ """
109
+ Returns the index of and the first item in the `iterable` that
110
+ satisfies the `condition`.
111
+
112
+ Args:
113
+ iterable: where to look
114
+ condition: must return a boolean
115
+
116
+ Returns:
117
+ If the condition is not given, returns 0 and the first item of
118
+ the iterable.
119
+
120
+ Raises `StopIteration` if no item satisfyng the condition is found.
121
+
122
+ Example:
123
+ >>> find_first( (1,2,3), condition=lambda x: x % 2 == 0)
124
+ (1, 2)
125
+ >>> find_first(range(3, 100))
126
+ (0, 3)
127
+ >>> find_first( () )
128
+ Traceback (most recent call last):
129
+ ...
130
+ StopIteration
131
+
132
+
133
+ """
134
+ return next((i, x) for i, x in enumerate(iterable) if condition(x))
135
+
136
+
137
+ def print_stars(title: str = None, n: int = 70) -> None:
138
+ """
139
+ prints a title within stars
140
+
141
+ Args:
142
+ title: title
143
+ n: number of stars on line
144
+
145
+ Returns:
146
+ prints a starred line, or two around the title
147
+ """
148
+ line_stars = "*" * n
149
+ print()
150
+ print(line_stars)
151
+ if title:
152
+ print(title.center(n))
153
+ print(line_stars)
154
+ print()
155
+
156
+
157
+ def file_print_stars(file_handle: TextIOBase, title: str = None, n: int = 70) -> None:
158
+ """
159
+ prints to a file a title within stars
160
+
161
+ Args:
162
+ file_handle: file handle
163
+ title: title
164
+ n: length of line
165
+
166
+ Returns:
167
+ prints a starred line to the file, or two around the title
168
+ """
169
+ line_stars = "*" * n
170
+ file_handle.write("\n")
171
+ file_handle.write(line_stars)
172
+ file_handle.write("\n")
173
+ if title:
174
+ file_handle.write(title.center(n))
175
+ file_handle.write("\n")
176
+ file_handle.write(line_stars)
177
+ file_handle.write("\n")
178
+ file_handle.write("\n")
179
+
180
+
181
+ def mkdir_if_needed(p: Path | str) -> Path:
182
+ """
183
+ create the directory if it does not exist
184
+
185
+ Args:
186
+ p: a path
187
+
188
+ Returns:
189
+ the directory Path
190
+
191
+ """
192
+ try:
193
+ q = Path(p)
194
+ except OSError:
195
+ bs_error_abort(f"{p} is not a path")
196
+ if not q.exists():
197
+ q.mkdir(parents=True)
198
+ return q
199
+
200
+
201
+ def bscomb(n: int, k: int) -> int:
202
+ """
203
+ number of combinations of k among n `{n \\choose k}`
204
+
205
+ Args:
206
+ n:
207
+ k: should be smaller than n
208
+
209
+ Returns:
210
+ `{n \\choose k}`
211
+ """
212
+ if not isinstance(n, int):
213
+ bs_error_abort(f"n should be an integer, not {n}")
214
+ if not isinstance(k, int):
215
+ bs_error_abort(f"k should be an integer, not {k}")
216
+ if n < k:
217
+ bs_error_abort(f"k={k} should not be larger than n={n}")
218
+ return factorial(n) // (factorial(k) * factorial(n - k))
219
+
220
+
221
+ def bslog(
222
+ x: float, eps: float = 1e-30, deriv: int = 0
223
+ ) -> float | TwoFloats | ThreeFloats:
224
+ """
225
+ extends the logarithm below `eps` by taking a second-order approximation
226
+ perhaps with derivatives
227
+
228
+ Args:
229
+ x: argument
230
+ eps: lower bound
231
+ deriv: if 1, also return first derivative; if 2, the first two derivatives
232
+
233
+ Returns:
234
+ `\\ln(x)` `C^2`-extended below `eps`, perhaps with derivatives
235
+ """
236
+ if deriv not in [0, 1, 2]:
237
+ bs_error_abort(f"deriv can only be 0, 1, or 2; not {deriv}")
238
+ if x > eps:
239
+ logx = log(x)
240
+ if deriv == 0:
241
+ return logx
242
+ dlogx = 1.0 / x
243
+ if deriv == 1:
244
+ return logx, dlogx
245
+ d2logx = -dlogx * dlogx
246
+ return logx, dlogx, d2logx
247
+ else:
248
+ dx = 1.0 - x / eps
249
+ log_smaller = log(eps) - dx - dx * dx / 2.0
250
+ if deriv == 0:
251
+ return log_smaller
252
+ dlog_smaller = (1.0 + dx) / eps
253
+ if deriv == 1:
254
+ return log_smaller, dlog_smaller
255
+ d2log_smaller = -1.0 / eps / eps
256
+ return log_smaller, dlog_smaller, d2log_smaller
257
+
258
+
259
+ def bsxlogx(
260
+ x: float, eps: float = 1e-30, deriv: int = 0
261
+ ) -> float | TwoFloats | ThreeFloats:
262
+ """
263
+ extends `x \\ln(x)` below `eps` by making it go to zero in a `C^2` extension
264
+ perhaps with derivatives
265
+
266
+ Args:
267
+ x: argument
268
+ eps: lower bound
269
+ deriv: if 1, also return first derivative; if 2, the first two derivatives
270
+
271
+ Returns:
272
+ `x \\ln(x)` `C^2`-extended below `eps`, perhaps with derivatives
273
+ """
274
+ if deriv not in [0, 1, 2]:
275
+ bs_error_abort(f"deriv can only be 0, 1, or 2; not {deriv}")
276
+ if x > eps:
277
+ logx = log(x)
278
+ if deriv == 0:
279
+ return x * logx
280
+ if deriv == 1:
281
+ return x * logx, 1.0 + logx
282
+ return x * logx, 1.0 + logx, 1.0 / x
283
+ else:
284
+ logeps = log(eps)
285
+ dx = x / eps
286
+ log_smaller = x * logeps - eps / 2.0 + x * dx / 2.0
287
+ if deriv == 0:
288
+ return log_smaller
289
+ if deriv == 1:
290
+ return log_smaller, logeps + dx
291
+ return log_smaller, logeps + dx, 1.0 / eps
292
+
293
+
294
+ def _bsexp_extend(x: float, deriv: int, limx: float) -> float | TwoFloats | ThreeFloats:
295
+ """extends the exponential C^2-wise beyond limx"""
296
+ elimx = exp(limx)
297
+ dx = x - limx
298
+ exp_extend = elimx * (1.0 + dx * (1.0 + 0.5 * dx))
299
+ if deriv == 0:
300
+ return exp_extend
301
+ dexp_extend = elimx * (1.0 + dx)
302
+ if deriv == 1:
303
+ return exp_extend, dexp_extend
304
+ # deriv = 2
305
+ return exp_extend, dexp_extend, elimx
306
+
307
+
308
+ def bsexp(
309
+ x: float,
310
+ bigx: float = 50.0,
311
+ lowx: float = -50.0,
312
+ deriv: int = 0,
313
+ ) -> float | TwoFloats | ThreeFloats:
314
+ """
315
+ `C^2`-extends the exponential above `bigx` and below `lowx`
316
+ perhaps with derivatives
317
+
318
+ Args:
319
+ x: argument
320
+ bigx: upper bound
321
+ lowx: lower bound
322
+ deriv: if 1, also return first derivative; if 2, the first two derivatives
323
+
324
+ Returns:
325
+ the exponential `C^2`-extended above `bigx` and below `lowx`
326
+ perhaps with derivatives
327
+ """
328
+ if deriv not in [0, 1, 2]:
329
+ bs_error_abort(f"deriv can only be 0, 1, or 2; not {deriv}")
330
+ if lowx < x < bigx:
331
+ expx = exp(x)
332
+ if deriv == 0:
333
+ return expx
334
+ if deriv == 1:
335
+ return expx, expx
336
+ return expx, expx, expx
337
+ elif x < lowx:
338
+ return _bsexp_extend(x, deriv, lowx)
339
+ else:
340
+ return _bsexp_extend(x, deriv, bigx)
341
+
342
+
343
+ def bs_projection_point(
344
+ x: float, y: float, a: float, b: float, c: float
345
+ ) -> tuple[float, float, float]:
346
+ """
347
+ projection of point (x,y) on line ax+by+c=0
348
+
349
+ Args:
350
+ x: y: coordinates
351
+ a: b: c: line parameters (as in ax+by+c=0)
352
+
353
+ Returns:
354
+ x_proj: y_proj: coordinates of projection point
355
+ dist: distance of point from line
356
+ """
357
+ a2b2 = a * a + b * b
358
+ denom = sqrt(a2b2)
359
+ value = a * x + b * y + c
360
+ x_proj = x - a * value / a2b2
361
+ y_proj = y - b * value / a2b2
362
+ dist = abs(value) / denom
363
+ return x_proj, y_proj, dist
@@ -0,0 +1,258 @@
1
+ """
2
+ distance covariance and partial distance covariance (Szekely and Rizzo)
3
+ evaluation and tests of independence and conditional independence
4
+ """
5
+
6
+ from dataclasses import dataclass
7
+ from math import sqrt
8
+ from typing import cast
9
+
10
+ import numpy as np
11
+
12
+ from bs_python_utils.bsnputils import check_square, check_vector_or_matrix
13
+ from bs_python_utils.bsutils import bs_error_abort
14
+
15
+
16
+ @dataclass
17
+ class DcovResults:
18
+ dcov: float
19
+ dcov_stat: float
20
+ dcor: float
21
+ X_dd: np.ndarray
22
+ Y_dd: np.ndarray
23
+ unbiased: bool
24
+
25
+
26
+ @dataclass
27
+ class PdcovResults:
28
+ pdcov: float
29
+ pdcov_stat: float
30
+ pdcor: float
31
+ X_dd: np.ndarray
32
+ Y_dd: np.ndarray
33
+ Z_dd: np.ndarray
34
+
35
+
36
+ def _compute_distances(T: np.ndarray) -> np.ndarray:
37
+ """
38
+ compute the Euclidian norms (or absolute values)
39
+ of all row differences `T_k - T_l`
40
+
41
+ Args:
42
+ T: a vector or a matrix
43
+
44
+ Returns:
45
+ the matrix of norms of differences
46
+ """
47
+ ndims_T = check_vector_or_matrix(T, "_compute_distances")
48
+ if ndims_T == 1:
49
+ return cast(np.ndarray, np.abs(np.subtract.outer(T, T)))
50
+ else:
51
+ n, nv = T.shape
52
+ A = np.zeros((n, n))
53
+ for iv in range(nv):
54
+ Tiv = T[:, iv]
55
+ Aiv = np.subtract.outer(Tiv, Tiv)
56
+ A += Aiv * Aiv
57
+ return np.sqrt(A)
58
+
59
+
60
+ def _double_decenter(A: np.ndarray, unbiased: bool = False) -> np.ndarray:
61
+ """
62
+ does double decentering on a square matrix A
63
+
64
+ Args:
65
+ A: a matrix
66
+ unbiased: if `True`, we use the Szekely and Rizzo 2014 formula
67
+
68
+ Returns:
69
+ the doubly decentered matrix
70
+ """
71
+ n = check_square(A, "_double_decenter")
72
+ A_1 = np.sum(A, 0)
73
+ A_2 = np.sum(A, 1).reshape((-1, 1))
74
+ A_0 = np.sum(A_1)
75
+ fac2 = (n - 2) if unbiased else n
76
+ fac1 = (n - 1) if unbiased else n
77
+ A_dd = A - A_1 / fac2 - A_2 / fac2 + A_0 / (fac1 * fac2)
78
+ if unbiased:
79
+ np.fill_diagonal(A_dd, np.zeros(n))
80
+ return cast(np.ndarray, A_dd)
81
+
82
+
83
+ def _dcov_prod(A: np.ndarray, B: np.ndarray, unbiased: bool = False) -> float:
84
+ n = check_square(A, "_dcov_prod")
85
+ m = check_square(B, "_dcov_prod")
86
+ if m == n:
87
+ fac3 = (n - 3) if unbiased else n
88
+ return cast(float, np.sum(A * B) / (n * fac3))
89
+ else:
90
+ bs_error_abort("A and B should be square matrices of the same size")
91
+ return 0.0 # for mypy
92
+
93
+
94
+ def dcov_dcor(X: np.ndarray, Y: np.ndarray, unbiased: bool = False) -> DcovResults:
95
+ """
96
+ evaluate the distance covariance and correlation of `X` and `Y`
97
+
98
+ Args:
99
+ X: `n` observations of a random variable or vector
100
+ Y: `n` observations of a random variable or vector
101
+ unbiased: if `True`, we use the Szekely and Rizzo 2014 formula
102
+
103
+ Returns:
104
+ `dCov^2(X,Y)` and `dCor^2(X,Y)`
105
+ """
106
+ X_dist = _compute_distances(X)
107
+ n = X_dist.shape[0]
108
+ X_dd = _double_decenter(X_dist, unbiased)
109
+ Y_dist = _compute_distances(Y)
110
+ Y_dd = _double_decenter(Y_dist, unbiased)
111
+ dcov2 = _dcov_prod(X_dd, Y_dd, unbiased)
112
+ dcor2 = dcov2 / sqrt(
113
+ _dcov_prod(X_dd, X_dd, unbiased) * _dcov_prod(Y_dd, Y_dd, unbiased)
114
+ )
115
+ return DcovResults(
116
+ dcov=dcov2,
117
+ dcor=dcor2,
118
+ X_dd=X_dd,
119
+ Y_dd=Y_dd,
120
+ unbiased=unbiased,
121
+ dcov_stat=n * dcov2,
122
+ )
123
+
124
+
125
+ def _dcov_bootstrap(
126
+ X_dd: np.ndarray,
127
+ Y_dd: np.ndarray,
128
+ unbiased: bool = False,
129
+ ndraws: int = 199,
130
+ ) -> np.ndarray:
131
+ """
132
+ use bootstrap on the test statistics of independence
133
+
134
+ Args:
135
+ X_dd: the doubly decentered distances for `X`
136
+ Y_dd: the doubly decentered distances for `Y`
137
+ unbiased: if `True`, we use the Szekely and Rizzo 2014 formula
138
+ ndraws: number of permutations
139
+
140
+ Returns:
141
+ the values of the `ndraws` bootstrapped test stats
142
+ """
143
+ n = X_dd.shape[0]
144
+ dcov_stats_boot = np.zeros(ndraws)
145
+ for idraw in range(ndraws):
146
+ draws = np.random.choice(np.arange(n), n)
147
+ X_ddi = X_dd[draws, :][:, draws]
148
+ Y_ddi = Y_dd[draws, :][:, draws]
149
+ if idraw % 50 == 0:
150
+ print(f" bootstrap draw {idraw}")
151
+ dcov_stats_boot[idraw] = _dcov_prod(X_ddi, Y_ddi, unbiased)
152
+ return cast(np.ndarray, n * dcov_stats_boot)
153
+
154
+
155
+ def pvalue_dcov(dcov_results: DcovResults, ndraws: int = 199) -> float:
156
+ """
157
+ test of no dependence between `X` and `Y` given `Z`
158
+
159
+ Args:
160
+ dcov_results: results from `dcov_dcor`
161
+ ndraws: the number of draws we use
162
+
163
+ Returns:
164
+ the bootstrapped p-value of the test
165
+ """
166
+ X_dd = dcov_results.X_dd
167
+ Y_dd = dcov_results.Y_dd
168
+ dcov_stat = dcov_results.dcov_stat
169
+ unbiased = dcov_results.unbiased
170
+ dcov_stats_boot = _dcov_bootstrap(X_dd, Y_dd, unbiased, ndraws)
171
+ sum_small = cast(int, np.sum(dcov_stat < dcov_stats_boot))
172
+ return (1.0 + sum_small) / (1.0 + ndraws)
173
+
174
+
175
+ def pdcov_pdcor(X: np.ndarray, Y: np.ndarray, Z: np.ndarray) -> PdcovResults:
176
+ """
177
+ evaluate the partial distance covariance and correlation of `X` and `Y` given `Z`
178
+
179
+ Args:
180
+ X: `n` observations of a random variable or vector
181
+ Y: `n` observations of a random variable or vector
182
+ Z: `n` observations of a random variable or vector
183
+
184
+ Returns:
185
+ a `PdcovResults` instance
186
+ """
187
+ unbiased = True
188
+ X_dist = _compute_distances(X)
189
+ X_dd = _double_decenter(X_dist, unbiased)
190
+ Y_dist = _compute_distances(Y)
191
+ Y_dd = _double_decenter(Y_dist, unbiased)
192
+ Z_dist = _compute_distances(Z)
193
+ Z_dd = _double_decenter(Z_dist, unbiased)
194
+ C_XX = _dcov_prod(X_dd, X_dd, unbiased)
195
+ C_XY = _dcov_prod(X_dd, Y_dd, unbiased)
196
+ C_YY = _dcov_prod(Y_dd, Y_dd, unbiased)
197
+ C_XZ = _dcov_prod(X_dd, Z_dd, unbiased)
198
+ C_YZ = _dcov_prod(Y_dd, Z_dd, unbiased)
199
+ C_ZZ = _dcov_prod(Z_dd, Z_dd, unbiased)
200
+ pdcov = C_XY - (C_XZ * C_YZ) / C_ZZ
201
+ pdcor = pdcov / sqrt((C_XX - C_XZ * C_XZ / C_ZZ) * (C_YY - C_YZ * C_YZ / C_ZZ))
202
+ n = X.shape[0]
203
+ return PdcovResults(
204
+ pdcov=pdcov, pdcor=pdcor, pdcov_stat=n * pdcov, X_dd=X_dd, Y_dd=Y_dd, Z_dd=Z_dd
205
+ )
206
+
207
+
208
+ def _pdcovs_bootstrap(
209
+ X_dd: np.ndarray, Y_dd: np.ndarray, Z_dd: np.ndarray, ndraws: int = 199
210
+ ) -> np.ndarray:
211
+ """
212
+ use permutations and recompute the test statistics of independence
213
+
214
+ Args:
215
+ X_dd: the doubly decentered distances for `X`
216
+ Y_dd: the doubly decentered distances for `Y`
217
+ Z_dd: the doubly decentered distances for `Y`
218
+ ndraws: the number of draws we use
219
+
220
+ Returns:
221
+ the `ndraws` values of `pdCov(X,Y ; Z)`
222
+ """
223
+ pdcov_stats_boot = np.zeros(ndraws)
224
+ unbiased = True
225
+ n = X_dd.shape[0]
226
+ for idraw in range(ndraws):
227
+ if idraw % 50 == 0:
228
+ print(f"pdcov test: bootstrap draw {idraw}")
229
+ draws = np.random.choice(np.arange(n), n)
230
+ X_ddi = X_dd[draws, :][:, draws]
231
+ Y_ddi = Y_dd[draws, :][:, draws]
232
+ Z_ddi = Z_dd[draws, :][:, draws]
233
+ C_XY = _dcov_prod(X_ddi, Y_ddi, unbiased)
234
+ C_XZ = _dcov_prod(X_ddi, Z_ddi, unbiased)
235
+ C_YZ = _dcov_prod(Y_ddi, Z_ddi, unbiased)
236
+ C_ZZ = _dcov_prod(Z_ddi, Z_ddi, unbiased)
237
+ pdcov_stats_boot[idraw] = C_XY - (C_XZ * C_YZ) / C_ZZ
238
+ return n * pdcov_stats_boot
239
+
240
+
241
+ def pvalue_pdcov(pdcov_results: PdcovResults, ndraws: int = 199) -> float:
242
+ """
243
+ test of no dependence between `X` and `Y` given `Z`
244
+
245
+ Args:
246
+ pdcov_results: the results of `pdcov_pdcor`
247
+ ndraws: the number of draws we use
248
+
249
+ Returns:
250
+ the bootstrapped p-value of the test
251
+ """
252
+ X_dd = pdcov_results.X_dd
253
+ Y_dd = pdcov_results.Y_dd
254
+ Z_dd = pdcov_results.Z_dd
255
+ pdcov_stat = pdcov_results.pdcov_stat
256
+ pdcov_stats_boot = _pdcovs_bootstrap(X_dd, Y_dd, Z_dd, ndraws)
257
+ sum_small = cast(int, np.sum(pdcov_stat < pdcov_stats_boot))
258
+ return (1.0 + sum_small) / (1.0 + ndraws)
@@ -0,0 +1,71 @@
1
+ """examples of optimization"""
2
+
3
+ import numpy as np
4
+
5
+ from bs_python_utils.bsutils import print_stars
6
+ from bs_python_utils.bssputils import describe_array
7
+ from bs_python_utils.bs_opt import (
8
+ acc_grad_descent,
9
+ minimize_some_fixed,
10
+ armijo_alpha,
11
+ barzilai_borwein_alpha,
12
+ )
13
+
14
+
15
+ print_stars("Testing acc_grad_descent")
16
+
17
+
18
+ def grad_f(x, p):
19
+ xp = x - p[0]
20
+ return 4.0 * xp * xp * xp
21
+
22
+
23
+ x_init = np.random.normal(size=10000)
24
+
25
+ p = 1.0
26
+ x_conv, ret_code = acc_grad_descent(
27
+ grad_f, x_init, other_params=np.array([p]), tol=1e-12, verbose=False
28
+ )
29
+
30
+ describe_array(x_conv - p, "x-p should be close to zero")
31
+
32
+
33
+ def obj(x, args):
34
+ res = x - args
35
+ return np.sum(res * res)
36
+
37
+
38
+ def grad_obj(x, args):
39
+ res = x - args
40
+ return 2.0 * res
41
+
42
+
43
+ n = 5
44
+ x_init = np.full(n, 0.5)
45
+ args = np.arange(n)
46
+ bounds = [(-10.0, 10.0) for _ in range(n)]
47
+
48
+ fixed_vars = [1, 3]
49
+ fixed_vals = -np.ones(2)
50
+
51
+ resopt = minimize_some_fixed(
52
+ obj,
53
+ grad_obj,
54
+ x_init,
55
+ args,
56
+ fixed_vars=fixed_vars,
57
+ fixed_vals=fixed_vals,
58
+ bounds=bounds,
59
+ )
60
+
61
+ print(resopt)
62
+
63
+ # test the step routines
64
+ g = grad_obj(x_init, args)
65
+ alpha_a = armijo_alpha(obj, x_init, -g, args)
66
+ print(f"\nArmijo alpha={alpha_a}")
67
+
68
+ alpha_b, g_b = barzilai_borwein_alpha(grad_obj, x_init, args)
69
+ print(f"\nBarzilai-Borwein alpha={alpha_a}")
70
+ print("g and g_b:")
71
+ print(np.column_stack((g, g_b)))