import numpy as np
import escape as esc
esc.require("0.9.8")
Loading material database from /home/dkor/Data/Development/workspace_escape/escape-core/python/src/escape/scattering/../data/mdb/materials.db
Functors¶
Theoretical description of scattering experiments is based on Scattering Theory, which is a general framework for studying scattering processes of waves and particles. The final equation of intensity of scattered beam depends on a sample type, thin film or bulky sample, isotropic or anisotropic; setup components like, detector type - stripe, point or 2d image detector, slits configuration, beam profile, etc. One should not forget about diffuse scattering and background which sometimes cannot be defined precisely and are included in the optimized intensity equation as a polynom. To include all that into one framework and to give a user a freedom to describe special customized solutions for their laboratory setups or samples is a challenging task. After some trial and error we cam to conclusion that ESCAPE framework requires a flexible functor object.
Functor is an object which plays the role of a function of one or several variables. The maximum number of variables which is currenly supported is 5. Functors support mathematical operations and arithmetic operators.
When ESCAPE was developed we tried to implement functors so, that their notations would be as close as possible to functions in algebra. For that several entites have been introduced. The first one is a function variable.
Variables¶
One creates a variable in the follwoing way:
X = esc.var("X")
Y = esc.var("Y")
Z = esc.var("Z")
Variables support algebraic expressions and operators. If one writes $x+y$, the result of this expression is a two-dimensional functor, i.e. function with two variables $f(x, y)$
f = X + Y
print("Type: ", type(f))
print(f.num_variables)
print(f.variables)
Type: <class 'escape.core.objects.functor_obj'> 2 [variable(name='X'), variable(name='Y')]
Const functor¶
Const functor is a functor which regardless of its variables values, always returns a given constant value. Constant can be also a parameter. As an input constant functor requires also a list of variables.
p1 = esc.par("par1", 23.5)
# one-dimensional const functors
f1 = esc.func("const functor", X, p1)
f2 = esc.func("const functor", Y, 10)
# two-dimensional const functors
f3 = esc.func("const functor", [X, Y], p1)
print(f1(10))
print(f2(10))
print(f3(1, 2))
23.5 10.0 23.5
Expressions¶
As it has already been mentioned, functors support arithmetic operators and mathematical functions: sin(), cos(), tan(), sinh(), cosh(), tanh(), exp(), sqrt(), log(), log10(), pow(), min(), max(), erf().
Below we demonstrate a simple oscillating functor with damping amplitude.
q = esc.par("Damping", 0.05, userlim=[0, 2])
expr = esc.pow(esc.sin(X), 2.0) * esc.exp(-q * esc.abs(X))
expr.show().config(title="Damping sine")
# two-dimensional example
Qx = esc.par("Damping X", 0.05, userlim=[0, 2])
Qy = esc.par("Damping Y", 0.05, userlim=[0, 2])
expr2d = (
esc.pow(esc.cos(X), 2.0)
* esc.pow(esc.cos(Y), 2.0)
* esc.exp(-Qx * esc.abs(X))
* esc.exp(-Qy * esc.abs(Y))
)
x = np.linspace(-5, 5, 100)
y = np.linspace(-5, 5, 100)
# create meshgrid of both coordinates
xv, yv = np.meshgrid(x, y)
# create coordinates array of type [x0, y0, x1, y1,...]
coords = np.column_stack([xv.flatten(), yv.flatten()])
# for the 2d case one has to provide a plot_type, coordinates index and number of rows
# cbmin, cbmax and cblof are the colorbox properties
expr2d.show(coordinates=coords).config(title="Map Plot")
Variables order¶
ESCAPE supports expressions with functors of different domain size and preserves the order of variables in the resulted functor. For example, $f_1(z, x)+f_2(x)+f_3(y)$ will result in the following functor $f_r(z, x, y)$, not $f_r(x, y, z)$ as maybe desired. One has to keep this in mind when creating large complex expressions. One can always check the order of variables using variables property of functors.
F1 = esc.func("F1(z,x)", [Z, X], 10)
F2 = esc.func("F2(x)", X, 0.1)
F3 = esc.func("F3(y)", Y, 0.05)
Fr = F1 + F2 + F3
print(Fr.variables)
[variable(name='Z'), variable(name='X'), variable(name='Y')]
Boolean functors and conditional expressions¶
If two functors are compared using one of the standard comparison operators >, <, >=, <=, ==, !=, a product of this comparison is a boolean functor. For example, we have two functors f1(x)=x^2 and f2(x,y)=x+y
F1 = X * X
F2 = X + Y
Fb = F1 >= F2
print(type(Fb))
Fb.variables
<class 'escape.core.objects.bool_functor_obj'>
[variable(name='X'), variable(name='Y')]
Fb is a boolean functor of two variables $f(x, y)$. When beeing called with with values of variables $x$ and $y$, it calls f1(x) and f2(x, y) and returns a result of f1(x) >= f2(x). Boolean functors support logical operators: or - |, and - & and not - ~.
Fb(1, 10)
False
Fb(4, 5)
True
Boolean functors are typically used with conditional expressions. The first argument of the conditional method is a condition itself, i.e. a boolean functor, the second one is a functor which has to be executed if the condition returns true, and the last one is executed if the condition returns false.
P = esc.par("P", 10, userlim=[-1e-3, 1e-3])
B = esc.par("B", 0, userlim=[-10, 10])
F1 = B * X + P
F2 = P * X + B
Fc = esc.conditional((X >= -5) & (X <= 5), F1, F2)
a = np.arange(-10, 10, 0.01)
p1 = F1.show().config(title="F1(x)")
p2 = F2.show().config(title="F2(x)")
p3 = Fc.show().config(title="Fc(x)")
esc.show(p1, p2, p3).config(title="Conditional expression")
Functors with complex return type¶
In scattering the connection between experiment geometry and equations describing measured intensity is done in terms of reciprocal space. In the first approximation, called Born approximation, the conversion between real- and reciprocal- space is performed using Fourier Transform. The result of Fourier Transform can be a complex function. Thus, it make sense to introduce a functor with complex return type.
# Constant complex functor
Cf = esc.cfunc("", [X], -1j)
type(Cf)
escape.core.objects.cplx_functor_obj
Complex functors support the same arithmetic operators and mathematical functors as functors with double return type. Additionally there are several other methods like, norm, real, imag, conjugate which require cplx_functor_obj input parameter. Few examples are given below.
F = esc.real((esc.exp(1j * X) + esc.exp(-1j * X)) / 2.0)
F.show()
F = esc.real((esc.exp(1j * X) - esc.exp(-1j * X)) / (2 * 1j))
F.show()
F = esc.imag(esc.sin(-1j * X)) + esc.real(esc.cos(-1j * X))
F.show()
Functor minimizer¶
There is a special type of functor used to find the minimum of another functor. When called, it minimizes the functor parameters for the given variable values.
For example:
your task is to find a minimum of the following functor:
$f= sin(3x)+sin(1/3x)$
# Let's create a functor
a = esc.par("a", 1, userlim=[0, 2 * np.pi])
f0 = esc.sin(3 * X) + esc.sin(X * 1 / 3)
display(f0.show(coordinates=np.linspace(0, 2 * np.pi, 100)))
# since the function minimizer finds a minimum of the function
# by optimizing its parameters, we need to substitute the variable
# with the parameter a. This is done using the reduce method.
f = esc.reduce("", f0, X, a)
# Now we create a minimizer using differential evolution algorithm
fmin = esc.diffevol("fmin", f, ftol=1e-6, maxiter=1000, popsize=15, status_exc=True)
print("f=", f(), "a=", a.value)
print("fmin=", fmin(), "amin=", a.value)
f= 0.46831470485601945 a= 1.0 fmin= -0.5046618764741795 amin= 1.5384755287762637
# Let's add a constraint to the parameter a
f.constrain((a > 3) & (a <= 5.5))
# Now we can find a minimum of the function with constraint
print("fmin=", fmin(), "amin=", a.value)
fmin= -0.0610380255229398 amin= 3.6523595908486315
# Now for 2d functor
a = esc.par("a", 1, userlim=[0, 2 * np.pi])
b = esc.par("b", 1, userlim=[0, 2 * np.pi])
f0 = esc.sin(3 * X) + esc.sin(X * 1 / 3) + esc.sin(3 * Y) + esc.sin(Y * 1 / 3)
f = esc.reduce("", f0, X, a, Y, b)
xc = np.linspace(0, 2 * np.pi, 100)
yc = np.linspace(0, 2 * np.pi, 100)
xv, yv = np.meshgrid(xc, yc)
coords = np.column_stack([xv.flatten(), yv.flatten()])
f0.show(coordinates=coords)
# Create a minimizer and call it
fmin = esc.diffevol("fmin", f, ftol=1e-6, maxiter=1000, popsize=15, status_exc=True)
print("f=", f(), "a=", a.value, "b=", b.value)
print("fmin=", fmin(), "amin=", a.value, "bmin=", b.value)
f= 0.9366294097120389 a= 1.0 b= 1.0 fmin= -1.00932371669689 amin= 1.5385633048945813 bmin= 1.538470959085024
# Add a constraint to the parameters
f.constrain(a**2 + b**2 >= 5)
print("fmin=", fmin(), "amin=", a.value, "bmin=", b.value)
fmin= -0.9930720040827135 amin= 1.581119733033358 bmin= 1.581158113100428