I am basically trying to solve a dynamic second order partial differential equation using GEKKO. This is the equation for reference:
2-D Heat transfer equation.
Here, t is time, T is temperature, (k, rho, Cp,l e, sigma and Z) are all constants.
T has been inputted as an array (I think that is where the problem lies).
To solve the equation numerically, boundary conditions are required which were inputted as other equations.
This is the code:
import numpy as np
from gekko import GEKKO
m = GEKKO()
tf = 5*60*60
dt = int(tf/300)+1
m.time = np.linspace(0,tf,dt)
# Number of nodes
n = 41
# Length of domain
Lx = 1
Ly = Lx # square domain
x_div = np.linspace(0,Lx,n)
y_div = np.linspace(Ly,0,n)
[X, Y] = np.meshgrid(x_div, y_div)
# step size
dx = x_div[1] - x_div[0]
dy = y_div[1] - y_div[0]
# Temp. initialization
T = m.Var(np.ones((n,n))*290)
# Equation set-up
# Middle segments
for i in range(1,n-1):
for j in range(1,n-1):
m.Equations(T[i,j].dt() == (k/(rho*Cp)*((T[i+1,j]-2*T[i,j]+T[i-1,j])/dx**2 + (T[i,j+1]-2*T[i,j]+T[i,j-1])/dy**2))
+ (G_total - em*sigma*(T[i,j]**4-T_surr**4))/(rho*Cp*Z))
# Boundary Conditions
m.Equations(T[0,:]==310,
T[-1,:]==310,
T[1:-2,0]==315,
T[1:-2,-1]==315,
T[0,0]==312,
T[n,0]==312,
T[0,n]==312,
T[n,n]==312)
Basically, I am trying to solve this meshgrid consisting of temperatures. I get the following error: 'numpy.float64' object has no attribute 'dt'
If I just write T instead of T[i,j], I get this error: 'int' object is not subscriptable
My questions:
Is GEKKO able to solve such equations, that are 2 dimensional in nature? How do I go about it?
Are there any other cool libraries out there for this purpose? I need to be able to draw a contour plot having temperatures of the plate as time progresses; for which I need to solve the equations.
Thank you for your time.
Here is a solution with Lutz Lehmann's suggestion to use m.Array to define T.
import numpy as np
from gekko import GEKKO
m = GEKKO(remote=False)
tf = 5*60*60
dt = int(tf/300)+1
#m.time = np.linspace(0,tf,dt)
# for testing
m.time = [0,0.01,0.02,0.03]
# Number of nodes
n = 41
# Length of domain
Lx = 1
Ly = Lx # square domain
# Define for material
k = 1; rho = 8000; Cp = 500
G_total=1; em=1; sigma=1
T_surr=298; Z=1
x_div = np.linspace(0,Lx,n)
y_div = np.linspace(Ly,0,n)
[X, Y] = np.meshgrid(x_div, y_div)
# step size
dx = x_div[1] - x_div[0]
dy = y_div[1] - y_div[0]
# Temp. initialization
T = m.Array(m.Var,(n,n),value=290)
# Equation set-up
# Middle segments
for i in range(1,n-1):
for j in range(1,n-1):
m.Equation(rho*Cp*T[i,j].dt() == (k*\
((T[i+1,j]-2*T[i,j]+T[i-1,j])/dx**2 \
+ (T[i,j+1]-2*T[i,j]+T[i,j-1])/dy**2))
+ (G_total - em*sigma*(T[i,j]**4-T_surr**4))/Z)
# Boundary Conditions
m.Equations([T[0,i]==310 for i in range(1,n-1)])
m.Equations([T[-1,i]==310 for i in range(1,n-1)])
m.Equations([T[i,0]==315 for i in range(1,n-1)])
m.Equations([T[i,-1]==315 for i in range(1,n-1)])
m.Equations([T[0,0]==312, T[n-1,0]==312, \
T[0,n-1]==312, T[n-1,n-1]==312])
m.options.IMODE=7
m.solve(disp=False)
import matplotlib.pyplot as plt
plt.figure(figsize=(8,8))
for i in range(0,4):
for j in range(0,4):
plt.subplot(4,4,i*4+j+1)
plt.plot(m.time,T[i,j].value)
plt.savefig('heat.png',dpi=600)
plt.show()
There are additional examples of hyperbolic and parabolic PDEs with Gekko. The strength of Gekko is with optimization. For simulation, it may be better to use standard simulation methods for PDEs. Also, you may get faster solutions for simulation using IMODE=7.
Related
I have a code that represents the diffusion equation (Concentration as a function of time and space):
∂²C/∂x² - ∂C/∂t= 0
I discretized to the following form:
C[n+1,j] = C[n,j] + (dt/dx²)(C[n,j+1] - 2(C[n,j]) + C[n,j-1])
I am trying to generate the following graph, however I haven't had much success. Is there anyone who could help me with this? Many thanks!
The graph that I obtain:
The code that I have to reproduce the diffusion equation:
import numpy as np
import matplotlib.pyplot as plt
dt = 0.001 # grid size for time (s)
dx = 0.05 # grid size for space (m)
x_max = 1 # in m
t_max = 1 # total time in s
C0 = 1 # concentration
# function to calculate concentration profiles based on a
# finite difference approximation to the 1D diffusion
# equation:
def diffusion(dt,dx,t_max,x_max,C0):
# diffusion number:
s = dt/dx**2
x = np.arange(0,x_max+dx,dx)
t = np.arange(0,t_max+dt,dt)
r = len(t)
a = len(x)
C = np.zeros([r,a]) # initial condition
C[:,0] = C0 # boundary condition on left side
C[:,-1] = 0 # boundary condition on right side
for n in range(0,r-1): # time
for j in range(1,a-1): # space
C[n+1,j] = C[n,j] + s*(C[n,j-1] -
2*C[n,j] + C[n,j+1])
return x,C,r,a
# note that this can be written without the for-loop
# in space, but it is easier to read it this way
x,C,r,a = diffusion(dt,dx,t_max,x_max,C0)
# plotting:
plt.figure()
plt.xlim([0,1])
plt.ylim([0,1])
plot_times = np.arange(0,1,0.02)
for t in plot_times:
plt.plot(x,C[int(t/dt),:],'Gray',label='numerical')
plt.xlabel('Membrane position x',fontsize=12)
plt.ylabel('Concentration',fontsize=12)
I am new to firedrake / fenics. I believe I have a relatively simple non-linear toy problem to solve: 1D, 1-component diffusion reaction equation at steady-state. Boundary conditions are no flux at x = 1 and a constant concentration at x = 0.
When the reaction is first order m = 1, this problem solves and gives a reasonable solution. However, I cannot figure out how to implement this problem for reaction orders where m is not equal to 1 or 0.
I have tried various formulations for rxn_func and I have documented the various errors I receive when using these different formulations.
I have consulted the firedrake documentation, specifically: this unsteady non-linear problem. I tried to mimic the formulation there where they use inner(dot(u,nabla_grad(u)), v) to evaluate ((u⋅∇)u)⋅v.
I am pretty sure there is a simple solution, but I cannot decode the error messages I am receiving. Any help is appreciated.
'''
Simple Diffusion Reaction Problem
PDE / Governing Equation
D₁ ∂²c₁/∂x² - kᵣₓₙ⋅(c₁)ᵐ = 0
Boundary Conditions
c₁(x=0) = c₁_∞
𝐍₁(x=L) = 0
'''
import firedrake as fd
import firedrake_adjoint as fda
from firedrake import inner, grad, dot, dx, dS, ds, jump, sqrt
import matplotlib.pyplot as plt
import numpy as np
import os
max_iter = 200
L = 1.0 # cm
Diff_1 = fd.Constant(1.0e-6) # cm² / s
k_rxn = fd.Constant(3.0e-6) # s⁻¹
c_1_bulk = fd.Constant(1.0e-3) # mol / cm³
Flux_1_x_eq_L = fd.Constant(0.0)
# Create mesh
n = 100
mesh = fd.IntervalMesh(n, L) # (ncells, length)
x = fd.SpatialCoordinate(mesh)
# Define discrete function spaces and functions
V = fd.FunctionSpace(mesh, "CG", 1)
W = V
u, v = fd.TrialFunction(W), fd.TestFunction(W)
u_sol = fd.Function(W)
print(f"DOFs: {W.dim()}")
rxn_func = k_rxn * u # works - reaction order = 1
rxn_func = k_rxn * dot(u, u) # NotImplementedError: Cannot take length of non-vector expression.
rxn_func = k_rxn * inner(u, u) # NotImplementedError: Cannot take length of non-vector expression.
rxn_func = k_rxn * pow(u, 2) # ufl.algorithms.check_arities.ArityMismatch: Applying nonlinear operator Power to expression depending on form argument v_1.
# Define and solve the PDE equations
A = (-Diff_1 * inner(grad(u), grad(v)) - inner(rxn_func, v) ) * dx
# Add in the boundary conditions
# weakly imposed
L = -v * Flux_1_x_eq_L * ds(2) # 𝐍₁(x=L) = Flux_1_x_eq_L
# strongly imposed
bc_c1_0 = fd.DirichletBC(W.sub(0), c_1_bulk, 1) # c₁(x=0) = c₁_∞
# solve the system of equations
fd.solve(A == L, u_sol, bcs=[bc_c1_0])
c1 = u_sol
x_coord = mesh.coordinates.dat.data
# plot the concentration profile
fig, axes = plt.subplots(nrows=1, ncols=1)
axes.plot(x_coord, c1.dat.data[:] * 1e3)
axes.set_title('Concentration Profiles')
axes.set_xlabel('Position (cm)')
axes.set_ylabel('Concentration (mol / L)')
fig.tight_layout()
fig.savefig('OneComponent_Diffusion_Reaction.png', format='png')
I'm working on this code that requires the use of elliptic integrals to solve an equation as a function of time. Most of the code is pretty straight forward but it runs into an error on the final equation containing the elliptic integrals, reading "cannot create mpf from array". Not quite sure if there is an easy fix but any insight would be greatly appreciated. The code can be found below:
import matplotlib.pyplot as plt
import numpy as np
from scipy import integrate
from mpmath import *
G = 6.6743*10**(-11) #Gravitational Constant
M = 10*(1.988*10**30) #Mass of the Black Hole (kg)
m = 1*(1.988*10**30) #Mass of the Companion Star (kg)
Mt = M + m #Total Mass(kg)
q = m/M #Mass Ratio
c = 2.99792458*10**8 #Speed of Light (m/s)
Period = 10 #Orbital Period (Days)
P = Period*86400 #Orbital Period (Seconds)
phi = 0.01*(np.pi/(180)) #Inclination from Line-of-Sight
t0 = Period/2 #Pulse Mid-Point
a = ((P**2*G*(Mt))/(4*np.pi**2))**(1/3) #Semi-Major Axis
omega = ((G*(m + M))/a**3)**0.5 #Angular Velocity
vtran = a*omega #Transverse Velocity
Rs = (2*G*M)/c**2 #Schwarzschild Radius
Rein = (2*Rs*a)**0.5 #Einstein Radius
te = (Rein/vtran)/86400 #Einstein Time
u0 = (a/Rein)*phi #Closest Angular Approach
pstar = ((1*(6.957*10**8))/Rein)
t = np.linspace(0,P,2500000) #Time Vector
u = ((u0**2)+((((t/86400)-t0)/te))**2)**0.5 #Angular Separation
n = ((4*u*pstar)/(u + pstar)**2)
k = ((4*n)/(4 + (u - pstar)**2))**0.5
Amp = (1/(2*(np.pi)))*(((((u+pstar)/(pstar**2))*np.sqrt(4+(u-pstar)**2)*ellipe(k**2))-(((u-pstar)/(pstar**2))*(((8+u**2-pstar**2)/(np.sqrt(4+(u-pstar)**2)))*ellipk(k**2)))+(((4*(u-pstar)**2)/(pstar**2*(u+pstar)))*(((1+pstar**2)/(np.sqrt(4+(u-pstar)**2)))*ellippi(n,k**2)))))
The problem is specifically with mpmath.ellipe() and mpmath.ellipk() and mpath.ellippi().
The docs are here:
https://mpmath.org/doc/current/functions/elliptic.html#ellipe
(and similarly for ellipk and ellippi).
But in summary, as the comment has pointed out, mpmath.ellipe(*args)
Called with a single argument m, evaluates the Legendre complete elliptic integral of the second kind, E(m)
You have passed in a list.
I am trying to use the python GEKKO non-linear regression tools to perform system identification of a second order over-damped system using the step response.
My code is as follows:
m = GEKKO()
m_input = m.Param(value=input)
m_time=m.Param(value=time)
m_T1 = m.FV(value=initT1, lb=T1bounds[0], ub=T1bounds[1])
m_T1.STATUS = 1
m_k = m.FV(value=initk,lb=100)
m_k.STATUS = 1
m_T2 = m.FV(value=initT2, lb=T2bounds[0], ub=T2bounds[1])
m_T2.STATUS = 1
m_output = m.CV(value=output)
m_output.FSTATUS=1
m.Equation(m_output==(m_k/(m_T1+m_T2))*(1+((m_T1/(m_T2-m_T1))*m.exp(-m_time/m_T2))-((m_T2/(m_T2-m_T1))*m.exp(-m_time/m_T1)))*m_input)
m.options.IMODE = 2
m.options.MAX_ITER = 10000
m.options.OTOL = 1e-8
m.options.RTOL = 1e-8
m.solve(disp=True)
The results have not been promising. It seems that the optimizer seems to get stuck in local minimas of the objective function leaving the objective function too high
The output of the solver is:
The final value of the objective function is 160453.282142838
---------------------------------------------------
Solver : IPOPT (v3.12)
Solution time : 7.60390000000189 sec
Objective : 160453.282605857
Successful solution
---------------------------------------------------
What can I do to improve the quality of the fit? Can I place limits on the objective function value?
Try using the equation for an underdamped 2nd order system instead of an overdamped 2nd order system. There is more information on the explicit solution to 2nd order systems on the Process Dynamics and Control website. An even better approach is to not use the explicit solution where it must be pre-determined if it is underdamped, critically damped, or overdamped. If the original 2nd order differential equation is used then the solver can decide if it is underdamped (overshoot with oscillations) or overdamped. Here is example code for fitting a 2nd order system.
import numpy as np
import pandas as pd
from gekko import GEKKO
import matplotlib.pyplot as plt
# Import CSV data file
# Column 1 = time (t)
# Column 2 = input (u)
# Column 3 = output (y)
url = 'http://apmonitor.com/pdc/uploads/Main/data_sopdt.txt'
data = pd.read_csv(url)
t = data['time'].values - data['time'].values[0]
u = data['u'].values
y = data['y'].values
m = GEKKO(remote=False)
m.time = t; time = m.Var(0); m.Equation(time.dt()==1)
K = m.FV(2,lb=0,ub=10); K.STATUS=1
tau = m.FV(3,lb=1,ub=200); tau.STATUS=1
theta_ub = 30 # upper bound to dead-time
theta = m.FV(0,lb=0,ub=theta_ub); theta.STATUS=1
zeta = m.FV(1,lb=0.1,ub=3); zeta.STATUS=1
# add extrapolation points
td = np.concatenate((np.linspace(-theta_ub,min(t)-1e-5,5),t))
ud = np.concatenate((u[0]*np.ones(5),u))
# create cubic spline with t versus u
uc = m.Var(u); tc = m.Var(t); m.Equation(tc==time-theta)
m.cspline(tc,uc,td,ud,bound_x=False)
ym = m.Param(y); yp = m.Var(y); xp = m.Var(y)
m.Equation(xp==yp.dt())
m.Equation((tau**2)*xp.dt()+2*zeta*tau*yp.dt()+yp==K*(uc-u[0]))
m.Minimize((yp-ym)**2)
m.options.IMODE=5
m.solve(disp=False)
print('Kp: ', K.value[0])
print('taup: ', tau.value[0])
print('thetap: ', theta.value[0])
print('zetap: ', zeta.value[0])
# plot results
plt.figure()
plt.subplot(2,1,1)
plt.plot(t,y,'k.-',lw=2,label='Process Data')
plt.plot(t,yp.value,'r--',lw=2,label='Optimized SOPDT')
plt.ylabel('Output')
plt.legend()
plt.subplot(2,1,2)
plt.plot(t,u,'b.-',lw=2,label='u')
plt.legend()
plt.ylabel('Input')
plt.show()
Please try this with your data to see if it gives a better solution.
How to put initial condition of ODE at a specific time point using odeint in Python?
So I have y(0) = 5 as initial condition,
following code works::
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
# function that returns dy/dt
def model(y,t):
k = 0.3
dydt = -k * y
return dydt
# initial condition
y0 = 5
# time points
t = np.linspace(0,20)
# solve ODE
y = odeint(model,y0,t)
# plot results
plt.plot(t,y)
plt.xlabel('time')
plt.ylabel('y(t)')
plt.show()
I wanna see the graph in both negative and positive time line.
So I change t = np.linspace(0,20) to t = np.linspace(-5,20), but then the initial condition is taken as y(-5) = 5.
How to solve this?
I do not think you can, according to the docs
But you can solve for positive and negative t's separately and then stich them together. Replace the relevant lines with
tp = np.linspace(0,20)
tm = np.linspace(0,-5)
# solve ODE
yp = odeint(model,y0,tp)
ym = odeint(model,y0,tm)
# stich together; note we flip the time direction with [::-1] construct
t = np.concatenate([tm[::-1],tp])
y = np.concatenate([ym[::-1],yp])
this produces