Note
Go to the end to download the full example code.
MathExpression: General math expressions
Evaluating general mathematical expressions with automatic symbolic differentiated sensitivities
This example is identical in behavior to Custom: Simple scalar network, but uses the
pymoto.MathExpression module instead of custom modules. The pymoto.MathExpression module allows the
user to enter a string with a mathematical formula, which is symbolically differentiated using sympy.
Side note: pymoto.MathExpression is able to operate on both scalar data or numpy-array data (vectors
and matrices) in an element-wise fashion. This is very useful, for instance for evaluation of SIMP equation
\(\rho = x_0 + (1-x_0)x^3\) used in Compliance minimization, or the
Heaviside projection functions used in Robust formulation.
15 import pymoto as pym
16
17 if __name__ == '__main__':
18 print(__doc__)
19
20 # --- SETUP ---
21 x = pym.Signal('x')
22 y = pym.Signal('y')
23 z = pym.Signal('z')
24
25 # Initial values
26 use_vector = True
27 if use_vector:
28 print("The initial values are vectors")
29 import numpy as np
30 x.state = np.array([1.0, 2.0, 3.0, 4.0])
31 y.state = np.array([0.8, 1.2, 1.8, 2.6])
32 z.state = np.array([3.4, 8.5, 4.1, 6.3])
33 else:
34 print("The initial values are scalar")
35 x.state = 1.0
36 y.state = 0.8
37 z.state = 3.4
38
39 # Create modules
40 with pym.Network() as func:
41 ordering = 0
42 if ordering == 0:
43 a = pym.MathExpression("inp0 * sin(inp1)")(x, y)
44 # Instead of 'inp0' and 'inp1', also the tags ('y', 'z') can be used (if they are defined!)
45 b = pym.MathExpression("cos(y) * cos(z)")(y, z)
46 # Signals a and b do not have a tag, so inp0 and inp1 are used
47 g = pym.MathExpression("inp0^2 * (1 + inp1)")(a, b)
48 elif ordering == 1:
49 print("Using an alternative module order")
50 a = pym.MathExpression("cos(inp0) * cos(inp1)")(x, y)
51 b = pym.MathExpression("inp0^2 * (1 + inp1)")(y, z)
52 g = pym.MathExpression("inp1 * sin(inp0)")(a, b)
53 else:
54 raise RuntimeError("Unknown ordering")
55
56 if use_vector:
57 # In case of vector, convert to scalar by summing over the vector elements
58 g_vector = g
59 g = pym.EinSum('i->')(g_vector)
60 g.tag = 'g'
61
62 print("\nCurrent Network:")
63 print(" -> ".join([type(m).__name__ for m in func.mods]))
64
65 print(f"\nThe result is g(x={x.state}, y={y.state}, z={z.state}) = {g.state}")
66
67 # --- FORWARD ANALYSIS ---
68 # Perform forward analysis with updated values
69 x.state *= 2
70 y.state += 0.1
71 func.response()
72 print(f"\nThe updated result is g(x={x.state}, y={y.state}, z={z.state}) = {g.state}")
73
74 # --- BACKPROPAGATION ---
75 # Seed the response sensitivity
76 g.sensitivity = 1.0
77
78 # Calculate the sensitivities
79 func.sensitivity()
80
81 print("\nThe sensitivities are:")
82 print("d{0}/d{1} = {2}".format(g.tag, x.tag, x.sensitivity))
83 print("d{0}/d{1} = {2}".format(g.tag, y.tag, y.sensitivity))
84 print("d{0}/d{1} = {2}".format(g.tag, z.tag, z.sensitivity))
85
86 # --- Finite difference checks ---
87 # Do a finite difference check on the entire network
88 pym.finite_difference(x, g, function=func, random=False)