Python gekko方程定义中的换行符

huangapple go评论98阅读模式
英文:

Python gekko line break in equation definition

问题

I'm currently implementing the Galerkin-Method for finite Elements by hand and using python gekko to solve the resulting non-linear algebraic equation system. This creates no issues for small systems and works fine.

As soon as the system becomes more complex involving long equations and exponential terms with m.exp() the equations might not become formatted properly anymore for the solver. The exact error message is @error: Equation Definition Equation without an equality (=) or inequality (>,<).

I checked the equations via m.open_folder() in the model.apm. And it seems, that somehow a line break is added into the equation loosing the mathematical relationship between the resulting expressions. This is why the solver does not recognize equality (=) or inequality (>,<) in the first expression.
Also I found out that the exponential term m.exp(-EA / (R * T)) causes the line break. Replacing it with np.exp(-EA/(R*T0)) hotfixes the issue. It might be due to the fact the gekko shortens long equations in the background and there is a bug within this procedure. Or something is wrong with my equation definition. Here is my code.

Basis for generating long and complex algebraic equations

  1. import pandas as pd
  2. import numpy as np
  3. from gekko import GEKKO
  4. from typing import List, Callable
  5. m = GEKKO(remote=False)
  6. # constants
  7. # -------------------------
  8. R = 8.314 # J/mol K
  9. # Rahmenbedingungen
  10. # -------------------------
  11. T0 = 550 # K
  12. p0 = 15 * 10 ** 5 # Pa
  13. # components
  14. # -------------------------
  15. x_bulk = np.array(
  16. [0.1, 0.08, 0.05]
  17. ) # no unit Ethen, O2, CO2, EO, H2O
  18. # catalyst pellet
  19. # -------------------------
  20. rho_p = 1171.2 # kg / m**3 Dichte eines Pellets
  21. epsilon_p = 0.7 # Poroesitaet
  22. tau = 4 # Tortuositaet
  23. # heat transfer
  24. # -------------------------
  25. lambda_pm = 16 # W/m K Wärmeleitfähigkeit des reinen Katalysatorfestoffes
  26. lambda_p = (1 - epsilon_p) * lambda_pm # W/m K Wärmeleitfähigkeit des porösen Pellets
  27. # reaction enthalpies
  28. # -------------------------
  29. dh_r1 = -107000 # J/mol
  30. dh_r2 = -1323000 # J/mol
  31. Deff = 5.42773893 * 10 **7
  32. # reaction speeds
  33. # --------
  34. def r_r1(p: np.array, T: float = T0) -> float:
  35. n_E = 0.6 # Reaktionsordnung Ethen
  36. n_O2 = 0.5 # Reaktionsordnung Sauerstoff
  37. k0 = 6.275 * 10**6 # pre-exponential collision factor [mol / (kg s Pa ** (nE+nO2))]
  38. EA = 74900 # activation energy [J/mol]
  39. K0 = 1.985 * 10**2 # pre-exponential adsorption factor [1/Pa]
  40. Tads = 2400 # adsorption temperature [K]
  41. r = k0 * m.exp(-EA / (R * T)) * p[0] ** n_E * p[1] ** n_O2 / (1 + K0 * np.exp(Tads / T0) * p[2])
  42. # this is m.exp() one cause for the quations not being formatted correctly
  43. # change it to np.exp(-EA/(R*T0)) and the equations are formatted correctly for the solver
  44. # this may be a mistake in my code or a bug in gekko
  45. return r
  46. # differential equations
  47. # --------
  48. def rhs(x: float, y: np.array, dy: np.array) -> np.array:
  49. # second derivatives of the partial pressures [bar/m**2]
  50. # use bar instead of Pa to increase numerical precision
  51. rhs_E = -2 / x * dy[0] + R * y[-1] * T0 * rho_p / (Deff * p0) * (r_r1(p=y[0:-1] * p0, T=y[-1] * T0))
  52. rhs_O2 = -2 / x * dy[1] + R * y[-1] * T0 * rho_p / (Deff * p0) * (0.5 * r_r1(p=y[0:-1] * p0, T=y[-1] * T0))
  53. rhs_CO2 = -2 / x * dy[2] + R * y[-1] * T0 * rho_p / (Deff * p0) * (-2) * r_r1(p=y[0:-1] * p0, T=y[-1] * T0)
  54. rhs_T = -2 / x * dy[-1] - rho_p / (lambda_p * T0) * (r_r1(p=y[0:-1] * p0, T=y[-1] * T0) * (-dh_r1))
  55. return np.array([rhs_E, rhs_O2, rhs_CO2, rhs_T])
  56. # quadrature over the right hand side of the difffereantial equation
  57. # ----------------
  58. def give_quad_expressions(
  59. rhs: Callable, phi: List[np.poly1d], dphi: List[np.poly1d]
  60. ) -> Callable:
  61. """Creates an expression for the quadrature of the right-hand-side of the
  62. differential equation that is only dependent on the function values at the
  63. nodes y_i. It takes the quadrature base points to evaluate the
  64. base functions phi_i and their derivatives dphi_i to set up the expression.
  65. Args:
  66. rhs (Callable): Right hand side of the differential equation.
  67. Needs to be a function of x, y, and dy
  68. phi (List[np.poly1d]): List with all base polynomials phi. Mostly, base polynomials of Legendre type.
  69. dphi (List[np.poly1d]): List with the corresponding derivatives of the base polynomials.
  70. Returns:
  71. Callable: Expression for the weighted quadratures of the right-hand-side for each node that is only
  72. dependent on the unknown function values y_i. It can be called by a solving algorithm.
  73. """
  74. # these will be defined during runtime
  75. number_base_points = 2
  76. base_points = np.array([0.00021132, 0.00078868])
  77. weights = np.array([0.0005, 0.0005])
  78. # evaluate base functions at quadrature base points
  79. phi_eval = np.zeros(shape=(len(phi), number_base_points))
  80. dphi_eval = np.zeros(shape=(len(dphi), number_base_points))
  81. for index, phi_i in enumerate(phi):
  82. phi_eval[index] = phi_i(base_points)
  83. dphi_eval[index] = dphi[index](base_points)
  84. # set
  85. <details>
  86. <summary>英文:</summary>
  87. I&#39;m currently implementing the Galerkin-Method for finite Elements by hand and using python gekko to solve the resulting non-linear algebraic equation system. This creates no issues for small systems and works fine.
  88. As soon as the system becomes more complex involving long equations and exponential terms with `m.exp()` the equations might not become formatted properly anymore for the solver. The exact error message is `@error: Equation Definition Equation without an equality (=) or inequality (&gt;,&lt;)`.
  89. I checked the equations via `m.open_folder()` in the `model.apm`. And it seems, that somehow a line break is added into the equation loosing the mathematical relationship between the resulting expressions. This is why the solver does not recognize `equality (=) or inequality (&gt;,&lt;)` in the first expression.
  90. Also I found out that the exponential term `m.exp(-EA / (R * T))` causes the line break. Replacing it with `np.exp(-EA/(R*T0))` hotfixes the issue. It might be due to the fact the gekko shortens long equations in the background and there is a bug within this procedure. Or something is wrong with my equation definition. Here is my code.
  91. Basis for generating long and complex algebraic equations

import pandas as pd
import numpy as np
from gekko import GEKKO
from typing import List, Callable
m = GEKKO(remote=False)

constants

-------------------------

R = 8.314 # J/mol K

Rahmenbedingungen

-------------------------

T0 = 550 # K
p0 = 15 * 10 ** 5 # Pa

components

-------------------------

x_bulk = np.array(
[0.1, 0.08, 0.05]
) # no unit Ethen, O2, CO2, EO, H2O

catalyst pellet

-------------------------

rho_p = 1171.2 # kg / m**3 Dichte eines Pellets
epsilon_p = 0.7 # Poroesitaet
tau = 4 # Tortuositaet

heat transfer

-------------------------

lambda_pm = 16 # W/m K Wärmeleitfähigkeit des reinen Katalysatorfestoffes
lambda_p = (1 - epsilon_p) * lambda_pm # W/m K Wärmeleitfähigkeit des porösen Pellets

reaction enthalpies

-------------------------

dh_r1 = -107000 # J/mol
dh_r2 = -1323000 # J/mol
Deff = 5.42773893 * 10 **7

reaction speeds

--------

def r_r1(p: np.array, T: float = T0) -> float:
n_E = 0.6 # Reaktionsordnung Ethen
n_O2 = 0.5 # Reaktionsordnung Sauerstoff
k0 = 6.275 * 106 # pre-exponential collision factor [mol / (kg s Pa ** (nE+nO2))]
EA = 74900 # activation energy [J/mol]
K0 = 1.985 * 10
2 # pre-exponential adsorption factor [1/Pa]
Tads = 2400 # adsorption temperature [K]
r = k0 * m.exp(-EA / (R * T)) * p[0] ** n_E * p[1] ** n_O2 / (1 + K0 * np.exp(Tads / T0) * p[2])
# this is m.exp() one cause for the quations not being formatted correctly
# change it to np.exp(-EA/(R*T0)) and the equations are formatted correctly for the solver
# this may be a mistake in my code or a bug in gekko
return r

differential equations

--------

def rhs(x: float, y: np.array, dy: np.array) -> np.array:
# second derivatives of the partial pressures [bar/m**2]
# use bar instead of Pa to increase numerical precision
rhs_E = -2 / x * dy[0] + R * y[-1] * T0 * rho_p / (Deff * p0) * (r_r1(p=y[0:-1] * p0, T=y[-1] * T0))
rhs_O2 = -2 / x * dy[1] + R * y[-1] * T0 * rho_p / (Deff * p0) * (0.5 * r_r1(p=y[0:-1] * p0, T=y[-1] * T0))
rhs_CO2 = -2 / x * dy[2] + R * y[-1] * T0 * rho_p / (Deff * p0) * (-2) * r_r1(p=y[0:-1] * p0, T=y[-1] * T0)
rhs_T = -2 / x * dy[-1] - rho_p / (lambda_p * T0) * (r_r1(p=y[0:-1] * p0, T=y[-1] * T0) * (-dh_r1))
return np.array([rhs_E, rhs_O2, rhs_CO2, rhs_T])

quadrature over the right hand side of the difffereantial equation

----------------

def give_quad_expressions(
rhs: Callable, phi: List[np.poly1d], dphi: List[np.poly1d]
) -> Callable:
"""Creates an expression for the quadrature of the right-hand-side of the
differential equation that is only dependent on the function values at the
nodes y_i. It takes the quadrature base points to evaluate the
base functions phi_i and their derivatives dphi_i to set up the expression.

  1. Args:
  2. rhs (Callable): Right hand side of the differential equation.
  3. Needs to be a function of x, y, and dy
  4. phi (List[np.poly1d]): List with all base polynomials phi. Mostly, base polynomials of Legendre type.
  5. dphi (List[np.poly1d]): List with the corresponding derivatives of the base polynomials.
  6. Returns:
  7. Callable: Expression for the weighted quadratures of the right-hand-side for each node that is only
  8. dependent on the unknown function values y_i. It can be called by a solving algorithm.
  9. &quot;&quot;&quot;
  10. # these will be defined during runtime
  11. number_base_points = 2
  12. base_points = np.array([0.00021132, 0.00078868])
  13. weights = np.array([0.0005, 0.0005])
  14. # evaluate base functions at quadrature base points
  15. phi_eval = np.zeros(shape=(len(phi), number_base_points))
  16. dphi_eval = np.zeros(shape=(len(dphi), number_base_points))
  17. for index, phi_i in enumerate(phi):
  18. phi_eval[index] = phi_i(base_points)
  19. dphi_eval[index] = dphi[index](base_points)
  20. # set up the expression for the quadrature only dependent on the unknown y_i values
  21. def quad_expressions(y: np.array, model:GEKKO) -&gt; np.array:
  22. &quot;&quot;&quot;Variable expressions for the weighted quadratures of the integral of the weighted right-hand-side
  23. for each node that are only dependend on y.
  24. The integral is crucial for the Galerkin Method. int(phi_j * rhs(x, y, dy))dx.
  25. It allows the gekko solver to embed a numpy array y into the quadrature
  26. that is safed into the gekko model by using &#39;m.Array(m.Var)&#39; for setting up the algebraic equation system.
  27. Args:
  28. y (np.array): Unknown function values that the gekko solver needs to find.
  29. Returns:
  30. np.array: Array with the weighted quadratures for each function and node as a function of y_i.
  31. first index specifies the function, second index the node
  32. &quot;&quot;&quot;
  33. # TODO: Work with numpy arrays instead of lists to enhance speed.
  34. y_tilde = []
  35. dy_tilde = []
  36. # set up the trail functions and their derivatives using the base functions which are already
  37. # evaluated and the supposed y values at the nodes.
  38. for eq_l in range(y.shape[0]):
  39. y_tilde_eq = []
  40. dy_tilde_eq = []
  41. for bpoint_iq in range(number_base_points):
  42. # iterates over quadrature base points
  43. y_tilde_eq.append(np.dot(y[eq_l], phi_eval[:, bpoint_iq]))
  44. dy_tilde_eq.append(np.dot(y[eq_l], dphi_eval[:, bpoint_iq]))
  45. y_tilde.append(y_tilde_eq)
  46. dy_tilde.append(dy_tilde_eq)
  47. # evaluate the right-hand-sides with the base points and the supposed trial function values at the base points
  48. rhsides_evaluated_bpoints = rhs(
  49. base_points, np.array(y_tilde), np.array(dy_tilde)
  50. )
  51. # multiply the right-hand-side with the trial function of each node
  52. integrals = np.zeros_like(y)
  53. for eq_l, rhs_eq in enumerate(rhsides_evaluated_bpoints):
  54. # iterate over the equations
  55. for node_i in range(y.shape[1]):
  56. # iterate over the weight function of each node
  57. # calcualte the quadrature using the weights
  58. rhs_w_func = rhs_eq * phi_eval[node_i]
  59. dotp = model.sum(
  60. [rhs_w_func[i] * weights[i] for i in range(number_base_points)]
  61. )
  62. # dotp = np.dot(
  63. # rhs_w_func, self.weights
  64. # )
  65. integrals[eq_l, node_i] = dotp # using the dot product via list comprehension with gekko to allow the model to split long equations
  66. return integrals
  67. return quad_expressions
  1. Setting up equations

phi = [
np.poly1d([ 2.e+06, -3.e+03, 1.e+00]),
np.poly1d([-4000000., 4000., -0.]),
np.poly1d([ 2.e+06, -1.e+03, 0.e+00])
]

dphi = [
np.poly1d([ 4.e+06, -3.e+03]),
np.poly1d([-8.e+06, 4.e+03]),
np.poly1d([ 4.e+06, -1.e+03])
]

diffusion_matrix = np.array(
[[ 2333.33333333, -2666.66666667, 333.33333333],
[-2666.66666667, 5333.33333333, -2666.66666667],
[ 333.33333333, -2666.66666667, 2333.33333333]]
)
y = m.Array(m.Var, (4, 3), value=x_bulk[0])

quad_expression = give_quad_expressions(rhs=rhs, phi=phi, dphi=dphi)
rhs_integrals = quad_expression(y=y, model=m)

for j in range(y.shape[0]):
# iterates over each differential equation
for i in range(y.shape[1]):
# iterates over each node
m.Equation(
np.dot(
y[j], (-1) * diffusion_matrix[:, i]
) == rhs_integrals[j, i]
)

for eq in m._equations:
print(eq)
m.solve()

  1. Error
  2. Exception: @error: Equation Definition
  3. Equation without an equality (=) or inequality (&gt;,&lt;)
  4. (-74900/((8.314)*(((((((v10)*(-0.12200771519999987))+((v11)*(0.6666554303999999)))+((v12)*(0.4553522848000001))))*(550)))))]))))*([((((((((v1)*(0.4553522848))+((v2)*(0.6666554304000001)))+((v3)*(-0.1220077152))))*(1500000)))^(0.6))
  5. STOPPING...
  6. </details>
  7. # 答案1
  8. **得分**: 2
  9. 我发现了问题:
  10. 出现这些换行符的原因是因为错误地使用了gekko内置函数,如`m.exp()``m.sin()`等。这些函数只能逐段评估,而不能像我试图做的那样一次性评估整个数组。
  11. 因此,从
  12. ```python
  13. rhsides_evaluated_bpoints = rhs(
  14. base_points, np.array(y_tilde), np.array(dy_tilde)
  15. )

改为

  1. rhs_eval = []
  2. for i, bpoint in enumerate(base_points):
  3. rhs_eval.append(
  4. rhs(
  5. x=bpoint,
  6. y=y_tilde[:, i],
  7. dy=dy_tilde[:, i]
  8. )
  9. )
  10. rhs_eval = np.array(rhs_eval).transpose()

解决了问题。

英文:

I found the issue:
The reason, why these line breaks appeared in the equation definition was due to the wrong usage of gekko built-in functions like m.exp(), m.sin(),... These can only be evaluated piecewise instead of evaluating a whole array at once like I tried them to do.

So a change from

  1. rhsides_evaluated_bpoints = rhs(
  2. base_points, np.array(y_tilde), np.array(dy_tilde)
  3. )

to

  1. rhs_eval = []
  2. for i, bpoint in enumerate(base_points):
  3. rhs_eval.append(
  4. rhs(
  5. x=bpoint,
  6. y=y_tilde[:, i],
  7. dy=dy_tilde[:, i]
  8. )
  9. )
  10. rhs_eval = np.array(rhs_eval).transpose()

did the job.

huangapple
  • 本文由 发表于 2023年6月19日 15:38:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/76504543.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定