Set negative value to zero in equation in Gekko? - python

How do I set all the negative value of a variable to zero in an equation. I've tried using m.max2(0,W) so when W is negative I get zero but when it's positive I get the value but since W is defined as W.dt()==s*p the value seems to be trailing I cant' set the lower bound to zero because I need the negative value elsewhere.

The max2 function is a Mathematical Program with Complementarity Constraints (MPCC) and sometimes has a hard time converging when at W=0 because it is a saddle point for optimization. Another option is the max3 function but this requires a mixed integer solver that can require more time to compute a solution. A third option is to use a function such as w * (0.5*tanh(b*w)+0.5) to get a continuously differentiable approximation to the max function. You can set the b to be a higher value but then it makes the problem harder to solve.
Another option is to successively solve the problem with higher values of b, like a barrier function in the interior point method.
Here is an example script that has all three functions:
import numpy as np
from gekko import GEKKO
m = GEKKO()
w = m.Param(np.linspace(-10,10,101))
x = m.Intermediate(w * 0.5*(m.tanh(10*w)+1))
y = m.max2(w,0)
z = m.max3(w,0)
m.options.IMODE=2
m.solve()
import matplotlib.pyplot as plt
plt.plot(w,x,'ko',label='x=0.5 w (tanh(10w)+1)')
plt.plot(w,y,'b-',label='y=min2(w,0)')
plt.plot(w,z,'r--',label='z=min3(w,0)')
plt.legend()
plt.show()

Related

Boundary value problem with singularity and boundary condition at infinity

I'm trying to solve the following boundary value problem on [0,\infty]:
f''=-f'/r+f/r^2+m^2*f+2 \lambda *f^3
f(0)=0 \ ; f(\infty)=\sqrt{-m^2/(2\lambda)}
for some constants m^2<0, \lambda>0. There is no closed form but we should have f monotonically increasing from 0 to sqrt{-m^2/(2\lambda). There is a removable singularity at r=0. This problem is just Bessel's equation plus a term in f^3.
I'm trying to solve this with Scipy's integrate.solve_bvp which can solve multi-boundary problems with a singularity at one boundary, defining y=[f,rf'] so that
y'=[0,r(m^2f+2\lambda f^3)]+(1/r)*[[0,1],[1,0]]*y
I impose the boundary condition at infinity at some large value max_x. Unfortunately my code, following the structure of the example at https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.solve_bvp.html, gives the wrong solution:
import scipy.integrate
import numpy as np
import matplotlib.pyplot as plt
m_squared=-1
Lambda=1
asymptote=np.sqrt(-m_squared/(2*Lambda))
#evaluate infinity b.c here
max_x=100
def fun(r,v):
z=(m_squared*v[0]+(2*Lambda)*(v[0]**3) )*r
return np.vstack((z-z, z))
#boundary condition
def bc(ya,yb):
return np.array([ya[0], yb[0]-asymptote])
# to treat singularity
S=np.array([[0,1],[1,0]])
x=np.linspace(0,max_x,5000)
# guess for vector y at points x
y=np.zeros((2, len(x)))
y[0,-1]=asymptote
print(y)
#solve
res=scipy.integrate.solve_bvp(fun, bc, x, y, p=None, S=S)
x_plot=np.linspace(0,max_x,1000)
y_plot=res.sol(x_plot)[0]
plt.plot(x_plot,y_plot,label="numerical")
plt.axhline(asymptote,linestyle="--",label="asymptote")
plt.xlabel("r")
plt.ylabel("f")
plt.legend()
I checked that modifying the above code to solve e.g $f''=f-1$ with $f(0)=0, f(\infty)=1$ works fine. There are no singularity in this case, so it suffices to modify fun and set S=None.
Is there an issue with my code or should I use a different boundary value solver?
I read the equation from your source as
f''(r)+f'(r)/r-f(r)/r^2 = 2*lambda*eta^2*f(r)*(f(r)^2-1)
with the proposed parameters lambda=0.2, eta=2.
In the long-term limit, the left side reduces to the second derivative and the equation to a conservative system with a center at f=0 and two saddle points at f=+-1. The task is to find a solution curve that converges to a saddle point. In more practical terms, this is similar to the task to push a rigid pendulum in such a way that it ends up in the upright position, or moves ever closer to that position.
Writing f=1-g(r) for a solution approaching the saddle point at f=1, the equation is approximately
g''(r) = a^2*g(r), a^2=4*lambda*eta^2=3.2
This again characterizes this equilibrium as a saddle point, the solutions converging toward it satisfy the reduced ODE g'(r)=-a*g(r). This can be used as upper boundary condition. Translated into the state vector this gives
def bc(ya,yb):
return np.array([ya[0], yb[1]+a*max_x*(yb[0]-1)])
(replace the equilibrium constant 1 with asymptote if you want to stay with your version).
I got good results from that, with the parameters in the paper as well as with your parameters.
However, the singular mode of the solver seems broken, it inserts nodes up to the allowed max_nodes close to zero where the solution should be simply linear. I set the initial guess to
x=np.logspace(-5,np.log10(max_x),10)
x[0]=0
# guess for vector y at points x
y=[np.tanh(a*x),a*x/np.cosh(a*x)**2]
so that this maximal node number is not violated from the start.
Not using the singular mechanism, transferring the singular terms back into the ODE function and using that the finite solutions are almost linear in the initial segment, one can use f(r)=r*f'(r) as initial condition, ya[0]-ya[1] == 0. The interval start is then some small positive number. This results in reasonable node numbers in the solution, 25 for the default tolerance 1e-3 and 100 for tolerance 1e-6.

Performance issue with Scipy's solve_bvp and coupled differential equations

I'm facing a problem while trying to implement the coupled differential equation below (also known as single-mode coupling equation) in Python 3.8.3. As for the solver, I am using Scipy's function scipy.integrate.solve_bvp, whose documentation can be read here. I want to solve the equations in the complex domain, for different values of the propagation axis (z) and different values of beta (beta_analysis).
The problem is that it is extremely slow (not manageable) compared with an equivalent implementation in Matlab using the functions bvp4c, bvpinit and bvpset. Evaluating the first few iterations of both executions, they return the same result, except for the resulting mesh which is a lot greater in the case of Scipy. The mesh sometimes even saturates to the maximum value.
The equation to be solved is shown here below, along with the boundary conditions function.
import h5py
import numpy as np
from scipy import integrate
def coupling_equation(z_mesh, a):
ka_z = k # Global
z_a = z # Global
a_p = np.empty_like(a).astype(complex)
for idx, z_i in enumerate(z_mesh):
beta_zf_i = np.interp(z_i, z_a, beta_zf) # Get beta at the desired point of the mesh
ka_z_i = np.interp(z_i, z_a, ka_z) # Get ka at the desired point of the mesh
coupling_matrix = np.empty((2, 2), complex)
coupling_matrix[0] = [-1j * beta_zf_i, ka_z_i]
coupling_matrix[1] = [ka_z_i, 1j * beta_zf_i]
a_p[:, idx] = np.matmul(coupling_matrix, a[:, idx]) # Solve the coupling matrix
return a_p
def boundary_conditions(a_a, a_b):
return np.hstack(((a_a[0]-1), a_b[1]))
Moreover, I couldn't find a way to pass k, z and beta_zf as arguments of the function coupling_equation, given that the fun argument of the solve_bpv function must be a callable with the parameters (x, y). My approach is to define some global variables, but I would appreciate any help on this too if there is a better solution.
The analysis function which I am trying to code is:
def analysis(k, z, beta_analysis, max_mesh):
s11_analysis = np.empty_like(beta_analysis, dtype=complex)
s21_analysis = np.empty_like(beta_analysis, dtype=complex)
initial_mesh = np.linspace(z[0], z[-1], 10) # Initial mesh of 10 samples along L
mesh = initial_mesh
# a_init must be complex in order to solve the problem in a complex domain
a_init = np.vstack((np.ones(np.size(initial_mesh)).astype(complex),
np.zeros(np.size(initial_mesh)).astype(complex)))
for idx, beta in enumerate(beta_analysis):
print(f"Iteration {idx}: beta_analysis = {beta}")
global beta_zf
beta_zf = beta * np.ones(len(z)) # Global variable so as to use it in coupling_equation(x, y)
a = integrate.solve_bvp(fun=coupling_equation,
bc=boundary_conditions,
x=mesh,
y=a_init,
max_nodes=max_mesh,
verbose=1)
# mesh = a.x # Mesh for the next iteration
# a_init = a.y # Initial guess for the next iteration, corresponding to the current solution
s11_analysis[idx] = a.y[1][0]
s21_analysis[idx] = a.y[0][-1]
return s11_analysis, s21_analysis
I suspect that the problem has something to do with the initial guess that is being passed to the different iterations (see commented lines inside the loop in the analysis function). I try to set the solution of an iteration as the initial guess for the following (which must reduce the time needed for the solver), but it is even slower, which I don't understand. Maybe I missed something, because it is my first time trying to solve differential equations.
The parameters used for the execution are the following:
f2 = h5py.File(r'path/to/file', 'r')
k = np.array(f2['k']).squeeze()
z = np.array(f2['z']).squeeze()
f2.close()
analysis_points = 501
max_mesh = 1e6
beta_0 = 3e2;
beta_low = 0; # Lower value of the frequency for the analysis
beta_up = beta_0; # Upper value of the frequency for the analysis
beta_analysis = np.linspace(beta_low, beta_up, analysis_points);
s11_analysis, s21_analysis = analysis(k, z, beta_analysis, max_mesh)
Any ideas on how to improve the performance of these functions? Thank you all in advance, and sorry if the question is not well-formulated, I accept any suggestions about this.
Edit: Added some information about performance and sizing of the problem.
In practice, I can't find a relation that determines de number of times coupling_equation is called. It must be a matter of the internal operation of the solver. I checked the number of callings in one iteration by printing a line, and it happened in 133 ocasions (this was one of the fastests). This must be multiplied by the number of iterations of beta. For the analyzed one, the solver returned this:
Solved in 11 iterations, number of nodes 529.
Maximum relative residual: 9.99e-04
Maximum boundary residual: 0.00e+00
The shapes of a and z_mesh are correlated, since z_mesh is a vector whose length corresponds with the size of the mesh, recalculated by the solver each time it calls coupling_equation. Given that a contains the amplitudes of the progressive and regressive waves at each point of z_mesh, the shape of a is (2, len(z_mesh)).
In terms of computation times, I only managed to achieve 19 iterations in about 2 hours with Python. In this case, the initial iterations were faster, but they start to take more time as their mesh grows, until the point that the mesh saturates to the maximum allowed value. I think this is because of the value of the input coupling coefficients in that point, because it also happens when no loop in beta_analysisis executed (just the solve_bvp function for the intermediate value of beta). Instead, Matlab managed to return a solution for the entire problem in just 6 minutes, aproximately. If I pass the result of the last iteration as initial_guess (commented lines in the analysis function, the mesh overflows even faster and it is impossible to get more than a couple iterations.
Based on semi-random inputs, we can see that max_mesh is sometimes reached. This means that coupling_equation can be called with a quite big z_mesh and a arrays. The problem is that coupling_equation contains a slow pure-Python loop iterating on each column of the arrays. You can speed the computation up a lot using Numpy vectorization. Here is an implementation:
def coupling_equation_fast(z_mesh, a):
ka_z = k # Global
z_a = z # Global
a_p = np.empty(a.shape, dtype=np.complex128)
beta_zf_i = np.interp(z_mesh, z_a, beta_zf) # Get beta at the desired point of the mesh
ka_z_i = np.interp(z_mesh, z_a, ka_z) # Get ka at the desired point of the mesh
# Fast manual matrix multiplication
a_p[0] = (-1j * beta_zf_i) * a[0] + ka_z_i * a[1]
a_p[1] = ka_z_i * a[0] + (1j * beta_zf_i) * a[1]
return a_p
This code provides a similar output with semi-random inputs compared to the original implementation but is roughly 20 times faster on my machine.
Furthermore, I do not know if max_mesh happens to be big with your inputs too and even if this is normal/intended. It may make sense to decrease the value of max_mesh in order to reduce the execution time even more.

Optimal control with time varying parameters and fixed control in Gekko

Consider an optimal control problem as follows:
min int(0,1) x(t).u(t)+u(t)**2
x.dt()= x(t)+u(t), x(0)=1
0 <= u(t) <= t**2+(1-t)**3 for 0<=t<=1
My first question is how to define the upper bound of control in Gekko. Also, suppose we want to compare this problem with the case the control is constant during the planning horizon, i.e., u(0)=...=u(t)=...=u(1). How can we define it?
In another case, how is it possible to have fixed but unknown control in different sub-intervals? For example, in [0,t1], control should be fixed, in [t1,t2] control should be fixed but can be different from control in [0,t1] (e.g. t1=0.5, t2=1, Tf=t2=1).
I would be thankful to know if it is possible to study a case where t1, t2, ... are also control and should be determined?
Here is code that gives a solution to the problem:
# min int(0,1) x(t).u(t)+u(t)**2
# x.dt()= x(t)+u(t), x(0)=1
# 0 <= u(t) <= t**2+(1-t)**3 for 0<=t<=1
import numpy as np
from gekko import GEKKO
m = GEKKO(remote=False); m.time=np.linspace(0,1,101)
t = m.Var(0); m.Equation(t.dt()==1)
ub = m.Intermediate(t**2+(1-t)**3)
u = m.MV(0,lb=0,ub=1); m.Equation(u<=ub)
u.STATUS=1; u.DCOST=0; m.free_initial(u)
x = m.Var(0); m.Equation(x.dt()==x+u)
p = np.zeros(101); p[-1]=1; final=m.Param(p)
m.Minimize(m.integral(x*u+u**2)*final)
m.options.IMODE=6; m.options.NODES=3; m.solve()
print(m.options.OBJFCNVAL)
import matplotlib.pyplot as plt
plt.plot(m.time,x.value,'b--',label='x')
plt.plot(m.time,u.value,'k-',label='u')
plt.plot(m.time,ub.value,'r--',label='ub')
plt.legend()
plt.show()
The solution isn't very interesting because the optimal objective is u(t)=0 and x(t)=0. If you add a final condition like x(1)=0.75 then the solution is more interesting.
m.Equation(final*(x-0.75)==0)
If you want all of the interval to be one value then I recommend that you use a u=m.FV() type. The u=m.MV() type is adjustable by the optimizer at every interval when you set u.STATUS=1. You can also reduce degrees of freedom with the m.options.MV_STEP_HOR=5 as a global option in Gekko for all MVs or else u.MV_STEP_HOR=5 to adjust it just for that MV. There is more information on the different Gekko types.
You can set the final time by using m.time = [0,...,1] and then scale it with time final tf. The derivatives in your problem need to be divided by tf as well. Here is a related rocket launch problem or the Jennings optimal control problem that minimize the final time. You can also set up multiple time intervals and then connect them with m.Connection().

Why is my code using 4th Runge-Kutta isn't giving me the expected values?

I'm having a little trouble trying to understand what's wrong with me code, any help would be extremely helpful.
I wanted to solve this simple equation
However, the values my code gives doesn't match with my book ones or wolfram ones as y goes up as x grows.
import matplotlib.pyplot as plt
from numpy import exp
from scipy.integrate import ode
# initial values
y0, t0 = [1.0], 0.0
def f(t, y):
f = [3.0*y[0] - 4.0/exp(t)]
return f
# initialize the 4th order Runge-Kutta solver
r = ode(f).set_integrator('dopri5')
r.set_initial_value(y0, t0)
t1 = 10
dt = 0.1
x, y = [], []
while r.successful() and r.t < t1:
x.append(r.t+dt); y.append(r.integrate(r.t+dt))
print(r.t+dt, r.integrate(r.t+dt))
Your equation in general has the solution
y(x) = (y0-1)*exp(3*x) + exp(-x)
Due to the choice of initial conditions, the exact solution does not contain the growing component of the first term. However, small perturbations due to discretization and floating point errors will generate a non-zero coefficient in the growing term. Now at the end of the integration interval this random coefficient is multiplied by exp(3*10)=1.107e+13 which will magnify small discretization errors of size 1e-7 to contributions in the result of size 1e+6 as observed when running the original code.
You can force the integrator to be more precise in its internal steps without reducing the output step size dt by setting error thresholds like in
r = ode(f).set_integrator('dopri5', atol=1e-16, rtol=1e-20)
However, you can not avoid the deterioration of the result completely as the floating point errors of size 1e-16 get magnified to global error contributions of size 1e-3.
Also, you should notice that each call of r.integrate(r.t+dt) will advance the integrator by dt so that the stored array and the printed values are in lock-step. If you want to just print the current state of the integrator use
print(r.t,r.y,yexact(r.t,y0))
where the last is to compare to the exact solution which is, as already said,
def yexact(x,y0):
return [ (y0[0]-1)*exp(3*x)+exp(-x) ]

Fitting Fresnel Equations Using Scipy

I am attempting a non-linear fit of Fresnel equations with data of reflectance against angle of incidence. Found on this site http://en.wikipedia.org/wiki/Fresnel_equations are two graphs that have a red and blue line. I need to basically fit the blue line when n1 = 1 to my data.
Here I use the following code where th is theta, the angle of incidence.
def Rperp(th, n, norm, constant):
numerator = np.cos(th) - np.sqrt(n**2.0 - np.sin(th)**2.0)
denominator = 1.0 * np.cos(th) + np.sqrt(n**2.0 - np.sin(th)**2.0)
return ((numerator / denominator)**2.0) * norm + constant
The parameters I'm looking for are:
the index of refraction n
some normalization to multiply by and
a constant to shift the baseline of the graph.
My attempt is the following:
xdata = angle[1:] * 1.0 # angle of incidence
ydata = greenDD[1:] # reflectance
params = curve_fit(Rperp, xdata, ydata)
What I get is a division of zero apparently and gives me [1, 1, 1] for the parameters. The Fresnel equation itself is the bit without the normalizer and the constant in Rperp. Theta in the equation is the angle of incidence also. Overall I am just not sure if I am doing this right at all to get the parameters.
The idea seems to be the first parameter in the function is the independent variable and the rest are the dependent variables going to be found. Then you just plug into scipy's curve_fit and it will give you a fit to your data for the parameters. If it is just getting around division of zero, which I had though might be integer division, then it seems like I should be set. Any help is appreciated and let me know if things need to be clarified (such as np is numpy).
Make sure to pass the arguments to the trigonometric functions, like sine, in radians, not degrees.
As for why you're getting a negative refractive index returned: it is because in your function, you're always squaring the refractive index. The curve_fit algorithm might end up in a local minimum state where (by accident) n is negative, because it has the same value as n positive.
Ideally, you'd add constraints to the minimization problem, but for this (simple) problem, just observe your formula and remember that a result of negative n is simply solved by changing the sign, as you did.
You could also try passing an initial guess to the algorithm and you might observe that it will not end up in the local minimum with negative value.

Categories

Resources