machinegnostics 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- __init__.py +0 -0
- machinegnostics/__init__.py +24 -0
- machinegnostics/magcal/__init__.py +37 -0
- machinegnostics/magcal/characteristics.py +460 -0
- machinegnostics/magcal/criteria_eval.py +268 -0
- machinegnostics/magcal/criterion.py +140 -0
- machinegnostics/magcal/data_conversion.py +381 -0
- machinegnostics/magcal/gcor.py +64 -0
- machinegnostics/magcal/gdf/__init__.py +2 -0
- machinegnostics/magcal/gdf/base_df.py +39 -0
- machinegnostics/magcal/gdf/base_distfunc.py +1202 -0
- machinegnostics/magcal/gdf/base_egdf.py +823 -0
- machinegnostics/magcal/gdf/base_eldf.py +830 -0
- machinegnostics/magcal/gdf/base_qgdf.py +1234 -0
- machinegnostics/magcal/gdf/base_qldf.py +1019 -0
- machinegnostics/magcal/gdf/cluster_analysis.py +456 -0
- machinegnostics/magcal/gdf/data_cluster.py +975 -0
- machinegnostics/magcal/gdf/data_intervals.py +853 -0
- machinegnostics/magcal/gdf/data_membership.py +536 -0
- machinegnostics/magcal/gdf/der_egdf.py +243 -0
- machinegnostics/magcal/gdf/distfunc_engine.py +841 -0
- machinegnostics/magcal/gdf/egdf.py +324 -0
- machinegnostics/magcal/gdf/eldf.py +297 -0
- machinegnostics/magcal/gdf/eldf_intv.py +609 -0
- machinegnostics/magcal/gdf/eldf_ma.py +627 -0
- machinegnostics/magcal/gdf/homogeneity.py +1218 -0
- machinegnostics/magcal/gdf/intv_engine.py +1523 -0
- machinegnostics/magcal/gdf/marginal_intv_analysis.py +558 -0
- machinegnostics/magcal/gdf/qgdf.py +289 -0
- machinegnostics/magcal/gdf/qldf.py +296 -0
- machinegnostics/magcal/gdf/scedasticity.py +197 -0
- machinegnostics/magcal/gdf/wedf.py +181 -0
- machinegnostics/magcal/gdf/z0_estimator.py +1047 -0
- machinegnostics/magcal/layer_base.py +42 -0
- machinegnostics/magcal/layer_history_base.py +74 -0
- machinegnostics/magcal/layer_io_process_base.py +238 -0
- machinegnostics/magcal/layer_param_base.py +448 -0
- machinegnostics/magcal/mg_weights.py +36 -0
- machinegnostics/magcal/sample_characteristics.py +532 -0
- machinegnostics/magcal/scale_optimization.py +185 -0
- machinegnostics/magcal/scale_param.py +313 -0
- machinegnostics/magcal/util/__init__.py +0 -0
- machinegnostics/magcal/util/dis_docstring.py +18 -0
- machinegnostics/magcal/util/logging.py +24 -0
- machinegnostics/magcal/util/min_max_float.py +34 -0
- machinegnostics/magnet/__init__.py +0 -0
- machinegnostics/metrics/__init__.py +28 -0
- machinegnostics/metrics/accu.py +61 -0
- machinegnostics/metrics/accuracy.py +67 -0
- machinegnostics/metrics/auto_correlation.py +183 -0
- machinegnostics/metrics/auto_covariance.py +204 -0
- machinegnostics/metrics/cls_report.py +130 -0
- machinegnostics/metrics/conf_matrix.py +93 -0
- machinegnostics/metrics/correlation.py +178 -0
- machinegnostics/metrics/cross_variance.py +167 -0
- machinegnostics/metrics/divi.py +82 -0
- machinegnostics/metrics/evalmet.py +109 -0
- machinegnostics/metrics/f1_score.py +128 -0
- machinegnostics/metrics/gmmfe.py +108 -0
- machinegnostics/metrics/hc.py +141 -0
- machinegnostics/metrics/mae.py +72 -0
- machinegnostics/metrics/mean.py +117 -0
- machinegnostics/metrics/median.py +122 -0
- machinegnostics/metrics/mg_r2.py +167 -0
- machinegnostics/metrics/mse.py +78 -0
- machinegnostics/metrics/precision.py +119 -0
- machinegnostics/metrics/r2.py +122 -0
- machinegnostics/metrics/recall.py +108 -0
- machinegnostics/metrics/rmse.py +77 -0
- machinegnostics/metrics/robr2.py +119 -0
- machinegnostics/metrics/std.py +144 -0
- machinegnostics/metrics/variance.py +101 -0
- machinegnostics/models/__init__.py +2 -0
- machinegnostics/models/classification/__init__.py +1 -0
- machinegnostics/models/classification/layer_history_log_reg.py +121 -0
- machinegnostics/models/classification/layer_io_process_log_reg.py +98 -0
- machinegnostics/models/classification/layer_mlflow_log_reg.py +107 -0
- machinegnostics/models/classification/layer_param_log_reg.py +275 -0
- machinegnostics/models/classification/mg_log_reg.py +273 -0
- machinegnostics/models/cross_validation.py +118 -0
- machinegnostics/models/data_split.py +106 -0
- machinegnostics/models/regression/__init__.py +2 -0
- machinegnostics/models/regression/layer_histroy_rob_reg.py +139 -0
- machinegnostics/models/regression/layer_io_process_rob_rig.py +88 -0
- machinegnostics/models/regression/layer_mlflow_rob_reg.py +134 -0
- machinegnostics/models/regression/layer_param_rob_reg.py +212 -0
- machinegnostics/models/regression/mg_lin_reg.py +253 -0
- machinegnostics/models/regression/mg_poly_reg.py +258 -0
- machinegnostics-0.0.1.dist-info/METADATA +246 -0
- machinegnostics-0.0.1.dist-info/RECORD +93 -0
- machinegnostics-0.0.1.dist-info/WHEEL +5 -0
- machinegnostics-0.0.1.dist-info/licenses/LICENSE +674 -0
- machinegnostics-0.0.1.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,830 @@
|
|
|
1
|
+
'''
|
|
2
|
+
base ELDF class
|
|
3
|
+
Estimating Local Distribution Functions
|
|
4
|
+
|
|
5
|
+
Author: Nirmal Parmar
|
|
6
|
+
Machine Gnostics
|
|
7
|
+
'''
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import warnings
|
|
11
|
+
import logging
|
|
12
|
+
from machinegnostics.magcal.util.logging import get_logger
|
|
13
|
+
from scipy.optimize import minimize
|
|
14
|
+
from typing import Dict, Any
|
|
15
|
+
from machinegnostics.magcal.characteristics import GnosticsCharacteristics
|
|
16
|
+
from machinegnostics.magcal.data_conversion import DataConversion
|
|
17
|
+
from machinegnostics.magcal.gdf.base_egdf import BaseEGDF
|
|
18
|
+
from machinegnostics.magcal.scale_param import ScaleParam
|
|
19
|
+
from machinegnostics.magcal.gdf.z0_estimator import Z0Estimator
|
|
20
|
+
|
|
21
|
+
class BaseELDF(BaseEGDF):
|
|
22
|
+
'''Base ELDF class'''
|
|
23
|
+
def __init__(self,
|
|
24
|
+
data: np.ndarray,
|
|
25
|
+
DLB: float = None,
|
|
26
|
+
DUB: float = None,
|
|
27
|
+
LB: float = None,
|
|
28
|
+
UB: float = None,
|
|
29
|
+
S = 'auto',
|
|
30
|
+
varS: bool = False,
|
|
31
|
+
z0_optimize: bool = True,
|
|
32
|
+
tolerance: float = 1e-3,
|
|
33
|
+
data_form: str = 'a',
|
|
34
|
+
n_points: int = 500,
|
|
35
|
+
homogeneous: bool = True,
|
|
36
|
+
catch: bool = True,
|
|
37
|
+
weights: np.ndarray = None,
|
|
38
|
+
wedf: bool = True,
|
|
39
|
+
opt_method: str = 'L-BFGS-B',
|
|
40
|
+
verbose: bool = False,
|
|
41
|
+
max_data_size: int = 1000,
|
|
42
|
+
flush: bool = True):
|
|
43
|
+
super().__init__(data=data,
|
|
44
|
+
DLB=DLB,
|
|
45
|
+
DUB=DUB,
|
|
46
|
+
LB=LB,
|
|
47
|
+
UB=UB,
|
|
48
|
+
S=S,
|
|
49
|
+
tolerance=tolerance,
|
|
50
|
+
data_form=data_form,
|
|
51
|
+
n_points=n_points,
|
|
52
|
+
homogeneous=homogeneous,
|
|
53
|
+
catch=catch,
|
|
54
|
+
weights=weights,
|
|
55
|
+
wedf=wedf,
|
|
56
|
+
opt_method=opt_method,
|
|
57
|
+
verbose=verbose,
|
|
58
|
+
max_data_size=max_data_size,
|
|
59
|
+
flush=flush)
|
|
60
|
+
|
|
61
|
+
# Store raw inputs
|
|
62
|
+
self.data = data
|
|
63
|
+
self.DLB = DLB
|
|
64
|
+
self.DUB = DUB
|
|
65
|
+
self.LB = LB
|
|
66
|
+
self.UB = UB
|
|
67
|
+
self.S = S
|
|
68
|
+
self.varS = varS # ELDF specific
|
|
69
|
+
self.z0_optimize = z0_optimize # ELDF specific
|
|
70
|
+
self.tolerance = tolerance
|
|
71
|
+
self.data_form = data_form
|
|
72
|
+
self.n_points = n_points
|
|
73
|
+
self.homogeneous = homogeneous
|
|
74
|
+
self.catch = catch
|
|
75
|
+
self.weights = weights if weights is not None else np.ones_like(data)
|
|
76
|
+
self.wedf = wedf
|
|
77
|
+
self.opt_method = opt_method
|
|
78
|
+
self.verbose = verbose
|
|
79
|
+
self.max_data_size = max_data_size
|
|
80
|
+
self.flush = flush
|
|
81
|
+
self._fitted = False # To track if fit has been called
|
|
82
|
+
|
|
83
|
+
# Store initial parameters if catching
|
|
84
|
+
if self.catch:
|
|
85
|
+
self._store_initial_params()
|
|
86
|
+
|
|
87
|
+
# Validate all inputs
|
|
88
|
+
# self._validate_inputs()
|
|
89
|
+
|
|
90
|
+
# logger
|
|
91
|
+
self.logger = get_logger(self.__class__.__name__, logging.DEBUG if verbose else logging.WARNING)
|
|
92
|
+
self.logger.debug(f"{self.__class__.__name__} initialized:")
|
|
93
|
+
|
|
94
|
+
# if S is float or int and is greater than 2, warn user
|
|
95
|
+
if (isinstance(self.S, float) or isinstance(self.S, int)) and self.S > 2:
|
|
96
|
+
self.logger.warning("S is greater than 2, which may not suitable for local distribution estimation. Consider using in range [0, 2]")
|
|
97
|
+
warnings.warn("S is greater than 2, which may not suitable for local distribution estimation. Consider using in range [0, 2]", UserWarning)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _fit_eldf(self, plot: bool = True):
|
|
102
|
+
"""Fit the ELDF model to the data."""
|
|
103
|
+
self.logger.info("Starting ELDF fitting process...")
|
|
104
|
+
try:
|
|
105
|
+
# Step 1: Data preprocessing
|
|
106
|
+
self.logger.info("Preprocessing data...")
|
|
107
|
+
self.data = np.sort(self.data)
|
|
108
|
+
self._estimate_data_bounds()
|
|
109
|
+
self._transform_data_to_standard_domain()
|
|
110
|
+
self._estimate_weights()
|
|
111
|
+
|
|
112
|
+
# Step 2: Bounds estimation
|
|
113
|
+
self.logger.info("Estimating initial probable bounds...")
|
|
114
|
+
self._estimate_initial_probable_bounds()
|
|
115
|
+
self._generate_evaluation_points()
|
|
116
|
+
|
|
117
|
+
# Step 3: Get distribution function values for optimization
|
|
118
|
+
self.logger.info("Getting distribution function values...")
|
|
119
|
+
self.df_values = self._get_distribution_function_values(use_wedf=self.wedf)
|
|
120
|
+
|
|
121
|
+
# Step 4: Parameter optimization
|
|
122
|
+
self.logger.info("Optimizing parameters...")
|
|
123
|
+
self._determine_optimization_strategy()
|
|
124
|
+
|
|
125
|
+
# Step 5: Calculate final ELDF and PDF
|
|
126
|
+
self.logger.info("Computing final ELDF and PDF values...")
|
|
127
|
+
self._compute_final_results()
|
|
128
|
+
|
|
129
|
+
# Step 6: Generate smooth curves for plotting and analysis
|
|
130
|
+
self.logger.info("Generating smooth curves for analysis...")
|
|
131
|
+
self._generate_smooth_curves()
|
|
132
|
+
|
|
133
|
+
# Step 7: Transform bounds back to original domain
|
|
134
|
+
|
|
135
|
+
self._transform_bounds_to_original_domain()
|
|
136
|
+
self.logger.info("Transformed bounds back to original data domain.")
|
|
137
|
+
# Mark as fitted (Step 8 is now optional via marginal_analysis())
|
|
138
|
+
self._fitted = True
|
|
139
|
+
|
|
140
|
+
# Step 8: Z0 estimate with Z0Estimator
|
|
141
|
+
self.logger.info("Estimating Z0...")
|
|
142
|
+
self._compute_z0(optimize=self.z0_optimize)
|
|
143
|
+
|
|
144
|
+
# step 9: varS
|
|
145
|
+
if self.varS:
|
|
146
|
+
self.logger.info("Estimating varying S...")
|
|
147
|
+
self._varS_calculation()
|
|
148
|
+
self.logger.info("Computing final results for varying S...")
|
|
149
|
+
self._compute_final_results_varS()
|
|
150
|
+
self.logger.info("Generating smooth curves for varying S...")
|
|
151
|
+
self._generate_smooth_curves_varS()
|
|
152
|
+
|
|
153
|
+
# Step 10: Z0 re-estimate with varS if enabled
|
|
154
|
+
if self.varS:
|
|
155
|
+
self.logger.info("Re-estimating Z0 with varying S...")
|
|
156
|
+
self._compute_z0(optimize=self.z0_optimize)
|
|
157
|
+
|
|
158
|
+
self.logger.info("ELDF fitting completed successfully.")
|
|
159
|
+
|
|
160
|
+
if plot:
|
|
161
|
+
self.logger.info("Plotting results...")
|
|
162
|
+
self._plot()
|
|
163
|
+
|
|
164
|
+
# clean up computation cache
|
|
165
|
+
if self.flush:
|
|
166
|
+
self.logger.info("Cleaning up computation cache...")
|
|
167
|
+
self._cleanup_computation_cache()
|
|
168
|
+
|
|
169
|
+
except Exception as e:
|
|
170
|
+
# log error
|
|
171
|
+
error_msg = f"ELDF fitting failed: {e}"
|
|
172
|
+
self.logger.error(error_msg)
|
|
173
|
+
self.params['errors'].append({
|
|
174
|
+
'method': '_fit_eldf',
|
|
175
|
+
'error': error_msg,
|
|
176
|
+
'exception_type': type(e).__name__
|
|
177
|
+
})
|
|
178
|
+
self.logger.info(f"Error during ELDF fitting: {e}")
|
|
179
|
+
raise e
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _compute_eldf_core(self, S, LB, UB, zi_data=None, zi_eval=None):
|
|
183
|
+
"""Core computation for the ELDF model."""
|
|
184
|
+
self.logger.info("Computing core ELDF values...")
|
|
185
|
+
# Use provided data or default to instance data
|
|
186
|
+
if zi_data is None:
|
|
187
|
+
zi_data = self.z
|
|
188
|
+
if zi_eval is None:
|
|
189
|
+
zi_eval = zi_data
|
|
190
|
+
|
|
191
|
+
# Convert to infinite domain
|
|
192
|
+
zi_n = DataConversion._convert_fininf(zi_eval, LB, UB)
|
|
193
|
+
zi_d = DataConversion._convert_fininf(zi_data, LB, UB)
|
|
194
|
+
|
|
195
|
+
# Calculate R matrix with numerical stability
|
|
196
|
+
R = zi_n.reshape(-1, 1) / (zi_d.reshape(1, -1) + self._NUMERICAL_EPS)
|
|
197
|
+
|
|
198
|
+
# Get characteristics
|
|
199
|
+
gc = GnosticsCharacteristics(R=R, verbose=self.verbose)
|
|
200
|
+
q, q1 = gc._get_q_q1(S=S)
|
|
201
|
+
|
|
202
|
+
# Calculate fidelities and irrelevances
|
|
203
|
+
fi = gc._fi(q=q, q1=q1)
|
|
204
|
+
hi = gc._hi(q=q, q1=q1)
|
|
205
|
+
return self._estimate_eldf_from_moments(fi, hi), fi, hi
|
|
206
|
+
|
|
207
|
+
def _estimate_eldf_from_moments(self, fidelity, irrelevance):
|
|
208
|
+
"""Estimate the ELDF from moments."""
|
|
209
|
+
self.logger.info("Estimating ELDF from moments...")
|
|
210
|
+
weights = self.weights.reshape(-1, 1)
|
|
211
|
+
|
|
212
|
+
mean_irrelevance = np.sum(weights * irrelevance, axis=0) / np.sum(weights)
|
|
213
|
+
|
|
214
|
+
eldf_values = (1 - mean_irrelevance) / 2
|
|
215
|
+
|
|
216
|
+
return eldf_values.flatten()
|
|
217
|
+
|
|
218
|
+
def _compute_final_results(self):
|
|
219
|
+
"""Compute the final results for the ELDF model."""
|
|
220
|
+
self.logger.info("Computing final ELDF and PDF results...")
|
|
221
|
+
# Implement final results computation logic here
|
|
222
|
+
zi_d = DataConversion._convert_fininf(self.z, self.LB_opt, self.UB_opt)
|
|
223
|
+
self.zi = zi_d
|
|
224
|
+
|
|
225
|
+
# Calculate ELDF and get moments
|
|
226
|
+
eldf_values, fi, hi = self._compute_eldf_core(self.S_opt, self.LB_opt, self.UB_opt)
|
|
227
|
+
|
|
228
|
+
# Store for derivative calculations
|
|
229
|
+
self.fi = fi
|
|
230
|
+
self.hi = hi
|
|
231
|
+
|
|
232
|
+
self.eldf = eldf_values
|
|
233
|
+
self.pdf = self._compute_eldf_pdf(self.fi, self.hi)
|
|
234
|
+
|
|
235
|
+
if self.catch:
|
|
236
|
+
self.logger.info("Catching parameters...")
|
|
237
|
+
self.params.update({
|
|
238
|
+
'eldf': self.eldf.copy(),
|
|
239
|
+
'pdf': self.pdf.copy(),
|
|
240
|
+
'zi': self.zi.copy(),
|
|
241
|
+
# 'S_var': self.S_var.copy() if self.varS else None
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
def _compute_eldf_pdf(self, fi, hi):
|
|
245
|
+
"""Compute the PDF for the ELDF model."""
|
|
246
|
+
self.logger.info("Computing PDF from ELDF moments...")
|
|
247
|
+
weights = self.weights.reshape(-1, 1)
|
|
248
|
+
|
|
249
|
+
# fi_mean
|
|
250
|
+
fi_mean = np.sum(weights * fi, axis=0) / np.sum(weights)
|
|
251
|
+
pdf_values = ((fi_mean)**2)/(self.S_opt)
|
|
252
|
+
return pdf_values.flatten()
|
|
253
|
+
|
|
254
|
+
def _generate_smooth_curves(self):
|
|
255
|
+
"""Generate smooth curves for plotting and analysis - ELDF."""
|
|
256
|
+
self.logger.info("Generating smooth curves for ELDF...")
|
|
257
|
+
try:
|
|
258
|
+
self.logger.info("Generating smooth curves without varying S...")
|
|
259
|
+
|
|
260
|
+
smooth_eldf, self.smooth_fi, self.smooth_hi = self._compute_eldf_core(
|
|
261
|
+
self.S_opt, self.LB_opt, self.UB_opt,
|
|
262
|
+
zi_data=self.z_points_n, zi_eval=self.z
|
|
263
|
+
)
|
|
264
|
+
smooth_pdf = self._compute_eldf_pdf(self.smooth_fi, self.smooth_hi)
|
|
265
|
+
|
|
266
|
+
self.eldf_points = smooth_eldf
|
|
267
|
+
self.pdf_points = smooth_pdf
|
|
268
|
+
|
|
269
|
+
# Store zi_n for derivative calculations
|
|
270
|
+
self.zi_n = DataConversion._convert_fininf(self.z_points_n, self.LB_opt, self.UB_opt)
|
|
271
|
+
|
|
272
|
+
# Mark as generated
|
|
273
|
+
self._computation_cache['smooth_curves_generated'] = True
|
|
274
|
+
|
|
275
|
+
if self.catch:
|
|
276
|
+
self.logger.info("Catching parameters...")
|
|
277
|
+
self.params.update({
|
|
278
|
+
'eldf_points': self.eldf_points.copy(),
|
|
279
|
+
'pdf_points': self.pdf_points.copy(),
|
|
280
|
+
'zi_points': self.zi_n.copy()
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
self.logger.info(f"Generated smooth curves with {self.n_points} points.")
|
|
284
|
+
|
|
285
|
+
except Exception as e:
|
|
286
|
+
# log error
|
|
287
|
+
error_msg = f"Smooth curve generation failed: {e}"
|
|
288
|
+
self.logger.error(error_msg)
|
|
289
|
+
self.params['errors'].append({
|
|
290
|
+
'method': '_generate_smooth_curves',
|
|
291
|
+
'error': error_msg,
|
|
292
|
+
'exception_type': type(e).__name__
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
self.logger.warning(f"Could not generate smooth curves: {e}")
|
|
296
|
+
|
|
297
|
+
# Create fallback points using original data
|
|
298
|
+
self.eldf_points = self.eldf.copy() if hasattr(self, 'eldf') else None
|
|
299
|
+
self.pdf_points = self.pdf.copy() if hasattr(self, 'pdf') else None
|
|
300
|
+
self._computation_cache['smooth_curves_generated'] = False
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def _plot(self, plot_smooth: bool = True, plot: str = 'both', bounds: bool = True, extra_df: bool = True, figsize: tuple = (12, 8)):
|
|
304
|
+
"""Enhanced plotting with better organization."""
|
|
305
|
+
self.logger.info("Preparing to plot ELDF and PDF...")
|
|
306
|
+
import matplotlib.pyplot as plt
|
|
307
|
+
|
|
308
|
+
if plot_smooth and (len(self.data) > self.max_data_size) and self.verbose:
|
|
309
|
+
self.logger.warning(f"Given data size ({len(self.data)}) exceeds max_data_size ({self.max_data_size}). For optimal compute performance, set 'plot_smooth=False', or 'max_data_size' to a larger value whichever is appropriate.")
|
|
310
|
+
|
|
311
|
+
if not self.catch:
|
|
312
|
+
self.logger.warning("Plot is not available with argument catch=False")
|
|
313
|
+
return
|
|
314
|
+
|
|
315
|
+
if not self._fitted:
|
|
316
|
+
raise RuntimeError("Must fit ELDF before plotting.")
|
|
317
|
+
|
|
318
|
+
# Validate plot parameter
|
|
319
|
+
if plot not in ['gdf', 'pdf', 'both']:
|
|
320
|
+
raise ValueError("plot parameter must be 'gdf', 'pdf', or 'both'")
|
|
321
|
+
|
|
322
|
+
# Check data availability
|
|
323
|
+
if plot in ['gdf', 'both'] and self.params.get('eldf') is None:
|
|
324
|
+
raise ValueError("ELDF must be calculated before plotting GDF")
|
|
325
|
+
if plot in ['pdf', 'both'] and self.params.get('pdf') is None:
|
|
326
|
+
raise ValueError("PDF must be calculated before plotting PDF")
|
|
327
|
+
|
|
328
|
+
# Prepare data
|
|
329
|
+
x_points = self.data
|
|
330
|
+
eldf_plot = self.params.get('eldf')
|
|
331
|
+
pdf_plot = self.params.get('pdf')
|
|
332
|
+
wedf = self.params.get('wedf')
|
|
333
|
+
ksdf = self.params.get('ksdf')
|
|
334
|
+
|
|
335
|
+
# Check smooth plotting availability
|
|
336
|
+
has_smooth = (hasattr(self, 'z_points_n') and hasattr(self, 'eldf_points')
|
|
337
|
+
and hasattr(self, 'pdf_points') and self.z_points_n is not None
|
|
338
|
+
and self.eldf_points is not None and self.pdf_points is not None)
|
|
339
|
+
plot_smooth = plot_smooth and has_smooth
|
|
340
|
+
|
|
341
|
+
# Create figure
|
|
342
|
+
fig, ax1 = plt.subplots(figsize=figsize)
|
|
343
|
+
|
|
344
|
+
# Plot ELDF if requested
|
|
345
|
+
if plot in ['gdf', 'both']:
|
|
346
|
+
self._plot_eldf(ax1, x_points, eldf_plot, plot_smooth, extra_df, wedf, ksdf)
|
|
347
|
+
|
|
348
|
+
# Plot PDF if requested
|
|
349
|
+
if plot in ['pdf', 'both']:
|
|
350
|
+
if plot == 'pdf':
|
|
351
|
+
self._plot_pdf(ax1, x_points, pdf_plot, plot_smooth, is_secondary=False)
|
|
352
|
+
else:
|
|
353
|
+
ax2 = ax1.twinx()
|
|
354
|
+
self._plot_pdf(ax2, x_points, pdf_plot, plot_smooth, is_secondary=True)
|
|
355
|
+
|
|
356
|
+
# Add bounds and formatting
|
|
357
|
+
self._add_plot_formatting(ax1, plot, bounds)
|
|
358
|
+
|
|
359
|
+
# Add Z0 vertical line if available
|
|
360
|
+
if hasattr(self, 'z0') and self.z0 is not None:
|
|
361
|
+
ax1.axvline(x=self.z0, color='magenta', linestyle='-.', linewidth=1,
|
|
362
|
+
alpha=0.8, label=f'Z0={self.z0:.3f}')
|
|
363
|
+
# Update legend to include Z0
|
|
364
|
+
ax1.legend(loc='upper left', bbox_to_anchor=(0, 1))
|
|
365
|
+
|
|
366
|
+
plt.tight_layout()
|
|
367
|
+
plt.show()
|
|
368
|
+
|
|
369
|
+
def _plot_pdf(self, ax, x_points, pdf_plot, plot_smooth, is_secondary=False):
|
|
370
|
+
"""Plot PDF components."""
|
|
371
|
+
self.logger.info("Plotting PDF...")
|
|
372
|
+
import numpy as np # Add numpy import
|
|
373
|
+
color = 'red'
|
|
374
|
+
|
|
375
|
+
if plot_smooth and hasattr(self, 'pdf_points') and self.pdf_points is not None:
|
|
376
|
+
ax.plot(x_points, pdf_plot, 'o', color=color, label='PDF', markersize=4)
|
|
377
|
+
ax.plot(self.di_points_n, self.pdf_points, color=color,
|
|
378
|
+
linestyle='-', linewidth=2, alpha=0.8)
|
|
379
|
+
max_pdf = np.max(self.pdf_points)
|
|
380
|
+
else:
|
|
381
|
+
ax.plot(x_points, pdf_plot, 'o-', color=color, label='PDF',
|
|
382
|
+
markersize=4, linewidth=1, alpha=0.8)
|
|
383
|
+
max_pdf = np.max(pdf_plot)
|
|
384
|
+
|
|
385
|
+
ax.set_ylabel('PDF', color=color)
|
|
386
|
+
ax.tick_params(axis='y', labelcolor=color)
|
|
387
|
+
ax.set_ylim(0, max_pdf * 1.1)
|
|
388
|
+
|
|
389
|
+
if is_secondary:
|
|
390
|
+
ax.legend(loc='upper right', bbox_to_anchor=(1, 1))
|
|
391
|
+
|
|
392
|
+
def _plot_eldf(self, ax, x_points, eldf_plot, plot_smooth, extra_df, wedf, ksdf):
|
|
393
|
+
"""Plot ELDF components."""
|
|
394
|
+
self.logger.info("Plotting ELDF...")
|
|
395
|
+
if plot_smooth and hasattr(self, 'eldf_points') and self.eldf_points is not None:
|
|
396
|
+
ax.plot(x_points, eldf_plot, 'o', color='blue', label='ELDF', markersize=4)
|
|
397
|
+
ax.plot(self.di_points_n, self.eldf_points, color='blue',
|
|
398
|
+
linestyle='-', linewidth=2, alpha=0.8)
|
|
399
|
+
else:
|
|
400
|
+
ax.plot(x_points, eldf_plot, 'o-', color='blue', label='ELDF',
|
|
401
|
+
markersize=4, linewidth=1, alpha=0.8)
|
|
402
|
+
|
|
403
|
+
if extra_df:
|
|
404
|
+
if wedf is not None:
|
|
405
|
+
ax.plot(x_points, wedf, 's', color='lightblue',
|
|
406
|
+
label='WEDF', markersize=3, alpha=0.8)
|
|
407
|
+
if ksdf is not None:
|
|
408
|
+
ax.plot(x_points, ksdf, 's', color='cyan',
|
|
409
|
+
label='KS Points', markersize=3, alpha=0.8)
|
|
410
|
+
|
|
411
|
+
ax.set_ylabel('ELDF', color='blue')
|
|
412
|
+
ax.tick_params(axis='y', labelcolor='blue')
|
|
413
|
+
ax.set_ylim(0, 1)
|
|
414
|
+
|
|
415
|
+
def _add_plot_formatting(self, ax1, plot, bounds):
|
|
416
|
+
"""Add formatting, bounds, and legends to plot."""
|
|
417
|
+
self.logger.info("Adding plot formatting and bounds...")
|
|
418
|
+
ax1.set_xlabel('Data Points')
|
|
419
|
+
|
|
420
|
+
# Add bounds if requested
|
|
421
|
+
if bounds:
|
|
422
|
+
bound_info = [
|
|
423
|
+
(self.params.get('DLB'), 'green', '-', 'DLB'),
|
|
424
|
+
(self.params.get('DUB'), 'orange', '-', 'DUB'),
|
|
425
|
+
(self.params.get('LB'), 'purple', '--', 'LB'),
|
|
426
|
+
(self.params.get('UB'), 'brown', '--', 'UB')
|
|
427
|
+
]
|
|
428
|
+
|
|
429
|
+
for bound, color, style, name in bound_info:
|
|
430
|
+
if bound is not None:
|
|
431
|
+
ax1.axvline(x=bound, color=color, linestyle=style, linewidth=2,
|
|
432
|
+
alpha=0.8, label=f"{name}={bound:.3f}")
|
|
433
|
+
|
|
434
|
+
# Add shaded regions
|
|
435
|
+
if self.params.get('LB') is not None:
|
|
436
|
+
ax1.axvspan(self.data.min(), self.params['LB'], alpha=0.15, color='purple')
|
|
437
|
+
if self.params.get('UB') is not None:
|
|
438
|
+
ax1.axvspan(self.params['UB'], self.data.max(), alpha=0.15, color='brown')
|
|
439
|
+
|
|
440
|
+
# Set limits and add grid
|
|
441
|
+
data_range = self.params['DUB'] - self.params['DLB']
|
|
442
|
+
padding = data_range * 0.1
|
|
443
|
+
ax1.set_xlim(self.params['DLB'] - padding, self.params['DUB'] + padding)
|
|
444
|
+
|
|
445
|
+
# Set title
|
|
446
|
+
titles = {
|
|
447
|
+
'gdf': 'ELDF' + (' with Bounds' if bounds else ''),
|
|
448
|
+
'pdf': 'PDF' + (' with Bounds' if bounds else ''),
|
|
449
|
+
'both': 'ELDF and PDF' + (' with Bounds' if bounds else '')
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
ax1.set_title(titles[plot])
|
|
453
|
+
ax1.legend(loc='upper left', bbox_to_anchor=(0, 1))
|
|
454
|
+
ax1.grid(True, alpha=0.3)
|
|
455
|
+
|
|
456
|
+
def _get_eldf_second_derivative(self, fi, hi):
|
|
457
|
+
"""Calculate second derivative of ELDF from stored fidelities and irrelevances."""
|
|
458
|
+
self.logger.info("Calculating second derivative of ELDF...")
|
|
459
|
+
if fi is None or hi is None:
|
|
460
|
+
fi = self.fi
|
|
461
|
+
hi = self.hi
|
|
462
|
+
|
|
463
|
+
if fi is None or hi is None:
|
|
464
|
+
raise ValueError("Fidelities and irrelevances must be calculated before second derivative estimation.")
|
|
465
|
+
|
|
466
|
+
weights = self.weights.reshape(-1, 1)
|
|
467
|
+
|
|
468
|
+
# Calculate f^2 * h (fidelity squared times irrelevance)
|
|
469
|
+
fh = (self.fi**2) * self.hi
|
|
470
|
+
|
|
471
|
+
# Weight and scale by S^2
|
|
472
|
+
weighted_fh = fh * (weights / (self.S_opt**2))
|
|
473
|
+
|
|
474
|
+
# Sum over data points
|
|
475
|
+
second_derivative = 4 * np.sum(weighted_fh, axis=0) / np.sum(weights)
|
|
476
|
+
|
|
477
|
+
return second_derivative.flatten()
|
|
478
|
+
|
|
479
|
+
def _get_eldf_third_derivative(self, fi, hi):
|
|
480
|
+
"""Calculate third derivative of ELDF from stored fidelities and irrelevances."""
|
|
481
|
+
self.logger.info("Calculating third derivative of ELDF...")
|
|
482
|
+
if fi is None or hi is None:
|
|
483
|
+
fi = self.fi
|
|
484
|
+
hi = self.hi
|
|
485
|
+
|
|
486
|
+
if fi is None or hi is None:
|
|
487
|
+
raise ValueError("Fidelities and irrelevances must be calculated before third derivative estimation.")
|
|
488
|
+
|
|
489
|
+
weights = self.weights.reshape(-1, 1)
|
|
490
|
+
|
|
491
|
+
# Calculate components
|
|
492
|
+
h2 = self.hi**2 # h^2
|
|
493
|
+
f2 = self.fi**2 # f^2
|
|
494
|
+
f2h2 = f2 * h2 # f^2 * h^2
|
|
495
|
+
f4 = f2**2 # f^4
|
|
496
|
+
|
|
497
|
+
# Calculate the expression: 8 * (2 * f^2 * h^2 - f^4) * (W / S^3)
|
|
498
|
+
expression = 2 * f2h2 - f4
|
|
499
|
+
weighted_expression = expression * (weights / (self.S_opt**3))
|
|
500
|
+
|
|
501
|
+
# Sum over data points
|
|
502
|
+
third_derivative = 8 * np.sum(weighted_expression, axis=0) / np.sum(weights)
|
|
503
|
+
|
|
504
|
+
return third_derivative.flatten()
|
|
505
|
+
|
|
506
|
+
def _get_eldf_fourth_derivative(self, fi, hi):
|
|
507
|
+
"""Calculate fourth derivative of ELDF from stored fidelities and irrelevances."""
|
|
508
|
+
self.logger.info("Calculating fourth derivative of ELDF...")
|
|
509
|
+
if fi is None or hi is None:
|
|
510
|
+
fi = self.fi
|
|
511
|
+
hi = self.hi
|
|
512
|
+
|
|
513
|
+
if self.fi is None or self.hi is None:
|
|
514
|
+
raise ValueError("Fidelities and irrelevances must be calculated before fourth derivative estimation.")
|
|
515
|
+
|
|
516
|
+
weights = self.weights.reshape(-1, 1)
|
|
517
|
+
|
|
518
|
+
# Calculate components
|
|
519
|
+
f2 = self.fi**2 # f^2
|
|
520
|
+
h = self.hi # h
|
|
521
|
+
h3 = h**3 # h^3
|
|
522
|
+
|
|
523
|
+
# Calculate f^2 * h^3 and f^4 * h
|
|
524
|
+
f2h3 = f2 * h3
|
|
525
|
+
f4h = (f2**2) * h # f^4 * h
|
|
526
|
+
|
|
527
|
+
# Weight and scale by (2/S)^4
|
|
528
|
+
scale_factor = (2 / self.S_opt)**4
|
|
529
|
+
weighted_f2h3 = f2h3 * (weights * scale_factor)
|
|
530
|
+
weighted_f4h = f4h * (weights * scale_factor)
|
|
531
|
+
|
|
532
|
+
# Calculate the expression: 4 * (f^2 * h^3 - 2 * f^4 * h)
|
|
533
|
+
sum_f2h3 = np.sum(weighted_f2h3, axis=0) / np.sum(weights)
|
|
534
|
+
sum_f4h = np.sum(weighted_f4h, axis=0) / np.sum(weights)
|
|
535
|
+
|
|
536
|
+
fourth_derivative = 4 * (sum_f2h3 - 2 * sum_f4h)
|
|
537
|
+
|
|
538
|
+
return fourth_derivative.flatten()
|
|
539
|
+
|
|
540
|
+
def _get_results(self)-> dict:
|
|
541
|
+
"""Return fitting results."""
|
|
542
|
+
self.logger.info("Retrieving ELDF fitting results...")
|
|
543
|
+
if not self._fitted:
|
|
544
|
+
raise RuntimeError("Must fit ELDF before getting results.")
|
|
545
|
+
|
|
546
|
+
# selected key from params if exists
|
|
547
|
+
keys = ['DLB', 'DUB', 'LB', 'UB', 'S_opt', 'z0', 'eldf', 'pdf',
|
|
548
|
+
'eldf_points', 'pdf_points', 'zi', 'zi_points', 'weights']
|
|
549
|
+
results = {key: self.params.get(key) for key in keys if key in self.params}
|
|
550
|
+
return results
|
|
551
|
+
|
|
552
|
+
# z0 compute
|
|
553
|
+
def _compute_z0(self, optimize: bool = None):
|
|
554
|
+
"""
|
|
555
|
+
Compute the Z0 point where PDF is maximum using the Z0Estimator class.
|
|
556
|
+
|
|
557
|
+
Parameters:
|
|
558
|
+
-----------
|
|
559
|
+
optimize : bool, optional
|
|
560
|
+
If True, use interpolation-based methods for higher accuracy.
|
|
561
|
+
If False, use simple linear search on existing points.
|
|
562
|
+
If None, uses the instance's z0_optimize setting.
|
|
563
|
+
"""
|
|
564
|
+
self.logger.info("Computing Z0 point using Z0Estimator...")
|
|
565
|
+
if self.z is None:
|
|
566
|
+
self.logger.error("Data must be transformed (self.z) before Z0 estimation.")
|
|
567
|
+
raise ValueError("Data must be transformed (self.z) before Z0 estimation.")
|
|
568
|
+
|
|
569
|
+
# Use provided optimize parameter or fall back to instance setting
|
|
570
|
+
use_optimize = optimize if optimize is not None else self.z0_optimize
|
|
571
|
+
|
|
572
|
+
self.logger.info('ELDF: Computing Z0 point using Z0Estimator...')
|
|
573
|
+
|
|
574
|
+
try:
|
|
575
|
+
# Create Z0Estimator instance with proper constructor signature
|
|
576
|
+
z0_estimator = Z0Estimator(
|
|
577
|
+
gdf_object=self, # Pass the ELDF object itself
|
|
578
|
+
optimize=use_optimize,
|
|
579
|
+
verbose=self.verbose
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
# Call fit() method to estimate Z0
|
|
583
|
+
self.z0 = z0_estimator.fit()
|
|
584
|
+
|
|
585
|
+
# Get estimation info for debugging and storage
|
|
586
|
+
if self.catch:
|
|
587
|
+
estimation_info = z0_estimator.get_estimation_info()
|
|
588
|
+
self.params.update({
|
|
589
|
+
'z0': float(self.z0) if self.z0 is not None else None,
|
|
590
|
+
'z0_method': estimation_info.get('z0_method', 'unknown'),
|
|
591
|
+
'z0_estimation_info': estimation_info
|
|
592
|
+
})
|
|
593
|
+
|
|
594
|
+
method_used = z0_estimator.get_estimation_info().get('z0_method', 'unknown')
|
|
595
|
+
self.logger.info(f'ELDF: Z0 point computed successfully, (method: {method_used})')
|
|
596
|
+
|
|
597
|
+
except Exception as e:
|
|
598
|
+
# Log the error
|
|
599
|
+
error_msg = f"Z0 estimation failed: {str(e)}"
|
|
600
|
+
self.logger.error(error_msg)
|
|
601
|
+
self.params['errors'].append({
|
|
602
|
+
'method': '_compute_z0',
|
|
603
|
+
'error': error_msg,
|
|
604
|
+
'exception_type': type(e).__name__
|
|
605
|
+
})
|
|
606
|
+
|
|
607
|
+
self.logger.warning(f"Warning: Z0Estimator failed with error: {e}")
|
|
608
|
+
self.logger.info("Falling back to simple maximum finding...")
|
|
609
|
+
|
|
610
|
+
# Fallback to simple maximum finding
|
|
611
|
+
self._compute_z0_fallback()
|
|
612
|
+
|
|
613
|
+
if self.catch:
|
|
614
|
+
self.params.update({
|
|
615
|
+
'z0': float(self.z0),
|
|
616
|
+
'z0_method': 'fallback_simple_maximum',
|
|
617
|
+
'z0_estimation_info': {'error': str(e)}
|
|
618
|
+
})
|
|
619
|
+
|
|
620
|
+
def _compute_z0_fallback(self):
|
|
621
|
+
"""
|
|
622
|
+
Fallback method for Z0 computation using simple maximum finding.
|
|
623
|
+
"""
|
|
624
|
+
self.logger.info("Using fallback method for Z0 point...")
|
|
625
|
+
|
|
626
|
+
if not hasattr(self, 'di_points_n') or not hasattr(self, 'pdf_points'):
|
|
627
|
+
self.logger.error("Both 'di_points_n' and 'pdf_points' must be defined for Z0 computation.")
|
|
628
|
+
raise ValueError("Both 'di_points_n' and 'pdf_points' must be defined for Z0 computation.")
|
|
629
|
+
|
|
630
|
+
self.logger.info('Using fallback method for Z0 point...')
|
|
631
|
+
|
|
632
|
+
# Find index with maximum PDF
|
|
633
|
+
max_idx = np.argmax(self.pdf_points)
|
|
634
|
+
self.z0 = self.di_points_n[max_idx]
|
|
635
|
+
|
|
636
|
+
self.logger.info(f"Z0 point (fallback method).")
|
|
637
|
+
|
|
638
|
+
def analyze_z0(self, figsize: tuple = (12, 6)) -> Dict[str, Any]:
|
|
639
|
+
"""
|
|
640
|
+
Analyze and visualize Z0 estimation results.
|
|
641
|
+
|
|
642
|
+
Parameters:
|
|
643
|
+
-----------
|
|
644
|
+
figsize : tuple
|
|
645
|
+
Figure size for the plot
|
|
646
|
+
|
|
647
|
+
Returns:
|
|
648
|
+
--------
|
|
649
|
+
Dict[str, Any]
|
|
650
|
+
Z0 analysis information
|
|
651
|
+
"""
|
|
652
|
+
self.logger.info("Analyzing Z0 estimation results...")
|
|
653
|
+
if not hasattr(self, 'z0') or self.z0 is None:
|
|
654
|
+
self.logger.error("Z0 must be computed before analysis. Call fit() first.")
|
|
655
|
+
raise ValueError("Z0 must be computed before analysis. Call fit() first.")
|
|
656
|
+
|
|
657
|
+
# Create Z0Estimator for analysis
|
|
658
|
+
z0_estimator = Z0Estimator(
|
|
659
|
+
gdf_object=self,
|
|
660
|
+
optimize=self.z0_optimize,
|
|
661
|
+
verbose=self.verbose
|
|
662
|
+
)
|
|
663
|
+
|
|
664
|
+
# Re-estimate for analysis (this is safe since it's already computed)
|
|
665
|
+
z0_estimator.fit()
|
|
666
|
+
|
|
667
|
+
# Get detailed info
|
|
668
|
+
analysis_info = z0_estimator.get_estimation_info()
|
|
669
|
+
|
|
670
|
+
# Create visualization
|
|
671
|
+
z0_estimator.plot_z0_analysis(figsize=figsize)
|
|
672
|
+
|
|
673
|
+
return analysis_info
|
|
674
|
+
|
|
675
|
+
def _calculate_fidelities_irrelevances_at_given_zi(self, zi):
|
|
676
|
+
"""Helper method to recalculate fidelities and irrelevances for current zi."""
|
|
677
|
+
self.logger.info("Calculating fidelities and irrelevances at given zi...")
|
|
678
|
+
# Convert to infinite domain
|
|
679
|
+
zi_n = DataConversion._convert_fininf(self.z, self.LB_opt, self.UB_opt)
|
|
680
|
+
# is zi given then use it, else use self.zi
|
|
681
|
+
if zi is None:
|
|
682
|
+
zi_d = self.zi
|
|
683
|
+
else:
|
|
684
|
+
zi_d = zi
|
|
685
|
+
|
|
686
|
+
# Calculate R matrix
|
|
687
|
+
eps = np.finfo(float).eps
|
|
688
|
+
R = zi_n.reshape(-1, 1) / (zi_d + eps).reshape(1, -1)
|
|
689
|
+
|
|
690
|
+
# Get characteristics
|
|
691
|
+
gc = GnosticsCharacteristics(R=R, verbose=self.verbose)
|
|
692
|
+
q, q1 = gc._get_q_q1(S=self.S_opt)
|
|
693
|
+
|
|
694
|
+
# Store fidelities and irrelevances
|
|
695
|
+
self.fi = gc._fi(q=q, q1=q1)
|
|
696
|
+
self.hi = gc._hi(q=q, q1=q1)
|
|
697
|
+
|
|
698
|
+
def _estimate_s0_sigma(self, z0, s_local, s_global, mode="sum"):
|
|
699
|
+
"""
|
|
700
|
+
Estimate S0 and sigma given z0 (float), s_local (array), and s_global (float).
|
|
701
|
+
|
|
702
|
+
Parameters
|
|
703
|
+
----------
|
|
704
|
+
z0 : float
|
|
705
|
+
Mean value of the data.
|
|
706
|
+
s_local : array-like
|
|
707
|
+
Local scale parameters.
|
|
708
|
+
s_global : float
|
|
709
|
+
Global scale parameter.
|
|
710
|
+
mode : str
|
|
711
|
+
"mean" -> match average predicted to s_global
|
|
712
|
+
"sum" -> match sum predicted to s_global
|
|
713
|
+
|
|
714
|
+
Returns
|
|
715
|
+
-------
|
|
716
|
+
S0, sigma : floats
|
|
717
|
+
Estimated parameters.
|
|
718
|
+
"""
|
|
719
|
+
self.logger.info("Estimating S0 and sigma...")
|
|
720
|
+
s_local = np.asarray(s_local)
|
|
721
|
+
|
|
722
|
+
def objective(params):
|
|
723
|
+
S0, sigma = params
|
|
724
|
+
preds = S0 * np.exp(sigma * z0) * s_local
|
|
725
|
+
if mode == "mean":
|
|
726
|
+
target = preds.mean()
|
|
727
|
+
elif mode == "sum":
|
|
728
|
+
target = preds.sum()
|
|
729
|
+
else:
|
|
730
|
+
raise ValueError("mode must be 'mean' or 'sum'")
|
|
731
|
+
return (s_global - target) ** 2
|
|
732
|
+
|
|
733
|
+
# Initial guess
|
|
734
|
+
p0 = [1.0, 0.0]
|
|
735
|
+
|
|
736
|
+
res = minimize(objective, p0, method="Nelder-Mead")
|
|
737
|
+
return res.x[0], res.x[1]
|
|
738
|
+
|
|
739
|
+
def _varS_calculation(self):
|
|
740
|
+
"""Calculate varS if enabled."""
|
|
741
|
+
self.logger.info("Calculating varying S (varS)...")
|
|
742
|
+
from machinegnostics import variance
|
|
743
|
+
|
|
744
|
+
self.logger.info("Calculating varS for ELDF...")
|
|
745
|
+
# estimate fi hi at z0
|
|
746
|
+
gc, q, q1 = self._calculate_gcq_at_given_zi(self.z0)
|
|
747
|
+
|
|
748
|
+
fi_z0 = gc._fi(q=q, q1=q1)
|
|
749
|
+
|
|
750
|
+
scale = ScaleParam()
|
|
751
|
+
self.S_local = scale._gscale_loc(fi_z0)
|
|
752
|
+
|
|
753
|
+
# # s0 # NOTE for future exploration
|
|
754
|
+
# self.S0, self.sigma = self._estimate_s0_sigma(
|
|
755
|
+
# z0=self.z0,
|
|
756
|
+
# s_local=fi_z0,
|
|
757
|
+
# s_global=self.S_opt,
|
|
758
|
+
# mode="sum"
|
|
759
|
+
# )
|
|
760
|
+
|
|
761
|
+
# Svar
|
|
762
|
+
self.S_var = self.S_local * self.S_opt
|
|
763
|
+
return self.S_var
|
|
764
|
+
|
|
765
|
+
def _compute_final_results_varS(self):
|
|
766
|
+
"""Compute the final results for the ELDF model."""
|
|
767
|
+
self.logger.info("Computing final ELDF and PDF results with varying S...")
|
|
768
|
+
# Implement final results computation logic here
|
|
769
|
+
# zi_d = DataConversion._convert_fininf(self.z, self.LB_opt, self.UB_opt)
|
|
770
|
+
# self.zi = zi_d
|
|
771
|
+
|
|
772
|
+
eldf_values, fi, hi = self._compute_eldf_core(self.S_var, self.LB_opt, self.UB_opt)
|
|
773
|
+
self.fi = fi
|
|
774
|
+
self.hi = hi
|
|
775
|
+
|
|
776
|
+
self.eldf = eldf_values
|
|
777
|
+
self.pdf = self._compute_eldf_pdf(self.fi, self.hi)
|
|
778
|
+
|
|
779
|
+
if self.catch:
|
|
780
|
+
self.logger.info("Catching parameters with varying S...")
|
|
781
|
+
self.params.update({
|
|
782
|
+
'eldf': self.eldf.copy(),
|
|
783
|
+
'pdf': self.pdf.copy(),
|
|
784
|
+
'zi': self.zi.copy(),
|
|
785
|
+
'S_var': self.S_var.copy() if self.varS else None
|
|
786
|
+
})
|
|
787
|
+
|
|
788
|
+
def _generate_smooth_curves_varS(self):
|
|
789
|
+
"""Generate smooth curves for plotting and analysis - ELDF."""
|
|
790
|
+
self.logger.info("Generating smooth curves for ELDF with varying S...")
|
|
791
|
+
try:
|
|
792
|
+
self.logger.info("Generating smooth curves with varying S...")
|
|
793
|
+
|
|
794
|
+
smooth_eldf, self.smooth_fi, self.smooth_hi = self._compute_eldf_core(
|
|
795
|
+
self.S_var, self.LB_opt, self.UB_opt,
|
|
796
|
+
zi_data=self.z_points_n, zi_eval=self.z
|
|
797
|
+
)
|
|
798
|
+
smooth_pdf = self._compute_eldf_pdf(self.smooth_fi, self.smooth_hi)
|
|
799
|
+
|
|
800
|
+
self.eldf_points = smooth_eldf
|
|
801
|
+
self.pdf_points = smooth_pdf
|
|
802
|
+
|
|
803
|
+
# Mark as generated
|
|
804
|
+
self._computation_cache['smooth_curves_generated'] = True
|
|
805
|
+
|
|
806
|
+
if self.catch:
|
|
807
|
+
self.params.update({
|
|
808
|
+
'eldf_points': self.eldf_points.copy(),
|
|
809
|
+
'pdf_points': self.pdf_points.copy(),
|
|
810
|
+
'zi_points': self.zi_n.copy()
|
|
811
|
+
})
|
|
812
|
+
|
|
813
|
+
self.logger.info(f"Generated smooth curves with {self.n_points} points.")
|
|
814
|
+
|
|
815
|
+
except Exception as e:
|
|
816
|
+
# log error
|
|
817
|
+
error_msg = f"Smooth curve generation failed: {e}"
|
|
818
|
+
self.logger.error(error_msg)
|
|
819
|
+
self.params['errors'].append({
|
|
820
|
+
'method': '_generate_smooth_curves_varS',
|
|
821
|
+
'error': error_msg,
|
|
822
|
+
'exception_type': type(e).__name__
|
|
823
|
+
})
|
|
824
|
+
|
|
825
|
+
self.logger.warning(f"Could not generate smooth curves: {e}")
|
|
826
|
+
|
|
827
|
+
# Create fallback points using original data
|
|
828
|
+
self.eldf_points = self.eldf.copy() if hasattr(self, 'eldf') else None
|
|
829
|
+
self.pdf_points = self.pdf.copy() if hasattr(self, 'pdf') else None
|
|
830
|
+
self._computation_cache['smooth_curves_generated'] = False
|