Source code for daetools.pyDAE.hr_upwind_scheme

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""***********************************************************************************
                           hr_upwind_scheme.py
                DAE Tools: pyDAE module, www.daetools.com
                Copyright (C) Dragan Nikolic
***********************************************************************************
DAE Tools is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License version 3 as published by the Free Software
Foundation. DAE Tools is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with the
DAE Tools software; if not, see <http://www.gnu.org/licenses/>.
************************************************************************************"""
import sys, numpy, math
from daetools.pyDAE import *

# These function operate on adouble objects
MIN     = Min
MAX     = Max
FABS    = Abs
beta    = 1.5 # For Sweby and Osher (1 <= beta  <= 2)
theta   = 1.5 # For van Leer minmod (1 <= theta <= 2)

[docs]class daeHRUpwindSchemeEquation(object): supported_flux_limiters = []
[docs] def __init__(self, variable, domain, phi, r_epsilon = 1e-10, reversedFlow = False): """ """ if not callable(phi): raise RuntimeError('Invalid flux limiter function specified (must be a callable)') if not callable(variable): raise RuntimeError('Invalid variable specified (must be a daeVariable object or a callable)') if not callable(domain): raise RuntimeError('Invalid domain specified (must be a daeDomain object or a callable)') self.c = variable # daeVariable object (or a callable) self.x = domain # daeDomain object self.phi = phi # Flux limiter function (a callable) self.r_epsilon = r_epsilon # epsilon in the upwind ratio (r) expression self.reversedFlow = reversedFlow
[docs] def dc_dt(self, i, variable = None): """Accumulation term in the cell-centered finite-volume discretisation: :math:`\int_{\Omega_i} {\partial c_i \over \partial t} dx` """ x = self.x Nx = self.x.NumberOfPoints if variable: if not callable(variable): raise RuntimeError('Invalid variable specified (must be a daeVariable object or a callable)') c = variable else: c = self.c dc_dt = lambda j: dt(c(j)) if not self.reversedFlow: if i == 0: # Sto ovde imam za 0?? return (x[i+1]-x[i]) * dc_dt(i) else: return (x[i]-x[i-1]) * dc_dt(i) else: # reversible flow if i == Nx-1: # Sto ovde imam za Nx-1?? return (x[Nx-1]-x[Nx-2]) * dc_dt(Nx-1) else: return (x[i]-x[i+1]) * dc_dt(i)
[docs] def dc_dx(self, i, S = None, variable = None): """Convection term in the cell-centered finite-volume discretisation: :math:`c_{i + {1 \over 2}} - c_{i - {1 \over 2}}`. Cell-face state :math:`c_{i+{1 \over 2}}` is given as: :math:`{c}_{i + {1 \over 2}} = c_i + \phi \left( r_{i + {1 \over 2}} \\right) \left( c_i - c_{i-1} \\right)` where :math:`\phi` is the flux limiter function and :math:`r_{i + {1 \over 2}}` the upwind ratio of consecutive solution gradients: :math:`r_{i + {1 \over 2}} = {{c_{i+1} - c_{i} + \epsilon} \over {c_{i} - c_{i-1} + \epsilon}}`. If the source term integral :math:`S= {1 \over u} \int_{\Omega_i} s(x) dx` is not ``None`` then the convection term is given as: :math:`(c-S)_{i + {1 \over 2}} - (c-S)_{i - {1 \over 2}}`. """ if variable: if not callable(variable): raise RuntimeError('Invalid variable specified (must be a daeVariable object or a callable)') c = variable else: c = self.c if not self.reversedFlow: return self._c_edge_plus(i, c, S) - self._c_edge_plus(i-1, c, S) else: # reversible flow return self._c_edge_plus_rev(i, c, S) - self._c_edge_plus_rev(i+1, c, S)
[docs] def d2c_dx2(self, i, variable = None): """Diffusion term in the cell-centered finite-volume discretisation: :math:`\left( \partial c_i \over \partial x \\right)_{i + {1 \over 2}} - \left( \partial c_i \over \partial x \\right)_{i - {1 \over 2}}` """ if variable: if not callable(variable): raise RuntimeError('Invalid variable specified (must be a daeVariable object or a callable)') c = variable else: c = self.c if not self.reversedFlow: return self._dc_dx_edge_plus(i, c) - self._dc_dx_edge_plus(i-1, c) else: # reversible flow return self._dc_dx_edge_plus_rev(i, c) - self._dc_dx_edge_plus_rev(i+1, c)
[docs] def source(self, s, i): """Source term in the cell-centered finite-volume discretisation: :math:`\int_{\Omega_i} s(x) dx` """ # The cell average shouldn't depend on a flow direction. # Anyhow, everything is fine as long as the grid is uniform. return self._AverageOverCell(s,i)
######################################################################## # Implementation details ######################################################################## def _r(self, i, S = None): # Upwind ratio of consecutive solution gradients. # It may include the source term integral S(x). eps = self.r_epsilon c = self.c cs = lambda j: c(j)-S(j) if S else c(j) return (cs(i+1) - cs(i) + eps) / (cs(i) - cs(i-1) + eps) def _r_rev(self, i, S = None): # Upwind ratio of consecutive solution gradients for the reversed flow. # It may include the source term integral S(x). eps = self.r_epsilon c = self.c cs = lambda j: c(j)-S(j) if S else c(j) return (cs(i-1) - cs(i) + eps) / (cs(i) - cs(i+1) + eps) def _c_edge_plus(self, i, c, S): # c at the i+1/2 face (cell outlet) phi = self.phi r = self._r x = self.x Nx = self.x.NumberOfPoints cs = lambda j: c(j)-S(j) if S else c(j) if i == 0: # Right face of the first cell: central interpolation (k=1) return 0.5 * (cs(0) + cs(1)) elif i == Nx-1: # Right face of the last cell: one-sided upwind scheme (k=-1) return cs(i) + 0.5 * (cs(i) - cs(i-1)) elif i > 0 and i < Nx-1: # Other cells: k=1/3 return cs(i) + 0.5 * phi(r(i,S)) * (cs(i) - cs(i-1)) else: raise RuntimeError('c_edge_plus: Invalid index specified: %d (no. points is %d)' % (i, Nx)) def _c_edge_plus_rev(self, i, c, S): # c at the i+1/2 face (cell outlet) for the reversed flow phi = self.phi r = self._r_rev x = self.x Nx = self.x.NumberOfPoints cs = lambda j: c(j)-S(j) if S else c(j) if i == 0: # Left face of the last cell (first in the reversed): return cs(i) + 0.5 * (cs(i) - cs(i+1)) elif i == Nx-1: # Left face of the first cell (last in the reversed): return 0.5 * (cs(Nx-1) + cs(Nx-2)) elif i > 0 and i < Nx-1: # Other cells: k=1/3 return cs(i) + 0.5 * phi(r(i,S)) * (cs(i) - cs(i+1)) else: raise RuntimeError('c_edge_plus_rev: Invalid index specified: %d (no. points is %d)' % (i, Nx)) def _dc_dx_edge_plus(self, i, c): # Diffusion at the i+1/2 face (cell outlet) x = self.x Nx = self.x.NumberOfPoints if i == 0: # Right face of the first cell: biased central-difference return (-8*c(i) + 9*c(i+1) + c(i+2)) / (3 * (x[i+1] - x[i])) elif i == Nx-1: # Right face of the last cell: biased central-difference return (8*c(i) - 9*c(i-1) + c(i-2)) / (3 * (x[i] - x[i-1])) elif i > 0 and i < Nx-1: # Other cells: central-difference O(h^2) return (c(i+1) - c(i)) / (x[i+1] - x[i]) else: raise RuntimeError('dc_dx_edge_plus: Invalid index specified: %d (no. points is %d)' % (i, Nx)) def _dc_dx_edge_plus_rev(self, i, c): # Diffusion at the i+1/2 face (cell outlet) for the reversed flow x = self.x Nx = self.x.NumberOfPoints if i == 0: # Left face of the last cell: biased central-difference return (8*c(i) - 9*c(i+1) + c(i+2)) / (3 * (x[i+1] - x[i])) elif i == Nx-1: # Left face of the first cell: biased central-difference return (-8*c(i) + 9*c(i-1) + c(i-2)) / (3 * (x[i] - x[i-1])) elif i > 0 and i < Nx-1: # Other cells: central-difference O(h^2) return (c(i) - c(i+1)) / (x[i+1] - x[i]) else: raise RuntimeError('dc_dx_edge_plus_rev: Invalid index specified: %d (no. points is %d)' % (i, Nx)) def _AverageOverCell(self, f, i): """ i+1/2 Integral over a cell i: S = integral (f(x)*dx) i-1/2 We could use a simple trapezoidal rule: (x - x ) * [f(x ) + f(x )] / 2 i+1/2 i-1/2 i+1/2 i-1/2 or to assume the source everywhere in the cell is equal to the value at the plus edge: (x - x ) * f(x ) i+1/2 i-1/2 i+1/2 The latter gives better results (double check why). """ x = self.x xp = self.x.Points Nx = self.x.NumberOfPoints if i == 0: # The first cell return f(i) * (xp[i+1] - xp[i]) elif i == Nx-1: # The last cell return f(i) * (xp[i] - xp[i-1]) elif i > 0 and i < Nx-1: # Other cells return f(i) * (xp[i] - xp[i-1]) else: raise RuntimeError('VolumeAverage: Invalid index specified: %d (no. points is %d)' % (i, Nx)) @staticmethod def Phi_CHARM(r): """CHARM""" return r * (3*r+1) / ((r+1)**2) @staticmethod def Phi_HCUS(r): """HCUS""" return 1.5 * (r + FABS(r)) / (r + 2) @staticmethod def Phi_HQUICK(r): """HQUICK""" return 2.0 * (r + FABS(r)) / (r + 3) @staticmethod def Phi_Koren(r): """Koren""" #r = 1.0/r return MAX(0.0, MIN(2.0*r, MIN(1.0/3.0 + 2.0*r/3, 2.0))) @staticmethod def Phi_minmod(r): """minmod""" return MAX(0.0, MIN(1.0, r)) @staticmethod def Phi_monotinized_central(r): """MC""" return MAX(0.0, MIN(2.0*r, MIN(0.5*(1+r), 2.0))) @staticmethod def Phi_Osher(r): """Osher""" # 1 <= beta <= 2 return MAX(0.0, MIN(r, beta)) @staticmethod def Phi_ospre(r): """ospre""" return 1.5 * (r*r + r) / (r*r + r + 1.0) @staticmethod def Phi_smart(r): """smart""" return MAX(0.0, MIN(2.0*r, MIN(0.25+0.75*r, 4.0))) @staticmethod def Phi_superbee(r): """superbee""" return MAX(0.0, MAX(MIN(2.0*r, 1.0), MIN(r, 2.0))) @staticmethod def Phi_Sweby(r): """Sweby""" # 1 <= beta <= 2 return MAX(0.0, MAX(MIN(beta*r, 1.0), MIN(r, beta))) @staticmethod def Phi_UMIST(r): """UMIST""" return MAX(0.0, MIN(2.0*r, MIN(0.25+0.75*r, MIN(0.75+0.25*r, 2.0)))) @staticmethod def Phi_vanAlbada1(r): """vanAlbada1""" return (r*r + r) / (r*r + 1.0) @staticmethod def Phi_vanAlbada2(r): """vanAlbada2""" return (2.0*r) / (r*r + 1.0) @staticmethod def Phi_vanLeer(r): """vanLeer""" return (r + FABS(r)) / (1.0 + FABS(r)) @staticmethod def Phi_vanLeer_minmod(r): """vanLeerMinmod""" # 1 <= theta <= 2 return MAX(0.0, MIN(theta*r, MIN(0.5*(1.0+r), theta)))
daeHRUpwindSchemeEquation.supported_flux_limiters = [daeHRUpwindSchemeEquation.Phi_HCUS, daeHRUpwindSchemeEquation.Phi_HQUICK, daeHRUpwindSchemeEquation.Phi_Koren, daeHRUpwindSchemeEquation.Phi_monotinized_central, daeHRUpwindSchemeEquation.Phi_minmod, daeHRUpwindSchemeEquation.Phi_Osher, daeHRUpwindSchemeEquation.Phi_ospre, daeHRUpwindSchemeEquation.Phi_smart, daeHRUpwindSchemeEquation.Phi_superbee, daeHRUpwindSchemeEquation.Phi_Sweby, daeHRUpwindSchemeEquation.Phi_UMIST, daeHRUpwindSchemeEquation.Phi_vanAlbada1, daeHRUpwindSchemeEquation.Phi_vanAlbada2, daeHRUpwindSchemeEquation.Phi_vanLeer, daeHRUpwindSchemeEquation.Phi_vanLeer_minmod] def plot_flux_limiters(): import matplotlib.pyplot n = 30 figure = matplotlib.pyplot.figure(figsize = (9,9), dpi=100, facecolor='white') figure.canvas.set_window_title('Flux Limiters') tvdx = numpy.zeros(n) tvdy1 = numpy.zeros(n) tvdy2 = numpy.zeros(n) tvdx[0:5] = numpy.linspace(1e-10, 0.5, 5) tvdx[5:10] = numpy.linspace(0.5, 1.0, 5) tvdx[10:20] = numpy.linspace(1.0, 2.0, 10) tvdx[20:30] = numpy.linspace(2.0, 3.0, 10) tvdy1[0:5] = numpy.linspace(0.0, 1.0, 5) tvdy1[5:10] = numpy.linspace(1.0, 1.0, 5) tvdy1[10:20] = numpy.linspace(1.0, 2.0, 10) tvdy1[20:30] = numpy.linspace(2.0, 2.0, 10) tvdy2[0:5] = numpy.linspace(0.0, 0.5, 5) tvdy2[5:10] = numpy.linspace(0.5, 1.0, 5) tvdy2[10:20] = numpy.linspace(1.0, 1.0, 10) tvdy2[20:30] = numpy.linspace(1.0, 1.0, 10) rs = numpy.linspace(1e-10, 3.0, n) rs_koren = rs phis = numpy.zeros(n) counter = 1 for fl in daeHRUpwindScheme.supported_flux_limiters: if fl == daeHRUpwindScheme.Phi_Koren: phis[:] = [fl(r) for r in rs_koren] else: phis[:] = [fl(r) for r in rs] axes = matplotlib.pyplot.subplot(4, 4, counter) fp9 = matplotlib.font_manager.FontProperties(family='Cantarell', style='normal', variant='normal', weight='normal', size=9) fp11 = matplotlib.font_manager.FontProperties(family='Cantarell', style='normal', variant='normal', weight='normal', size=12) axes.plot(rs, phis, 'r-') axes.fill_between(tvdx, tvdy1, tvdy2, alpha = 0.3) axes.set_title(fl.__doc__, fontproperties=fp11) axes.set_xlim((0.0, 3.0)) axes.set_ylim((0.0, 3.0)) axes.set_xticks([0, 1, 2, 3]) axes.set_yticks([0, 1, 2, 3]) axes.set_ylabel('$\phi( r )$', fontproperties=fp11) axes.set_xlabel('$r$', fontproperties=fp11) axes.grid(True) counter += 1 for xlabel in axes.get_xticklabels(): xlabel.set_fontproperties(fp9) for ylabel in axes.get_yticklabels(): ylabel.set_fontproperties(fp9) matplotlib.pyplot.tight_layout() matplotlib.pyplot.show() if __name__ == "__main__": # Change the functions definitions to work with floats MIN = min MAX = max FABS = math.fabs beta = 1.5 # For Sweby and Osher (1 <= beta <= 2) theta = 1.5 # For van Leer minmod (1 <= theta <= 2) plot_flux_limiters()