hdim-opt 1.3.0__tar.gz → 1.3.2__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.
- {hdim_opt-1.3.0 → hdim_opt-1.3.2}/PKG-INFO +23 -8
- {hdim_opt-1.3.0 → hdim_opt-1.3.2}/README.md +6 -5
- {hdim_opt-1.3.0 → hdim_opt-1.3.2}/hdim_opt/__init__.py +10 -9
- {hdim_opt-1.3.0 → hdim_opt-1.3.2}/hdim_opt/hyperellipsoid_sampling.py +18 -20
- {hdim_opt-1.3.0 → hdim_opt-1.3.2}/hdim_opt/quasar_helpers.py +22 -25
- {hdim_opt-1.3.0 → hdim_opt-1.3.2}/hdim_opt/quasar_optimization.py +54 -52
- hdim_opt-1.3.2/hdim_opt/sobol_sensitivity.py +185 -0
- {hdim_opt-1.3.0 → hdim_opt-1.3.2}/hdim_opt/waveform_analysis.py +4 -1
- {hdim_opt-1.3.0 → hdim_opt-1.3.2}/hdim_opt.egg-info/PKG-INFO +23 -8
- {hdim_opt-1.3.0 → hdim_opt-1.3.2}/hdim_opt.egg-info/SOURCES.txt +0 -1
- hdim_opt-1.3.2/pyproject.toml +76 -0
- hdim_opt-1.3.0/hdim_opt/sobol_sampling.py +0 -29
- hdim_opt-1.3.0/hdim_opt/sobol_sensitivity.py +0 -125
- hdim_opt-1.3.0/pyproject.toml +0 -51
- {hdim_opt-1.3.0 → hdim_opt-1.3.2}/hdim_opt/test_functions.py +0 -0
- {hdim_opt-1.3.0 → hdim_opt-1.3.2}/hdim_opt.egg-info/dependency_links.txt +0 -0
- {hdim_opt-1.3.0 → hdim_opt-1.3.2}/hdim_opt.egg-info/requires.txt +0 -0
- {hdim_opt-1.3.0 → hdim_opt-1.3.2}/hdim_opt.egg-info/top_level.txt +0 -0
- {hdim_opt-1.3.0 → hdim_opt-1.3.2}/setup.cfg +0 -0
|
@@ -1,19 +1,33 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hdim_opt
|
|
3
|
-
Version: 1.3.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 1.3.2
|
|
4
|
+
Summary: High-dimensional numerical optimization and sampling toolkit for complex, non-differentiable problems.
|
|
5
5
|
Author-email: Julian Soltes <jsoltes@regis.edu>
|
|
6
6
|
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/jgsoltes/hdim_opt
|
|
7
8
|
Project-URL: Repository, https://github.com/jgsoltes/hdim_opt
|
|
8
|
-
|
|
9
|
+
Project-URL: Issues, https://github.com/jgsoltes/hdim_opt/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/jgsoltes/hdim_opt/releases
|
|
11
|
+
Keywords: optimization,high-dimensional,sampling,QUASAR,hyperellipsoid,evolutionary-algorithm,non-differentiable,global-optimization,stochastic-optimization,black-box-optimization
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
19
|
Classifier: License :: OSI Approved :: MIT License
|
|
11
20
|
Classifier: Operating System :: OS Independent
|
|
12
21
|
Classifier: Intended Audience :: Science/Research
|
|
13
22
|
Classifier: Intended Audience :: Developers
|
|
23
|
+
Classifier: Intended Audience :: Education
|
|
24
|
+
Classifier: Natural Language :: English
|
|
14
25
|
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
15
26
|
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
16
27
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
28
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
29
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
30
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
17
31
|
Requires-Python: >=3.8
|
|
18
32
|
Description-Content-Type: text/markdown
|
|
19
33
|
Requires-Dist: numpy
|
|
@@ -28,13 +42,14 @@ Requires-Dist: SALib; extra == "sensitivity"
|
|
|
28
42
|
|
|
29
43
|
# hdim-opt: High-Dimensional Optimization Toolkit
|
|
30
44
|
|
|
31
|
-
|
|
45
|
+
Modern optimization package to accelerate convergence in complex, high-dimensional problems. Includes the QUASAR evolutionary algorithm, HDS exploitative QMC sampler, Sobol sensitivity analysis, signal waveform decomposition, and data transformations.
|
|
32
46
|
|
|
33
47
|
All core functions, listed below, are single-line executable and require three essential parameters: [obj_function, bounds, n_samples].
|
|
34
48
|
* **quasar**: QUASAR optimization for high-dimensional, non-differentiable problems.
|
|
35
|
-
* **
|
|
49
|
+
* **hyperellipsoid**: Generate a non-uniform Hyperellipsoid Density sequence, to focus sample distributions.
|
|
36
50
|
* **sobol**: Generate a uniform Sobol sequence (via SciPy).
|
|
37
51
|
* **sensitivity**: Perform Sobol sensitivity analysis to measure each variable's importance on objective function results (via SALib).
|
|
52
|
+
* **isotropize**: Isotropizes the input matrix.
|
|
38
53
|
* **waveform**: Decompose the input waveform array (handles time- and frequency-domain via FFT / IFFT) into a diagnostic summary.
|
|
39
54
|
|
|
40
55
|
---
|
|
@@ -63,7 +78,7 @@ time, pulse = h.waveform_analysis.e1_waveform()
|
|
|
63
78
|
solution, fitness = h.quasar(obj_func, bounds)
|
|
64
79
|
sens_matrix = h.sensitivity(obj_func, bounds)
|
|
65
80
|
|
|
66
|
-
hds_samples = h.
|
|
81
|
+
hds_samples = h.hyperellipsoid(n_samples, bounds)
|
|
67
82
|
sobol_samples = h.sobol(n_samples, bounds)
|
|
68
83
|
isotropic_samples = h.isotropize(sobol_samples)
|
|
69
84
|
|
|
@@ -75,7 +90,7 @@ signal_data = h.waveform(x=time,y=pulse)
|
|
|
75
90
|
|
|
76
91
|
* Benefit: Significant improvements in convergence speed and solution quality compared to contemporary optimizers. (Reference: [https://arxiv.org/abs/2511.13843]).
|
|
77
92
|
|
|
78
|
-
## HDS Sampler
|
|
79
|
-
**HDS** is a non-uniform Quasi-Monte Carlo sampling method, specifically designed to exploit promising regions of the search space.
|
|
93
|
+
## HDS Sampler
|
|
94
|
+
**HDS** (Hyperellipsoid Density Sampling) is a non-uniform Quasi-Monte Carlo sampling method, specifically designed to exploit promising regions of the search space.
|
|
80
95
|
|
|
81
96
|
* Benefit: Provides control over the sample distribution. Results in higher average optimization solution quality when used for population initialization compared to uniform QMC methods. (Reference: [https://arxiv.org/abs/2511.07836]).
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# hdim-opt: High-Dimensional Optimization Toolkit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Modern optimization package to accelerate convergence in complex, high-dimensional problems. Includes the QUASAR evolutionary algorithm, HDS exploitative QMC sampler, Sobol sensitivity analysis, signal waveform decomposition, and data transformations.
|
|
4
4
|
|
|
5
5
|
All core functions, listed below, are single-line executable and require three essential parameters: [obj_function, bounds, n_samples].
|
|
6
6
|
* **quasar**: QUASAR optimization for high-dimensional, non-differentiable problems.
|
|
7
|
-
* **
|
|
7
|
+
* **hyperellipsoid**: Generate a non-uniform Hyperellipsoid Density sequence, to focus sample distributions.
|
|
8
8
|
* **sobol**: Generate a uniform Sobol sequence (via SciPy).
|
|
9
9
|
* **sensitivity**: Perform Sobol sensitivity analysis to measure each variable's importance on objective function results (via SALib).
|
|
10
|
+
* **isotropize**: Isotropizes the input matrix.
|
|
10
11
|
* **waveform**: Decompose the input waveform array (handles time- and frequency-domain via FFT / IFFT) into a diagnostic summary.
|
|
11
12
|
|
|
12
13
|
---
|
|
@@ -35,7 +36,7 @@ time, pulse = h.waveform_analysis.e1_waveform()
|
|
|
35
36
|
solution, fitness = h.quasar(obj_func, bounds)
|
|
36
37
|
sens_matrix = h.sensitivity(obj_func, bounds)
|
|
37
38
|
|
|
38
|
-
hds_samples = h.
|
|
39
|
+
hds_samples = h.hyperellipsoid(n_samples, bounds)
|
|
39
40
|
sobol_samples = h.sobol(n_samples, bounds)
|
|
40
41
|
isotropic_samples = h.isotropize(sobol_samples)
|
|
41
42
|
|
|
@@ -47,7 +48,7 @@ signal_data = h.waveform(x=time,y=pulse)
|
|
|
47
48
|
|
|
48
49
|
* Benefit: Significant improvements in convergence speed and solution quality compared to contemporary optimizers. (Reference: [https://arxiv.org/abs/2511.13843]).
|
|
49
50
|
|
|
50
|
-
## HDS Sampler
|
|
51
|
-
**HDS** is a non-uniform Quasi-Monte Carlo sampling method, specifically designed to exploit promising regions of the search space.
|
|
51
|
+
## HDS Sampler
|
|
52
|
+
**HDS** (Hyperellipsoid Density Sampling) is a non-uniform Quasi-Monte Carlo sampling method, specifically designed to exploit promising regions of the search space.
|
|
52
53
|
|
|
53
54
|
* Benefit: Provides control over the sample distribution. Results in higher average optimization solution quality when used for population initialization compared to uniform QMC methods. (Reference: [https://arxiv.org/abs/2511.07836]).
|
|
@@ -4,14 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
Functions:
|
|
6
6
|
- quasar: QUASAR optimization for high-dimensional, non-differentiable problems.
|
|
7
|
-
-
|
|
7
|
+
- hyperellipsoid: Generate a non-uniform hyperellipsoid density sequence.
|
|
8
8
|
- sobol: Generate a uniform Sobol sequence (via SciPy).
|
|
9
9
|
- sensitivity: Perform Sobol sensitivity analysis to measure each variable's importance on objective function results (via SALib).
|
|
10
|
+
- isotropize/deisotropize: Isotropize the input matrix using ZCA.
|
|
10
11
|
- waveform: Decompose the input waveform array (handles time- and frequency-domain via FFT / IFFT) into a diagnostic summary.
|
|
11
12
|
|
|
12
13
|
Modules:
|
|
13
14
|
- test_functions: Contains test functions for local optimization testing.
|
|
14
|
-
- waveform_analysis: Contains pulse generation functions.
|
|
15
|
+
- waveform_analysis: Contains pulse signal generation functions.
|
|
15
16
|
|
|
16
17
|
Example Usage:
|
|
17
18
|
|
|
@@ -29,7 +30,7 @@ Example Usage:
|
|
|
29
30
|
>>> solution, fitness = h.quasar(obj_func, bounds)
|
|
30
31
|
>>> sens_matrix = h.sensitivity(obj_func, bounds)
|
|
31
32
|
|
|
32
|
-
>>> hds_samples = h.
|
|
33
|
+
>>> hds_samples = h.hyperellipsoid(n_samples, bounds)
|
|
33
34
|
>>> sobol_samples = h.sobol(n_samples, bounds)
|
|
34
35
|
>>> isotropic_samples = h.isotropize(sobol_samples)
|
|
35
36
|
|
|
@@ -37,17 +38,17 @@ Example Usage:
|
|
|
37
38
|
"""
|
|
38
39
|
|
|
39
40
|
# package version
|
|
40
|
-
__version__ = "1.3.
|
|
41
|
-
__all__ = ['quasar',
|
|
41
|
+
__version__ = "1.3.2"
|
|
42
|
+
__all__ = ['quasar','hyperellipsoid','sensitivity','waveform','sobol','isotropize','deisotropize',
|
|
43
|
+
'test_functions','quasar_helpers','waveform_analysis'] # available for star imports
|
|
42
44
|
|
|
43
45
|
# import core components
|
|
44
46
|
from .quasar_optimization import optimize as quasar
|
|
45
|
-
from .hyperellipsoid_sampling import sample as
|
|
46
|
-
from .
|
|
47
|
+
from .hyperellipsoid_sampling import sample as hyperellipsoid
|
|
48
|
+
from .sobol_sensitivity import sobol_sample as sobol
|
|
47
49
|
from .sobol_sensitivity import sens_analysis as sensitivity
|
|
50
|
+
from .quasar_helpers import isotropize, deisotropize
|
|
48
51
|
from .waveform_analysis import analyze_waveform as waveform
|
|
49
|
-
from .quasar_helpers import isotropize
|
|
50
|
-
from .quasar_helpers import deisotropize
|
|
51
52
|
from . import test_functions
|
|
52
53
|
from . import quasar_helpers
|
|
53
54
|
from . import waveform_analysis
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
# global
|
|
2
|
-
epsilon = 1e-
|
|
1
|
+
# global epsilon
|
|
2
|
+
epsilon = 1e-16
|
|
3
|
+
import numpy as np
|
|
3
4
|
|
|
4
5
|
### misc helper functions
|
|
5
6
|
|
|
6
|
-
def sample_hypersphere(n_dimensions, radius,
|
|
7
|
+
def sample_hypersphere(n_dimensions, radius,
|
|
8
|
+
n_samples_in_sphere, radius_qmc_sequence):
|
|
7
9
|
'''
|
|
8
10
|
Objective:
|
|
9
11
|
- Samples unit hyperspheres using Marsaglia polar vectors scaled by a QMC sequence.
|
|
10
12
|
'''
|
|
11
|
-
import numpy as np
|
|
12
13
|
|
|
13
14
|
# generate normal distribution (for angular direction)
|
|
14
15
|
samples = np.random.normal(size=(n_samples_in_sphere, n_dimensions))
|
|
@@ -28,14 +29,15 @@ def sample_hypersphere(n_dimensions, radius, n_samples_in_sphere, radius_qmc_seq
|
|
|
28
29
|
|
|
29
30
|
return samples
|
|
30
31
|
|
|
31
|
-
def sample_hyperellipsoid(n_dimensions, n_samples_in_ellipsoid,
|
|
32
|
+
def sample_hyperellipsoid(n_dimensions, n_samples_in_ellipsoid,
|
|
33
|
+
origin, pca_components, pca_variances,
|
|
34
|
+
scaling_factor, radius_qmc_sequence=None):
|
|
32
35
|
'''
|
|
33
36
|
Objective:
|
|
34
37
|
- Generates samples inside the hyperellipsoid.
|
|
35
38
|
- Calls the function to sample unit hyperspheres.
|
|
36
39
|
- Transforms the hyperspherical samples to the ellipsoid axes defined using the PCA variances.
|
|
37
40
|
'''
|
|
38
|
-
import numpy as np
|
|
39
41
|
|
|
40
42
|
# generate samples in unit hypersphere
|
|
41
43
|
unit_sphere_samples = sample_hypersphere(n_dimensions, 1.0, n_samples_in_ellipsoid, radius_qmc_sequence)
|
|
@@ -61,7 +63,6 @@ def sample_in_voids(existing_samples, n_to_fill, bounds_min, bounds_max,
|
|
|
61
63
|
'''
|
|
62
64
|
from sklearn.neighbors import BallTree
|
|
63
65
|
from sklearn.random_projection import GaussianRandomProjection
|
|
64
|
-
import numpy as np
|
|
65
66
|
from scipy import stats
|
|
66
67
|
import time
|
|
67
68
|
|
|
@@ -146,14 +147,14 @@ def sample_in_voids(existing_samples, n_to_fill, bounds_min, bounds_max,
|
|
|
146
147
|
|
|
147
148
|
return new_samples
|
|
148
149
|
|
|
149
|
-
def fit_pca_for_cluster(cluster_samples, current_origin,
|
|
150
|
+
def fit_pca_for_cluster(cluster_samples, current_origin,
|
|
151
|
+
initial_samples_std, n_dimensions):
|
|
150
152
|
'''
|
|
151
153
|
Performs PCA on a single cluster's samples or returns a default,
|
|
152
154
|
called in parallel.
|
|
153
155
|
'''
|
|
154
156
|
|
|
155
157
|
from sklearn.decomposition import PCA
|
|
156
|
-
import numpy as np
|
|
157
158
|
|
|
158
159
|
# extract shape
|
|
159
160
|
n_cluster_samples = len(cluster_samples)
|
|
@@ -174,11 +175,12 @@ def fit_pca_for_cluster(cluster_samples, current_origin, initial_samples_std, n_
|
|
|
174
175
|
'variances': fixed_variance}
|
|
175
176
|
|
|
176
177
|
|
|
177
|
-
|
|
178
|
+
### main sampling function
|
|
178
179
|
|
|
179
180
|
def sample(n_samples, bounds,
|
|
180
181
|
weights=None, normalize=False,
|
|
181
182
|
n_ellipsoids=None, n_initial_clusters=None, n_initial_qmc=None,
|
|
183
|
+
ellipsoid_scaling_factor=None,
|
|
182
184
|
seed=None, plot_dendrogram=False, verbose=False):
|
|
183
185
|
'''
|
|
184
186
|
Objective:
|
|
@@ -208,7 +210,6 @@ def sample(n_samples, bounds,
|
|
|
208
210
|
|
|
209
211
|
# imports
|
|
210
212
|
try:
|
|
211
|
-
import numpy as np
|
|
212
213
|
import pandas as pd
|
|
213
214
|
from scipy import stats
|
|
214
215
|
from joblib import Parallel, delayed
|
|
@@ -367,11 +368,12 @@ def sample(n_samples, bounds,
|
|
|
367
368
|
confidence_level = 0.9999 # captures 99.99% of cluster's samples
|
|
368
369
|
|
|
369
370
|
# critical value (the statistical radius squared)
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
371
|
+
if ellipsoid_scaling_factor == None:
|
|
372
|
+
chi2_critical_value = stats.chi2.ppf(confidence_level, df=n_dimensions)
|
|
373
|
+
baseline_factor = 0.55 - 0.01*np.log(n_dimensions) # empirically derived to resample out-of-bounds points
|
|
374
|
+
|
|
375
|
+
# square root as the scaling factor (Mahalanobis distance)
|
|
376
|
+
ellipsoid_scaling_factor = baseline_factor * np.sqrt(chi2_critical_value)
|
|
375
377
|
|
|
376
378
|
# QMC sequence for radius scaling
|
|
377
379
|
radius_qmc_sampler = stats.qmc.Sobol(d=1, seed=seed+1) # offset seed from initial qmc
|
|
@@ -522,7 +524,6 @@ def sample(n_samples, bounds,
|
|
|
522
524
|
|
|
523
525
|
|
|
524
526
|
### PCA for n_dim > 2:
|
|
525
|
-
|
|
526
527
|
if normalize:
|
|
527
528
|
data_to_plot_raw = initial_samples
|
|
528
529
|
else:
|
|
@@ -546,8 +547,6 @@ def sample(n_samples, bounds,
|
|
|
546
547
|
xlabel_str = f'Dimension 0'
|
|
547
548
|
ylabel_str = f'Dimension 1'
|
|
548
549
|
|
|
549
|
-
# dark visualization parameters for better sample visuals
|
|
550
|
-
|
|
551
550
|
# samples
|
|
552
551
|
fig, ax = plt.subplots(1,2,figsize=(9,5))
|
|
553
552
|
|
|
@@ -566,7 +565,6 @@ def sample(n_samples, bounds,
|
|
|
566
565
|
ax[0].set_title(title_str, fontsize=14)
|
|
567
566
|
ax[0].set_xlabel(xlabel_str)
|
|
568
567
|
ax[0].set_ylabel(ylabel_str)
|
|
569
|
-
# ax[0].axis(False)
|
|
570
568
|
ax[0].legend(loc=(0.7,0.87), fontsize=8)
|
|
571
569
|
|
|
572
570
|
# plot histograms
|
|
@@ -1,56 +1,53 @@
|
|
|
1
1
|
# global imports
|
|
2
|
-
import pandas as pd
|
|
3
|
-
from sklearn.preprocessing import StandardScaler
|
|
4
2
|
import numpy as np
|
|
5
|
-
|
|
6
|
-
epsilon = 1e-12
|
|
3
|
+
epsilon = 1e-16
|
|
7
4
|
|
|
8
5
|
def isotropize(data):
|
|
9
6
|
'''
|
|
10
7
|
Objective:
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
- data: Input data.
|
|
15
|
-
Outputs:
|
|
16
|
-
- data_isotropic: Isotropized data.
|
|
17
|
-
- metadata: Scaler and whitening matrix
|
|
8
|
+
- Isotropizes the input matrix using Zero-Phase Component Analysis (ZCA).
|
|
9
|
+
- Maintains original parameter orientation while removing correlations.
|
|
10
|
+
- 'deisotropize' function inverse transforms to the original parameter space.
|
|
18
11
|
'''
|
|
19
|
-
|
|
12
|
+
from scipy.linalg import eigh
|
|
20
13
|
# convert to array
|
|
21
14
|
X = np.array(data)
|
|
22
15
|
|
|
23
16
|
# standard scaling (mean = 0, var = 1)
|
|
24
17
|
mean = np.mean(X, axis=0)
|
|
25
|
-
stdev = np.std(X, axis=0)
|
|
18
|
+
stdev = np.std(X, axis=0) + epsilon # add epsilon to avoid div0
|
|
26
19
|
X_centered = (X - mean) / stdev
|
|
27
20
|
|
|
28
|
-
#
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
L = cholesky(cov, lower=True)
|
|
21
|
+
# eigen-decomposition of the correlation matrix
|
|
22
|
+
cov = np.cov(X_centered, rowvar=False) + np.eye(X_centered.shape[1]) * epsilon
|
|
23
|
+
eigenvalues, eigenvectors = eigh(cov) # eigh is more stable for symmetric matrices like covariance
|
|
32
24
|
|
|
33
|
-
#
|
|
34
|
-
|
|
25
|
+
# ZCA whitening matrix: W_zca = U @ diag(1/sqrt(lambda)) @ U.T
|
|
26
|
+
diag_inv_sqrt = np.diag(1.0 / np.sqrt(np.maximum(eigenvalues, epsilon))) # use maximum to avoid div0
|
|
27
|
+
W_zca = eigenvectors @ diag_inv_sqrt @ eigenvectors.T # whitening matrix
|
|
28
|
+
W_zca_inv = (eigenvectors * np.sqrt(np.maximum(eigenvalues, epsilon))) @ eigenvectors.T # save for deisotropization
|
|
29
|
+
|
|
30
|
+
# transform: y = X_centered @ W_zca.T
|
|
31
|
+
data_iso = np.dot(X_centered, W_zca) # no .T needed because W_zca is symmetric
|
|
35
32
|
|
|
36
33
|
# store parameters for deisotropization
|
|
37
34
|
params = {
|
|
38
35
|
'mean': mean,
|
|
39
36
|
'stdev': stdev,
|
|
40
|
-
'
|
|
37
|
+
'W_zca_inv': W_zca_inv
|
|
41
38
|
}
|
|
42
39
|
return data_iso, params
|
|
43
40
|
|
|
44
41
|
def deisotropize(data_iso, params):
|
|
45
|
-
'''De-isotropize data to its original parameter space.'''
|
|
42
|
+
'''De-isotropize data to its original parameter space via inverse ZCA.'''
|
|
46
43
|
|
|
47
|
-
#
|
|
48
|
-
data_centered = np.dot(data_iso, params['
|
|
44
|
+
# inverse ZCA: X_centered = y @ W_zca_inv.T
|
|
45
|
+
data_centered = np.dot(data_iso, params['W_zca_inv'].T)
|
|
49
46
|
|
|
50
|
-
#
|
|
47
|
+
# inverse scaling: X = (X_centered * std) + mean
|
|
51
48
|
data_original = (data_centered * params['stdev']) + params['mean']
|
|
52
49
|
return data_original
|
|
53
|
-
|
|
50
|
+
|
|
54
51
|
############## CONSTRAINTS ##############
|
|
55
52
|
def apply_penalty(fitnesses, solutions, constraints, constraint_penalty, vectorized):
|
|
56
53
|
'''
|
|
@@ -164,7 +164,6 @@ def evolve_generation(obj_function, population, fitnesses, best_solution,
|
|
|
164
164
|
|
|
165
165
|
# greedy elitism selection
|
|
166
166
|
selection_indices = trial_fitnesses < fitnesses
|
|
167
|
-
|
|
168
167
|
new_population = np.where(selection_indices[np.newaxis, :], trial_vectors, population)
|
|
169
168
|
new_fitnesses = np.where(selection_indices, trial_fitnesses, fitnesses)
|
|
170
169
|
|
|
@@ -263,7 +262,7 @@ def evolve_generation(obj_function, population, fitnesses, best_solution,
|
|
|
263
262
|
|
|
264
263
|
return new_population, new_fitnesses
|
|
265
264
|
|
|
266
|
-
def asym_reinit(population, current_fitnesses, bounds, reinit_method, seed, vectorized):
|
|
265
|
+
def asym_reinit(population, current_fitnesses, bounds, reinit_method, seed, generation, vectorized):
|
|
267
266
|
'''
|
|
268
267
|
Objective:
|
|
269
268
|
- Reinitializes the worst 33% solutions in the population.
|
|
@@ -340,7 +339,7 @@ def asym_reinit(population, current_fitnesses, bounds, reinit_method, seed, vect
|
|
|
340
339
|
elif reinit_method == 'sobol':
|
|
341
340
|
|
|
342
341
|
# generate sobol samples
|
|
343
|
-
sobol_sampler = stats.qmc.Sobol(d=dimensions, seed=seed)
|
|
342
|
+
sobol_sampler = stats.qmc.Sobol(d=dimensions, seed=seed+generation)
|
|
344
343
|
sobol_samples_unit = sobol_sampler.random(n=num_to_replace)
|
|
345
344
|
|
|
346
345
|
bounds_low = bounds[:, 0]
|
|
@@ -367,14 +366,13 @@ def asym_reinit(population, current_fitnesses, bounds, reinit_method, seed, vect
|
|
|
367
366
|
|
|
368
367
|
|
|
369
368
|
# main optimize function
|
|
370
|
-
|
|
371
369
|
def optimize(func, bounds, args=(),
|
|
372
370
|
init='sobol', popsize=None, maxiter=100,
|
|
373
371
|
entangle_rate=0.33, polish=True, polish_minimizer=None,
|
|
374
|
-
patience=np.inf, vectorized=False,
|
|
375
|
-
|
|
372
|
+
patience=np.inf, tolerance=-np.inf, vectorized=False,
|
|
373
|
+
kwargs={},
|
|
376
374
|
constraints=None, constraint_penalty=1e9,
|
|
377
|
-
|
|
375
|
+
reinitialization='covariance', hds_weights=None,
|
|
378
376
|
verbose=True, plot_solutions=True, num_to_plot=10, plot_contour=True,
|
|
379
377
|
workers=1, seed=None
|
|
380
378
|
):
|
|
@@ -382,7 +380,6 @@ def optimize(func, bounds, args=(),
|
|
|
382
380
|
Objective:
|
|
383
381
|
- Finds the optimal solution for a given objective function.
|
|
384
382
|
- Designed for non-differentiable, high-dimensional problems.
|
|
385
|
-
- For explorative problems chance reinitialization_method to '
|
|
386
383
|
- Test functions available for local testing, called as hdim_opt.test_functions.function_name.
|
|
387
384
|
- Existing test functions: [rastrigin, ackley, sinusoid, sphere, shubert].
|
|
388
385
|
|
|
@@ -393,31 +390,30 @@ def optimize(func, bounds, args=(),
|
|
|
393
390
|
- kwargs: Dictionary of keyword arguments for the objective function.
|
|
394
391
|
|
|
395
392
|
- init: Initial population sampling method.
|
|
396
|
-
- Defaults to 'sobol'
|
|
393
|
+
- Defaults to 'sobol' (recommended power-of-2 population sizes for maximum uniformity).
|
|
397
394
|
- Existing options are:
|
|
398
|
-
- 'sobol': Sobol (
|
|
399
|
-
- 'hds': Hyperellipsoid
|
|
400
|
-
- 'lhs': Latin Hypercube (uniform
|
|
401
|
-
- 'random': Random sampling (uniform).
|
|
395
|
+
- 'sobol': Sobol (spatially uniform; power of 2 population sizes recommended).
|
|
396
|
+
- 'hds': Hyperellipsoid (non-uniform; density weights 'hds_weights' recommended).
|
|
397
|
+
- 'lhs': Latin Hypercube (uniform across each dimension).
|
|
398
|
+
- 'random': Random sampling (quasi-uniform).
|
|
402
399
|
- custom population (N x D matrix).
|
|
403
|
-
- popsize: Number of solution vectors to evolve (default 10 * n_dimensions).
|
|
404
|
-
- Recommended to be a power of 2 for Sobol initialization.
|
|
400
|
+
- popsize: Number of solution vectors to evolve (default is next power of 2 of 10 * n_dimensions).
|
|
405
401
|
- maxiter: Number of generations to evolve (default 100).
|
|
406
402
|
|
|
407
403
|
- entangle_rate: Probability of solutions using the local Spooky-Best mutation strategy.
|
|
408
|
-
- Defaults to 0.33
|
|
404
|
+
- Defaults to 0.33; each mutation strategy is applied equally.
|
|
409
405
|
- Higher implies more exploitation.
|
|
406
|
+
|
|
410
407
|
- polish: Boolean to implement final polishing step, using SciPy.optimize.minimize.
|
|
411
|
-
- polish_minimizer:
|
|
408
|
+
- polish_minimizer: Name of Scipy minimization function to polish with.
|
|
412
409
|
- Defaults to 'Powell' minimization, or 'SLSQP' if 'constraints' parameter is provided.
|
|
413
|
-
-
|
|
410
|
+
- Accepts all minimizers ('L-BFGS-B', ...).
|
|
414
411
|
|
|
415
412
|
- patience: Number of generations without improvement before early convergence.
|
|
413
|
+
- tolerance: Target objective function value for early convergence.
|
|
416
414
|
- vectorized: Boolean to accept vectorized (N,D) objective functions
|
|
417
|
-
-
|
|
415
|
+
- Highly efficient, recommended when possible.
|
|
418
416
|
|
|
419
|
-
- hds_weights: Optional weights for hyperellipsoid density sampling initialization.
|
|
420
|
-
- {0 : {'center' : center, 'std': stdev}, 1: {...} }
|
|
421
417
|
- kwargs: Dictionary of keyword arguments for the objective function.
|
|
422
418
|
|
|
423
419
|
- constraints: Dictionary of constraints to penalize.
|
|
@@ -433,11 +429,13 @@ def optimize(func, bounds, args=(),
|
|
|
433
429
|
}
|
|
434
430
|
- constraint_penalty: Penalty applied to each constraint violated, defaults to 1e12.
|
|
435
431
|
|
|
436
|
-
-
|
|
432
|
+
- reinitialization: Type of re-sampling to use in the asymptotic reinitialization.
|
|
437
433
|
- Options are ['covariance', 'sobol'].
|
|
438
|
-
- 'covariance' (exploitative) is default for
|
|
434
|
+
- 'covariance' (exploitative) is default for N > D problems.
|
|
439
435
|
- 'sobol' (explorative) is optional, for high exploration and faster computation.
|
|
440
436
|
- None to disable reinitialization calculations.
|
|
437
|
+
- hds_weights: Optional weights for hyperellipsoid density sampling initialization.
|
|
438
|
+
- {0 : {'center' : center, 'std': stdev}, 1: {...} }
|
|
441
439
|
|
|
442
440
|
- verbose: Displays prints and plots.
|
|
443
441
|
- Mutation factor distribution shown with hdim_opt.test_functions.plot_mutations()
|
|
@@ -446,7 +444,7 @@ def optimize(func, bounds, args=(),
|
|
|
446
444
|
|
|
447
445
|
- workers: Number of workers / jobs / cores to use.
|
|
448
446
|
- Default is 1. Set to -1 to use all available.
|
|
449
|
-
- If workers != 1, constraint & objective functions must be imported from external module
|
|
447
|
+
- If workers != 1, constraint & objective functions must be imported from external module for pickling.
|
|
450
448
|
- seed: Random seed for deterministic & reproducible results.
|
|
451
449
|
|
|
452
450
|
Outputs:
|
|
@@ -499,10 +497,7 @@ def optimize(func, bounds, args=(),
|
|
|
499
497
|
# ensure bounds is array; shape (n_dimensions,2)
|
|
500
498
|
bounds = np.array(bounds)
|
|
501
499
|
n_dimensions = bounds.shape[0]
|
|
502
|
-
|
|
503
|
-
if n_dimensions == 1:
|
|
504
|
-
reinitialization = False
|
|
505
|
-
|
|
500
|
+
|
|
506
501
|
# if init is not a string, assume it is a custom population
|
|
507
502
|
if not isinstance(init, str):
|
|
508
503
|
popsize = init.shape[0]
|
|
@@ -516,7 +511,11 @@ def optimize(func, bounds, args=(),
|
|
|
516
511
|
# ensure integers
|
|
517
512
|
popsize, maxiter = int(popsize), int(maxiter)
|
|
518
513
|
|
|
514
|
+
# map to sobol if N < D
|
|
515
|
+
if n_dimensions == 1:
|
|
516
|
+
reinitialization = None
|
|
519
517
|
|
|
518
|
+
|
|
520
519
|
################################# INPUT ERRORS #################################
|
|
521
520
|
|
|
522
521
|
# entangle rate error
|
|
@@ -528,8 +527,8 @@ def optimize(func, bounds, args=(),
|
|
|
528
527
|
raise ValueError("Initial sampler must be one of ['sobol','random','hds','lhs'], or a custom population.")
|
|
529
528
|
|
|
530
529
|
# patience error
|
|
531
|
-
if patience
|
|
532
|
-
raise ValueError('Patience must be >
|
|
530
|
+
if patience < 1:
|
|
531
|
+
raise ValueError('Patience must be > 0 generations.')
|
|
533
532
|
|
|
534
533
|
|
|
535
534
|
################################# INITIAL POPULATION #################################
|
|
@@ -537,11 +536,11 @@ def optimize(func, bounds, args=(),
|
|
|
537
536
|
# generate initial population
|
|
538
537
|
initial_population = initialize_population(popsize, bounds, init, hds_weights, seed, verbose)
|
|
539
538
|
if verbose:
|
|
540
|
-
if
|
|
541
|
-
print("
|
|
539
|
+
if reinitialization not in ['sobol', 'covariance', None]:
|
|
540
|
+
print("reinitialization must be one of ['covariance', 'sobol', None].")
|
|
542
541
|
print(f'\nEvolving (None):')
|
|
543
542
|
else:
|
|
544
|
-
print(f'\nEvolving ({
|
|
543
|
+
print(f'\nEvolving ({reinitialization}):')
|
|
545
544
|
|
|
546
545
|
# match differential evolution conventions
|
|
547
546
|
if vectorized:
|
|
@@ -648,12 +647,12 @@ def optimize(func, bounds, args=(),
|
|
|
648
647
|
# apply asymptotic covariance reinitialization to population
|
|
649
648
|
final_proba = 0.33
|
|
650
649
|
decay_generation = 0.33
|
|
651
|
-
if
|
|
650
|
+
if (reinitialization in ['sobol','covariance']):
|
|
652
651
|
reinit_proba = np.e**((np.log(final_proba)/(decay_generation*maxiter))*generation)
|
|
653
652
|
else:
|
|
654
653
|
reinit_proba = 0.0
|
|
655
654
|
if np.random.rand() < reinit_proba:
|
|
656
|
-
population = asym_reinit(population, current_fitnesses, bounds,
|
|
655
|
+
population = asym_reinit(population, current_fitnesses, bounds, reinitialization, seed, generation, vectorized=vectorized)
|
|
657
656
|
|
|
658
657
|
# clip population to bounds
|
|
659
658
|
if vectorized:
|
|
@@ -663,19 +662,19 @@ def optimize(func, bounds, args=(),
|
|
|
663
662
|
|
|
664
663
|
# add to population history
|
|
665
664
|
if verbose:
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
665
|
+
# determine which solutions to sample
|
|
666
|
+
if popsize <= num_to_plot:
|
|
667
|
+
indices_to_sample = np.arange(popsize)
|
|
668
|
+
else:
|
|
669
|
+
indices_to_sample = np.random.choice(popsize, num_to_plot, replace=False)
|
|
670
|
+
|
|
671
|
+
if vectorized:
|
|
672
|
+
sampled_population = population[:, indices_to_sample].T.copy()
|
|
673
|
+
else:
|
|
674
|
+
sampled_population = population[indices_to_sample].copy()
|
|
675
|
+
|
|
676
|
+
pop_history.append(sampled_population)
|
|
677
|
+
best_history.append(best_solution.copy())
|
|
679
678
|
|
|
680
679
|
# print generation status
|
|
681
680
|
if verbose:
|
|
@@ -683,11 +682,14 @@ def optimize(func, bounds, args=(),
|
|
|
683
682
|
print(f' Gen. {generation+1}/{maxiter} | f(x)={best_fitness:.2e} | stdev={stdev:.2e} | reinit={reinit_proba:.2f}')
|
|
684
683
|
|
|
685
684
|
# patience for early convergence
|
|
686
|
-
if
|
|
685
|
+
if last_improvement_gen >= patience:
|
|
687
686
|
if verbose:
|
|
688
|
-
print(f'
|
|
687
|
+
print(f'Early convergence: patience exceeded ({patience}).')
|
|
688
|
+
break
|
|
689
|
+
if best_fitness <= tolerance:
|
|
690
|
+
if verbose:
|
|
691
|
+
print(f'Early convergence: f(x) below tolerance ({tolerance:.2e}).')
|
|
689
692
|
break
|
|
690
|
-
|
|
691
693
|
|
|
692
694
|
################################# POLISH #################################
|
|
693
695
|
|
|
@@ -703,7 +705,7 @@ def optimize(func, bounds, args=(),
|
|
|
703
705
|
maxiter=maxiter, vectorized=vectorized, constraints=constraints,
|
|
704
706
|
args=args, kwargs=kwargs,
|
|
705
707
|
polish_minimizer=polish_minimizer, verbose=verbose
|
|
706
|
-
|
|
708
|
+
)
|
|
707
709
|
|
|
708
710
|
|
|
709
711
|
################################# VERBOSE #################################
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy import stats
|
|
3
|
+
|
|
4
|
+
### sensitivity analysis
|
|
5
|
+
def sens_analysis(func, bounds, n_samples=2**7,
|
|
6
|
+
args=None, kwargs=None,
|
|
7
|
+
param_names=None, calc_second_order=True,
|
|
8
|
+
log_scale=False, num_to_plot=10, verbose=True):
|
|
9
|
+
'''
|
|
10
|
+
Objective:
|
|
11
|
+
- Perform global Sobol sensitivity analysis on the objective function.
|
|
12
|
+
- Utilizes the SALib package.
|
|
13
|
+
Inputs:
|
|
14
|
+
- func: Objective function (Problem) to analyze.
|
|
15
|
+
- bounds: Parameter space bounds, as an array of tuples.
|
|
16
|
+
- n_samples: Number of Sobol samples to generate.
|
|
17
|
+
- kwargs: Keyword arguments (dictionary) for objective function.
|
|
18
|
+
- param_names: Optional parameter names for each dimension.
|
|
19
|
+
- calc_second_order: Boolean to calculate second-order interactions. Disable to improve computation speed.
|
|
20
|
+
- log_scale: Boolean to log-scale plots.
|
|
21
|
+
- num_to_plot: Number of dimensions to plot.
|
|
22
|
+
- verbose: Boolean to display plots.
|
|
23
|
+
Outputs:
|
|
24
|
+
- Si: Matrix of first- and total-order sensitivity indices and confidences.
|
|
25
|
+
- S2_matrix: Matrix of second-order interactions.
|
|
26
|
+
'''
|
|
27
|
+
|
|
28
|
+
### imports
|
|
29
|
+
try:
|
|
30
|
+
import numpy as np
|
|
31
|
+
from SALib.sample import sobol as sobol_sample
|
|
32
|
+
from SALib.analyze import sobol as sobol_analyze
|
|
33
|
+
import pandas as pd
|
|
34
|
+
import time
|
|
35
|
+
except ImportError as e:
|
|
36
|
+
raise ImportError(f'Sensitivity analysis requires dependencies: (SALib, pandas).') from e
|
|
37
|
+
|
|
38
|
+
### extract inputs
|
|
39
|
+
start_time = time.time()
|
|
40
|
+
bounds = np.array(bounds)
|
|
41
|
+
n_params = bounds.shape[0]
|
|
42
|
+
if param_names == None:
|
|
43
|
+
param_names = range(0,n_params)
|
|
44
|
+
elif len(param_names) != len(bounds):
|
|
45
|
+
raise ValueError('Length of param_names does not match length of bounds.')
|
|
46
|
+
|
|
47
|
+
### define problem for SALib
|
|
48
|
+
problem = {
|
|
49
|
+
'num_vars': n_params,
|
|
50
|
+
'names': param_names,
|
|
51
|
+
'bounds' : bounds
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
### generate samples
|
|
55
|
+
if verbose:
|
|
56
|
+
print(f'Generating Sobol samples (N={n_samples:,.0f}, D={n_params}).')
|
|
57
|
+
param_values = sobol_sample.sample(problem, n_samples, calc_second_order=calc_second_order)
|
|
58
|
+
|
|
59
|
+
### args / kwargs for the objective function
|
|
60
|
+
if args is None:
|
|
61
|
+
args = []
|
|
62
|
+
if kwargs is None:
|
|
63
|
+
kwargs = {}
|
|
64
|
+
def wrapped_func(x_samples):
|
|
65
|
+
return func(x_samples, *args, **kwargs)
|
|
66
|
+
|
|
67
|
+
### evaluate samples
|
|
68
|
+
# vectorized evaluation
|
|
69
|
+
n_expected = param_values.shape[0]
|
|
70
|
+
try:
|
|
71
|
+
values = wrapped_func(param_values)
|
|
72
|
+
values = np.asarray(values).flatten()
|
|
73
|
+
if values.shape[0] != n_expected:
|
|
74
|
+
raise ValueError('Non-vectorized objective function.')
|
|
75
|
+
|
|
76
|
+
# loop-based evaluation
|
|
77
|
+
except ValueError as e:
|
|
78
|
+
if verbose:
|
|
79
|
+
print(f'Non-vectorized objective function; loop-based evaluation.')
|
|
80
|
+
values = np.array([wrapped_func(sample) for sample in param_values])
|
|
81
|
+
|
|
82
|
+
# run sensitivity analysis
|
|
83
|
+
print('Running sensitivity analysis.')
|
|
84
|
+
Si = sobol_analyze.analyze(problem, values, calc_second_order=calc_second_order, print_to_console=False)
|
|
85
|
+
|
|
86
|
+
# create Si output dataframe
|
|
87
|
+
Si_keys = ['S1', 'S1_conf', 'ST', 'ST_conf']
|
|
88
|
+
Si_filtered = {k: Si[k] for k in Si_keys if k in Si} # filter for output
|
|
89
|
+
Si_df = pd.DataFrame(Si_filtered, index=param_names)
|
|
90
|
+
|
|
91
|
+
# create S2 output dataframe
|
|
92
|
+
if calc_second_order:
|
|
93
|
+
S2_matrix = Si['S2']
|
|
94
|
+
S2_df = pd.DataFrame(S2_matrix, index=param_names, columns=param_names)
|
|
95
|
+
S2_df = S2_df.fillna(S2_df.T)
|
|
96
|
+
else:
|
|
97
|
+
S2_df = pd.DataFrame()
|
|
98
|
+
|
|
99
|
+
### end of calculations
|
|
100
|
+
end_time = time.time()
|
|
101
|
+
run_time = end_time - start_time
|
|
102
|
+
if verbose:
|
|
103
|
+
num_to_plot = np.minimum(num_to_plot, n_params)
|
|
104
|
+
print(f'\nRun time: {run_time:.2f}s')
|
|
105
|
+
# plotting imports
|
|
106
|
+
try:
|
|
107
|
+
import matplotlib.pyplot as plt
|
|
108
|
+
import seaborn as sns
|
|
109
|
+
except ImportError as e:
|
|
110
|
+
raise ImportError(f'Plotting requires dependencies: (matplotlib, seaborn).') from e
|
|
111
|
+
|
|
112
|
+
# sort by S1
|
|
113
|
+
sort_idx = np.argsort(Si['S1'])
|
|
114
|
+
s1_sorted = Si['S1'][sort_idx][-num_to_plot:]
|
|
115
|
+
st_sorted = Si['ST'][sort_idx][-num_to_plot:]
|
|
116
|
+
s1_conf_sorted = Si['S1_conf'][sort_idx][-num_to_plot:]
|
|
117
|
+
st_conf_sorted = Si['ST_conf'][sort_idx][-num_to_plot:]
|
|
118
|
+
names_sorted = [np.array(param_names)[i] for i in sort_idx][-num_to_plot:]
|
|
119
|
+
index = np.arange(len(names_sorted))
|
|
120
|
+
|
|
121
|
+
### plot 1: first-order (S1) and total-order (ST) indices
|
|
122
|
+
fig, ax = plt.subplots(1,1,figsize=(9, 7))
|
|
123
|
+
|
|
124
|
+
bar_width = 0.35
|
|
125
|
+
ax.barh(index + bar_width/2, s1_sorted, bar_width, xerr=s1_conf_sorted,
|
|
126
|
+
label='First-order ($S_1$)',
|
|
127
|
+
alpha=1,
|
|
128
|
+
capsize=2.5)
|
|
129
|
+
ax.set_yticks(index)
|
|
130
|
+
ax.set_yticklabels(names_sorted)
|
|
131
|
+
|
|
132
|
+
ax.barh(index - bar_width/2, st_sorted, bar_width,
|
|
133
|
+
xerr=st_conf_sorted,
|
|
134
|
+
label='Total-order ($S_T$)',
|
|
135
|
+
alpha=0.75,
|
|
136
|
+
capsize=2.5)
|
|
137
|
+
if log_scale:
|
|
138
|
+
ax.set_xscale('log')
|
|
139
|
+
ax.set_title('Sensitivity Indices ($S_1$, $S_T$)')
|
|
140
|
+
ax.legend()
|
|
141
|
+
plt.tight_layout()
|
|
142
|
+
plt.show()
|
|
143
|
+
|
|
144
|
+
if calc_second_order:
|
|
145
|
+
### plot 2: heatmap of second order indices
|
|
146
|
+
s2_plot, ax = plt.subplots(1,1,figsize=(9, 7))
|
|
147
|
+
|
|
148
|
+
top_idx_to_show = sort_idx[-num_to_plot:]
|
|
149
|
+
S2_filtered = S2_df.iloc[top_idx_to_show, top_idx_to_show]
|
|
150
|
+
mask_filtered = np.tril(np.ones_like(S2_filtered, dtype=bool))
|
|
151
|
+
sns.heatmap(data=S2_filtered, mask=mask_filtered, annot=True, vmin=0.0, fmt='.2f')
|
|
152
|
+
ax.set_title('Second-order Interactions ($S_2$)')
|
|
153
|
+
ax.invert_yaxis()
|
|
154
|
+
plt.tight_layout()
|
|
155
|
+
plt.show()
|
|
156
|
+
|
|
157
|
+
return Si_df, S2_df
|
|
158
|
+
|
|
159
|
+
### sobol sampling
|
|
160
|
+
def sobol_sample(n_samples, bounds, normalize=False, seed=None):
|
|
161
|
+
'''
|
|
162
|
+
Objective:
|
|
163
|
+
- Generates a uniform scrambled Sobol sample sequence.
|
|
164
|
+
Inputs:
|
|
165
|
+
- n_samples: Number of samples to generate.
|
|
166
|
+
- bounds: Range to sample over.
|
|
167
|
+
- normalize: Boolean, if True keeps samples normalized to [0,1].
|
|
168
|
+
- seed: Random seed.
|
|
169
|
+
Outputs:
|
|
170
|
+
- sobol_sequence: Sobol sample sequence.
|
|
171
|
+
'''
|
|
172
|
+
|
|
173
|
+
# clean bounds & n_dimensions
|
|
174
|
+
bounds = np.array(bounds)
|
|
175
|
+
n_dimensions = bounds.shape[0]
|
|
176
|
+
|
|
177
|
+
sobol_sampler = stats.qmc.Sobol(d=n_dimensions, scramble=True, seed=seed)
|
|
178
|
+
sobol_samples_unit = sobol_sampler.random(n=n_samples)
|
|
179
|
+
|
|
180
|
+
if not normalize:
|
|
181
|
+
sobol_sequence = stats.qmc.scale(sobol_samples_unit, bounds[:, 0], bounds[:, 1])
|
|
182
|
+
else:
|
|
183
|
+
sobol_sequence = sobol_samples_unit
|
|
184
|
+
|
|
185
|
+
return sobol_sequence
|
|
@@ -406,7 +406,7 @@ def plot_diagnostic_dashboard(temporal, spectral, metrics):
|
|
|
406
406
|
ax_cum.plot(f_mhz, spectral['energy'], color='gold', linewidth=2)
|
|
407
407
|
ax_cum.fill_between(f_mhz, spectral['energy'], color='gold', alpha=0.2)
|
|
408
408
|
ax_cum.axvline(metrics['bw_90_hz'], color='red', linestyle='--',
|
|
409
|
-
label=f'90% Band: {metrics['bw_90_hz']:.
|
|
409
|
+
label=f'90% Band: {metrics['bw_90_hz']:.1e} Hz')
|
|
410
410
|
ax_cum.set_title('Cumulative Energy')
|
|
411
411
|
ax_cum.set_ylabel('Normalized Energy')
|
|
412
412
|
ax_cum.set_xlabel('Frequency (MHz)')
|
|
@@ -440,7 +440,10 @@ def plot_diagnostic_dashboard(temporal, spectral, metrics):
|
|
|
440
440
|
im = ax_spec.pcolormesh(t_spec*1e6, f, 10*np.log10(Sxx + epsilon),
|
|
441
441
|
shading='gouraud', cmap='plasma')
|
|
442
442
|
|
|
443
|
+
ax_spec.set_title('Spectrogram')
|
|
443
444
|
ax_spec.set_yscale('log')
|
|
444
445
|
ax_spec.set_ylim(np.abs(spectral['freq']).min()+epsilon, np.abs(spectral['freq'].max()/sample_rate))
|
|
446
|
+
cbar = fig.colorbar(im, ax=ax_spec)
|
|
447
|
+
cbar.set_label('Power/Frequency (dB/Hz)')
|
|
445
448
|
|
|
446
449
|
plt.show()
|
|
@@ -1,19 +1,33 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hdim_opt
|
|
3
|
-
Version: 1.3.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 1.3.2
|
|
4
|
+
Summary: High-dimensional numerical optimization and sampling toolkit for complex, non-differentiable problems.
|
|
5
5
|
Author-email: Julian Soltes <jsoltes@regis.edu>
|
|
6
6
|
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/jgsoltes/hdim_opt
|
|
7
8
|
Project-URL: Repository, https://github.com/jgsoltes/hdim_opt
|
|
8
|
-
|
|
9
|
+
Project-URL: Issues, https://github.com/jgsoltes/hdim_opt/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/jgsoltes/hdim_opt/releases
|
|
11
|
+
Keywords: optimization,high-dimensional,sampling,QUASAR,hyperellipsoid,evolutionary-algorithm,non-differentiable,global-optimization,stochastic-optimization,black-box-optimization
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
19
|
Classifier: License :: OSI Approved :: MIT License
|
|
11
20
|
Classifier: Operating System :: OS Independent
|
|
12
21
|
Classifier: Intended Audience :: Science/Research
|
|
13
22
|
Classifier: Intended Audience :: Developers
|
|
23
|
+
Classifier: Intended Audience :: Education
|
|
24
|
+
Classifier: Natural Language :: English
|
|
14
25
|
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
15
26
|
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
16
27
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
28
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
29
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
30
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
17
31
|
Requires-Python: >=3.8
|
|
18
32
|
Description-Content-Type: text/markdown
|
|
19
33
|
Requires-Dist: numpy
|
|
@@ -28,13 +42,14 @@ Requires-Dist: SALib; extra == "sensitivity"
|
|
|
28
42
|
|
|
29
43
|
# hdim-opt: High-Dimensional Optimization Toolkit
|
|
30
44
|
|
|
31
|
-
|
|
45
|
+
Modern optimization package to accelerate convergence in complex, high-dimensional problems. Includes the QUASAR evolutionary algorithm, HDS exploitative QMC sampler, Sobol sensitivity analysis, signal waveform decomposition, and data transformations.
|
|
32
46
|
|
|
33
47
|
All core functions, listed below, are single-line executable and require three essential parameters: [obj_function, bounds, n_samples].
|
|
34
48
|
* **quasar**: QUASAR optimization for high-dimensional, non-differentiable problems.
|
|
35
|
-
* **
|
|
49
|
+
* **hyperellipsoid**: Generate a non-uniform Hyperellipsoid Density sequence, to focus sample distributions.
|
|
36
50
|
* **sobol**: Generate a uniform Sobol sequence (via SciPy).
|
|
37
51
|
* **sensitivity**: Perform Sobol sensitivity analysis to measure each variable's importance on objective function results (via SALib).
|
|
52
|
+
* **isotropize**: Isotropizes the input matrix.
|
|
38
53
|
* **waveform**: Decompose the input waveform array (handles time- and frequency-domain via FFT / IFFT) into a diagnostic summary.
|
|
39
54
|
|
|
40
55
|
---
|
|
@@ -63,7 +78,7 @@ time, pulse = h.waveform_analysis.e1_waveform()
|
|
|
63
78
|
solution, fitness = h.quasar(obj_func, bounds)
|
|
64
79
|
sens_matrix = h.sensitivity(obj_func, bounds)
|
|
65
80
|
|
|
66
|
-
hds_samples = h.
|
|
81
|
+
hds_samples = h.hyperellipsoid(n_samples, bounds)
|
|
67
82
|
sobol_samples = h.sobol(n_samples, bounds)
|
|
68
83
|
isotropic_samples = h.isotropize(sobol_samples)
|
|
69
84
|
|
|
@@ -75,7 +90,7 @@ signal_data = h.waveform(x=time,y=pulse)
|
|
|
75
90
|
|
|
76
91
|
* Benefit: Significant improvements in convergence speed and solution quality compared to contemporary optimizers. (Reference: [https://arxiv.org/abs/2511.13843]).
|
|
77
92
|
|
|
78
|
-
## HDS Sampler
|
|
79
|
-
**HDS** is a non-uniform Quasi-Monte Carlo sampling method, specifically designed to exploit promising regions of the search space.
|
|
93
|
+
## HDS Sampler
|
|
94
|
+
**HDS** (Hyperellipsoid Density Sampling) is a non-uniform Quasi-Monte Carlo sampling method, specifically designed to exploit promising regions of the search space.
|
|
80
95
|
|
|
81
96
|
* Benefit: Provides control over the sample distribution. Results in higher average optimization solution quality when used for population initialization compared to uniform QMC methods. (Reference: [https://arxiv.org/abs/2511.07836]).
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# pyproject.toml
|
|
2
|
+
|
|
3
|
+
[build-system]
|
|
4
|
+
requires = ["setuptools>=61.0.0", "wheel"]
|
|
5
|
+
build-backend = "setuptools.build_meta"
|
|
6
|
+
|
|
7
|
+
[project]
|
|
8
|
+
name = "hdim_opt"
|
|
9
|
+
version = "1.3.2" # match version in __init__.py
|
|
10
|
+
description = "High-dimensional numerical optimization and sampling toolkit for complex, non-differentiable problems."
|
|
11
|
+
readme = {file = "README.md", content-type = "text/markdown"}
|
|
12
|
+
|
|
13
|
+
authors = [
|
|
14
|
+
{name="Julian Soltes", email="jsoltes@regis.edu"}
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
license = { text = "MIT" }
|
|
18
|
+
requires-python = ">=3.8"
|
|
19
|
+
keywords = [
|
|
20
|
+
"optimization",
|
|
21
|
+
"high-dimensional",
|
|
22
|
+
"sampling",
|
|
23
|
+
"QUASAR",
|
|
24
|
+
"hyperellipsoid",
|
|
25
|
+
"evolutionary-algorithm",
|
|
26
|
+
"non-differentiable",
|
|
27
|
+
"global-optimization",
|
|
28
|
+
"stochastic-optimization",
|
|
29
|
+
"black-box-optimization"
|
|
30
|
+
]
|
|
31
|
+
classifiers = [
|
|
32
|
+
"Development Status :: 5 - Production/Stable",
|
|
33
|
+
"Programming Language :: Python :: 3",
|
|
34
|
+
"Programming Language :: Python :: 3.8",
|
|
35
|
+
"Programming Language :: Python :: 3.9",
|
|
36
|
+
"Programming Language :: Python :: 3.10",
|
|
37
|
+
"Programming Language :: Python :: 3.11",
|
|
38
|
+
"Programming Language :: Python :: 3.12",
|
|
39
|
+
"License :: OSI Approved :: MIT License",
|
|
40
|
+
"Operating System :: OS Independent",
|
|
41
|
+
"Intended Audience :: Science/Research",
|
|
42
|
+
"Intended Audience :: Developers",
|
|
43
|
+
"Intended Audience :: Education",
|
|
44
|
+
"Natural Language :: English",
|
|
45
|
+
"Topic :: Scientific/Engineering :: Mathematics",
|
|
46
|
+
"Topic :: Scientific/Engineering :: Physics",
|
|
47
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
48
|
+
"Topic :: Scientific/Engineering :: Information Analysis",
|
|
49
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
50
|
+
"Topic :: Scientific/Engineering :: Bio-Informatics"
|
|
51
|
+
]
|
|
52
|
+
dependencies = ["numpy", "scipy"]
|
|
53
|
+
|
|
54
|
+
[project.optional-dependencies]
|
|
55
|
+
hds = [
|
|
56
|
+
# required by HDS `pip install hdim_opt[hds]`
|
|
57
|
+
"pandas",
|
|
58
|
+
"scikit-learn",
|
|
59
|
+
"joblib"
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
sensitivity = [
|
|
63
|
+
# required by sensitivity `pip install hdim_opt[sensitivity]`
|
|
64
|
+
"pandas",
|
|
65
|
+
"SALib"
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
[project.urls]
|
|
69
|
+
Homepage = "https://github.com/jgsoltes/hdim_opt"
|
|
70
|
+
Repository = "https://github.com/jgsoltes/hdim_opt"
|
|
71
|
+
Issues = "https://github.com/jgsoltes/hdim_opt/issues"
|
|
72
|
+
Changelog = "https://github.com/jgsoltes/hdim_opt/releases"
|
|
73
|
+
|
|
74
|
+
[tool.setuptools.packages.find]
|
|
75
|
+
where = ["."]
|
|
76
|
+
include = ["hdim_opt*"] # find hdim_opt package
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
from scipy import stats
|
|
2
|
-
import numpy as np
|
|
3
|
-
|
|
4
|
-
def sobol_sample(n_samples, bounds, normalize=False, seed=None):
|
|
5
|
-
'''
|
|
6
|
-
Objective:
|
|
7
|
-
- Generate a uniform scrambled Sobol sample sequence.
|
|
8
|
-
Inputs:
|
|
9
|
-
- n_samples: Number of samples to generate.
|
|
10
|
-
- bounds: Range to sample over.
|
|
11
|
-
- normalize: Boolean, if True keeps samples normalized to [0,1].
|
|
12
|
-
- seed: Random seed.
|
|
13
|
-
Outputs:
|
|
14
|
-
- sobol_sequence: Sobol sample sequence.
|
|
15
|
-
'''
|
|
16
|
-
|
|
17
|
-
# clean bounds & n_dimensions
|
|
18
|
-
bounds = np.array(bounds)
|
|
19
|
-
n_dimensions = bounds.shape[0]
|
|
20
|
-
|
|
21
|
-
sobol_sampler = stats.qmc.Sobol(d=n_dimensions, scramble=True, seed=seed)
|
|
22
|
-
sobol_samples_unit = sobol_sampler.random(n=n_samples)
|
|
23
|
-
|
|
24
|
-
if not normalize:
|
|
25
|
-
sobol_sequence = stats.qmc.scale(sobol_samples_unit, bounds[:, 0], bounds[:, 1])
|
|
26
|
-
else:
|
|
27
|
-
sobol_sequence = sobol_samples_unit
|
|
28
|
-
|
|
29
|
-
return sobol_sequence
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
def sens_analysis(func, bounds, n_samples=None,
|
|
2
|
-
kwargs=None, param_names=None,
|
|
3
|
-
verbose=True, log_scale=True):
|
|
4
|
-
'''
|
|
5
|
-
Objective:
|
|
6
|
-
- Perform Sobol sensitivity analysis on the vectorized objective function.
|
|
7
|
-
Inputs:
|
|
8
|
-
- func: Objective function (Problem) to analyze.
|
|
9
|
-
- bounds: Parameter space bounds, as an array of tuples.
|
|
10
|
-
- n_samples: Number of Sobol samples to generate.
|
|
11
|
-
- kwargs: Keyword arguments (dictionary) for objective function.
|
|
12
|
-
- param_names: Optional parameter names for each dimension.
|
|
13
|
-
- verbose: Boolean to display plots.
|
|
14
|
-
- log_scale: Boolean to log-scale plots.
|
|
15
|
-
Outputs:
|
|
16
|
-
- Si: Full sensitivity indices and confidences.
|
|
17
|
-
- S2_matrix: Matrix of S2 relationship sensitivity indices.
|
|
18
|
-
'''
|
|
19
|
-
|
|
20
|
-
# imports
|
|
21
|
-
try:
|
|
22
|
-
import numpy as np
|
|
23
|
-
from SALib.sample import sobol as sobol_sample
|
|
24
|
-
from SALib.analyze import sobol as sobol_analyze
|
|
25
|
-
import pandas as pd
|
|
26
|
-
from functools import partial
|
|
27
|
-
except ImportError as e:
|
|
28
|
-
raise ImportError(f'Sensitivity analysis requires dependencies: (SALib, pandas, functools).') from e
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
# define input parameters and their ranges
|
|
32
|
-
bounds = np.array(bounds)
|
|
33
|
-
n_params = bounds.shape[0]
|
|
34
|
-
if param_names == None:
|
|
35
|
-
param_names = range(0,n_params)
|
|
36
|
-
|
|
37
|
-
# scale default n_samples by dimension (power of 2)
|
|
38
|
-
if n_samples == None:
|
|
39
|
-
n_samples = int(2**np.ceil(np.log2(10*n_params)))
|
|
40
|
-
|
|
41
|
-
# define problem
|
|
42
|
-
problem = {
|
|
43
|
-
'num_vars': n_params,
|
|
44
|
-
'names': param_names,
|
|
45
|
-
'bounds' : bounds
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
# generate samples
|
|
49
|
-
if verbose:
|
|
50
|
-
print(f'Generating {n_samples:,.0f} Sobol samples for sensitivity analysis.')
|
|
51
|
-
param_values = sobol_sample.sample(problem, n_samples)
|
|
52
|
-
|
|
53
|
-
# kwargs for the objective function
|
|
54
|
-
if kwargs:
|
|
55
|
-
func = partial(func, **kwargs)
|
|
56
|
-
|
|
57
|
-
# evaluate the samples
|
|
58
|
-
values = func(param_values)
|
|
59
|
-
|
|
60
|
-
# running sensitivity analysis
|
|
61
|
-
print('Running sensitivity analysis.')
|
|
62
|
-
Si = sobol_analyze.analyze(problem, values, calc_second_order=True, print_to_console=False)
|
|
63
|
-
|
|
64
|
-
# calculate S2 sensitivities
|
|
65
|
-
# convert S2 indices to dataframe to process easier
|
|
66
|
-
S2_matrix = Si['S2']
|
|
67
|
-
S2_df = pd.DataFrame(S2_matrix, index=param_names, columns=param_names)
|
|
68
|
-
S2_df = S2_df.fillna(S2_df.T)
|
|
69
|
-
mask = np.tril(np.ones_like(S2_df, dtype=bool))
|
|
70
|
-
|
|
71
|
-
if verbose:
|
|
72
|
-
# import
|
|
73
|
-
try:
|
|
74
|
-
import matplotlib.pyplot as plt
|
|
75
|
-
import seaborn as sns
|
|
76
|
-
except ImportError as e:
|
|
77
|
-
raise ImportError(f'Plotting requires dependencies: (matplotlib, seaborn).') from e
|
|
78
|
-
|
|
79
|
-
# sort by S1 values
|
|
80
|
-
sort_idx = np.argsort(Si['S1'])
|
|
81
|
-
s1_sorted = Si['S1'][sort_idx]
|
|
82
|
-
st_sorted = Si['ST'][sort_idx]
|
|
83
|
-
s1_conf_sorted = Si['S1_conf'][sort_idx]
|
|
84
|
-
st_conf_sorted = Si['ST_conf'][sort_idx]
|
|
85
|
-
names_sorted = [np.array(param_names)[i] for i in sort_idx]
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
bar_width = 0.35
|
|
89
|
-
index = np.arange(n_params)
|
|
90
|
-
|
|
91
|
-
# plot 1: first-order (S1) and total-order (ST) indices
|
|
92
|
-
sens_plot, axs = plt.subplots(2,1,figsize=(9, 13))
|
|
93
|
-
|
|
94
|
-
# define bar width and positions
|
|
95
|
-
bar_width = 0.35
|
|
96
|
-
index = np.arange(n_params)
|
|
97
|
-
|
|
98
|
-
# plot S1 (first order) sensitivities
|
|
99
|
-
axs[0].barh(index - bar_width/2, s1_sorted, bar_width,
|
|
100
|
-
xerr=s1_conf_sorted,
|
|
101
|
-
label='First-order ($S_1$)',
|
|
102
|
-
alpha=1,
|
|
103
|
-
capsize=2.5)
|
|
104
|
-
|
|
105
|
-
axs[0].barh(index + bar_width/2, st_sorted, bar_width,
|
|
106
|
-
xerr=st_conf_sorted,
|
|
107
|
-
label='Total-order ($S_T$)',
|
|
108
|
-
alpha=0.75,
|
|
109
|
-
capsize=2.5)
|
|
110
|
-
|
|
111
|
-
axs[0].set_title('Sensitivity Indices ($S_1$, $S_T$)')
|
|
112
|
-
if log_scale:
|
|
113
|
-
axs[0].set_xscale('log')
|
|
114
|
-
|
|
115
|
-
axs[0].set_yticks(index)
|
|
116
|
-
axs[0].set_yticklabels(names_sorted)
|
|
117
|
-
|
|
118
|
-
# plot 2: heatmap of second order indices
|
|
119
|
-
sns.heatmap(data=S2_df, mask=mask, cbar_kws={'label': 'Second-order Index ($S_2$)'},ax=axs[1]) # magma
|
|
120
|
-
axs[1].set_title('Second-order Interactions ($S_2$)')
|
|
121
|
-
axs[1].invert_yaxis()
|
|
122
|
-
sens_plot.tight_layout()
|
|
123
|
-
plt.show()
|
|
124
|
-
|
|
125
|
-
return Si, S2_matrix
|
hdim_opt-1.3.0/pyproject.toml
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
# pyproject.toml
|
|
2
|
-
|
|
3
|
-
[build-system]
|
|
4
|
-
requires = ["setuptools>=61.0.0", "wheel"]
|
|
5
|
-
build-backend = "setuptools.build_meta"
|
|
6
|
-
|
|
7
|
-
[project]
|
|
8
|
-
name = "hdim_opt"
|
|
9
|
-
version = "1.3.0" # match __version__ in __init__.py
|
|
10
|
-
description = "Optimization toolkit for high-dimensional, non-differentiable problems."
|
|
11
|
-
readme = {file = "README.md", content-type = "text/markdown"}
|
|
12
|
-
|
|
13
|
-
authors = [
|
|
14
|
-
{name="Julian Soltes", email="jsoltes@regis.edu"}
|
|
15
|
-
]
|
|
16
|
-
|
|
17
|
-
license = { text = "MIT" }
|
|
18
|
-
requires-python = ">=3.8"
|
|
19
|
-
keywords = ["optimization", "high-dimensional", "sampling", "QUASAR", "hyperellipsoid"]
|
|
20
|
-
classifiers = [
|
|
21
|
-
"Programming Language :: Python :: 3",
|
|
22
|
-
"License :: OSI Approved :: MIT License",
|
|
23
|
-
"Operating System :: OS Independent",
|
|
24
|
-
"Intended Audience :: Science/Research",
|
|
25
|
-
"Intended Audience :: Developers",
|
|
26
|
-
"Topic :: Scientific/Engineering :: Mathematics",
|
|
27
|
-
"Topic :: Scientific/Engineering :: Physics",
|
|
28
|
-
"Topic :: Scientific/Engineering :: Artificial Intelligence"
|
|
29
|
-
]
|
|
30
|
-
dependencies = ["numpy", "scipy"]
|
|
31
|
-
|
|
32
|
-
[project.optional-dependencies]
|
|
33
|
-
hds = [
|
|
34
|
-
# required by HDS `pip install hdim_opt[hds]`
|
|
35
|
-
"pandas",
|
|
36
|
-
"scikit-learn",
|
|
37
|
-
"joblib"
|
|
38
|
-
]
|
|
39
|
-
|
|
40
|
-
sensitivity = [
|
|
41
|
-
# required by sensitivity `pip install hdim_opt[sensitivity]`
|
|
42
|
-
"pandas",
|
|
43
|
-
"SALib"
|
|
44
|
-
]
|
|
45
|
-
|
|
46
|
-
[project.urls]
|
|
47
|
-
Repository = "https://github.com/jgsoltes/hdim_opt"
|
|
48
|
-
|
|
49
|
-
[tool.setuptools.packages.find]
|
|
50
|
-
where = ["."]
|
|
51
|
-
include = ["hdim_opt*"] # find hdim_opt package
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|