Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 57 additions & 9 deletions EasyReflectometryApp/Backends/Py/logic/assemblies.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,29 @@ class Assemblies:
def __init__(self, project_lib: ProjectLib):
self._project_lib = project_lib

def _has_valid_assembly_index(self, index: int) -> bool:
return 0 <= index < len(self._assemblies)

def _target_insert_index(self, current_index: int, previous_length: int) -> int:
if previous_length <= 1:
return previous_length
return min(current_index + 1, previous_length - 1)

def _move_new_assembly_into_position(self, existing_ids: set[int], target_index: int) -> int | None:
new_index = next((idx for idx, assembly in enumerate(self._assemblies) if id(assembly) not in existing_ids), None)
if new_index is None:
return None

while new_index > target_index:
self._assemblies.move_up(new_index)
new_index -= 1

while new_index < target_index:
self._assemblies.move_down(new_index)
new_index += 1

return new_index

@property
def _assemblies(self) -> Sample:
return self._project_lib._models[self._project_lib.current_model_index].sample # Sample is a collection of assemblies
Expand Down Expand Up @@ -43,12 +66,23 @@ def remove_at_index(self, value: str) -> None:
self._assemblies.remove_assembly(int(value))

def add_new(self) -> None:
previous_length = len(self._assemblies)
target_index = self._target_insert_index(self.index, previous_length)
existing_ids = {id(assembly) for assembly in self._assemblies}
self._assemblies.add_assembly()
new_index = self._move_new_assembly_into_position(existing_ids, target_index)
index_si = self._project_lib.get_index_si()
self._assemblies[-1].layers[0].material = self._project_lib._materials[index_si]
if new_index is not None:
self._assemblies[new_index].layers[0].material = self._project_lib._materials[index_si]

def duplicate_selected(self) -> None:
if not self._has_valid_assembly_index(self.index):
return
previous_length = len(self._assemblies)
target_index = self._target_insert_index(self.index, previous_length)
existing_ids = {id(assembly) for assembly in self._assemblies}
self._assemblies.duplicate_assembly(self.index)
self._move_new_assembly_into_position(existing_ids, target_index)

def move_selected_up(self) -> None:
if self.index > 0:
Expand All @@ -60,31 +94,45 @@ def move_selected_down(self) -> None:
self._assemblies.move_down(self.index)
self.index = self.index + 1

def set_name_at_current_index(self, new_value: str) -> None:
self._assemblies[self.index].name = new_value
return True
def set_name_at_current_index(self, new_value: str) -> bool:
return self.set_name_at_index(self.index, new_value)

def set_name_at_index(self, index: int, new_value: str) -> bool:
if not self._has_valid_assembly_index(index):
return False
if self._assemblies[index].name != new_value:
self._assemblies[index].name = new_value
return True
return False

def set_type_at_current_index(self, new_value: str) -> bool:
if new_value == self._assemblies[self.index].type:
return self.set_type_at_index(self.index, new_value)

def set_type_at_index(self, index: int, new_value: str) -> bool:
if not self._has_valid_assembly_index(index):
return False
if new_value == self._assemblies[index].type:
return False

if new_value == 'Multi-layer':
new_assembly = Multilayer()
new_assembly.layers[0].material = self._assemblies[self.index].layers.data[0].material
new_assembly.layers[0].material = self._assemblies[index].layers.data[0].material
elif new_value == 'Repeating Multi-layer':
new_assembly = RepeatingMultilayer(repetitions=1, name=new_value)
new_assembly.layers[0].material = self._assemblies[self.index].layers.data[0].material
new_assembly.layers[0].material = self._assemblies[index].layers.data[0].material
elif new_value == 'Surfactant Layer':
index_air = self._project_lib.get_index_air()
index_d2o = self._project_lib.get_index_d2o()
new_assembly = SurfactantLayer()
new_assembly.layers[0].solvent = self._project_lib._materials[index_air]
new_assembly.layers[1].solvent = self._project_lib._materials[index_d2o]
else:
return False

if new_assembly.name is None:
new_assembly.name = self._assemblies[self.index].name
new_assembly.name = self._assemblies[index].name

self._assemblies[self.index] = new_assembly
self._assemblies[index] = new_assembly
return True

# Only for repeating multilayer
Expand Down
71 changes: 71 additions & 0 deletions EasyReflectometryApp/Backends/Py/logic/layers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ class Layers:
def __init__(self, project_lib: ProjectLib):
self._project_lib = project_lib

def _has_valid_layer_index(self, index: int) -> bool:
return 0 <= index < len(self._layers)

def _has_valid_material_index(self, index: int) -> bool:
return 0 <= index < len(self._project_lib._materials)

@property
def _sample(self) -> Sample:
return self._project_lib._models[self._project_lib.current_model_index].sample
Expand Down Expand Up @@ -82,18 +88,42 @@ def set_name_at_current_index(self, new_value: str) -> bool:
return True
return False

def set_name_at_index(self, index: int, new_value: str) -> bool:
if not self._has_valid_layer_index(index):
return False
if self._layers[index].name != new_value:
self._layers[index].name = new_value
return True
return False

def set_thickness_at_current_index(self, new_value: float) -> bool:
if self._layers[self.index].thickness.value != new_value:
self._layers[self.index].thickness.value = new_value
return True
return False

def set_thickness_at_index(self, index: int, new_value: float) -> bool:
if not self._has_valid_layer_index(index):
return False
if self._layers[index].thickness.value != new_value:
self._layers[index].thickness.value = new_value
return True
return False

def set_roughness_at_current_index(self, new_value: float) -> bool:
if self._layers[self.index].roughness.value != new_value:
self._layers[self.index].roughness.value = new_value
return True
return False

def set_roughness_at_index(self, index: int, new_value: float) -> bool:
if not self._has_valid_layer_index(index):
return False
if self._layers[index].roughness.value != new_value:
self._layers[index].roughness.value = new_value
return True
return False

def set_material_at_current_index(self, new_value: int) -> bool:
if self._layers[self.index].material != self._project_lib._materials[new_value]:
self._layers[self.index].material = self._project_lib._materials[new_value]
Expand All @@ -102,30 +132,71 @@ def set_material_at_current_index(self, new_value: int) -> bool:
return True
return False

def set_material_at_index(self, index: int, new_value: int) -> bool:
if not self._has_valid_layer_index(index) or not self._has_valid_material_index(new_value):
return False
if self._layers[index].material != self._project_lib._materials[new_value]:
self._layers[index].material = self._project_lib._materials[new_value]
self._layers[index].name = self._project_lib._materials[new_value].name + ' Layer'
return True
return False

def set_solvent_at_current_index(self, new_value: int) -> bool:
if self._layers[self.index].solvent != self._project_lib._materials[new_value]:
self._layers[self.index].solvent = self._project_lib._materials[new_value]
return True
return False

def set_solvent_at_index(self, index: int, new_value: int) -> bool:
if not self._has_valid_layer_index(index) or not self._has_valid_material_index(new_value):
return False
if self._layers[index].solvent != self._project_lib._materials[new_value]:
self._layers[index].solvent = self._project_lib._materials[new_value]
return True
return False

def set_apm_at_current_index(self, new_value: float) -> bool:
if self._layers[self.index].area_per_molecule != new_value:
self._layers[self.index].area_per_molecule = new_value
return True
return False

def set_apm_at_index(self, index: int, new_value: float) -> bool:
if not self._has_valid_layer_index(index):
return False
if self._layers[index].area_per_molecule != new_value:
self._layers[index].area_per_molecule = new_value
return True
return False

def set_solvation_at_current_index(self, new_value: float) -> bool:
if self._layers[self.index].solvent_fraction != new_value:
self._layers[self.index].solvent_fraction = new_value
return True
return False

def set_solvation_at_index(self, index: int, new_value: float) -> bool:
if not self._has_valid_layer_index(index):
return False
if self._layers[index].solvent_fraction != new_value:
self._layers[index].solvent_fraction = new_value
return True
return False

def set_formula(self, new_value: str) -> bool:
if self._layers[self.index].molecular_formula != new_value:
self._layers[self.index].molecular_formula = new_value
return True
return False

def set_formula_at_index(self, index: int, new_value: str) -> bool:
if not self._has_valid_layer_index(index):
return False
if self._layers[index].molecular_formula != new_value:
self._layers[index].molecular_formula = new_value
return True
return False


def _from_layers_collection_to_list_of_dicts(
layers_collection: LayerCollection, assembly_type: str = 'regular'
Expand Down
24 changes: 24 additions & 0 deletions EasyReflectometryApp/Backends/Py/logic/material.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,42 @@ def set_name_at_current_index(self, new_value: str) -> bool:
return True
return False

def set_name_at_index(self, index: int, new_value: str) -> bool:
if not (0 <= index < len(self._materials)):
return False
if self._materials[index].name != new_value:
self._materials[index].name = new_value
return True
return False

def set_sld_at_current_index(self, new_value: float) -> bool:
if self._materials[self.index].sld.value != new_value:
self._materials[self.index].sld.value = new_value
return True
return False

def set_sld_at_index(self, index: int, new_value: float) -> bool:
if not (0 <= index < len(self._materials)):
return False
if self._materials[index].sld.value != new_value:
self._materials[index].sld.value = new_value
return True
return False

def set_isld_at_current_index(self, new_value: float) -> bool:
if self._materials[self.index].isld.value != new_value:
self._materials[self.index].isld.value = new_value
return True
return False

def set_isld_at_index(self, index: int, new_value: float) -> bool:
if not (0 <= index < len(self._materials)):
return False
if self._materials[index].isld.value != new_value:
self._materials[index].isld.value = new_value
return True
return False


def _from_materials_collection_to_list_of_dicts(materials_collection: MaterialCollection) -> list[dict[str, str]]:
materials_list = []
Expand Down
8 changes: 8 additions & 0 deletions EasyReflectometryApp/Backends/Py/logic/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ def set_name_at_current_index(self, new_value: str) -> bool:
return True
return False

def set_name_at_index(self, index: int, new_value: str) -> bool:
if not (0 <= index < len(self._models)):
return False
if self._models[index].name != new_value:
self._models[index].name = new_value
return True
return False

def set_scaling_at_current_index(self, new_value: str) -> bool:
if self._models[self.index].scale.value != float(new_value):
self._models[self.index].scale.value = float(new_value)
Expand Down
5 changes: 3 additions & 2 deletions EasyReflectometryApp/Backends/Py/logic/summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ def plot_file_path(self) -> Path:
@property
def as_html(self) -> str:
base_html = self._summary.compile_html_summary()
return self._inject_multimodel_multiexperiment_sections(base_html)
return base_html
# return self._inject_multimodel_multiexperiment_sections(base_html)

def save_as_html(self, file_path: str | None = None) -> None:
if not self._project_lib.path.exists():
Expand All @@ -57,7 +58,7 @@ def save_as_html(self, file_path: str | None = None) -> None:
target_path = Path(file_path) if file_path else self.file_path.with_suffix('.html')
target_path.parent.mkdir(parents=True, exist_ok=True)
html_content = self._summary.compile_html_summary(figures=True)
html_content = self._inject_multimodel_multiexperiment_sections(html_content)
# html_content = self._inject_multimodel_multiexperiment_sections(html_content)

with open(target_path, 'w', encoding='utf-8') as report_file:
report_file.write(html_content)
Expand Down
51 changes: 0 additions & 51 deletions EasyReflectometryApp/Backends/Py/plotting_1d.py
Original file line number Diff line number Diff line change
Expand Up @@ -562,22 +562,6 @@ def individualExperimentDataList(self) -> list:
)
return qml_data_list

@Property(float, notify=sampleChartRangesChanged)
def residualMinX(self):
return self._get_residual_range()[0]

@Property(float, notify=sampleChartRangesChanged)
def residualMaxX(self):
return self._get_residual_range()[1]

@Property(float, notify=sampleChartRangesChanged)
def residualMinY(self):
return self._get_residual_range()[2]

@Property(float, notify=sampleChartRangesChanged)
def residualMaxY(self):
return self._get_residual_range()[3]

@Slot(str, str, 'QVariant')
def setQtChartsSerieRef(self, page: str, serie: str, ref: QObject):
self._chartRefs['QtCharts'][page][serie] = ref
Expand Down Expand Up @@ -758,41 +742,6 @@ def getResidualDataPoints(self, experiment_index: int) -> list:
console.debug(f'Error getting residual data points for index {experiment_index}: {e}')
return []

def _get_residual_range(self) -> tuple[float, float, float, float]:
"""Return residual plot ranges for the current selection."""
try:
if self.is_multi_experiment_mode:
selected_indices = getattr(self._proxy._analysis, '_selected_experiment_indices', [])
else:
selected_indices = [self._project_lib.current_experiment_index]

all_points = []
for experiment_index in selected_indices:
all_points.extend(self.getResidualDataPoints(experiment_index))

if not all_points:
return 0.0, 1.0, -1.0, 1.0

x_values = np.asarray([point['x'] for point in all_points], dtype=float)
y_values = np.asarray([point['y'] for point in all_points], dtype=float)
if x_values.size == 0 or y_values.size == 0:
return 0.0, 1.0, -1.0, 1.0

min_x = float(np.min(x_values))
max_x = float(np.max(x_values))
min_y = float(np.min(y_values))
max_y = float(np.max(y_values))

if min_y == max_y:
margin = max(abs(min_y) * 0.05, 1.0)
else:
margin = (max_y - min_y) * 0.05

return min_x, max_x, min_y - margin, max_y + margin
except Exception as e:
console.debug(f'Error getting residual range: {e}')
return 0.0, 1.0, -1.0, 1.0

def refreshSamplePage(self):
# Clear cached data so it gets recalculated
self._sample_data = {}
Expand Down
Loading
Loading