Python `for` loop doesn't update `numpy` array values - python

I'm trying to make a simple numerical gradient function and part of it is a for loop updating parameter values that would later be evaluated. The code is as follows:
import numpy as np
def target_gradient(theta):
e = 10
for i in range(theta.shape[0]):
theta_upper = theta
theta_lower = theta
theta_upper[i] = theta[i] + e
theta_lower[i] = theta[i] - e
print(f"theta_upper {theta_upper}")
print(f"theta_lower {theta_lower}")
return theta_upper, theta_lower
u, l = target_gradient(np.array([1, 1, 1, 1, 1]))
However, instead of the anticipated output, I get [1 1 1 1 1] for both arrays. Print statements are there for monitoring and they show that throughout the loop the arrays didn't change (i.e. were [1 1 1 1 1]).e=10 is so that the effect is more pronounced. I also tried the enumerate() approach, but get the same result.
The full gradient funtion would look something like this
def target_gradient(theta, x, y):
e = 0.01
gradient = np.zeros(theta.shape[0])
for i in range(theta.shape[0]):
theta_upper = theta
theta_lower = theta
theta_upper[i] = theta[i] + e
theta_lower[i] = theta[i] - e
gradient[i] = (
foo(theta=theta_upper, x=x, y=y) - foo(theta=theta_lower, x=x, y=y)
) / (2 * e)
return gradient
Therefore, I am intentionally declaring theta_upper = theta inside the loop because I want to calculate the gradient for which I need partial (numerical) derivatives.

The best approach depends on what foo is:
If foo can take vector arguments and return vector values, e.g.
def foo(theta, x, y):
return x * y * np.sin(theta)
Then you can simply do:
def target_gradient(theta, x, y, e=0.01):
foo_upper = foo(theta + e, x, y) # Add e to the entire theta vector, and call foo
foo_lower = foo(theta - e, x, y) # Subtract e from the entire theta vector, and call foo
return (foo_upper - foo_lower) / (2 * e)
Based on your code, where you pass a vector theta_upper to foo, I suspect this is the case.
If foo can't take vector arguments and return vector values, e.g.
def foo(theta, x, y):
return x * y * math.sin(theta)
then you need to iterate over theta, and call foo for each value.
def target_gradient(theta, x, y, e=0.01):
gradient = np.zeros(theta.shape[0])
for i in range(theta.shape[0]):
foo_upper = foo(theta[i] + e, x[i], y[i]) # Take a single value of theta, and add e
foo_lower = foo(theta[i] - e, x[i], y[i]) # Take a single value of theta, and subtract e
gradient[i] = (foo_upper - foo_lower) / (2 * e)
return gradient

The reason why theta_upper and theta_lower are not changing inside the loop is because you are creating copies of theta and assigning them to theta_upper and theta_lower. Therefore, when you modify theta_upper[i] or theta_lower[i], you are not modifying the original theta array.
To fix this, you can use the copy() method to create a copy of theta that you can modify inside the loop, like this:
def target_gradient(theta):
e = 10
for i in range(theta.shape[0]):
theta_upper = theta.copy()
theta_lower = theta.copy()
theta_upper[i] = theta[i] + e
theta_lower[i] = theta[i] - e
print(f"theta_upper {theta_upper}")
print(f"theta_lower {theta_lower}")
return theta_upper, theta_lower

Related

Call argument from multi returned arguments function using Python

By using Python : I want to call just one argument from defined function (Angles) which return two arguments theta and phi. How can I call just theta value from Angles funcition and assign it to x value?
values = [1,-1]
g = np.random.choice(values)
q= np.random.choice(values)
def Angles ():
theta = (((1 + g*g - ((1 - g*g)/(1 - g + 2*g*q))**2)/(2*g)))
phi = 2 * math.radians(180) * q
return theta,phi
x = cos(theta)
You have to call Angles first; theta will be the first element of the tuple returned by Angles.
th, ph = Angles()
x = cos(th)
or
x = cos(Angles()[0])

Using 4th order Runge Kutta to solve the 2nd order differential equation of a damped oscillator

So im tasked with using the 4th order Runge Kutta Meathod to solve the 2nd order differential equation of a damped occilator.
my function for the runge-kutta meathod looks as such
def RungeKutta(f,y0,x):
y=np.zeros((len(x),len(y0)))
y[0,:]=np.array(y0)
h=x[1]-x[0]
for i in range(0,len(x)-1):
k1=h*np.array(f(y[i,:],x[i]))
k2=h*np.array(f(y[i,:]+k1/2,x[i]+h/2))
k3=h*np.array(f(y[i,:]+k2/2,x[i]+h/2))
k4=h*np.array(f(y[i,:]+k3,x[i]+h))
y[i+1,:]=y[i,:]+k1/6+k2/3+k3/3+k4/6
return y
the rungeKutta function works fine, and I have tested it with a list of example inputs so that doesnt seem to be the problem
im given
question parameters
and have to make a class to solve the problem
class harmonicOscillator:
def __init__(self,m,c,k):
if((m>0) and ((type(m) == int) or (type(m) == float))):
self.m = m
else:
raise ValueError
if((c>0) and ((type(c) == int) or (type(c) == float))):
self.c = c
else:
raise ValueError
if((k>0) and ((type(k) == int) or (type(k) == float))):
self.k = k
else:
raise ValueError
def period(self):
self.T = 2 * np.pi * (self.m / self.k)**(0.5)
return(self.T)
def solve(self, func, y0):
m = self.m
c = self.c
k = self.k
T = self.T
t = np.linspace(0,10*T,1000)
but im unsure where to really progress. ive tried turning the 2nd order differential equation into a lambda function like such
F = lambda X,t: [X[1], (-c) * X[1] + (-k) * X[0] + func(t)]
and then passing that into my RungeKutta function
result = RungeKutta(F, y0, t, func)
return(result)
but im not really well versed in lambda functions and am clearly going wrong somewhere.
an example input that it should pass would be something like this
####### example inputs #######
m=1
c=0.5
k=2
a = harmonicOscillator(m,c,k)
a.period()
x0 = [0,0]
tHO,xHO= a.solve(lambda t: omega0**2,x0)
would really appreciate some help. the requirments for the questions are that I have to use the above rungeKutta function, but im just kind of lost at this point
thanks.
I think there may be some confusion over the external forcing term and the Runge Kutta derivative helper function F. The F in RK4 returns the derivative dX/dt of the system of first order differential equations X. The forcing term in a damped oscillator is unfortunately also called F but it is a function of t.
One of your issues is that the arity (number of parameters) of your RungeKutta() function and your call to that function do not match: you tried to do RungeKutta(F, y0, t, func), but the RungeKutta() function only takes arguments (f, y0, x) in that order.
In other words, the f parameter in your current RungeKutta() function should encapsulate the forcing function F(t).
You can do this with helpers:
# A constant function in your case, but this can be any function of `t`
def applied_force(t):
# Note, you did not provide a value for `omega0`
return omega0 ** 2
def rk_derivative_factory(c, k, F):
return lambda X, t: np.array([X[1], -c * X[1] - k * X[0] + F(t)])
The rk_derivative_factory() is a function which takes a damping coefficient, a spring constant, and a forcing function F(t), and returns a function which takes a system X and a time step t as arguments (because this is what is demanded of you by the implementation of RungeKutta()).
Then,
omega0 = 0.234
m, c, k = 1, 0.25, 2
oscillator = HarmonicOscillator(m, c, k)
f = rk_derivative_factory(oscillator, applied_force)
x_osc = oscillator.solve(f, [1, 0])
Where solve() is defined like so:
def solve(self, func, y0):
t = np.linspace(0,10 * self.period(), 1000)
return RungeKutta(f, y0, t)
As an aside, I strongly recommend being more consistent about your variable names. You named the initial state of your oscillator x0, and were passing it to RungeKutta() as the argument for the parameter y0, and the x parameter of RungeKutta() represents time... Gets pretty confusing.
Full solution
Lastly, your implementation of RK4 wasn't quite correct, so I've fixed that and made some other slight improvements.
Note that one thing you might want to consider is making the HarmonicOscillator.solve() function take a solver. Then you could play around with different integrators.
import numpy as np
def RungeKutta(f, y0, x):
y = np.zeros((len(x), len(y0)))
y[0, :] = np.array(y0)
h = x[1] - x[0]
for i in range(0, len(x) - 1):
# Many slight changes below
k1 = np.array(f(y[i, :], x[i]))
k2 = np.array(f(y[i, :] + h * k1 / 2, x[i] + h / 2))
k3 = np.array(f(y[i, :] + h * k2 / 2, x[i] + h / 2))
k4 = np.array(f(y[i, :] + h * k3, x[i] + h))
y[i + 1, :] = y[i, :] + (h / 6) * (k1 + 2 * k2 + 2 * k3 + k4)
return y
# A constant function in your case, but this can be any function of `t`
def applied_force(t):
# Note, you did not provide a value for `omega0`
return omega0 ** 2
def rk_derivative_factory(osc, F):
return lambda X, t: np.array([X[1], (F(t) - osc.c * X[1] - osc.k * X[0]) / osc.m])
class HarmonicOscillator:
def __init__(self, m, c, k):
if (type(m) in (int, float)) and (m > 0):
self.m = m
else:
raise ValueError("Parameter 'm' must be a positive number")
if (type(c) in (int, float)) and (c > 0):
self.c = c
else:
raise ValueError("Parameter 'c' must be a positive number")
if (type(k) in (int, float)) and (k > 0):
self.k = k
else:
raise ValueError("Parameter 'k' must be a positive number")
self.T = 2 * np.pi * (self.m / self.k)**(0.5)
def period(self):
return self.T
def solve(self, func, y0):
t = np.linspace(0, 10 * self.period(), 1000)
return RungeKutta(func, y0, t)
Demo:
import plotly.graph_objects as go
omega0 = 0.234
m, c, k = 1, 0.25, 2
oscillator = HarmonicOscillator(m, c, k)
f = rk_derivative_factory(oscillator, applied_force)
x_osc = oscillator.solve(f, [1, 0])
x, dx = x_osc.T
t = np.linspace(0, 10 * oscillator.period(), 1000)
fig = go.Figure(go.Scatter(x=t, y=x, name="x(t)"))
fig.add_trace(go.Scatter(x=t, y=dx, name="x'(t)"))
Output:

Cubic spline for non-monotonic data (not a 1d function)

I have a curve as shown below:
The x coordinates and the y coordinates for this plot are:
path_x= (4.0, 5.638304088577984, 6.785456961280076, 5.638304088577984, 4.0)
path_y =(0.0, 1.147152872702092, 2.7854569612800755, 4.423761049858059, 3.2766081771559668)
And I obtained the above picture by:
x_min =min(path_x)-1
x_max =max(path_x)+1
y_min =min(path_y)-1
y_max =max(path_y)+1
num_pts = len(path_x)
fig = plt.figure(figsize=(8,8))
#fig = plt.figure()
plt.suptitle("Curve and the boundary")
ax = fig.add_subplot(1,1,1)
ax.set_xlim([min(x_min,y_min),max(x_max,y_max)])
ax.set_ylim([min(x_min,y_min),max(x_max,y_max)])
ax.plot(path_x,path_y)
Now my intention is to draw a smooth curve using cubic splines. But looks like for cubic splines you need the x coordinates to be on ascending order. whereas in this case, neither x values nor y values are in the ascending order.
Also this is not a function. That is an x value is mapped with more than one element in the range.
I also went over this post. But I couldn't figure out a proper method to solve my problem.
I really appreciate your help in this regard
As suggested in the comments, you can always parameterize any curve/surface with an arbitrary (and linear!) parameter.
For example, define t as a parameter such that you get x=x(t) and y=y(t). Since t is arbitrary, you can define it such that at t=0, you get your first path_x[0],path_y[0], and at t=1, you get your last pair of coordinates, path_x[-1],path_y[-1].
Here is a code using scipy.interpolate
import numpy
import scipy.interpolate
import matplotlib.pyplot as plt
path_x = numpy.asarray((4.0, 5.638304088577984, 6.785456961280076, 5.638304088577984, 4.0),dtype=float)
path_y = numpy.asarray((0.0, 1.147152872702092, 2.7854569612800755, 4.423761049858059, 3.2766081771559668),dtype=float)
# defining arbitrary parameter to parameterize the curve
path_t = numpy.linspace(0,1,path_x.size)
# this is the position vector with
# x coord (1st row) given by path_x, and
# y coord (2nd row) given by path_y
r = numpy.vstack((path_x.reshape((1,path_x.size)),path_y.reshape((1,path_y.size))))
# creating the spline object
spline = scipy.interpolate.interp1d(path_t,r,kind='cubic')
# defining values of the arbitrary parameter over which
# you want to interpolate x and y
# it MUST be within 0 and 1, since you defined
# the spline between path_t=0 and path_t=1
t = numpy.linspace(numpy.min(path_t),numpy.max(path_t),100)
# interpolating along t
# r[0,:] -> interpolated x coordinates
# r[1,:] -> interpolated y coordinates
r = spline(t)
plt.plot(path_x,path_y,'or')
plt.plot(r[0,:],r[1,:],'-k')
plt.xlabel('x')
plt.ylabel('y')
plt.show()
With output
For non-ascending x splines can be easily computed if you make both x and y functions of another parameter t: x(t), y(t).
In your case you have 5 points so t should be just enumeration of these points, i.e. t = 0, 1, 2, 3, 4 for 5 points.
So if x = [5, 2, 7, 3, 6] then x(t) = x(0) = 5, x(1) = 2, x(2) = 7, x(3) = 3, x(4) = 6. Same for y.
Then compute spline function for both x(t) and y(t). Afterwards compute values of splines in all many intermediate t points. Lastly just use all calculated values x(t) and y(t) as a function y(x).
Once before I implemented cubic spline computation from scratch using Numpy, so I use this code in my example below if you don't mind (it could be useful for you to learn about spline math), replace with your library functions. Also in my code you can see numba lines commented out, if you want you can use these Numba annotations to speed up computation.
You have to look at main() function at the bottom of code, it shows how to compute and use x(t) and y(t).
Try it online!
import numpy as np, matplotlib.pyplot as plt
# Solves linear system given by Tridiagonal Matrix
# Helper for calculating cubic splines
##numba.njit(cache = True, fastmath = True, inline = 'always')
def tri_diag_solve(A, B, C, F):
n = B.size
assert A.ndim == B.ndim == C.ndim == F.ndim == 1 and (
A.size == B.size == C.size == F.size == n
) #, (A.shape, B.shape, C.shape, F.shape)
Bs, Fs = np.zeros_like(B), np.zeros_like(F)
Bs[0], Fs[0] = B[0], F[0]
for i in range(1, n):
Bs[i] = B[i] - A[i] / Bs[i - 1] * C[i - 1]
Fs[i] = F[i] - A[i] / Bs[i - 1] * Fs[i - 1]
x = np.zeros_like(B)
x[-1] = Fs[-1] / Bs[-1]
for i in range(n - 2, -1, -1):
x[i] = (Fs[i] - C[i] * x[i + 1]) / Bs[i]
return x
# Calculate cubic spline params
##numba.njit(cache = True, fastmath = True, inline = 'always')
def calc_spline_params(x, y):
a = y
h = np.diff(x)
c = np.concatenate((np.zeros((1,), dtype = y.dtype),
np.append(tri_diag_solve(h[:-1], (h[:-1] + h[1:]) * 2, h[1:],
((a[2:] - a[1:-1]) / h[1:] - (a[1:-1] - a[:-2]) / h[:-1]) * 3), 0)))
d = np.diff(c) / (3 * h)
b = (a[1:] - a[:-1]) / h + (2 * c[1:] + c[:-1]) / 3 * h
return a[1:], b, c[1:], d
# Spline value calculating function, given params and "x"
##numba.njit(cache = True, fastmath = True, inline = 'always')
def func_spline(x, ix, x0, a, b, c, d):
dx = x - x0[1:][ix]
return a[ix] + (b[ix] + (c[ix] + d[ix] * dx) * dx) * dx
# Compute piece-wise spline function for "x" out of sorted "x0" points
##numba.njit([f'f{ii}[:](f{ii}[:], f{ii}[:], f{ii}[:], f{ii}[:], f{ii}[:], f{ii}[:])' for ii in (4, 8)],
# cache = True, fastmath = True, inline = 'always')
def piece_wise_spline(x, x0, a, b, c, d):
xsh = x.shape
x = x.ravel()
ix = np.searchsorted(x0[1 : -1], x)
y = func_spline(x, ix, x0, a, b, c, d)
y = y.reshape(xsh)
return y
def main():
x0 = np.array([4.0, 5.638304088577984, 6.785456961280076, 5.638304088577984, 4.0])
y0 = np.array([0.0, 1.147152872702092, 2.7854569612800755, 4.423761049858059, 3.2766081771559668])
t0 = np.arange(len(x0)).astype(np.float64)
plt.plot(x0, y0)
vs = []
for e in (x0, y0):
a, b, c, d = calc_spline_params(t0, e)
x = np.linspace(0, t0[-1], 100)
vs.append(piece_wise_spline(x, t0, a, b, c, d))
plt.plot(vs[0], vs[1])
plt.show()
if __name__ == '__main__':
main()
Output:

Need basic understanding of python arguments in order to be able code system of ODE's solution

I am an old Fortran programmer and need to help a young person to numerically solve an ODE system using Heun's method. He only knows Python so I have to learn the minimum python to get this done.
Here is what I came up with. The test code is for a simple 2 degree of freedom system with exponential growth for each degree of freedom.
The error I get is:
Traceback (most recent call last):
File "program.py", line 28, in <module>
heun (imax, y, dt, t)
File "program.py", line 7, in heun
rhs(y, ydot)
NameError: global name 'ydot' is not defined
Here is the code:
# Test RHS of ODE system:
def rhs (y, ydot):
ydot[0] = y[0]
ydot[1] = y[1]
return;
# Does one step of Heun's method:
def heun (imax, y, dt, t):
rhs(y, ydot)
for i in range(0, imax):
y_tilde[i] = y[i] + dt * ydot[i]
rhs(y_tilde, ydot_at_tilde)
for i in range(0, imax):
y[i] = y[i] + dt/2 * (ydot[i] + ydot_at_tilde[i])
t = t + dt
return;
# Initial condition
y = [0, 0]
t = 0
dt = 0.01
nsteps = 100
imax = 1
istep = 1
while istep <= nsteps:
heun (imax, y, dt, t)
print istep, y[0], y[1]
istep = istep + 1
Question: Why does python think that the object ydot in routine heun is global? Even if it were global, why can't I pass it as an argument?
The problem is here:
def heun(imax, y, dt, t):
rhs(y, ydot)
You're calling your rhs function, with the input arguments y and ydot. But ydot doesn't exist inside the scope of your heun function. Only imax, y, dt and t do.
Similarly you never define the variables ydot_tilde or ydot_at_tilde
Also, you're going to need your functions to return some values.
OK thank-you all. Here is a code that works with comments to indicate what I learnt:
def rhs (y, ydot):
ydot[0] = y[0]
ydot[1] = y[1]
return ydot;
def heun (ndof, dt, y, t):
# These initializations of local arrays take the place of Fortran declarations:
ydot = [0] * (ndof)
y_tilde = [0] * (ndof)
ydot_at_tilde = [0] * (ndof)
ydot = rhs(y, ydot)
# Note: In python range means:
# range (first element, upto but not including last element)
for i in range(0, ndof):
y_tilde[i] = y[i] + dt * ydot[i]
ydot_at_tilde = rhs(y_tilde, ydot_at_tilde)
for i in range(0, ndof):
y[i] = y[i] + dt/2 * (ydot[i] + ydot_at_tilde[i])
t = t + dt
# Note: This lists the output arguments:
return y, t;
# Initial condition
y = [1, 1]
t = 0
dt = 0.01
nsteps = 100
ndof = 2
istep = 1
while istep <= nsteps:
# Note: This is how you get the output arguments:
y, t = heun (ndof, dt, y, t)
istep = istep + 1
print t, y[0], y[1]
This is what I would do:
import numpy
def heun(ndof, dt, y, t):
ydot = numpy.zeros(ndof)
y_tilde = numpy.zeros(ndof)
ydot_at_tilde = numpy.zeros(ndof)
# Replacing first two elements does not need a function `rhs()`
ydot[:1] = y[:1]
# Vectorized operation, numpy does this loop for you at C speeds
y_tilde = y + dt * ydot
ydot_at_tilde[:1] = y_tilde[:1]
y = y + dt/2 * (ydot + ydot_at_tilde)
t = t + dt
return y, t
y = numpy.ones(2)
t = 0
dt = 0.01
nsteps = 100
ndof = 2
for istep in range(nsteps):
y, t = heun(ndof, dt, y, t)
print(t, y[0], y[1])
return; From this it is clear that you're modifying the ydot but not returning anything.
And in rhs(y, ydot) you're passing in ydot without first specifying what it should be. Hence declare a function variable ydot first with ydot = [] and hold the result in ydot as ydot = rhs(y,ydot) in heun and use
ydot.append(y[0])
ydot.append(y[1])
in 'rhs'.
return ydot in rhs and return y in heun.
Fix all the not defined errors in a similar manner.

solving dynamic number of non-linear equations in python

Fsolve in Scipy seems to be the right candidate for this, I just need help passing equations dynamically. I appreciate any thoughts in advance.
By dynamic I mean number of equations differ from one run to another for example one situation i have :
alpha*x + (1-alpha)*x*y - y = 0
beta*x + (1- beta)*x*z - z = 0
A*x + B*y + C*z = D
and another situation i have:
alpha*x + (1-alpha)*x*y - y = 0
beta*x + (1- beta)*x*z - z = 0
gama*x + (1 -gama)*x*w - w =0
A*x + B*y + C*z + D*w = E
alpha, beta, A, B, C, D and E are all constants. x, y, z, w are variables.
I haven't used Fsolve myself, but according to its documentation it takes a callable function.
Something like this handles multiple functions with unknown number of variables. Bear in mind that the args must be ordered correctly here, but each function simply takes a list.
def f1(argList):
x = argList[0]
return x**2
def f2(argList):
x = argList[0]
y = argList[1]
return (x+y)**2
def f3(argList):
x = argList[0]
return x/3
fs = [f1,f2,f3]
args = [3,5]
for f in fs:
print f(args)
For Fsolve, you could try something like this (untested):
def func1(argList, constList):
x = argList[0]
y = argList[1]
alpha = constList[0]
return alpha*x + (1-alpha)*x*y - y
def func2(argList, constList):
x = argList[0]
y = argList[1]
z = argList[2]
beta = constList[1]
return beta*x + (1- beta)*x*z - z
def func3(argList, constList):
x = argList[0]
w = argList[1] ## or, if you want to pass the exact same list to each function, make w argList[4]
gamma = constList[2]
return gama*x + (1 -gama)*x*w - w
def func4(argList, constList):
return A*x + B*y + C*z + D*w -E ## note that I moved E to the left hand side
functions = []
functions.append((func1, argList1, constList1, args01))
# args here can be tailored to fit your function structure
# Just make sure to align it with the way you call your function:
# args = [argList, constLit]
# args0 can be null.
functions.append((func1, argList2, constList2, args02))
functions.append((func1, argList3, constList3, args03))
functions.append((func1, argList4, constList4, args04))
for func,argList, constList, args0 in functions: ## argList is the (Vector) variable you're solving for.
Fsolve(func = func, x0 = ..., args = constList, ...)

Categories

Resources