hdim-opt 1.2.2__py3-none-any.whl → 1.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hdim_opt/__init__.py +43 -3
- hdim_opt/hyperellipsoid_sampling.py +3 -7
- hdim_opt/quasar_helpers.py +49 -0
- hdim_opt/quasar_optimization.py +18 -13
- hdim_opt/sobol_sensitivity.py +27 -24
- hdim_opt/waveform_analysis.py +446 -0
- {hdim_opt-1.2.2.dist-info → hdim_opt-1.3.0.dist-info}/METADATA +11 -4
- hdim_opt-1.3.0.dist-info/RECORD +12 -0
- hdim_opt-1.2.2.dist-info/RECORD +0 -11
- {hdim_opt-1.2.2.dist-info → hdim_opt-1.3.0.dist-info}/WHEEL +0 -0
- {hdim_opt-1.2.2.dist-info → hdim_opt-1.3.0.dist-info}/top_level.txt +0 -0
hdim_opt/__init__.py
CHANGED
|
@@ -1,7 +1,43 @@
|
|
|
1
|
-
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
# hdim-opt: High-Dimensional Optimization Toolkit
|
|
4
|
+
|
|
5
|
+
Functions:
|
|
6
|
+
- quasar: QUASAR optimization for high-dimensional, non-differentiable problems.
|
|
7
|
+
- hds: Generate an exploitative HDS sequence, to distribute samples in focused regions.
|
|
8
|
+
- sobol: Generate a uniform Sobol sequence (via SciPy).
|
|
9
|
+
- sensitivity: Perform Sobol sensitivity analysis to measure each variable's importance on objective function results (via SALib).
|
|
10
|
+
- waveform: Decompose the input waveform array (handles time- and frequency-domain via FFT / IFFT) into a diagnostic summary.
|
|
11
|
+
|
|
12
|
+
Modules:
|
|
13
|
+
- test_functions: Contains test functions for local optimization testing.
|
|
14
|
+
- waveform_analysis: Contains pulse generation functions.
|
|
15
|
+
|
|
16
|
+
Example Usage:
|
|
17
|
+
|
|
18
|
+
# Import
|
|
19
|
+
>>> import hdim_opt as h
|
|
20
|
+
|
|
21
|
+
# Parameter Space
|
|
22
|
+
>>> n_dimensions = 30
|
|
23
|
+
>>> bounds = [(-100,100)] * n_dimensions
|
|
24
|
+
>>> n_samples = 1000
|
|
25
|
+
>>> obj_func = h.test_functions.rastrigin
|
|
26
|
+
>>> time, pulse = h.waveform_analysis.e1_waveform()
|
|
27
|
+
|
|
28
|
+
# Functions
|
|
29
|
+
>>> solution, fitness = h.quasar(obj_func, bounds)
|
|
30
|
+
>>> sens_matrix = h.sensitivity(obj_func, bounds)
|
|
31
|
+
|
|
32
|
+
>>> hds_samples = h.hds(n_samples, bounds)
|
|
33
|
+
>>> sobol_samples = h.sobol(n_samples, bounds)
|
|
34
|
+
>>> isotropic_samples = h.isotropize(sobol_samples)
|
|
35
|
+
|
|
36
|
+
>>> signal_data = h.waveform(x=time,y=pulse)
|
|
37
|
+
"""
|
|
2
38
|
|
|
3
39
|
# package version
|
|
4
|
-
__version__ = "1.
|
|
40
|
+
__version__ = "1.3.0"
|
|
5
41
|
__all__ = ['quasar', 'hds', 'sobol', 'sensitivity', 'test_functions', 'quasar_helpers'] # available for star imports
|
|
6
42
|
|
|
7
43
|
# import core components
|
|
@@ -9,5 +45,9 @@ from .quasar_optimization import optimize as quasar
|
|
|
9
45
|
from .hyperellipsoid_sampling import sample as hds
|
|
10
46
|
from .sobol_sampling import sobol_sample as sobol
|
|
11
47
|
from .sobol_sensitivity import sens_analysis as sensitivity
|
|
48
|
+
from .waveform_analysis import analyze_waveform as waveform
|
|
49
|
+
from .quasar_helpers import isotropize
|
|
50
|
+
from .quasar_helpers import deisotropize
|
|
12
51
|
from . import test_functions
|
|
13
|
-
from . import quasar_helpers
|
|
52
|
+
from . import quasar_helpers
|
|
53
|
+
from . import waveform_analysis
|
|
@@ -472,10 +472,8 @@ def sample(n_samples, bounds,
|
|
|
472
472
|
print(' - number of samples:', len(hds_sequence))
|
|
473
473
|
print(f' - sample generation time: {sample_generation_time:.2f}')
|
|
474
474
|
print(f' - number of hyperellipsoids: {n_hyperellipsoids}')
|
|
475
|
-
print(f' - number of initial QMC: {n_initial_qmc}')
|
|
476
|
-
print(f' - number of initial clusters: {n_initial_clusters}')
|
|
477
475
|
if weights:
|
|
478
|
-
print(f' -
|
|
476
|
+
print(f' - weights: {weights}')
|
|
479
477
|
|
|
480
478
|
# generate a sobol sequence for comparison
|
|
481
479
|
sobol_sampler = stats.qmc.Sobol(d=n_dimensions, seed=seed+2) # offset seed to be different from initial qmc
|
|
@@ -492,10 +490,8 @@ def sample(n_samples, bounds,
|
|
|
492
490
|
sobol_std = np.std(sobol_samples)
|
|
493
491
|
|
|
494
492
|
print('\nstats:')
|
|
495
|
-
print(f' - mean
|
|
496
|
-
print(f' -
|
|
497
|
-
print(f' - stdev HDS: {hds_std:.2f}')
|
|
498
|
-
print(f' - stdev comparison QMC: {sobol_std:.2f}\n')
|
|
493
|
+
print(f' - HDS mean: {hds_mean:.2f}')
|
|
494
|
+
print(f' - HDS stdev: {hds_std:.2f}\n')
|
|
499
495
|
|
|
500
496
|
# dendrogram of centroids
|
|
501
497
|
if plot_dendrogram:
|
hdim_opt/quasar_helpers.py
CHANGED
|
@@ -1,7 +1,56 @@
|
|
|
1
1
|
# global imports
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from sklearn.preprocessing import StandardScaler
|
|
2
4
|
import numpy as np
|
|
5
|
+
from scipy.linalg import cholesky, solve_triangular
|
|
3
6
|
epsilon = 1e-12
|
|
4
7
|
|
|
8
|
+
def isotropize(data):
|
|
9
|
+
'''
|
|
10
|
+
Objective:
|
|
11
|
+
- Converts data to isotropic space. Removes correlations and scales to mean and variance.
|
|
12
|
+
- Promotes optimization stability.
|
|
13
|
+
Inputs:
|
|
14
|
+
- data: Input data.
|
|
15
|
+
Outputs:
|
|
16
|
+
- data_isotropic: Isotropized data.
|
|
17
|
+
- metadata: Scaler and whitening matrix
|
|
18
|
+
'''
|
|
19
|
+
|
|
20
|
+
# convert to array
|
|
21
|
+
X = np.array(data)
|
|
22
|
+
|
|
23
|
+
# standard scaling (mean = 0, var = 1)
|
|
24
|
+
mean = np.mean(X, axis=0)
|
|
25
|
+
stdev = np.std(X, axis=0)
|
|
26
|
+
X_centered = (X - mean) / stdev
|
|
27
|
+
|
|
28
|
+
# whitening parameters
|
|
29
|
+
n_dims = X_centered.shape[1]
|
|
30
|
+
cov = np.cov(X_centered, rowvar=False) + np.eye(n_dims) * epsilon
|
|
31
|
+
L = cholesky(cov, lower=True)
|
|
32
|
+
|
|
33
|
+
# transform Y = (X_centered) @ (L^-1).T
|
|
34
|
+
data_iso = solve_triangular(L, X_centered.T, lower=True).T
|
|
35
|
+
|
|
36
|
+
# store parameters for deisotropization
|
|
37
|
+
params = {
|
|
38
|
+
'mean': mean,
|
|
39
|
+
'stdev': stdev,
|
|
40
|
+
'L': L
|
|
41
|
+
}
|
|
42
|
+
return data_iso, params
|
|
43
|
+
|
|
44
|
+
def deisotropize(data_iso, params):
|
|
45
|
+
'''De-isotropize data to its original parameter space.'''
|
|
46
|
+
|
|
47
|
+
# reverse whitening: X_centered = Y @ L.T
|
|
48
|
+
data_centered = np.dot(data_iso, params['L'].T)
|
|
49
|
+
|
|
50
|
+
# reverse scaling: X = (X_centered * std) + mean
|
|
51
|
+
data_original = (data_centered * params['stdev']) + params['mean']
|
|
52
|
+
return data_original
|
|
53
|
+
|
|
5
54
|
############## CONSTRAINTS ##############
|
|
6
55
|
def apply_penalty(fitnesses, solutions, constraints, constraint_penalty, vectorized):
|
|
7
56
|
'''
|
hdim_opt/quasar_optimization.py
CHANGED
|
@@ -32,14 +32,14 @@ def initialize_population(popsize, bounds, init, hds_weights, seed, verbose):
|
|
|
32
32
|
|
|
33
33
|
# generate samples
|
|
34
34
|
if verbose:
|
|
35
|
-
print(f'Initializing: Hyperellipsoid
|
|
35
|
+
print(f'Initializing: Hyperellipsoid (N={popsize}, D={n_dimensions}).')
|
|
36
36
|
initial_population = hds.sample(popsize, bounds, weights=hds_weights,
|
|
37
37
|
seed=seed, verbose=False)
|
|
38
38
|
|
|
39
39
|
# generate sobol sequence
|
|
40
40
|
elif init == 'sobol':
|
|
41
41
|
if verbose:
|
|
42
|
-
print(f'Initializing: Sobol
|
|
42
|
+
print(f'Initializing: Sobol (N={popsize}, D={n_dimensions}).')
|
|
43
43
|
import warnings
|
|
44
44
|
warnings.filterwarnings('ignore', category=UserWarning) # ignore power-of-2 warning
|
|
45
45
|
sobol_sampler = stats.qmc.Sobol(d=n_dimensions, seed=seed)
|
|
@@ -48,14 +48,14 @@ def initialize_population(popsize, bounds, init, hds_weights, seed, verbose):
|
|
|
48
48
|
|
|
49
49
|
elif (init == 'lhs') or (init == 'latinhypercube'):
|
|
50
50
|
if verbose:
|
|
51
|
-
print(f'Initializing: Latin Hypercube
|
|
51
|
+
print(f'Initializing: Latin Hypercube (N={popsize}, D={n_dimensions}).')
|
|
52
52
|
lhs_sampler = stats.qmc.LatinHypercube(d=n_dimensions, seed=seed)
|
|
53
53
|
lhs_samples_unit = lhs_sampler.random(n=popsize)
|
|
54
54
|
initial_population = stats.qmc.scale(lhs_samples_unit, bounds[:, 0], bounds[:, 1])
|
|
55
55
|
|
|
56
56
|
elif init == 'random':
|
|
57
57
|
if verbose:
|
|
58
|
-
print(f'Initializing: Random
|
|
58
|
+
print(f'Initializing: Random (N={popsize}, D={n_dimensions}).')
|
|
59
59
|
initial_population = np.random.uniform(low=bounds[:, 0], high=bounds[:, 1], size=(popsize, n_dimensions))
|
|
60
60
|
else:
|
|
61
61
|
if init.ndim == 1:
|
|
@@ -64,7 +64,7 @@ def initialize_population(popsize, bounds, init, hds_weights, seed, verbose):
|
|
|
64
64
|
initial_population = init
|
|
65
65
|
if verbose:
|
|
66
66
|
custom_popsize, custom_n_dimensions = initial_population.shape
|
|
67
|
-
print(f'Initializing: Custom
|
|
67
|
+
print(f'Initializing: Custom (N={custom_popsize}, D={custom_n_dimensions}).')
|
|
68
68
|
|
|
69
69
|
return initial_population
|
|
70
70
|
|
|
@@ -374,7 +374,7 @@ def optimize(func, bounds, args=(),
|
|
|
374
374
|
patience=np.inf, vectorized=False,
|
|
375
375
|
hds_weights=None, kwargs={},
|
|
376
376
|
constraints=None, constraint_penalty=1e9,
|
|
377
|
-
|
|
377
|
+
reinitialization_method='covariance',
|
|
378
378
|
verbose=True, plot_solutions=True, num_to_plot=10, plot_contour=True,
|
|
379
379
|
workers=1, seed=None
|
|
380
380
|
):
|
|
@@ -382,6 +382,7 @@ def optimize(func, bounds, args=(),
|
|
|
382
382
|
Objective:
|
|
383
383
|
- Finds the optimal solution for a given objective function.
|
|
384
384
|
- Designed for non-differentiable, high-dimensional problems.
|
|
385
|
+
- For explorative problems chance reinitialization_method to '
|
|
385
386
|
- Test functions available for local testing, called as hdim_opt.test_functions.function_name.
|
|
386
387
|
- Existing test functions: [rastrigin, ackley, sinusoid, sphere, shubert].
|
|
387
388
|
|
|
@@ -432,13 +433,11 @@ def optimize(func, bounds, args=(),
|
|
|
432
433
|
}
|
|
433
434
|
- constraint_penalty: Penalty applied to each constraint violated, defaults to 1e12.
|
|
434
435
|
|
|
435
|
-
- reinitialization: Boolean to disable covariance reinitialization if needed.
|
|
436
|
-
- For cases where the population size is computationally prohibitive.
|
|
437
|
-
- Disabled by default for 1D problems.
|
|
438
436
|
- reinitialization_method: Type of re-sampling to use in the asymptotic reinitialization.
|
|
439
437
|
- Options are ['covariance', 'sobol'].
|
|
440
438
|
- 'covariance' (exploitative) is default for most problems.
|
|
441
439
|
- 'sobol' (explorative) is optional, for high exploration and faster computation.
|
|
440
|
+
- None to disable reinitialization calculations.
|
|
442
441
|
|
|
443
442
|
- verbose: Displays prints and plots.
|
|
444
443
|
- Mutation factor distribution shown with hdim_opt.test_functions.plot_mutations()
|
|
@@ -508,9 +507,11 @@ def optimize(func, bounds, args=(),
|
|
|
508
507
|
if not isinstance(init, str):
|
|
509
508
|
popsize = init.shape[0]
|
|
510
509
|
|
|
511
|
-
# default popsize to 10*n_dimensions
|
|
510
|
+
# default popsize to highest power of 2 from 10*n_dimensions
|
|
512
511
|
if popsize == None:
|
|
513
|
-
|
|
512
|
+
min_popsize = 2**7
|
|
513
|
+
default_popsize = int(2**np.ceil(np.log2(10*n_dimensions)))
|
|
514
|
+
popsize = max(min_popsize, default_popsize)
|
|
514
515
|
|
|
515
516
|
# ensure integers
|
|
516
517
|
popsize, maxiter = int(popsize), int(maxiter)
|
|
@@ -536,7 +537,11 @@ def optimize(func, bounds, args=(),
|
|
|
536
537
|
# generate initial population
|
|
537
538
|
initial_population = initialize_population(popsize, bounds, init, hds_weights, seed, verbose)
|
|
538
539
|
if verbose:
|
|
539
|
-
|
|
540
|
+
if reinitialization_method not in ['sobol', 'covariance', None]:
|
|
541
|
+
print("reinitialization_method must be one of ['covariance', 'sobol', None].")
|
|
542
|
+
print(f'\nEvolving (None):')
|
|
543
|
+
else:
|
|
544
|
+
print(f'\nEvolving ({reinitialization_method}):')
|
|
540
545
|
|
|
541
546
|
# match differential evolution conventions
|
|
542
547
|
if vectorized:
|
|
@@ -643,7 +648,7 @@ def optimize(func, bounds, args=(),
|
|
|
643
648
|
# apply asymptotic covariance reinitialization to population
|
|
644
649
|
final_proba = 0.33
|
|
645
650
|
decay_generation = 0.33
|
|
646
|
-
if
|
|
651
|
+
if reinitialization_method in ['sobol','covariance']:
|
|
647
652
|
reinit_proba = np.e**((np.log(final_proba)/(decay_generation*maxiter))*generation)
|
|
648
653
|
else:
|
|
649
654
|
reinit_proba = 0.0
|
hdim_opt/sobol_sensitivity.py
CHANGED
|
@@ -75,7 +75,19 @@ def sens_analysis(func, bounds, n_samples=None,
|
|
|
75
75
|
import seaborn as sns
|
|
76
76
|
except ImportError as e:
|
|
77
77
|
raise ImportError(f'Plotting requires dependencies: (matplotlib, seaborn).') from e
|
|
78
|
-
|
|
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
|
+
|
|
79
91
|
# plot 1: first-order (S1) and total-order (ST) indices
|
|
80
92
|
sens_plot, axs = plt.subplots(2,1,figsize=(9, 13))
|
|
81
93
|
|
|
@@ -84,35 +96,26 @@ def sens_analysis(func, bounds, n_samples=None,
|
|
|
84
96
|
index = np.arange(n_params)
|
|
85
97
|
|
|
86
98
|
# plot S1 (first order) sensitivities
|
|
87
|
-
axs[0].barh(index - bar_width/2,
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
# ecolor='lightgray',
|
|
93
|
-
capsize=2.5)
|
|
94
|
-
# edgecolor='black')
|
|
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)
|
|
95
104
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
alpha=0.75,
|
|
103
|
-
capsize=2.5)
|
|
104
|
-
# edgecolor='black')
|
|
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
|
+
|
|
105
111
|
axs[0].set_title('Sensitivity Indices ($S_1$, $S_T$)')
|
|
106
112
|
if log_scale:
|
|
107
113
|
axs[0].set_xscale('log')
|
|
108
|
-
|
|
109
|
-
axs[0].set_ylabel('Parameter')
|
|
110
|
-
axs[0].legend(loc='upper right')
|
|
111
|
-
axs[0].grid(False)
|
|
114
|
+
|
|
112
115
|
axs[0].set_yticks(index)
|
|
113
|
-
axs[0].set_yticklabels(
|
|
116
|
+
axs[0].set_yticklabels(names_sorted)
|
|
114
117
|
|
|
115
|
-
# heatmap of second order indices
|
|
118
|
+
# plot 2: heatmap of second order indices
|
|
116
119
|
sns.heatmap(data=S2_df, mask=mask, cbar_kws={'label': 'Second-order Index ($S_2$)'},ax=axs[1]) # magma
|
|
117
120
|
axs[1].set_title('Second-order Interactions ($S_2$)')
|
|
118
121
|
axs[1].invert_yaxis()
|
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
from scipy import signal
|
|
5
|
+
|
|
6
|
+
# constants
|
|
7
|
+
epsilon = 1e-12 # to avoid mathematical singularities
|
|
8
|
+
|
|
9
|
+
# visualization parameters
|
|
10
|
+
plt.rcParams['figure.facecolor'] = 'black'
|
|
11
|
+
plt.rcParams['axes.facecolor'] = 'black'
|
|
12
|
+
plt.rcParams['text.color'] = 'white'
|
|
13
|
+
plt.rcParams['axes.labelcolor'] = 'white'
|
|
14
|
+
plt.rcParams['xtick.color'] = 'white'
|
|
15
|
+
plt.rcParams['ytick.color'] = 'white'
|
|
16
|
+
plt.rcParams['axes.edgecolor'] = 'white'
|
|
17
|
+
plt.rcParams['grid.color'] = 'white'
|
|
18
|
+
plt.rcParams['lines.color'] = 'white'
|
|
19
|
+
|
|
20
|
+
def apply_noise(signal, noise):
|
|
21
|
+
# adding random gaussian noise within 1% of discrete amplitudes
|
|
22
|
+
stdev = np.std(np.abs(signal))
|
|
23
|
+
signal_noisy = signal + noise * np.random.normal(loc=0, scale=stdev, size=signal.shape)
|
|
24
|
+
|
|
25
|
+
return signal_noisy
|
|
26
|
+
|
|
27
|
+
def e1_waveform(peak_Vm=50e3, rise_s=5e-9, decay_s=200e-9,
|
|
28
|
+
sample_Hz=10e9, duration_s=1e-6, noise=0.0):
|
|
29
|
+
'''
|
|
30
|
+
Objective:
|
|
31
|
+
- Generates time-domain double exponential H-EMP E1 waveform, per MIL-STD / IEC specifications.
|
|
32
|
+
- Pulse waveform: E(t) = E0 * k * (exp(-alpha*t) - exp(-beta*t))
|
|
33
|
+
|
|
34
|
+
Inputs:
|
|
35
|
+
- E_peak_V_m: Target EMP amplitude (V/m).
|
|
36
|
+
- E_rise_time_ns: Target EMP risetime (ns).
|
|
37
|
+
- E_decay_time_ns: Target decay time of the pulse (FWHM).
|
|
38
|
+
- sampling_rate: Sampling rate of pulse (Hz).
|
|
39
|
+
- duration_ns: Duration of pulse (ns).
|
|
40
|
+
|
|
41
|
+
Outputs:
|
|
42
|
+
- time_s: Time array of pulse (s).
|
|
43
|
+
- E_t: Electric field amplitude array at each time (V/m).
|
|
44
|
+
|
|
45
|
+
'''
|
|
46
|
+
|
|
47
|
+
dt_s = 1 / sample_Hz
|
|
48
|
+
time_s = np.arange(0, duration_s, dt_s)
|
|
49
|
+
|
|
50
|
+
# alpha controls the decay and broadband shape
|
|
51
|
+
# input E_decay_time_ns = desired pulse FWHM
|
|
52
|
+
alpha = np.log(2) / decay_s # derived from from FWHM = ln(2) / FWHM
|
|
53
|
+
|
|
54
|
+
# beta controls the rise time and high-frequency content
|
|
55
|
+
beta = 2.0035 / rise_s # # approximate relationship: beta ~= 2.2 / (rise_time) (in seconds^-1)
|
|
56
|
+
|
|
57
|
+
# calculate time of pulse peak for k_norm calculation
|
|
58
|
+
t_peak_s = np.log(beta / alpha) / (beta - alpha)
|
|
59
|
+
|
|
60
|
+
# 'k_norm' factor normalizes pulse peak to input E_peak_V_m (50,000 V/m)
|
|
61
|
+
denominator = (np.exp(-alpha * t_peak_s) - np.exp(-beta * t_peak_s))
|
|
62
|
+
if np.isclose(denominator, 0):
|
|
63
|
+
k_norm = peak_Vm
|
|
64
|
+
else:
|
|
65
|
+
k_norm = peak_Vm / denominator
|
|
66
|
+
|
|
67
|
+
# generate the E1 waveform
|
|
68
|
+
E_t = k_norm * (np.exp(-alpha * time_s) - np.exp(-beta * time_s))
|
|
69
|
+
|
|
70
|
+
# adding random gaussian noise
|
|
71
|
+
E_t = apply_noise(E_t, noise)
|
|
72
|
+
|
|
73
|
+
return time_s, E_t
|
|
74
|
+
|
|
75
|
+
def e2_waveform(E_peak=100, tr_us=1.5, tf_ms=1.0, sample_rate=1e6, duration_s=0.01, noise=0.0):
|
|
76
|
+
'''Generates E2 H-EMP pulse waveform (lightning-like).'''
|
|
77
|
+
t = np.arange(0, duration_s, 1/sample_rate)
|
|
78
|
+
alpha = 1 / (tf_ms * 1e-3)
|
|
79
|
+
beta = 1 / (tr_us * 1e-6)
|
|
80
|
+
E_t = E_peak * 1.1 * (np.exp(-alpha * t) - np.exp(-beta * t))
|
|
81
|
+
|
|
82
|
+
# adding random gaussian noise
|
|
83
|
+
E_t = apply_noise(E_t, noise)
|
|
84
|
+
|
|
85
|
+
return t, E_t
|
|
86
|
+
|
|
87
|
+
def e3_waveform(E_peak=40, t_peak_s=10, sample_rate=10, duration_s=500, noise=0.0):
|
|
88
|
+
'''Generates E3 H-EMP pulse waveform (geostorm-like; magnetohydrodynamic).'''
|
|
89
|
+
t = np.arange(0, duration_s, 1/sample_rate)
|
|
90
|
+
|
|
91
|
+
# simplified IEC 61000-2-9 E3 waveform
|
|
92
|
+
E_t = E_peak * (np.exp(-t/120) - np.exp(-t/20))
|
|
93
|
+
|
|
94
|
+
# adding random gaussian noise
|
|
95
|
+
E_t = apply_noise(E_t, noise)
|
|
96
|
+
|
|
97
|
+
return t, E_t
|
|
98
|
+
|
|
99
|
+
def calculate_rise_time(time_array, pulse_array):
|
|
100
|
+
'''
|
|
101
|
+
Objective:
|
|
102
|
+
- Calculates the 10%-90% rise time of a double exponential pulse waveform.
|
|
103
|
+
|
|
104
|
+
Inputs:
|
|
105
|
+
- time_array: Pulse time array (s).
|
|
106
|
+
- pulse_array: Pulse waveform array (V/m).
|
|
107
|
+
|
|
108
|
+
Outputs:
|
|
109
|
+
- rise_time: Rise time (10%-90%) of pulse waveform (ns).
|
|
110
|
+
- t_90_percent: Time at rising 90% peak amplitude (ns).
|
|
111
|
+
- t_10_percent: Time at rising 10% amplitude (ns).
|
|
112
|
+
|
|
113
|
+
'''
|
|
114
|
+
|
|
115
|
+
peak_amplitude = np.max(pulse_array)
|
|
116
|
+
|
|
117
|
+
# calculate 10% and 90% thresholds
|
|
118
|
+
threshold_10_percent = 0.1 * peak_amplitude
|
|
119
|
+
threshold_90_percent = 0.9 * peak_amplitude
|
|
120
|
+
|
|
121
|
+
# find indices where the pulse first crosses the 10% threshold (rising side)
|
|
122
|
+
idx_10 = np.where(pulse_array >= threshold_10_percent)[0]
|
|
123
|
+
if len(idx_10) == 0:
|
|
124
|
+
return None
|
|
125
|
+
idx_10_first = idx_10[0]
|
|
126
|
+
|
|
127
|
+
# find indices where the pulse first crosses the 90% threshold (rising side)
|
|
128
|
+
idx_90 = np.where(pulse_array >= threshold_90_percent)[0]
|
|
129
|
+
if len(idx_90) == 0:
|
|
130
|
+
return None
|
|
131
|
+
idx_90_first = idx_90[0]
|
|
132
|
+
|
|
133
|
+
# interpolate to find the exact time points at 10% and 90% thresholds
|
|
134
|
+
# time at 10% threshold
|
|
135
|
+
t_10_percent = time_array[idx_10_first-1] + (threshold_10_percent - pulse_array[idx_10_first-1]) * \
|
|
136
|
+
(time_array[idx_10_first] - time_array[idx_10_first-1]) / \
|
|
137
|
+
(pulse_array[idx_10_first] - pulse_array[idx_10_first-1])
|
|
138
|
+
|
|
139
|
+
# time at 90% threshold
|
|
140
|
+
t_90_percent = time_array[idx_90_first-1] + (threshold_90_percent - pulse_array[idx_90_first-1]) * \
|
|
141
|
+
(time_array[idx_90_first] - time_array[idx_90_first-1]) / \
|
|
142
|
+
(pulse_array[idx_90_first] - pulse_array[idx_90_first-1])
|
|
143
|
+
|
|
144
|
+
# calculate risetime
|
|
145
|
+
rise_time = t_90_percent - t_10_percent
|
|
146
|
+
|
|
147
|
+
return rise_time, t_90_percent, t_10_percent
|
|
148
|
+
|
|
149
|
+
def calculate_fwhm(time_array, pulse_array):
|
|
150
|
+
'''
|
|
151
|
+
Objective:
|
|
152
|
+
- Calculates the full width at half maximum (FWHM) of a double exponential pulse waveform.
|
|
153
|
+
|
|
154
|
+
Inputs:
|
|
155
|
+
- time_array: Pulse time array (s).
|
|
156
|
+
- pulse_array: Pulse waveform array (V/m).
|
|
157
|
+
|
|
158
|
+
Outputs:
|
|
159
|
+
- fwhm: Full-width half-max of pulse waveform (ns).
|
|
160
|
+
- t_fwhm2: Time at rising half-max (ns).
|
|
161
|
+
- t_fwhm1: Time at decaying half-max (ns).
|
|
162
|
+
|
|
163
|
+
'''
|
|
164
|
+
|
|
165
|
+
# find the peak value of the pulse
|
|
166
|
+
peak_amplitude = np.max(pulse_array)
|
|
167
|
+
half_max = peak_amplitude / 2.0
|
|
168
|
+
|
|
169
|
+
# find indices where the pulse is above half_max
|
|
170
|
+
indices_above_half_max = np.where(pulse_array >= half_max)[0]
|
|
171
|
+
|
|
172
|
+
# find the first and last points where the pulse crosses half_max
|
|
173
|
+
idx1 = indices_above_half_max[0]
|
|
174
|
+
idx2 = indices_above_half_max[-1]
|
|
175
|
+
|
|
176
|
+
# first FWHM crossing point (rising side)
|
|
177
|
+
if idx1 == 0: # pulse starts above half_max
|
|
178
|
+
t_fwhm1 = time_array[idx1]
|
|
179
|
+
else:
|
|
180
|
+
# interpolate to find first time point at half_max
|
|
181
|
+
t_fwhm1 = time_array[idx1-1] + (half_max - pulse_array[idx1-1]) * \
|
|
182
|
+
(time_array[idx1] - time_array[idx1-1]) / \
|
|
183
|
+
(pulse_array[idx1] - pulse_array[idx1-1])
|
|
184
|
+
|
|
185
|
+
# second FWHM crossing point (decaying side)
|
|
186
|
+
if idx2 == len(pulse_array) - 1: # pulse ends above half_max
|
|
187
|
+
t_fwhm2 = time_array[idx2]
|
|
188
|
+
else:
|
|
189
|
+
# interpolate to find second time point at half_max
|
|
190
|
+
t_fwhm2 = time_array[idx2] + (half_max - pulse_array[idx2]) * \
|
|
191
|
+
(time_array[idx2+1] - time_array[idx2]) / \
|
|
192
|
+
(pulse_array[idx2+1] - pulse_array[idx2])
|
|
193
|
+
|
|
194
|
+
# calculate FWHM
|
|
195
|
+
fwhm = t_fwhm2 - t_fwhm1
|
|
196
|
+
|
|
197
|
+
return fwhm, t_fwhm2, t_fwhm1
|
|
198
|
+
|
|
199
|
+
def apply_shielding(f, shielding_dB, rolloff_hf=500e6, rolloff_lf=1e3):
|
|
200
|
+
'''
|
|
201
|
+
Combines complex transfer function math with real-world
|
|
202
|
+
LF (Magnetic) and HF (Leakage) rolloff physics.
|
|
203
|
+
'''
|
|
204
|
+
# base linear gain
|
|
205
|
+
base_gain = 10**(-shielding_dB/20)
|
|
206
|
+
|
|
207
|
+
# HF rolloff
|
|
208
|
+
h_hf = 1 / (1 + 1j * (f / rolloff_hf))
|
|
209
|
+
|
|
210
|
+
# LF rolloff
|
|
211
|
+
h_lf = 1 / (1 + (rolloff_lf / (f + 1e-12)))
|
|
212
|
+
|
|
213
|
+
# total complex shielding function
|
|
214
|
+
h_total = base_gain * h_hf * h_lf
|
|
215
|
+
|
|
216
|
+
return h_total
|
|
217
|
+
|
|
218
|
+
def analyze_waveform(x=None, y=None, sample_rate=None, domain='time', method='complex',
|
|
219
|
+
tf_function=None, tf_kwargs=None, noise=0.0, verbose=True):
|
|
220
|
+
'''
|
|
221
|
+
Decomposes & analyzes the given signal waveform.
|
|
222
|
+
Outputs:
|
|
223
|
+
- df_time: DataFrame of time-domain data (N rows)
|
|
224
|
+
- df_freq: DataFrame of positive frequency-domain data (N/2+1 rows)
|
|
225
|
+
- metrics: Dictionary of scalar results
|
|
226
|
+
'''
|
|
227
|
+
|
|
228
|
+
# clean
|
|
229
|
+
x = np.array(x)
|
|
230
|
+
y = np.array(y).flatten()
|
|
231
|
+
domain = 'time' if domain.lower() in ['t', 'time'] else 'freq'
|
|
232
|
+
|
|
233
|
+
# apply transfer function
|
|
234
|
+
if tf_function:
|
|
235
|
+
y = apply_transfer_function(x, y, tf_function, domain=domain, **(tf_kwargs or {}))
|
|
236
|
+
|
|
237
|
+
# apply gaussian noise
|
|
238
|
+
y = apply_noise(y, noise)
|
|
239
|
+
|
|
240
|
+
# extract parameters
|
|
241
|
+
if domain == 'time':
|
|
242
|
+
n = len(y)
|
|
243
|
+
fs = 1 / (x[1] - x[0])
|
|
244
|
+
if method == 'complex':
|
|
245
|
+
y_f = np.fft.fft(y) / n
|
|
246
|
+
freqs = np.fft.fftfreq(n, 1/fs)
|
|
247
|
+
elif method == 'real':
|
|
248
|
+
y_f = np.fft.rfft(y) / n
|
|
249
|
+
freqs = np.fft.rfftfreq(n, 1/fs)
|
|
250
|
+
|
|
251
|
+
y_t, t = y, x
|
|
252
|
+
else:
|
|
253
|
+
n = len(y)
|
|
254
|
+
fs = sample_rate if sample_rate else x[len(x)//2] * 2
|
|
255
|
+
if method == 'complex':
|
|
256
|
+
y_t = np.fft.ifft(y).real * n
|
|
257
|
+
elif method == 'real':
|
|
258
|
+
y_t = np.fft.irfft(y).real * n
|
|
259
|
+
t = np.arange(0, n) / fs
|
|
260
|
+
y_f, freqs = y, x
|
|
261
|
+
|
|
262
|
+
# positive half of frequencies
|
|
263
|
+
mask = freqs >= 0
|
|
264
|
+
f_pos = freqs[mask]
|
|
265
|
+
yf_pos = y_f[mask]
|
|
266
|
+
|
|
267
|
+
# energy calculation
|
|
268
|
+
esd = (np.abs(yf_pos)**2) * (2 / fs)
|
|
269
|
+
|
|
270
|
+
# handle DC
|
|
271
|
+
esd = (np.abs(yf_pos)**2) * (2 / fs)
|
|
272
|
+
esd[0] = esd[0] / 2 # DC only exists once
|
|
273
|
+
|
|
274
|
+
# if real, last bin (nyquist) only exists once
|
|
275
|
+
if method == 'real' and len(esd) > 0:
|
|
276
|
+
esd[-1] = esd[-1] / 2
|
|
277
|
+
|
|
278
|
+
df_freq = f_pos[1] - f_pos[0]
|
|
279
|
+
cumul_energy = np.cumsum(esd) * df_freq
|
|
280
|
+
total_energy = cumul_energy[-1]
|
|
281
|
+
|
|
282
|
+
# convert to dataframes:
|
|
283
|
+
# time domain
|
|
284
|
+
temporal = {
|
|
285
|
+
'time_s': t,
|
|
286
|
+
'amplitude': y_t
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
# frequency domain
|
|
290
|
+
spectral = {
|
|
291
|
+
'freq': f_pos,
|
|
292
|
+
'signal': yf_pos,
|
|
293
|
+
'esd': esd,
|
|
294
|
+
'energy': cumul_energy
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
# max gradient
|
|
298
|
+
dv_dt = np.diff(y_t) * fs
|
|
299
|
+
|
|
300
|
+
# action integral
|
|
301
|
+
action_integral = np.trapezoid(y_t**2, t)
|
|
302
|
+
|
|
303
|
+
# calculate rise time/decay time
|
|
304
|
+
rise_s, _, _ = calculate_rise_time(t, np.abs(y_t))
|
|
305
|
+
rise_ns = 1e9 * rise_s
|
|
306
|
+
fwhm_s, _, _ = calculate_fwhm(t, np.abs(y_t))
|
|
307
|
+
fwhm_ns = 1e9 * fwhm_s
|
|
308
|
+
|
|
309
|
+
# scalar metrics
|
|
310
|
+
metrics = {
|
|
311
|
+
'peak_t': np.max(np.abs(y_t)),
|
|
312
|
+
'peak_f': np.max(np.abs(yf_pos)),
|
|
313
|
+
'total_energy': total_energy,
|
|
314
|
+
'action_integral': action_integral,
|
|
315
|
+
'max_dv_dt': np.max(np.abs(dv_dt)),
|
|
316
|
+
'bw_90_hz': f_pos[np.where(cumul_energy >= 0.9 * total_energy)[0][0]],
|
|
317
|
+
'center_freq_hz': np.sum(f_pos * esd) / (total_energy + 1e-12),
|
|
318
|
+
'papr_db': 10 * np.log10(np.max(y_t**2) / np.mean(y_t**2)),
|
|
319
|
+
'sample_rate': fs,
|
|
320
|
+
'rise90_ns': rise_ns,
|
|
321
|
+
'fwhm_ns': fwhm_ns,
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
# plot
|
|
325
|
+
if verbose:
|
|
326
|
+
plot_diagnostic_dashboard(temporal, spectral, metrics)
|
|
327
|
+
|
|
328
|
+
return temporal, spectral, metrics
|
|
329
|
+
|
|
330
|
+
def apply_transfer_function(x, y, tf_function, domain='freq', **kwargs):
|
|
331
|
+
'''
|
|
332
|
+
x: Time or frequency array.
|
|
333
|
+
y: Input signal.
|
|
334
|
+
tf_function: The transfer function.
|
|
335
|
+
domain: 'freq' or 'time'.
|
|
336
|
+
**kwargs: Arguments passed to the transfer function.
|
|
337
|
+
'''
|
|
338
|
+
|
|
339
|
+
if domain == 'freq':
|
|
340
|
+
H_f = tf_function(np.abs(x), **kwargs)
|
|
341
|
+
output = y * H_f
|
|
342
|
+
elif domain == 'time':
|
|
343
|
+
h_t = tf_function(x, **kwargs)
|
|
344
|
+
output = np.convolve(y, h_t, mode='same')
|
|
345
|
+
output *= (x[1] - x[0]) # normalize by dt
|
|
346
|
+
else:
|
|
347
|
+
raise ValueError('Unrecognized signal domain.')
|
|
348
|
+
|
|
349
|
+
return output
|
|
350
|
+
|
|
351
|
+
def plot_diagnostic_dashboard(temporal, spectral, metrics):
|
|
352
|
+
'''
|
|
353
|
+
6-plot diagnostic dashboard:
|
|
354
|
+
Time Domain | Frequency Magnitude
|
|
355
|
+
Phase Spectrum | Energy Spectral Density (ESD)
|
|
356
|
+
Cumulative Energy | Spectrogram
|
|
357
|
+
'''
|
|
358
|
+
|
|
359
|
+
# 3 rows, 2 columns
|
|
360
|
+
fig = plt.figure(figsize=(16, 20))
|
|
361
|
+
gs = fig.add_gridspec(3, 2, hspace=0.3, wspace=0.3)
|
|
362
|
+
|
|
363
|
+
ax_time = fig.add_subplot(gs[0, 0])
|
|
364
|
+
ax_freq = fig.add_subplot(gs[0, 1])
|
|
365
|
+
ax_phase = fig.add_subplot(gs[1, 0])
|
|
366
|
+
ax_esd = fig.add_subplot(gs[1, 1])
|
|
367
|
+
ax_cum = fig.add_subplot(gs[2, 0])
|
|
368
|
+
ax_spec = fig.add_subplot(gs[2, 1])
|
|
369
|
+
|
|
370
|
+
# mask for positive frequencies to ensure dimensions match
|
|
371
|
+
pos_mask = spectral['freq'] >= 0
|
|
372
|
+
f_mhz = spectral['freq'][pos_mask] / 1e6
|
|
373
|
+
mag_pos = np.abs(spectral['signal'])[pos_mask]
|
|
374
|
+
|
|
375
|
+
# time domain
|
|
376
|
+
ax_time.plot(temporal['time_s']*1e6, temporal['amplitude'], color='cyan', label=f'Peak: {metrics['peak_t']:.2f}')
|
|
377
|
+
ax_time.set_title(f'Time-Domain Signal')
|
|
378
|
+
ax_time.set_xlabel(r'Time ($\mu s$)')
|
|
379
|
+
ax_time.set_ylabel('Amplitude')
|
|
380
|
+
ax_time.legend()
|
|
381
|
+
|
|
382
|
+
# frequency domain
|
|
383
|
+
ax_freq.semilogy(f_mhz, np.abs(spectral['signal']), color='violet')
|
|
384
|
+
ax_freq.set_title('Frequency-Domain Signal')
|
|
385
|
+
ax_freq.set_xlabel('Frequency (MHz)')
|
|
386
|
+
max_f = metrics['sample_rate'] / 2e6 # limit no higher than nyquist
|
|
387
|
+
ax_freq.set_xlim(0, min(1000, max_f))
|
|
388
|
+
ax_freq.grid(alpha=0.2, which='both')
|
|
389
|
+
|
|
390
|
+
# phase spectrum
|
|
391
|
+
ax_phase.plot(f_mhz, np.angle(spectral['signal']), color='limegreen', linewidth=0.5)
|
|
392
|
+
ax_phase.set_title('Phase Spectrum')
|
|
393
|
+
ax_phase.set_xlabel('Frequency (MHz)')
|
|
394
|
+
ax_phase.set_ylabel('Phase (rad)')
|
|
395
|
+
ax_phase.set_xlim(ax_freq.get_xlim())
|
|
396
|
+
|
|
397
|
+
# energy spectral density (ESD)
|
|
398
|
+
ax_esd.semilogy(f_mhz, spectral['esd'], color='gold')
|
|
399
|
+
ax_esd.set_title('Energy Spectral Density (ESD)')
|
|
400
|
+
ax_esd.set_ylabel(r'$V^2 \cdot s / Hz$')
|
|
401
|
+
ax_esd.set_xlabel('Frequency (MHz)')
|
|
402
|
+
ax_esd.set_xlim(ax_freq.get_xlim())
|
|
403
|
+
ax_esd.set_xlim(0, min(1000, max_f))
|
|
404
|
+
|
|
405
|
+
# cumulative energy distribution
|
|
406
|
+
ax_cum.plot(f_mhz, spectral['energy'], color='gold', linewidth=2)
|
|
407
|
+
ax_cum.fill_between(f_mhz, spectral['energy'], color='gold', alpha=0.2)
|
|
408
|
+
ax_cum.axvline(metrics['bw_90_hz'], color='red', linestyle='--',
|
|
409
|
+
label=f'90% Band: {metrics['bw_90_hz']:.1f} MHz')
|
|
410
|
+
ax_cum.set_title('Cumulative Energy')
|
|
411
|
+
ax_cum.set_ylabel('Normalized Energy')
|
|
412
|
+
ax_cum.set_xlabel('Frequency (MHz)')
|
|
413
|
+
ax_cum.set_xlim(ax_freq.get_xlim())
|
|
414
|
+
ax_cum.legend(fontsize='small')
|
|
415
|
+
|
|
416
|
+
# spectrogram
|
|
417
|
+
# ensure amplitude is real for spectrogram
|
|
418
|
+
sample_rate = metrics['sample_rate']
|
|
419
|
+
y_signal = np.real(temporal['amplitude'])
|
|
420
|
+
window_duration_s = 0.01*(temporal['time_s'].max() - temporal['time_s'].min())
|
|
421
|
+
nperseg = int(window_duration_s * sample_rate)
|
|
422
|
+
|
|
423
|
+
raw_n = (1/1.15) * (window_duration_s * sample_rate)
|
|
424
|
+
|
|
425
|
+
# powers of 2
|
|
426
|
+
if raw_n <= 1:
|
|
427
|
+
nperseg = 16 # minimum window
|
|
428
|
+
else:
|
|
429
|
+
# round to nearest power of 2
|
|
430
|
+
nperseg = int(2**np.round(np.log2(raw_n)))
|
|
431
|
+
|
|
432
|
+
# reinforce minimum
|
|
433
|
+
nperseg = max(nperseg, 16)
|
|
434
|
+
|
|
435
|
+
# nonoverlap always less than num per segment
|
|
436
|
+
noverlap = nperseg // 2
|
|
437
|
+
f, t_spec, Sxx = signal.spectrogram(y_signal, fs=sample_rate, window='hann',
|
|
438
|
+
nperseg=nperseg, noverlap=nperseg//2)
|
|
439
|
+
|
|
440
|
+
im = ax_spec.pcolormesh(t_spec*1e6, f, 10*np.log10(Sxx + epsilon),
|
|
441
|
+
shading='gouraud', cmap='plasma')
|
|
442
|
+
|
|
443
|
+
ax_spec.set_yscale('log')
|
|
444
|
+
ax_spec.set_ylim(np.abs(spectral['freq']).min()+epsilon, np.abs(spectral['freq'].max()/sample_rate))
|
|
445
|
+
|
|
446
|
+
plt.show()
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hdim_opt
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Optimization toolkit for high-dimensional, non-differentiable problems.
|
|
5
5
|
Author-email: Julian Soltes <jsoltes@regis.edu>
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Repository, https://github.com/jgsoltes/hdim_opt
|
|
8
|
-
Keywords: optimization,high-dimensional,sampling,QUASAR,
|
|
8
|
+
Keywords: optimization,high-dimensional,sampling,QUASAR,hyperellipsoid
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
10
|
Classifier: License :: OSI Approved :: MIT License
|
|
11
11
|
Classifier: Operating System :: OS Independent
|
|
@@ -28,13 +28,14 @@ Requires-Dist: SALib; extra == "sensitivity"
|
|
|
28
28
|
|
|
29
29
|
# hdim-opt: High-Dimensional Optimization Toolkit
|
|
30
30
|
|
|
31
|
-
A modern optimization package to accelerate convergence in complex, high-dimensional problems. Includes the QUASAR evolutionary algorithm
|
|
31
|
+
A modern optimization package to accelerate convergence in complex, high-dimensional problems. Includes the QUASAR evolutionary algorithm, HDS exploitative QMC sampler, Sobol sensitivity analysis, and signal waveform decomposition.
|
|
32
32
|
|
|
33
33
|
All core functions, listed below, are single-line executable and require three essential parameters: [obj_function, bounds, n_samples].
|
|
34
34
|
* **quasar**: QUASAR optimization for high-dimensional, non-differentiable problems.
|
|
35
35
|
* **hds**: Generate an exploitative HDS sequence, to distribute samples in focused regions.
|
|
36
36
|
* **sobol**: Generate a uniform Sobol sequence (via SciPy).
|
|
37
37
|
* **sensitivity**: Perform Sobol sensitivity analysis to measure each variable's importance on objective function results (via SALib).
|
|
38
|
+
* **waveform**: Decompose the input waveform array (handles time- and frequency-domain via FFT / IFFT) into a diagnostic summary.
|
|
38
39
|
|
|
39
40
|
---
|
|
40
41
|
|
|
@@ -52,15 +53,21 @@ pip install hdim_opt
|
|
|
52
53
|
import hdim_opt as h
|
|
53
54
|
|
|
54
55
|
# Parameter Space
|
|
55
|
-
n_dimensions =
|
|
56
|
+
n_dimensions = 30
|
|
56
57
|
bounds = [(-100,100)] * n_dimensions
|
|
57
58
|
n_samples = 1000
|
|
58
59
|
obj_func = h.test_functions.rastrigin
|
|
60
|
+
time, pulse = h.waveform_analysis.e1_waveform()
|
|
59
61
|
|
|
62
|
+
# Functions
|
|
60
63
|
solution, fitness = h.quasar(obj_func, bounds)
|
|
61
64
|
sens_matrix = h.sensitivity(obj_func, bounds)
|
|
65
|
+
|
|
62
66
|
hds_samples = h.hds(n_samples, bounds)
|
|
63
67
|
sobol_samples = h.sobol(n_samples, bounds)
|
|
68
|
+
isotropic_samples = h.isotropize(sobol_samples)
|
|
69
|
+
|
|
70
|
+
signal_data = h.waveform(x=time,y=pulse)
|
|
64
71
|
```
|
|
65
72
|
|
|
66
73
|
## QUASAR Optimizer
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
hdim_opt/__init__.py,sha256=CeLrf9egFUScaSirq3gkT8WXcvw0F58L3b_i6oT8Egs,1894
|
|
2
|
+
hdim_opt/hyperellipsoid_sampling.py,sha256=WYlFJxeLKUf8frrl1vM4b28WRJ8zpneKn1NitSPtQMI,24356
|
|
3
|
+
hdim_opt/quasar_helpers.py,sha256=zcoBggHfc6XhZFlJFJy6p0MGVsQwHa6nMudWauT5Vzo,16255
|
|
4
|
+
hdim_opt/quasar_optimization.py,sha256=U7a4ZbgsNJ24V7sff710D7LGYsUWE0CVNoZCmjahZmE,32329
|
|
5
|
+
hdim_opt/sobol_sampling.py,sha256=Xe_Zzs13xMxCben17gT85lFsoV-GKVOAAgi7lMxnlBI,912
|
|
6
|
+
hdim_opt/sobol_sensitivity.py,sha256=HmwqNSDvofX5u1hOayLgjMZOdjQdOHYHzrOLxMTfhhc,4390
|
|
7
|
+
hdim_opt/test_functions.py,sha256=RqjKYIiwAqWplGUsH4oPHLBrVdnLRyw7f0dJX5iyJ4g,2821
|
|
8
|
+
hdim_opt/waveform_analysis.py,sha256=EV87W4D1rJ31x2X5M2_uFaCFDLhhnMwIft-0djCNXoI,15083
|
|
9
|
+
hdim_opt-1.3.0.dist-info/METADATA,sha256=2HJSVNmCZ_zYYrkMHNYo2s53UR3eLQXmi2t-J77IaeQ,3481
|
|
10
|
+
hdim_opt-1.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
11
|
+
hdim_opt-1.3.0.dist-info/top_level.txt,sha256=1KtWo9tEfEK3GC8D43cwVsC8yVG2Kc-9pl0hhcDjw4o,9
|
|
12
|
+
hdim_opt-1.3.0.dist-info/RECORD,,
|
hdim_opt-1.2.2.dist-info/RECORD
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
hdim_opt/__init__.py,sha256=eGhYIxj6AUhhlfqgAHtPQdul64gYYLaWl8esUs-vkgI,477
|
|
2
|
-
hdim_opt/hyperellipsoid_sampling.py,sha256=c34JkVciZbdAXjdfNjfC4h5NsrT2CD7Epsxpef5a1xY,24625
|
|
3
|
-
hdim_opt/quasar_helpers.py,sha256=zTgar2EuWs4MLSLEO7HRcP7At1xbXLP3q4Gg7-GrggQ,14799
|
|
4
|
-
hdim_opt/quasar_optimization.py,sha256=cSF_aOijVhdtvr7VEBUNVyBQ1--1s8cOuWZMuPogf5A,32093
|
|
5
|
-
hdim_opt/sobol_sampling.py,sha256=Xe_Zzs13xMxCben17gT85lFsoV-GKVOAAgi7lMxnlBI,912
|
|
6
|
-
hdim_opt/sobol_sensitivity.py,sha256=1ebeDSTmcLn03_MKDGiyJJ7r_ZSNCq2AKNcTX-hI23A,4384
|
|
7
|
-
hdim_opt/test_functions.py,sha256=RqjKYIiwAqWplGUsH4oPHLBrVdnLRyw7f0dJX5iyJ4g,2821
|
|
8
|
-
hdim_opt-1.2.2.dist-info/METADATA,sha256=jId-zu3VAQTuQFgwlSQqKTgnCtwbORXuLbqsCVbSvA4,3130
|
|
9
|
-
hdim_opt-1.2.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
10
|
-
hdim_opt-1.2.2.dist-info/top_level.txt,sha256=1KtWo9tEfEK3GC8D43cwVsC8yVG2Kc-9pl0hhcDjw4o,9
|
|
11
|
-
hdim_opt-1.2.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|