I am trying to understand a Python program that solves differential equations numerically using the Runge-Kutta method. I have developed my own solution but was looking for other implementations. I found several but this one intrigued me as I am having a difficult time understanding how lambda works.
Here is the code:
def RK4(f):
return lambda t, y, dt: (
lambda dy1: (
lambda dy2: (
lambda dy3: (
lambda dy4: (dy1 + 2*dy2 + 2*dy3 + dy4)/6
)( dt * f( t + dt , y + dy3 ) )
)( dt * f( t + dt/2, y + dy2/2 ) )
)( dt * f( t + dt/2, y + dy1/2 ) )
)( dt * f( t , y ) )
def theory(t): return (t**2 + 4)**2 /16
from math import sqrt
dy = RK4(lambda t, y: t*sqrt(y))
t, y, dt = 0., 1., .1
while t <= 10:
if abs(round(t) - t) < 1e-5:
print("y(%2.1f)\t= %4.6f \t error: %4.6g" % ( t, y, abs(y - theory(t))))
t, y = t + dt, y + dy( t, y, dt )
The long strings of lambdas and the dy function is confusing me.
First: How is the function RK4 receiving (t, y, dt) when dy is called? It looks appears that the lambda in dy = RK4(..) is only taking two parameters.
Second: How do the repeated lambda calls in RK4 work?
First of all, read about higher-order functions.
First: How is the function RK4 receiving (t, y, dt) when dy is called?
It looks appears that the lambda in dy = RK4(..) is only taking two
parameters.
Ok, simple facts:
def RK4(f)
RK4 is recieving one argument. Which, in this case, is a (lambda) function:
dy = RK4(lambda t, y: t*sqrt(y))
So in this case, f is going to be lambda t, y: t*sqrt(y).
RK4 returns a function, which takes 3 arguments:
return lambda t, y, dt: ( ... )
So the call dy( t, y, dt ) is fine.
A lambda is syntactic sugar for creating a simple function with minimal boilerplate.
For example:
f = lamdbda x: x + 5
The above is morally equivalent to:
def __lambda_123_line_567(x): # name is for illustrative purposes, only
return x + 5 # Python internally may name it however it
# chooses to do so
f = __lambda_123_line_567
The general syntax of a lambda is the keyword "lambda", followed by a list of parameters, followed by a colon, followed by the expression that should be returned by the function.
The lambda operator or lambda function is a way to create small anonymous functions. Here's the general format:
lambda(variable: test for, apply to)
Related
I am trying to convert this code into MATLAB but I am not sure how to do the subscripts (Y[i] = Y[i-1]) as well as the func and f_exact variables
heres the code:
def Forward_Euler(y0,t0,T,dt,f):
t = np.arange(t0,T+dt,dt)
Y = np.zeros(len(t))
Y[0] = y0
for i in range(1,len(t)):
Y[i] = Y[i-1]+dt*f(Y[i-1], t[i-1])
return Y, t
func = lambda y,t: y-t
f_exact = lambda t: t+1-1/2*np.exp(t)
You can use anonymous functions in matlab:
func = #(y,t)(y - t)
f_exact = #(t)(t + 1 - exp(t)/2) % it works with any matrix t as well
And you can use for matrices as well (they should keep matrix operation rules). For example, in func function, as there is a minus in the form of function, the dimension of y and t must be the same.
I'm trying to make some basic substitutions but SymPy doesn't want to help me out
x, y, z, k = symbols("x y z k", positive=True, real=True)
exp = x**4 + x**3 + x**2 + x
what_im_expecting = simplify(y**(Rational(1/4)) + y**(Rational(3/4)) + sqrt(y) + y)
what_i_actually_get = exp.subs(x**4,y)
exp, what_i_actually_get, what_im_expecting
returns
x + y**(Rational(3, 4)) + sqrt(y) + y
Can anyone help me out?
a more complex example:
The method subs can be trusted to replace the terms that exactly match the given "old" expression, which x**4 here. The replacement of other things related to x**4 is not so certain. (There are many open issues with subs: some say it substitutes too much, some say too little.) There is some substitution logic specific to powers, but x by itself is not formally a power, so it escapes that logic. A workaround: temporarily replace x by x**1, preventing automatic evaluation of that power to x.
x1 = sp.Pow(x, 1, evaluate=False)
subbed = exp.subs(x, x1).subs(x**4, y).subs(x1, x)
Now subbed is y**(3/4) + y**(1/4) + sqrt(y) + y.
But, don't expect human-like ingenuity from subs. With the same workaround, trying to do subs(x**4 - 1, y) results in x**3 + x**2 + x + y + 1: nothing like sqrt(y+1), etc, appears. It's better to substitute in the most direct way possible:
subs(x, (y+1)**Rational(1, 4))
Then you don't need any workarounds.
Let us take an example. In certain libraries like "scipy.integrate" calling a function like "odeint" (integrating functions) has to be expressed as "odeint(func, y0, T, ...)" where "func" is a name of a function that has to be predefined with two parameters (y,t0), y vector and t0 a scalar.
The question is how to use "odeint" if the already defined function "func" is specified with two parameters but in the inverse order "(t0, y)".
Best regards.
You can use a lambda function to reorder the arguments like so:
odeint(lambda p, q: func(q, p), y0, T, ...)
Alternatively, you can supposedly swap the orders in the main call if all odeint does is call func on the arguments and does interact directly with the arguments:
odeint(func, T, y0, ...)
You can explicitly call parameters in arbitrary sequence:
def func(a, b, c):
print('a = {}'.format(a))
print('b = {}'.format(b))
print('c = {}'.format(c))
x = 1
y = 2
z = 3
func(a = x, b = y, c = z)
func(c = z, b = y, a = x)
func(b = y, c = z, a = x)
P.S. I'm not on 100% sure, but try odeint(func, y0 = y0, T = T, ...)
or odeint(func, T = T, y0 = y0, ...).
I'm trying to compute higher derivatives of f(y(x)) w.r.t to x using sympys diff.
from sympy import *
from IPython.display import display
init_printing(use_latex=True)
x = symbols('x')
f, y = symbols('f, y', cls=Function)
d2 = diff(f(y(x)),x,2)
print(d2)
print(d2.doit())
Sympy returns :
Derivative(y(x), x)**2*Derivative(f(y(x)), y(x), y(x)) + Derivative(y(x), x, x)*Subs(Derivative(f(_xi_1), _xi_1), (_xi_1,), (y(x),))
Derivative(f(y(x)), y(x))*Derivative(y(x), x, x) + 2*Derivative(y(x), x)**2*Derivative(f(y(x)), y(x), y(x))
Latex image: Sympy result.
While the first result seems to be correct, I do not understand the factor 2 in the second expression after the doit() operation.
It looks like you stumbled upon a bug, which was just fixed a few weeks ago.
You can test this by substituting f, y, and x with some functions or values respectively (append to your code):
f_ex = Lambda(x, x**2)
y_ex = Lambda(x, sin(x))
x_ex = 2
substitutions = [ (f,f_ex), (y,y_ex), (x,x_ex) ]
print( d2.subs(substitutions).doit().n() ) #-1.30728724172722
print( d2.doit().subs(substitutions).doit().n() ) #-0.960930862590836
The printed values should be the same.
The issue can be further isolated to:
print((Derivative(f(y(x)), x, x)))
print((Derivative(f(y(x)), y(x), y(x))).doit())
Here, a plain doit simply adds a factor of 2, which is obviously wrong.
I have following test program. My query is two folded: (1) Some how the solution is giving zero and (2) Is it appropriate to use this x2= np.where(x > y, 1, x) kind of conditions on variables ? Are there any constrained optimization routines in Scipy ?
a = 13.235
b = 70.678
def system(X, a,b):
x=X[0]
y=X[1]
x2= np.where(x > y, 1, x)
f=np.zeros(3)
f[0] = 2*x2 - y - a
f[1] = 3*x2 + 2*y- b
return (X)
func= lambda X: system(X, a, b)
guess=[5,5]
sol = optimize.root(func,guess)
print(sol)
edit: (2a) Here with x2= np.where(x > y, 1, x) condition, two equations becomes one equation.
(2b) In another variation requirement is: x2= np.where(x > y, x^2, x^3). Let me comments on these two as well. Thanks !
First up, your system function is an identity, since you return X instead of return f. The return should be the same shape as the X so you had better have
f = np.array([2*x2 - y - a, 3*x2 + 2*y- b])
Next the function, as written has a discontinuity where x=y, and this is causing there to be be a problem for the initial guess of (5,5). Setting the initial guess to (5,6) allows for the the solution [13.87828571, 14.52157143] to be found rapidly.
With the second example, again using an initial guess of [5,5] causes problems of discontinuity, using [5,6] gives a solution of [ 2.40313743, 14.52157143].
Here is my code:
import numpy as np
from scipy import optimize
def system(X, a=13.235, b=70.678):
x = np.where(X[0] > X[1], X[0]**2, X[0]**3)
y=X[1]
return np.array( [2*x - y - a, 3*x + 2*y - b])
guess = [5,6]
sol = optimize.root(system, guess)
print(sol)