pyANOVAapprox 2.0.5__tar.gz → 2.0.7__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.
Files changed (38) hide show
  1. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/PKG-INFO +1 -1
  2. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/pyproject.toml +1 -1
  3. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/src/pyANOVAapprox/approx.py +55 -42
  4. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/src/pyANOVAapprox/bandwidth.py +78 -30
  5. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/.github/workflows/ci.yml +0 -0
  6. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/.github/workflows/documentation.yml +0 -0
  7. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/.github/workflows/format.yml +0 -0
  8. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/.github/workflows/release.yml +0 -0
  9. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/.gitignore +0 -0
  10. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/LICENSE +0 -0
  11. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/README.md +0 -0
  12. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/docs/Makefile +0 -0
  13. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/docs/source/Analysis.rst +0 -0
  14. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/docs/source/Approximation.rst +0 -0
  15. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/docs/source/Errors.rst +0 -0
  16. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/docs/source/conf.py +0 -0
  17. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/docs/source/index.rst +0 -0
  18. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/simpleTest/TestFunctionPeriodic.py +0 -0
  19. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/simpleTest/exampleAutoapproximate.py +0 -0
  20. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/simpleTest/exampleCheb.py +0 -0
  21. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/simpleTest/exampleClassification.py +0 -0
  22. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/simpleTest/exampleNonPeriodic.py +0 -0
  23. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/simpleTest/examplePeriodic.py +0 -0
  24. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/simpleTest/exampleWavelet.py +0 -0
  25. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/simpleTest/friedman.ipynb +0 -0
  26. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/src/pyANOVAapprox/__init__.py +0 -0
  27. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/src/pyANOVAapprox/analysis.py +0 -0
  28. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/src/pyANOVAapprox/errors.py +0 -0
  29. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/src/pyANOVAapprox/fista.py +0 -0
  30. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/src/pyANOVAapprox/trafos.py +0 -0
  31. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/tests/TestFunctionCheb.py +0 -0
  32. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/tests/TestFunctionPeriodic.py +0 -0
  33. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/tests/cheb_fista.py +0 -0
  34. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/tests/cheb_lsqr.py +0 -0
  35. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/tests/per_fista.py +0 -0
  36. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/tests/per_lsqr.py +0 -0
  37. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/tests/run_tests.py +0 -0
  38. {pyanovaapprox-2.0.5 → pyanovaapprox-2.0.7}/tests/wav_lsqr.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyANOVAapprox
3
- Version: 2.0.5
3
+ Version: 2.0.7
4
4
  Summary: Approximation Package for High-Dimensional Functions
5
5
  Project-URL: Homepage, https://github.com/NFFT/pyGroupedTransforms
6
6
  Author-email: Felix Wirth <fwi012001@gmail.com>
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "pyANOVAapprox"
7
- version = "2.0.5"
7
+ version = "2.0.7"
8
8
  authors = [
9
9
  { name="Felix Wirth", email="fwi012001@gmail.com" },
10
10
  ]
@@ -1,4 +1,5 @@
1
1
  import copy
2
+ import warnings
2
3
 
3
4
  import pyANOVAapprox.analysis as ANOVAanalysis
4
5
  import pyANOVAapprox.bandwidth as ANOVAbandwidth
@@ -186,6 +187,11 @@ class approx:
186
187
  lam={0.0},
187
188
  ):
188
189
 
190
+ if type(lam) is np.ndarray:
191
+ lam = {float(i) for i in lam}
192
+ if type(lam) is list:
193
+ lam = set(lam)
194
+
189
195
  self.X = X
190
196
 
191
197
  if X.shape[0] != y.shape[0]:
@@ -222,6 +228,9 @@ class approx:
222
228
  self.trafo = [None]
223
229
  self.fc = [{}]
224
230
  self.aktsetting = 0
231
+ self.lam = {}
232
+ for i in lam:
233
+ self.lam[i] = [0]
225
234
 
226
235
  if setting.N is not None:
227
236
  self.addTrafo()
@@ -233,17 +242,24 @@ class approx:
233
242
  self.trafo.append(None)
234
243
  self.fc.append({})
235
244
 
236
- def getSettingNr(self, settingnr=None):
237
- return self.aktsetting if settingnr is None else settingnr
245
+ def getSettingNr(self, settingnr=None, lam=None, warn=True):
246
+ if lam is None:
247
+ return self.aktsetting if settingnr is None else settingnr
248
+ else:
249
+ if lam in self.lam.keys():
250
+ return self.lam[lam][-1] if settingnr is None else settingnr
251
+ else:
252
+ warnings.warn("Not yet approximated for this lambda")
253
+ return self.aktsetting if settingnr is None else settingnr
238
254
 
239
- def getSetting(self, settingnr=None):
240
- return self.setting[self.getSettingNr(settingnr)]
255
+ def getSetting(self, settingnr=None, lam=None):
256
+ return self.setting[self.getSettingNr(settingnr, lam)]
241
257
 
242
- def getTrafo(self, settingnr=None):
243
- return self.trafo[self.getSettingNr(settingnr)]
258
+ def getTrafo(self, settingnr=None, lam=None):
259
+ return self.trafo[self.getSettingNr(settingnr, lam)]
244
260
 
245
- def getFc(self, settingnr=None):
246
- return self.fc[self.getSettingNr(settingnr)]
261
+ def getFc(self, settingnr=None, lam=None):
262
+ return self.fc[self.getSettingNr(settingnr, lam)]
247
263
 
248
264
  def addTrafo(self, settingnr=None):
249
265
  setting = self.getSetting(settingnr)
@@ -356,6 +372,7 @@ class approx:
356
372
  If lam is a np.ndarray of dtype float, this function computes the approximation for the regularization parameters contained in lam.
357
373
  If lam is a float, this function computes the approximation for the regularization parameter lam.
358
374
  """
375
+
359
376
  setting = self.getSetting(settingnr)
360
377
 
361
378
  if lam is None:
@@ -393,6 +410,7 @@ class approx:
393
410
  solver_verbose,
394
411
  solver_tol,
395
412
  ):
413
+ settingnrs = []
396
414
  setting = self.getSetting(settingnr)
397
415
 
398
416
  n = len(self.y)
@@ -401,20 +419,24 @@ class approx:
401
419
 
402
420
  D = dict([(u, tuple([1.0] * len(u))) for u in setting.U])
403
421
  t = dict([(u, tuple([1.0] * len(u))) for u in setting.U])
422
+ fix = dict([(u, tuple([False] * len(u))) for u in setting.U])
423
+ bw = [tuple([0] * len(u)) for u in setting.U]
404
424
 
405
425
  if verbosity > 3:
406
426
  if not os.path.exists("log"):
407
427
  os.mkdir("log")
408
428
  with open("log/log.csv", "w", newline="") as csvfile:
409
429
  csv.writer(csvfile, delimiter=",").writerow(["it"] + setting.U)
410
-
430
+ if verbosity > 2:
431
+ print("B =", B)
411
432
  for idx in range(maxiter):
412
433
  if verbosity > 0:
413
434
  print("===== Iteration ", str(idx + 1), " =====")
414
- bw = compute_bandwidth(B, D, t)
435
+ bw, fix = compute_bandwidth(B, D, t, fix, bw)
415
436
  if setting.N is not None:
416
437
  self.addSetting(setting)
417
438
  settingnr = self.aktsetting
439
+ settingnrs = settingnrs + [settingnr]
418
440
  setting = self.getSetting(settingnr)
419
441
 
420
442
  setting.N = [bw[i] for i in setting.U]
@@ -469,9 +491,12 @@ class approx:
469
491
  wr.writerow(
470
492
  ["t in it" + str(idx + 1)] + [str(t[i]) for i in setting.U]
471
493
  )
472
- if t == dict([(u, [math.nan] * len(u)) for u in setting.U]) or D == dict([(u, [math.nan] * len(u)) for u in setting.U]):
473
- return D, t
474
- return D, t
494
+
495
+ if all([math.isnan(j) for j in sum([D[i] for i in D], [])]) and all(
496
+ [math.isnan(j) for j in sum([t[i] for i in D], [])]
497
+ ):
498
+ break
499
+ return settingnrs
475
500
 
476
501
  def autoapproximate(
477
502
  self,
@@ -486,18 +511,21 @@ class approx:
486
511
  solver_verbose=False,
487
512
  solver_tol=1e-8,
488
513
  ):
514
+ settingnr = self.getSettingNr(settingnr)
489
515
  setting = self.getSetting(settingnr)
490
516
 
491
517
  if lam is None:
492
518
  lam = setting.lam
493
519
  else:
520
+ if isinstance(lam, np.ndarray):
521
+ lam = {float(l) for l in lam}
494
522
  if not isinstance(lam, set):
495
523
  lam = {lam}
496
524
  setting.lam = lam
497
525
 
498
- if len(lam) == 1:
499
- return self._autoapproximate(
500
- lam.pop(),
526
+ for l in lam:
527
+ settingnrs = self._autoapproximate(
528
+ l,
501
529
  settingnr=settingnr,
502
530
  B=B,
503
531
  maxiter=maxiter,
@@ -508,22 +536,7 @@ class approx:
508
536
  solver_verbose=solver_verbose,
509
537
  solver_tol=solver_tol,
510
538
  )
511
- else:
512
- return {
513
- l: self._autoapproximate(
514
- l,
515
- settingnr=settingnr,
516
- B=B,
517
- maxiter=maxiter,
518
- solver=solver,
519
- verbosity=verbosity,
520
- solver_max_iter=solver_max_iter,
521
- solver_weights=solver_weights,
522
- solver_verbose=solver_verbose,
523
- solver_tol=solver_tol,
524
- )
525
- for l in lam
526
- }
539
+ self.lam[l] = self.lam[l] + settingnrs
527
540
 
528
541
  def evaluate(self, settingnr=None, lam=None, X=None):
529
542
  """
@@ -534,7 +547,7 @@ class approx:
534
547
  - If only lam is given: evaluate at self.X for specific lam.
535
548
  - If neither are given: evaluate at self.X for all lam.
536
549
  """
537
- setting = self.getSetting(settingnr)
550
+ setting = self.getSetting(settingnr, lam)
538
551
 
539
552
  if X is not None:
540
553
  if setting.basis == "per" and (np.min(X) < -0.5 or np.max(X) >= 0.5):
@@ -552,14 +565,14 @@ class approx:
552
565
  basis_vect=setting.basis_vect,
553
566
  )
554
567
  else:
555
- trafo = self.getTrafo(settingnr)
568
+ trafo = self.getTrafo(settingnr, lam)
556
569
 
557
570
  if (
558
571
  lam is not None
559
572
  ): # evaluate( a::approx; X::Matrix{Float64}, λ::Float64 )::Union{Vector{ComplexF64},Vector{Float64}}
560
- return trafo @ self.getFc(settingnr)[lam]
573
+ return trafo @ self.getFc(settingnr, lam)[lam]
561
574
  else: # evaluate( a::approx; X::Matrix{Float64} )::Dict{Float64,Union{Vector{ComplexF64},Vector{Float64}}}
562
- return {λ: trafo @ self.getFc(settingnr)[λ] for λ in setting.lam}
575
+ return {λ: trafo @ self.getFc(settingnr, lam)[λ] for λ in self.lam.keys()}
563
576
 
564
577
  def evaluateANOVAterms(self, settingnr=None, X=None, lam=None):
565
578
  """
@@ -568,7 +581,7 @@ class approx:
568
581
  - If lam is given: evaluate at X for specific lam.
569
582
  - If lam is not given: evaluate at X for all lam.
570
583
  """
571
- setting = self.getSetting(settingnr)
584
+ setting = self.getSetting(settingnr, lam)
572
585
  if X is None:
573
586
  X = self.X
574
587
 
@@ -600,14 +613,14 @@ class approx:
600
613
  lam is not None
601
614
  ): # evaluateANOVAterms( a::approx; X::Matrix{Float64}, λ::Float64 )::Union{Matrix{ComplexF64},Matrix{Float64}}
602
615
  for j, u in enumerate(setting.U):
603
- values[:, j] = trafo[u] @ self.getFc(settingnr)[lam][u]
616
+ values[:, j] = trafo[u] @ self.getFc(settingnr, lam)[lam][u]
604
617
  return values
605
618
  else: # evaluateANOVAterms( a::approx; X::Matrix{Float64} )::Dict{Float64,Union{Matrix{ComplexF64},Matrix{Float64}}}
606
619
  results = {}
607
- for λ in setting.lam:
620
+ for λ in self.lam.keys():
608
621
  vals = np.zeros_like(values)
609
622
  for j, u in enumerate(setting.U):
610
- vals[:, j] = trafo[u] @ self.getFc(settingnr)[λ][u]
623
+ vals[:, j] = trafo[u] @ self.getFc(settingnr, lam)[λ][u]
611
624
  results[λ] = vals
612
625
  return results
613
626
 
@@ -618,7 +631,7 @@ class approx:
618
631
  - If lam is given: evaluate at X for specific lam.
619
632
  - If lam is not given: evaluate at X for all lam.
620
633
  """
621
- setting = self.getSetting(settingnr)
634
+ setting = self.getSetting(settingnr, lam)
622
635
 
623
636
  if X is None:
624
637
  X = self.X
@@ -648,7 +661,7 @@ class approx:
648
661
 
649
662
  else: # evaluateSHAPterms( a::approx; X::Matrix{Float64} )::Dict{Float64,Union{Matrix{ComplexF64},Matrix{Float64}}}
650
663
  results = {}
651
- for l in setting.lam:
664
+ for l in self.lam.keys():
652
665
  terms = self.evaluateANOVAterms(X, l)
653
666
 
654
667
  Dtype = np.complex128 if setting.basis == "per" else np.float64
@@ -9,6 +9,8 @@ def getfcu(ghat, u):
9
9
  # fcu = np.permute_dims(fcu, range(len(bws))[::-1])
10
10
  fcu = ghat[u].reshape(bws - 1)
11
11
  fcu = np.permute_dims(fcu, range(len(bws)))
12
+ #if(u == (3,7)):
13
+ # print(fcu)
12
14
 
13
15
  return fcu
14
16
 
@@ -30,48 +32,90 @@ def getaxissum(ghat, u, j, system):
30
32
  else:
31
33
  raise ValueError("For this basis is estimate rates not implemented")
32
34
  # fcuj = np.concatenate(fcuj[math.ceil(bws[j]/2):] + [fcuj[0]]) + fcuj[math.ceil(bws[j]/2)-1::-1]
33
- return fcuj
35
+ #if(u == (3,7)):
36
+ # print(j,fcuj)
34
37
 
38
+ return fcuj
35
39
 
36
- def compute_bandwidth(B, D, t):
40
+ def compute_bandwidth(B, D, t, fix, oldbw):
37
41
  us = set(D.keys()) - {()}
38
42
  bw = {u: [6] * len(u) for u in us}
39
43
  bw[()] = []
40
44
 
41
- minfreqs = sum(math.prod((bw[u][j] - 1) for j in range(len(u))) for u in us)
45
+ # fix erweitern: NaN-Einträge in t werden automatisch fixiert
46
+ fix = {u: list(fix[u]) for u in us} # deep copy
47
+ for u in us:
48
+ for j in range(len(u)):
49
+ if math.isnan(t[u][j]):
50
+ fix[u][j] = True
51
+
52
+ # Für fixierte Einträge: Bandbreite aus oldbw übernehmen
53
+ for u in us:
54
+ for j in range(len(u)):
55
+ if fix[u][j]:
56
+ bw[u][j] = oldbw[u][j]
57
+
58
+ minfreqs = sum(
59
+ math.prod(
60
+ (oldbw[u][j] - 1) if fix[u][j] else (6 - 1)
61
+ for j in range(len(u))
62
+ )
63
+ for u in us
64
+ )
42
65
  if B < minfreqs:
43
66
  raise ValueError(f"Budget too small: {B} < {minfreqs}")
44
67
 
45
- def fun_lmbda_u(lmbda, u):
46
- p1 = math.prod((bw[u][j] - 1) for j in range(len(u)) if math.isnan(t[u][j]))
47
- p2 = math.prod(
48
- ((2 * t[u][j] * D[u][j]) / lmbda) ** (0.5 / t[u][j])
68
+ def A_u(u):
69
+ return sum(
70
+ 0.5 / t[u][j]
49
71
  for j in range(len(u))
50
- if not math.isnan(t[u][j])
51
- )
52
- denom_exp = 1 + sum(
53
- 0.5 / t[u][j] for j in range(len(u)) if not math.isnan(t[u][j])
72
+ if not fix[u][j]
54
73
  )
55
- return (p1 * p2) ** (1 / denom_exp)
56
-
57
- def fun_lmbda(lmbda, B):
58
- total = 0.0
59
- for u in us:
60
- total += fun_lmbda_u(math.exp(lmbda), u)
61
- return total - B
62
74
 
63
- lmbda = math.exp(bisect(lambda t: fun_lmbda(t, B), -100, 100))
75
+ def B_u(u):
76
+ prod_C = math.prod(
77
+ D[u][j] ** (0.5 / t[u][j])
78
+ for j in range(len(u))
79
+ if not fix[u][j]
80
+ )
81
+ prod_fix = math.prod(
82
+ (bw[u][j] - 1)
83
+ for j in range(len(u))
84
+ if fix[u][j]
85
+ )
86
+ return prod_C * prod_fix
64
87
 
88
+ def fun_lmbda_u(lmbda, u):
89
+ Au = A_u(u)
90
+ Bu = B_u(u)
91
+ if Au == 0:
92
+ # Alle Einträge in u sind fixiert: kein Beitrag zur Optimierung
93
+ return 0.0
94
+ exp = 1.0 / (1.0 + Au)
95
+ return Bu ** exp * (lmbda * Au) ** (-Au * exp)
96
+
97
+ def fun_lmbda(log_lmbda):
98
+ lmbda = math.exp(log_lmbda)
99
+ return sum(fun_lmbda_u(lmbda, u) for u in us) - (B - 1)
100
+
101
+ log_lmbda = bisect(fun_lmbda, -100, 100)
102
+ lmbda = math.exp(log_lmbda)
103
+
104
+ # Optimale Bandbreiten berechnen
65
105
  for u in us:
106
+ Au = A_u(u)
107
+ Bu = B_u(u)
108
+ if Au == 0:
109
+ continue # alle fixiert, bw[u] bereits gesetzt
66
110
  sizeIu = fun_lmbda_u(lmbda, u)
67
111
  for j in range(len(u)):
68
- if not math.isnan(t[u][j]):
69
- bwuj = ((2 * t[u][j] * D[u][j]) / (lmbda * sizeIu)) ** (
112
+ if not fix[u][j]:
113
+ bwuj = (D[u][j] / (lmbda * Bu * Au) ** (1.0 / (1.0 + Au))) ** (
70
114
  0.5 / t[u][j]
71
115
  ) + 1
72
- bw[u][j] = 2 * round(0.5 * bwuj)
116
+ bw[u][j] = max(6, 2 * round(0.5 * bwuj))
73
117
 
74
- return bw
118
+ return bw, fix
75
119
 
76
120
 
77
121
  def fitrate_log(y):
@@ -155,8 +199,8 @@ def estimate_rates(self, lam, settingnr=None, verbosity=0):
155
199
  )
156
200
 
157
201
  if verbosity > 5:
158
-
159
- y = np.cumsum(axissum)[::-1]
202
+
203
+ y = np.cumsum(axissum[::-1])[::-1]
160
204
  ax.plot(
161
205
  range(1, len(y) + 1),
162
206
  y,
@@ -164,15 +208,14 @@ def estimate_rates(self, lam, settingnr=None, verbosity=0):
164
208
  marker="o",
165
209
  # color=j
166
210
  )
167
-
168
211
  if (idx is None) or idx >= len(axissum):
169
212
  D[u][j] = math.nan
170
213
  t[u][j] = math.nan
171
214
  else:
172
215
  idx = min(len(axissum), len(axissum) - idx + 2)
173
- Duj, tuj = fitrate_log((np.cumsum(axissum)[::-1])[0:idx])
174
-
175
- if tuj < 0.004:
216
+ Duj, tuj = fitrate_log((np.cumsum(axissum[::-1])[::-1])[0:idx])
217
+ #print(u,j,tuj)
218
+ if abs(tuj) < 0.004:
176
219
  D[u][j] = math.nan
177
220
  t[u][j] = math.nan
178
221
  else:
@@ -191,11 +234,16 @@ def estimate_rates(self, lam, settingnr=None, verbosity=0):
191
234
  # plt.figure(figsize=(12, 9))
192
235
  # for ax in ps:
193
236
  # plt.sca(ax)
237
+ time = ""
238
+ if verbosity > 8:
239
+ from datetime import datetime
240
+
241
+ time = str(round(datetime.timestamp(datetime.now())))
194
242
  fig.savefig(
195
243
  os.path.join(
196
244
  "log",
197
245
  "figures",
198
- str(num).strip() + "_rates_" + str(u).strip() + ".png",
246
+ time + "_" + str(num).strip() + "_rates_" + str(u).strip() + ".png",
199
247
  )
200
248
  )
201
249
  num = num + 1
File without changes
File without changes
File without changes