diff --git a/EasyReflectometryApp/Backends/Py/experiment.py b/EasyReflectometryApp/Backends/Py/experiment.py index 6a4a0eb1..4d75d52c 100644 --- a/EasyReflectometryApp/Backends/Py/experiment.py +++ b/EasyReflectometryApp/Backends/Py/experiment.py @@ -60,7 +60,11 @@ def load(self, paths: str) -> None: paths = paths.split(',') for path in paths: - self._project_logic.load_new_experiment(IO.generalizePath(path)) + generalized = IO.generalizePath(path) + if self._project_logic.count_datasets_in_file(generalized) > 1: + self._project_logic.load_all_experiments_from_file(generalized) + else: + self._project_logic.load_new_experiment(generalized) self.experimentChanged.emit() self.externalExperimentChanged.emit() pass # debug anchor diff --git a/EasyReflectometryApp/Backends/Py/logic/fitting.py b/EasyReflectometryApp/Backends/Py/logic/fitting.py index 18d683af..ad18c8aa 100644 --- a/EasyReflectometryApp/Backends/Py/logic/fitting.py +++ b/EasyReflectometryApp/Backends/Py/logic/fitting.py @@ -161,25 +161,32 @@ def prepare_threaded_fit(self, minimizers_logic: 'Minimizers') -> tuple: if minimizers_logic.max_iterations is not None: multi_fitter.easy_science_multi_fitter.max_evaluations = minimizers_logic.max_iterations - # Prepare data arrays for all experiments - x_data = [experiment.x for experiment in experiments] - y_data = [experiment.y for experiment in experiments] - - # Validate error values before computing weights to avoid division by zero + # Prepare data arrays for all experiments, masking out zero-variance points import numpy as np + x_data = [] + y_data = [] + weights = [] for idx, experiment in enumerate(experiments): - ye = experiment.ye - if np.any(ye == 0): + x_vals = np.asarray(experiment.x) + y_vals = np.asarray(experiment.y) + ye_vals = np.asarray(experiment.ye) + + # Mask out points with zero variance (same as MultiFitter.fit in EasyReflectometryLib) + valid = ye_vals > 0 + num_masked = int(np.sum(~valid)) + if num_masked > 0: exp_name = experiment.name if hasattr(experiment, 'name') else f'index {idx}' - self._fit_error_message = f'Experiment {exp_name} has zero error values which would cause division by zero' - self._running = False - self._finished = True - self._show_results_dialog = True - return None, None, None, None, None - - # ye contains variances (sigma²); weights = 1/sigma = 1/sqrt(variance) - weights = [1.0 / np.sqrt(experiment.ye) for experiment in experiments] + logger.warning( + 'Masked %d data point(s) in experiment %s due to zero variance.', + num_masked, + exp_name, + ) + + x_data.append(x_vals[valid]) + y_data.append(y_vals[valid]) + # ye contains variances (sigma²); weights = 1/sigma = 1/sqrt(variance) + weights.append(1.0 / np.sqrt(ye_vals[valid])) # Method is optional in fit() - pass None to use minimizer's default method = None diff --git a/EasyReflectometryApp/Backends/Py/logic/project.py b/EasyReflectometryApp/Backends/Py/logic/project.py index 47ee5be7..d13674c2 100644 --- a/EasyReflectometryApp/Backends/Py/logic/project.py +++ b/EasyReflectometryApp/Backends/Py/logic/project.py @@ -113,6 +113,12 @@ def load_experiment(self, path: str) -> None: def load_new_experiment(self, path: str) -> None: self._project_lib.load_new_experiment(path) + def count_datasets_in_file(self, path: str) -> int: + return self._project_lib.count_datasets_in_file(path) + + def load_all_experiments_from_file(self, path: str) -> int: + return self._project_lib.load_all_experiments_from_file(path) + def set_sample_from_orso(self, sample) -> None: self._project_lib.set_sample_from_orso(sample) diff --git a/EasyReflectometryApp/Backends/Py/plotting_1d.py b/EasyReflectometryApp/Backends/Py/plotting_1d.py index 36ee9f88..a34744b6 100644 --- a/EasyReflectometryApp/Backends/Py/plotting_1d.py +++ b/EasyReflectometryApp/Backends/Py/plotting_1d.py @@ -515,22 +515,23 @@ def getExperimentDataPoints(self, experiment_index: int) -> list: data = self._project_lib.experimental_data_for_model_at_index(experiment_index) points = [] for point in data.data_points(): - if point[0] < self._project_lib.q_max and self._project_lib.q_min < point[0]: - q = point[0] - r = point[1] - error_var = point[2] - error_lower_linear = max(r - np.sqrt(error_var), 1e-10) - r_val = self._apply_rq4(q, r) - error_upper = self._apply_rq4(q, r + np.sqrt(error_var)) - error_lower = self._apply_rq4(q, error_lower_linear) - points.append( - { - 'x': float(q), - 'y': float(np.log10(r_val)), - 'errorUpper': float(np.log10(error_upper)), - 'errorLower': float(np.log10(error_lower)), - } - ) + q = point[0] + r = point[1] + if r <= 0: + continue + error_var = point[2] + error_lower_linear = max(r - np.sqrt(error_var), 1e-10) + r_val = self._apply_rq4(q, r) + error_upper = self._apply_rq4(q, r + np.sqrt(error_var)) + error_lower = self._apply_rq4(q, error_lower_linear) + points.append( + { + 'x': float(q), + 'y': float(np.log10(r_val)), + 'errorUpper': float(np.log10(error_upper)), + 'errorLower': float(np.log10(error_lower)), + } + ) return points except Exception as e: console.debug(f'Error getting experiment data points for index {experiment_index}: {e}') @@ -678,18 +679,19 @@ def qtchartsReplaceMeasuredOnExperimentChartAndRedraw(self): series_error_lower.clear() nr_points = 0 for point in self.experiment_data.data_points(): - if point[0] < self._project_lib.q_max and self._project_lib.q_min < point[0]: - q = point[0] - r = point[1] - error_var = point[2] - error_lower_linear = max(r - np.sqrt(error_var), 1e-10) - r_val = self._apply_rq4(q, r) - error_upper = self._apply_rq4(q, r + np.sqrt(error_var)) - error_lower = self._apply_rq4(q, error_lower_linear) - series_measured.append(q, np.log10(r_val)) - series_error_upper.append(q, np.log10(error_upper)) - series_error_lower.append(q, np.log10(error_lower)) - nr_points = nr_points + 1 + q = point[0] + r = point[1] + if r <= 0: + continue + error_var = point[2] + error_lower_linear = max(r - np.sqrt(error_var), 1e-10) + r_val = self._apply_rq4(q, r) + error_upper = self._apply_rq4(q, r + np.sqrt(error_var)) + error_lower = self._apply_rq4(q, error_lower_linear) + series_measured.append(q, np.log10(r_val)) + series_error_upper.append(q, np.log10(error_upper)) + series_error_lower.append(q, np.log10(error_lower)) + nr_points = nr_points + 1 console.debug(IO.formatMsg('sub', 'Measured curve', f'{nr_points} points', 'on experiment page', 'replaced')) @@ -738,11 +740,13 @@ def qtchartsReplaceCalculatedAndMeasuredOnAnalysisChartAndRedraw(self): series_calculated.clear() nr_points = 0 for point in self.experiment_data.data_points(): - if point[0] < self._project_lib.q_max and self._project_lib.q_min < point[0]: - q = point[0] - r_meas = self._apply_rq4(q, point[1]) - series_measured.append(q, np.log10(r_meas)) - nr_points = nr_points + 1 + q = point[0] + r_meas = point[1] + if r_meas <= 0: + continue + r_meas = self._apply_rq4(q, r_meas) + series_measured.append(q, np.log10(r_meas)) + nr_points = nr_points + 1 console.debug(IO.formatMsg('sub', 'Measured curve', f'{nr_points} points', 'on analysis page', 'replaced')) for point in self.model_data.data_points():