(sec-cinterface-symbolic)= # Symbolic The Symbolic sub-module in `exudyn.symbolic` allows limited symbolic manipulations in Exudyn and is currently under development In particular, symbolic user functions can be created, which allow significant speedup of Python user functions. However, **always verify your symbolic expressions or user functions**, as behavior may be unexpected in some cases. ## symbolic.Real The symbolic Real type allows to replace Python's float by a symbolic quantity. The `symbolic.Real` may be directly set to a float and be evaluated as float. However, turning on recording by using `exudyn.symbolic.SetRecording(True)` (on by default), results are stored as expression trees, which may be evaluated in C++ or Python, in particular in user functions, see the following example: ```python import exudyn as exu esym = exu.symbolic #abbreviation SymReal = esym.Real #abbreviation #create some variables a = SymReal('a',42.) #use named expression b = SymReal(13) #b is 13 c = a+b*7.+1.-3 #c stores expression tree d = c #d and c are containing same tree! print('a: ',a,' = ',a.Evaluate()) print('c: ',c,' = ',c.Evaluate()) #use special functions: d = a+b*esym.sin(a)+esym.cos(SymReal(7)) print('d: ',d,' = ',d.Evaluate()) a.SetValue(14) #variable a set to new value; influences d print('d: ',d,' = ',d.Evaluate()) a = SymReal(1000) #a is now a new variable; not updated in d! print('d: ',d,' = ',d.Evaluate()) #compute derivatives (automatic differentiation): x = SymReal("x",0.5) f = a+b*esym.sin(x)+esym.cos(SymReal(7))+x**4 print('f=',f.Evaluate(), ', diff=',f.Diff(x)) #turn off recording of trees (globally for all symbolic.Real!): esym.SetRecording(False) x = SymReal(42) #now, only represents a value y = x/3. #directly evaluates to 14 #back to default behavior, otherwise expr. only evaluated: esym.SetRecording(True) ``` ```text a: a = 42.0 c: (((a + (13 * 7)) + 1) - 3) = 131.0 d: ((a + (13 * sin(a))) + cos(7)) = 30.839122131440067 d: ((a + (13 * sin(a))) + cos(7)) = 27.63179787837662 d: ((a + (13 * sin(a))) + cos(7)) = 27.63179787837662 f= 1007.048934256198 , diff= 11.908573304574846 ``` ```{container} notebookorigin `python/Notebooks/reference/symbolic.ipynb` ``` To create a symbolic Real, use `aa=symbolic.Real(1.23)` to build a Python object aa with value 1.23. In order to use a named value, use `pi=symbolic.Real('pi',3.14)`. Note that in the following, we use the abbreviation `SymReal=exudyn.symbolic.Real`, and the examples `esym=exudyn.symbolic`. Member functions of `SymReal`, which are **not recorded**, are: The class **symbolic.Real** has the following **functions and structures**: - **`__init__(value)`**: Construct symbolic.Real from float. - **`__init__(name, value)`**: Construct named symbolic.Real from name and float. - **`SetValue(valueInit)`**: Set either internal float value or value of named expression; cannot change symbolic expressions. *Example*: ```python b = esym.Real(13) b.SetValue(14) #now b is 14 #b.SetValue(a+3.) #not possible! ``` - **`Evaluate()`**: return evaluated expression (prioritized) or stored Real value. - **`Diff(var)`**: (UNTESTED!) return derivative of stored expression with respect to given symbolic named variable; NOTE: when defining the expression of the variable which shall be differentiated, the variable may only be changed with the SetValue(...) method hereafter! *Example*: ```python x=esym.Real('x',2) f=3*x+x**2*esym.sin(x) f.Diff(x) #evaluate derivative w.r.t. x ``` - **`value`**: access to internal float value, which is used in case that symbolic.Real has been built from a float (but without a name and without symbolic expression) - **`operator __float__()`**: evaluation of expression and conversion of symbolic.Real to Python float - **`operator __str__()`**: conversion of symbolic.Real to string - **`operator __repr__()`**: representation of symbolic.Real in Python The remaining operators and mathematical functions are recorded within expressions. Main mathematical operators for `SymReal` exist, similar to Python, such as: ```python a = SymReal(1) b = SymReal(2) r1 = a+b r1 = a-b r1 = a*b r1 = a/b r1 = -a r1 = a**b c = SymReal(3.3) c += b c -= b c *= b c /= b c = (a == b) c = (a != b) c = (a < b) c = (a > b) c = (a <= b) c = (a >= b) #in most cases, we can also mix with float: c = a*7 + esym.sin(8) ``` Mathematical functions may be called with an `SymReal` or with a `float`. Most standard mathematical functions exist for `symbolic`, e.g., as `symbolic.abs`. **HINT**: function names are lower-case for compatibility with Python's math library. Thus, you can easily exchange math.sin with esym.sin, and you may want to use a generic name, such as myMath=symbolic in order to switch between Python and symbolic user functions. The following functions exist: The class **symbolic** has the following **functions and structures**: - **`isfinite(x)`**: according to specification of C++ std::isfinite - **`abs(x)`**: according to specification of C++ std::fabs - **`round(x)`**: according to specification of C++ std::round - **`ceil(x)`**: according to specification of C++ std::ceil - **`floor(x)`**: according to specification of C++ std::floor - **`sqrt(x)`**: according to specification of C++ std::sqrt - **`exp(x)`**: according to specification of C++ std::exp - **`log(x)`**: according to specification of C++ std::log - **`sin(x)`**: according to specification of C++ std::sin - **`cos(x)`**: according to specification of C++ std::cos - **`tan(x)`**: according to specification of C++ std::tan - **`asin(x)`**: according to specification of C++ std::asin - **`acos(x)`**: according to specification of C++ std::acos - **`atan(x)`**: according to specification of C++ std::atan - **`sinh(x)`**: according to specification of C++ std::sinh - **`cosh(x)`**: according to specification of C++ std::cosh - **`tanh(x)`**: according to specification of C++ std::tanh - **`asinh(x)`**: according to specification of C++ std::asinh - **`acosh(x)`**: according to specification of C++ std::acosh - **`atanh(x)`**: according to specification of C++ std::atanh The following table lists special functions for `SymReal`: The class **symbolic** has the following **functions and structures**: - **`sign(x)`**: returns 0 for x=0, -1 for x<0 and 1 for x>1. - **`Not(x)`**: returns logical not of expression, equal to Python's 'not'. Not(True)=False, Not(0.)=True, Not(-0.1)=False - **`min(x, y)`**: return minimum of x and y. - **`max(x, y)`**: return maximum of x and y. - **`mod(x, y)`**: return floating-point remainder of the division operation x / y. For example, mod(5.1, 3) gives 2.1 as a remainder. - **`pow(x, y)`**: return $x^y$. - **`IfThenElse(condition, ifTrue, ifFalse)`**: Symbolic function for conditional evaluation. If the condition evaluates to True, the expression ifTrue is evaluated, while otherwise expression ifFalse is evaluated *Example*: ```python x=esym.Real(-1) y=esym.Real('y',2) a=esym.IfThenElse(x<0, y+1, y-1) ``` - **`SetRecording(flag)`**: Set current (global / module-wide) status of expression recording. By default, recording is on. *Example*: ```python esym.SetRecording(True) ``` - **`GetRecording()`**: Get current (global / module-wide) status of expression recording. *Example*: ```python esym.GetRecording() ``` ## symbolic.Vector A symbolic Vector type to replace Python's (1D) numpy array in symbolic expressions. The `symbolic.Vector` may be directly set to a list of floats or (1D) numpy array and be evaluated as array. However, turning on recording by using `exudyn.symbolic.SetRecording(True)` (on by default), results are stored as expression trees, which may be evaluated in C++ or Python, in particular in user functions, see the following example: ```python import exudyn as exu import numpy as np esym = exu.symbolic SymVector = esym.Vector SymReal = esym.Real a = SymReal('a',42.) b = SymReal(13) c = a-3*b #create from list: v1 = SymVector([1,3,2]) print('v1: ',v1) #create from numpy array: v2 = SymVector(np.array([1,3,2])) print('v2 initial: ',v2) #create from list, mixing symbolic expressions and numbers: v2 = SymVector([a,42,c]) print('v2 now: ',v2,"=",v2.Evaluate()) print('v1+v2: ',v1+v2,"=",(v1+v2).Evaluate()) #evaluate as vector print('v1*v2: ',v1*v2,"=",(v1*v2).Evaluate()) #evaluate as Real #access of vector component: print('v1[2]: ',v1[2],"=",v1[2].Evaluate()) #evaluate as Real ``` ```text v1: [1,3,2] v2 initial: [1,3,2] v2 now: [a,42,(a - (3 * 13))] = [42. 42. 3.] v1+v2: ([1,3,2] + [a,42,(a - (3 * 13))]) = [43. 45. 5.] v1*v2: ([1,3,2] * [a,42,(a - (3 * 13))]) = 174.0 v1[2]: [1,3,2][2] = 2.0 ``` To create a symbolic Vector, use `aa=symbolic.Vector([3,4.2,5]` to build a Python object aa with values [3,4.2,5]. In order to use a named vector, use `v=symbolic.Vector('myVec',[3,4.2,5])`. Vectors can be also created from mixed symbolic expressions and numbers, such as `v=symbolic.Vector([x,x**2,3.14])`, however, this cannot become a named vector as it contains expressions. There is a significance difference to numpy, such that '*' represents the scalar vector multplication which gives a scalar. Furthermore, the comparison operator '==' gives only True, if all components are equal, and the operator '!=' gives True, if any component is unequal. Note that in the following, we use the abbreviation `SymVector=exudyn.symbolic.Vector`, and the examples `esym=exudyn.symbolic`. Note that only functions are able to be recorded. Member functions of `SymVector` are: The class **symbolic.Vector** has the following **functions and structures**: - **`__init__(vector)`**: Construct symbolic.Vector from vector represented as numpy array or list (which may contain symbolic expressions). - **`__init__(name, vector)`**: Construct named symbolic.Vector from name and vector represented as numpy array or list (which may contain symbolic expressions). - **`Evaluate()`**: Return evaluated expression (prioritized) or stored vector value. (not recorded) - **`SetVector(vector)`**: Set stored vector or named vector expression to new given (non-symbolic) vector. Only works, if SymVector contains no expression. (may lead to inconsistencies in recording) - **`NumberOfItems()`**: Get size of Vector (may require to evaluate expression; not recording) - **`operator __setitem__(index)`**: bracket [] operator for setting a component of the vector. Only works, if SymVector contains no expression. (may lead to inconsistencies in recording) - **`NormL2()`**: return (symbolic) L2-norm of vector. *Example*: ```python v1 = esym.Vector([1,4,8]) length = v1.NormL2() #gives 9. ``` - **`MultComponents(other)`**: Perform component-wise multiplication of vector times other vector and return result. This corresponds to the numpy multiplication using '*'. *Example*: ```python v1 = esym.Vector([1,2,4]) v2 = esym.Vector([1,0.5,0.25]) v3 = v1.MultComponents(v2) ``` - **`operator __getitem__(index)`**: bracket [] operator to return (symbolic) component of vector, allowing read-access. Index may also evaluate from an expression. - **`operator __str__()`**: conversion of SymVector to string - **`operator __repr__()`**: representation of SymVector in Python Standard vector operators are available for `SymVector`, see the following examples: ```python v = SymVector([1,3,2]) w = SymVector([3.3,2.2,1.1]) u = v+w u = v-w u = -v #scalar multiplication; evaluates to SymReal: x = v*w #NOTE: component-wise multiplication, returns SymVector: u = v.MultComponents(w) #inplace operators: v += w v -= w v *= SymReal(0.5) ``` ## symbolic.Matrix A symbolic Matrix type to replace Python's (2D) numpy array in symbolic expressions. The `symbolic.Matrix` may be directly set to a list of list of floats or (2D) numpy array and be evaluated as array. However, turning on recording by using `exudyn.symbolic.SetRecording(True)` (on by default), results are stored as expression trees, which may be evaluated in C++ or Python, in particular in user functions, see the following example: ```python import exudyn as exu import numpy as np esym = exu.symbolic SymMatrix = esym.Matrix SymReal = esym.Real a = SymReal('a',42.) b = SymReal(13) #create matrix from list of lists m1 = SymMatrix([[1,3,2],[4,5,6]]) #create symbolic matrix from list of lists m3 = SymMatrix([[a,3*b,2],[4,5,6]]) #create from numpy array m2 = SymMatrix(np.ones((3,3))-np.eye(3)) m1 += m3 m1 *= 3 m1 -= 3*m3 print('m1: ',m1) print('m2: ',m2) ``` ```text m1: ((3 * ([[1,3,2],[4,5,6]] + [[a,(3 * 13),2], [4,5,6]])) - (3 * [[a,(3 * 13),2], [4,5,6]])) m2: [[0,1,1],[1,0,1],[1,1,0]] ``` To create a symbolic Matrix, use `aa=symbolic.Matrix([[3,4.2],[3.3,1.2]]` to build a Python object aa. In order to use a named matrix, use `v=symbolic.Matrix('myMat',[3,4.2,5])`. Matrixs can be also created from mixed symbolic expressions and numbers, such as `v=symbolic.Matrix([x,x**2,3.14])`, however, this cannot become a named matrix as it contains expressions. There is a significance difference to numpy, such that '*' represents the matrix multplication (compute components from row times column operations). Note that in the following, we use the abbreviation `SymMatrix=exudyn.symbolic.Matrix`, and the examples `esym=exudyn.symbolic`. Member functions of `SymMatrix` are: The class **symbolic.Matrix** has the following **functions and structures**: - **`__init__(matrix)`**: Construct symbolic.Matrix from vector represented as numpy array or list of lists (which may contain symbolic expressions). - **`__init__(name, matrix)`**: Construct named symbolic.Matrix from name and vector represented as numpy array or list of lists (which may contain symbolic expressions). - **`Evaluate()`**: Return evaluated expression (prioritized) or stored Matrix value. (not recorded) - **`SetMatrix(matrix)`**: Set stored Matrix or named Matrix expression to new given (non-symbolic) Matrix. Only works, if SymMatrix contains no expression. (may lead to inconsistencies in recording) - **`NumberOfRows()`**: Get number of rows (may require to evaluate expression; not recording) - **`NumberOfColumns()`**: Get number of columns (may require to evaluate expression; not recording) - **`operator __setitem__(row, column)`**: bracket [] operator for (symbolic) component of Matrix (write-access). Only works, if SymMatrix contains no expression. (may lead to inconsistencies in recording) - **`operator __getitem__(row, column)`**: bracket [] operator for (symbolic) component of Matrix (read-access). Row and column may also evaluate from an expression. - **`operator __str__()`**: conversion of SymMatrix to string - **`operator __repr__()`**: representation of SymMatrix in Python Standard Matrix operators are available for `SymMatrix`, see the following examples: ```python m1 = SymMatrix([[1,7],[4,5]]) m2 = SymMatrix([[1,2.2],[4,4.3]]) v = SymVector([1.5,3]) m3 = m1+m2 m3 = m1-m2 m3 = m1*m2 #multiply with scalar m3 = 13*m2 m3 = m2*3.14 #multiply with vector m3 = m2*v #transposed: m3 = v*m2 #equals numpy operation m2.T @ v #inplace operators: m1 += m1 m1 -= m1 m1 *= 3.14 ``` ## symbolic.VariableSet A container for symbolic variables, in particular for exchange between user functions and the model. For details, see the following example: ```python import exudyn as exu import math esym = exu.symbolic SymReal = esym.Real #use global variable set: variables = esym.variables #create a named Real a = SymReal('a',42.) #regular way to add variable: variables.Add('pi', math.pi) #add named variable (doesn't need a name): variables.Add(a) #print current variable set print(variables) print('pi=',variables.Get('pi').Evaluate()) #3.14 print('a=',variables.Get('a')) #prints 'a' x=variables.Get('a') print('x=',x.Evaluate()) #x=42 #override a variables.Set('a',3.33) #x is depending on a: print('x:',x,"=",x.Evaluate()) #3.33 #create your own variable set mySet = esym.VariableSet() ``` ```text {'pi': 3.14159, 'a': 42} pi= 3.141592653589793 a= a x= 42.0 x: a = 3.33 ``` The class **symbolic.VariableSet** has the following **functions and structures**: - **`Add(name, value)`**: Add a variable with name and value (name may not exist) - **`Add(namedReal)`**: Add a variable with named real (name may not exist) - **`Set(name, value)`**: Set a variable with name and value (adds new or overrides existing) - **`Get(name)`**: Get a variable by name - **`Exists(name)`**: Return True, if variable name exists - **`Reset()`**: Erase all variables and reset VariableSet - **`NumberOfItems(name)`**: Return True, if variable name exists - **`GetNames()`**: Get list of stored variable names - **`data[index]= ...name, value`**: bracket [] operator for setting a variable to a specific value - **`... = data[index]name`**: bracket [] operator for getting a specific variable by name - **`operator __str__()`**: create string of set of variables - **`operator __repr__()`**: representation of SymMatrix in Python ## symbolic.UserFunction A class for creating and handling symbolic user functions in C++. Use these functions for high performance extensions, e.g., of existing objects or loadsFor details, see the following example: ```python import exudyn as exu esym = exu.symbolic from exudyn.utilities import * #advancedUtilities with user function utilities included SymReal = exu.symbolic.Real SC = exu.SystemContainer() mbs = SC.AddSystem() #regular Python user function with esym math functions def UFload(mbs, t, load): return load*esym.sin(10*(2*pi)*t) #add ground and mass point: oGround = mbs.CreateGround() oMassPoint = mbs.CreateMassPoint(referencePosition=[1.+0.05,0,0], mass=1) #add marker and load: mc = mbs.AddMarker(MarkerNodeCoordinate(nodeNumber=mbs.GetObject(oMassPoint)['nodeNumber'], coordinate=0)) load = mbs.AddLoad(LoadCoordinate(markerNumber=mc, load=10)) #create symbolic user function from Python user function, for this load: symFuncLoad = CreateSymbolicUserFunction(mbs, UFload, 'loadUserFunction', load, verbose=1) mbs.SetLoadParameter(load, 'loadUserFunction', symFuncLoad) #print string of symbolic expression of user function (to check if it looks ok): print('load user function: ',symFuncLoad) #test evaluate user function; requires args of user function: print('load user function: ',symFuncLoad.Evaluate(mbs, 0.025, 10.)) #now you could add further items or simulate ... ``` ```text Function Name: UFload Number of Arguments: 3 Argument Names: ('mbs', 't', 'load') return value= (load * sin((62.8319 * t))) load user function: (load * sin((62.8319 * t))) load user function: 10.0 ``` The class **symbolic.UserFunction** has the following **functions and structures**: - **`Evaluate()`**: Evaluate symbolic function with test values; requires exactly same args as Python user functions; this is slow and only intended for testing - **`SetUserFunctionFromDict(mainSystem, fcnDict, itemIndex, userFunctionName)`**: Create C++ std::function (as requested in C++ item) with symbolic user function as recorded in given dictionary, as created with ConvertFunctionToSymbolic(...). - **`operator __repr__()`**: Representation of Symbolic function - **`operator __str__()`**: Convert stored symbolic function to string