Skip to content

Commit 654240e

Browse files
authored
New easycore (#259)
* changes for new easycore. * work on dependent parameters. * install libs before installing python deps * fixes for dependent parameters * added constraint display * remove constraint works now * CR fixes
1 parent 96a96e2 commit 654240e

13 files changed

Lines changed: 396 additions & 70 deletions

File tree

‎.github/workflows/installer.yml‎

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ jobs:
3131
- name: Upgrade package installer for Python
3232
run: python -m pip install --upgrade pip
3333

34+
- name: Install needed libraries (Linux)
35+
if: runner.os == 'Linux'
36+
run: |
37+
sudo apt-get update
38+
sudo apt-get install libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xfixes0 libxcb-shape0 libxcb-cursor0
39+
sudo apt-get install libcairo2-dev pkg-config
40+
3441
- name: Install Python dependences
3542
run: |
3643
python -m pip install flit
@@ -74,11 +81,12 @@ jobs:
7481
run: |
7582
echo "SETUP_EXE_PATH=$(python ${{ env.SCRIPTS_PATH }}/Config.py ${{ env.BRANCH_NAME }} ${{ matrix.os }} setup_exe_path)" >> $GITHUB_ENV
7683
77-
- name: Install needed libraries (Linux)
78-
if: runner.os == 'Linux'
79-
run: |
80-
sudo apt-get update
81-
sudo apt-get install libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xfixes0 libxcb-shape0 libxcb-cursor0
84+
# - name: Install needed libraries (Linux)
85+
# if: runner.os == 'Linux'
86+
# run: |
87+
# sudo apt-get update
88+
# sudo apt-get install libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xfixes0 libxcb-shape0 libxcb-cursor0
89+
# sudo apt-get install libcairo2-dev pkg-config
8290

8391
# - name: Run app in testmode and quit
8492
# shell: bash

‎EasyReflectometryApp/Backends/Mock/Sample.qml‎

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ pragma Singleton
33
import QtQuick
44

55
QtObject {
6+
// Signals to match the Python backend
7+
signal constraintsChanged
68
// MATERIALS
79
readonly property int currentMaterialIndex: -1
810

@@ -271,11 +273,58 @@ QtObject {
271273
'parameter 2',
272274
'parameter 3'
273275
]
274-
readonly property var relationOperators: ['=', '&lt', '&gt']
276+
readonly property var relationOperators: ['=', '<', '>']
275277
readonly property var arithmicOperators: ['', '*', '/', '+', '-']
276278

279+
// Mock constraints data - matches the new simplified format
280+
property var constraintsList: [
281+
{
282+
dependentName: "Thickness Layer 1",
283+
expression: "2.0 * parameter 2 + 1.5"
284+
},
285+
{
286+
dependentName: "Roughness Layer 2",
287+
expression: "parameter 1 / 3.14"
288+
},
289+
{
290+
dependentName: "SLD Layer 3",
291+
expression: "5.0"
292+
}
293+
]
294+
277295
function addConstraint(value1, value2, value3, value4, value5) {
278296
console.debug(`addConstraint ${value1} ${value2} ${value3} ${value4} ${value5}`)
297+
298+
// Create constraint object in the new simplified format
299+
let expression = ""
300+
if (value5 >= 0 && value5 < parameterNames.length) {
301+
// Parameter-parameter constraint
302+
expression = `${value3} ${value4} ${parameterNames[value5]}`
303+
} else {
304+
// Numeric constraint
305+
expression = `= ${value3}`
306+
}
307+
308+
const constraint = {
309+
dependentName: parameterNames[value1] || "Unknown",
310+
expression: expression
311+
}
312+
313+
// Add to constraints list - need to reassign the array to trigger property change
314+
var newConstraints = constraintsList.slice() // Create a copy
315+
newConstraints.push(constraint)
316+
constraintsList = newConstraints
317+
constraintsChanged()
318+
}
319+
320+
function removeConstraintByIndex(index) {
321+
console.debug(`removeConstraintByIndex ${index}`)
322+
if (index >= 0 && index < constraintsList.length) {
323+
var newConstraints = constraintsList.slice() // Create a copy
324+
newConstraints.splice(index, 1)
325+
constraintsList = newConstraints
326+
constraintsChanged()
327+
}
279328
}
280329

281330
// Q Range

‎EasyReflectometryApp/Backends/Py/logic/models.py‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,23 +81,23 @@ def add_new(self) -> None:
8181
self._models[-1].sample.data[0].layers.data[0].material = self._project_lib._materials[
8282
self._project_lib.get_index_air()
8383
]
84-
self._models[-1].sample.data[0].layers.data[0].thickness = 0.0
85-
self._models[-1].sample.data[0].layers.data[0].roughness = 0.0
84+
self._models[-1].sample.data[0].layers.data[0].thickness.value = 0.0
85+
self._models[-1].sample.data[0].layers.data[0].roughness.value = 0.0
8686
self._models[-1].sample.data[0].name = 'Superphase'
8787

8888
self._models[-1].sample.data[1].layers.data[0].material = self._project_lib._materials[
8989
self._project_lib.get_index_sio2()
9090
]
91-
self._models[-1].sample.data[1].layers.data[0].thickness = 20.0
92-
self._models[-1].sample.data[1].layers.data[0].roughness = 3.0
91+
self._models[-1].sample.data[1].layers.data[0].thickness.value = 20.0
92+
self._models[-1].sample.data[1].layers.data[0].roughness.value = 3.0
9393
self._models[-1].sample.data[1].name = 'SiO2'
9494

9595
self._models[-1].sample.data[2].layers.data[0].material = self._project_lib._materials[
9696
self._project_lib.get_index_si()
9797
]
9898
self._models[-1].sample.data[2].name = 'Substrate'
99-
self._models[-1].sample.data[2].layers.data[0].thickness = 0.0
100-
self._models[-1].sample.data[2].layers.data[0].roughness = 1.2
99+
self._models[-1].sample.data[2].layers.data[0].thickness.value = 0.0
100+
self._models[-1].sample.data[2].layers.data[0].roughness.value = 1.2
101101

102102
self._models[-1].sample._disable_changes_to_outermost_layers()
103103

‎EasyReflectometryApp/Backends/Py/logic/parameters.py‎

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44
from easyreflectometry.utils import count_fixed_parameters
55
from easyreflectometry.utils import count_free_parameters
66
from easyscience import global_object
7-
from easyscience.Constraints import NumericConstraint
8-
from easyscience.Constraints import ObjConstraint
9-
from easyscience.Objects.variable import Parameter
7+
from easyscience.variable import Parameter
108

119

1210
class Parameters:
@@ -90,39 +88,63 @@ def add_constraint(
9088
dependent = self._project_lib.parameters[dependent_idx]
9189

9290
if arithmetic_operator != '' and independent_idx > -1:
93-
constaint = ObjConstraint(
94-
dependent_obj=dependent, operator=str(float(value)) + arithmetic_operator, independent_obj=independent
95-
)
91+
dependent.make_dependent_on(
92+
dependency_expression='a' + arithmetic_operator + 'b', dependency_map={'a': independent, 'b': float(value)})
9693
elif arithmetic_operator == '' and independent_idx == -1:
9794
relational_operator = relational_operator.replace('=', '==')
9895
relational_operator = relational_operator.replace('&lt', '>')
9996
relational_operator = relational_operator.replace('&gt', '<')
100-
constaint = NumericConstraint(dependent_obj=dependent, operator=relational_operator, value=float(value))
97+
98+
dependent.make_dependent_on(dependency_expression='a', dependency_map={'a': float(value)})
10199
else:
102100
print('Failed to add constraint: Unsupported type')
103101
return
104-
# print(c)
105-
independent.user_constraints[dependent.name] = constaint
106-
constaint()
107102

108103
print(f'{dependent_idx}, {relational_operator}, {value}, {arithmetic_operator}, {independent_idx}')
109104

110-
111105
def _from_parameters_to_list_of_dicts(parameters: List[Parameter], model_unique_name: str) -> list[dict[str, str]]:
106+
"""Convert parameters to list of dictionaries with simplified logic."""
107+
108+
def _get_parameter_display_name(param: Parameter) -> str:
109+
"""Extract display name from parameter path."""
110+
path = global_object.map.find_path(model_unique_name, param.unique_name)
111+
if len(path) >= 2:
112+
parent_name = global_object.map.get_item_by_key(path[-2]).name
113+
param_name = global_object.map.get_item_by_key(path[-1]).name
114+
return f'{parent_name} {param_name}'
115+
return param.name # Fallback to parameter name
116+
117+
def _get_dependency_expression(param: Parameter) -> str:
118+
"""Get simplified dependency expression."""
119+
if param.independent:
120+
return ''
121+
122+
# Check if parameter has dependency map with 'a' key (parameter dependency)
123+
if hasattr(param, 'dependency_map') and 'a' in param.dependency_map:
124+
dependent_param = param.dependency_map['a']
125+
dep_name = _get_parameter_display_name(dependent_param)
126+
return param.dependency_expression.replace('a', dep_name)
127+
128+
# Simple numerical dependency
129+
return f'= {param.value}'
130+
112131
parameter_list = []
113132
for parameter in parameters:
114-
path = global_object.map.find_path(model_unique_name, parameter.unique_name)
115-
if 0 < len(path):
116-
name = f'{global_object.map.get_item_by_key(path[-2]).name} {global_object.map.get_item_by_key(path[-1]).name}'
117-
parameter_list.append(
118-
{
119-
'name': name,
120-
'value': float(parameter.value),
121-
'error': float(parameter.variance),
122-
'max': float(parameter.max),
123-
'min': float(parameter.min),
124-
'units': parameter.unit,
125-
'fit': parameter.free,
126-
}
127-
)
133+
# Skip parameters not in the current model path
134+
if not global_object.map.find_path(model_unique_name, parameter.unique_name):
135+
continue
136+
137+
parameter_list.append({
138+
'name': _get_parameter_display_name(parameter),
139+
'value': float(parameter.value),
140+
'error': float(parameter.variance),
141+
'max': float(parameter.max),
142+
'min': float(parameter.min),
143+
'units': parameter.unit,
144+
'fit': parameter.free,
145+
'independent': parameter.independent,
146+
'dependency': _get_dependency_expression(parameter),
147+
'object': parameter, # Direct reference to the Parameter object
148+
})
149+
128150
return parameter_list

‎EasyReflectometryApp/Backends/Py/sample.py‎

Lines changed: 89 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class Sample(QObject):
2626
layersIndexChanged = Signal()
2727

2828
qRangeChanged = Signal()
29+
constraintsChanged = Signal()
2930

3031
externalRefreshPlot = Signal()
3132
externalSampleChanged = Signal()
@@ -412,6 +413,10 @@ def _clearCacheAndEmitLayersChanged(self):
412413
def parameterNames(self) -> list[dict[str, str]]:
413414
return [parameter['name'] for parameter in self._parameters_logic.parameters]
414415

416+
@Property('QVariantList', notify=layersChange)
417+
def dependentParameterNames(self) -> list[dict[str, str]]:
418+
return [parameter['name'] for parameter in self._parameters_logic.parameters if parameter['independent']]
419+
415420
@Property('QVariantList', notify=layersChange)
416421
def relationOperators(self) -> list[str]:
417422
return self._parameters_logic.constraint_relations()
@@ -420,16 +425,93 @@ def relationOperators(self) -> list[str]:
420425
def arithmicOperators(self) -> list[str]:
421426
return self._parameters_logic.constraint_arithmetic()
422427

428+
@Property('QVariantList', notify=constraintsChanged)
429+
def constraintsList(self) -> list[dict[str, str]]:
430+
"""Get the list of active constraints from dependent parameters."""
431+
constraints = []
432+
parameters = self._parameters_logic.parameters
433+
for param in parameters:
434+
if not param['independent']:
435+
constraints.append({param['name']: param['dependency']})
436+
437+
return constraints
438+
439+
@Slot(int)
440+
def removeConstraintByIndex(self, index_str: str) -> None:
441+
"""Remove constraint by index by making the parameter independent."""
442+
try:
443+
index = int(index_str)
444+
except ValueError:
445+
return
446+
447+
constraints_list = self.constraintsList
448+
if index >= len(constraints_list):
449+
return
450+
451+
param_name = list(constraints_list[index].keys())[0]
452+
param_obj = self._find_parameter_object_by_name(param_name)
453+
454+
if param_obj is None or param_obj.independent:
455+
return
456+
457+
self._make_parameter_independent(param_obj)
458+
self.constraintsChanged.emit()
459+
self.externalSampleChanged.emit()
460+
461+
def _find_parameter_object_by_name(self, param_name: str):
462+
"""Find parameter object by name."""
463+
parameters = self._parameters_logic.parameters
464+
for param in parameters:
465+
if param['name'] == param_name:
466+
return param['object']
467+
return None
468+
469+
def _make_parameter_independent(self, param_obj) -> None:
470+
"""Make a parameter independent, handling different parameter types."""
471+
try:
472+
param_obj.make_independent()
473+
except AttributeError:
474+
param_obj._independent = True # Fallback for custom ERL constraints
475+
423476
@Slot(str, str, str, str, str)
424477
def addConstraint(self, value1: str, value2: str, value3: str, value4: str, value5: str) -> None:
425-
self._parameters_logic.add_constraint(
426-
dependent_idx=int(value1),
427-
relational_operator=value2,
428-
value=float(value3),
429-
arithmetic_operator=value4,
430-
independent_idx=int(value5),
431-
)
478+
dependent = self._project_lib.parameters[int(value1)]
479+
if value5 == '-1':
480+
independent = None
481+
else:
482+
independent = self._project_lib.parameters[int(value5)]
483+
relational_operator = value2
484+
arithmetic_operator = value4
485+
value = float(value3)
486+
if arithmetic_operator == '':
487+
if relational_operator == '=':
488+
# simple parameter equality
489+
# assign to the parameter and make it frozen
490+
dependent.value = value
491+
dependent.free = False
492+
# here, we need to make the parameter "dependent" so it can't be changed in GUI
493+
dependent._independent = False
494+
495+
elif relational_operator == '>':
496+
# set the minimum value of the parameter
497+
dependent.min = value
498+
dependent.free = True
499+
500+
elif relational_operator == '<':
501+
# set the maximum value of the parameter
502+
dependent.max = value
503+
dependent.free = True
504+
else:
505+
# make the parameter dependent on another parameter
506+
# e.g. thickness = 2 * a + 5
507+
expr = "a " + arithmetic_operator + str(float(value))
508+
dependency_map = {'a': independent}
509+
dependent.make_dependent_on(
510+
dependency_expression=expr, dependency_map=dependency_map
511+
)
512+
self.constraintsChanged.emit()
432513
self.externalSampleChanged.emit()
514+
self.layersChange.emit()
433515

434516
# # #
435517
# Q Range

‎EasyReflectometryApp/Gui/Globals/BackendWrapper.qml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,13 @@ QtObject {
159159

160160
// Constraints
161161
readonly property var sampleParameterNames: activeBackend.sample.parameterNames
162+
readonly property var sampleDepParameterNames: activeBackend.sample.dependentParameterNames
162163
readonly property var sampleRelationOperators: activeBackend.sample.relationOperators
163164
readonly property var sampleArithmicOperators: activeBackend.sample.arithmicOperators
165+
readonly property var sampleConstraintsList: activeBackend.sample.constraintsList
164166

165167
function sampleAddConstraint(value1, value2, value3, value4, value5) { activeBackend.sample.addConstraint(value1, value2, value3, value4, value5) }
168+
function sampleRemoveConstraintByIndex(value) { activeBackend.sample.removeConstraintByIndex(value) }
166169

167170
// Q range
168171
readonly property var sampleQMin: activeBackend.sample.q_min

0 commit comments

Comments
 (0)