import escape as esc
esc.require("0.9.8")
Loading material database from C:\dev\escape-core\python\src\escape\scattering\..\data\mdb\materials.db
Parameters¶
Parameters are the optimizable quantities in ESCAPE: they connect models (and their functors) to the optimizer, which adjusts parameter values to minimize the cost, usually a mismatch between theory and measured data. In scattering this often means adjusting layer thicknesses, roughnesses, densities, peak widths, background terms, or similar physical quantities.
Create a parameter with esc.par; the main arguments are: name (identifier), value (initial/default), userlim (hard bounds [min, max]), scale (multiplicator for display and fitting), limscale (for automatic limits when userlim is not set), units (string), and fixed (if True, the parameter is not optimized). Parameters can be independent (optimized) or dependent (defined by expressions of other parameters). Below are examples of parameter creation.
p1 = esc.par("parameter 1", value=1.5, userlim=[0, 5])
p2 = esc.par("parameter 2", value=2.0, scale=1e-6, userlim=[0, 5])
p3 = esc.par("parameter 3", value=400.0, userlim=[0, 500])
p1, p2, p3
(parameter_obj(name='parameter 1', value=1.5, min=0.0, max=5.0, fixed=False, units=''), parameter_obj(name='parameter 2', value=2e-06, min=0.0, max=4.9999999999999996e-06, fixed=False, units=''), parameter_obj(name='parameter 3', value=400.0, min=0.0, max=500.0, fixed=False, units=''))
How to assign a value¶
Assign via the value property (respects bounds; a warning is issued if the value is outside userlim) or via force_value (sets the value and updates the boundaries if needed). Use force_value when you intentionally want to move outside the current bounds.
p1.value = 3
print(p1)
# assigned value cannot be out of users or physical boundaries causing warning
p1.value = 10
print(p1)
parameter_obj(name='parameter 1', value=3.0, min=0.0, max=5.0, fixed=False, units='') parameter_obj(name='parameter 1', value=5.0, min=0.0, max=5.0, fixed=False, units='')
p1.force_value(10)
print(p1)
parameter_obj(name='parameter 1', value=10.0, min=0.0, max=5.0, fixed=False, units='')
Expressions¶
Parameters support arithmetic and mathematical functions (sin, cos, tan, sinh, cosh, tanh, exp, sqrt, log, log10, pow, min, max, erf). Dependent parameters defined by such expressions are not optimized; only the independent parameters they depend on are fitted. This is useful for constraints and derived quantities (e.g. layer thicknesses given by T, T+dT, T+2*dT).
p4 = esc.sin((p1 - p2) * (p1 + p2) / p1)
print(p4.value)
print(p4.independent)
-0.5440211108890345 False
p5 = esc.min(p1, p3)
print("p1=", p1.value, "p3=", p3.value, "p5=", p5.value)
p1= 10.0 p3= 400.0 p5= 10.0
Other parameter attributes¶
Parameter objects also provide: weight (for weighted cost), error (uncertainty after fit), scale (display/fitting scale), fixed (exclude from optimization), trained (used by neural-network regressor), independent (True if not defined by an expression), id (unique identifier), and set_boundaries(minval, maxval) to set bounds (independent parameters only).
Settings¶
esc.setting(label, value, units="", readonly=False) creates a setting_obj for use in GUI or configuration (e.g. layer or instrument settings). The value can be bool, int, float, or str. Properties: label, value, units, is_readonly.
s = esc.setting("Temperature", 300, units="K")
print("Setting:", s.label, "=", s.value, s.units)
Setting: b'Temperature' = 300 b'K'
As you have probably noticed par factory function returns an object of type parameter_obj. Class parameter_obj has a static method convert which is responsible for conversion from int and float types to parameters. In every method which requires parameter as an input, you can, if necessary, provide a float value. The value provided by the user will be converted to parameter_obj with constant value. This parameter is considered as dependent and will be ignored by the optimizer.
Conditional parameters¶
Conditional parameter is the parameter which, depending on condition result, true or false, returns a value of either the first parameter or the second one, respectively. Below is an example of conditional parameter. Conditional parameters can be used everywhere where parameter is required as an input.
p10 = esc.par("P10", 10)
p11 = esc.par("P11", 8)
p12 = esc.par("P12", 1)
p13 = esc.par("P13", 5)
cp = esc.conditional(p11 < p10, p12, p13)
cp.value
1.0
p11.value = 12
cp.value
5.0
Constraint parameters¶
Parameters in ESCAPE can be constrained using logical conditions. Constraints are used to restrict parameter values based on mathematical expressions involving other parameters.
For example, if we have parameters A and B, we can add constraints like:
- A + B > 10
- A > 0 & B > 0
- A^2 + B^2 > 100
Constraints are added to parameterized objects using the constrain() method. An object can have multiple constraints.
When constraints are violated, the paramerized object becomes infeasible (is_feasible property becomes False). This is useful for optimization problems where we want to ensure parameters stay within valid ranges.
Constraints are inherited by child functors - if functor f has a constraint and we create f2 = f + d, then f2 will inherit f's constraints.
The constraints can use all logical operators:
- Greater than (>)
- Less than (<)
- Equal to (==)
- Not equal to (!=)
- Greater than or equal to (>=)
- Less than or equal to (<=)
- AND (&)
- OR (|)
- NOT (~)
# Create parameters with boundaries
a = esc.par("A", "10+-30")
b = esc.par("B", "20+-40")
# Create variables
x = esc.var("X")
y = esc.var("Y")
# Create a functor with parameters
f = a * x**2 + b * y**2
# Add constraints: a^2 + b^2 > 100 AND a > 0 AND b > 0
c = (a**2 + b**2 > 100) & (a > 0) & (b > 0)
f.constrain(c)
print("Initial values: a =", a.value, ", b =", b.value)
print("Functor is feasible:", f.is_feasible)
# Change parameter values to make it infeasible
a.value = -10 # Violates a > 0
print("\nAfter setting a = -10:")
print("Functor is feasible:", f.is_feasible)
# Try another infeasible case
a.value = 10
b.value = -20 # Violates b > 0
print("\nAfter setting a = 10, b = -20:")
print("Functor is feasible:", f.is_feasible)
# Try values that violate a^2 + b^2 > 100
a.value = 1
b.value = 2
print("\nAfter setting a = 1, b = 2:")
print("Functor is feasible:", f.is_feasible)
# Set values back to feasible region
a.value = 10
b.value = 20
print("\nAfter setting a = 10, b = 20:")
print("Functor is feasible:", f.is_feasible)
Initial values: a = 10.0 , b = 20.0 Functor is feasible: True After setting a = -10: Functor is feasible: False After setting a = 10, b = -20: Functor is feasible: False After setting a = 1, b = 2: Functor is feasible: False After setting a = 10, b = 20: Functor is feasible: True