From 44083a30df8ad4162939e95f23a3e60fa67b44ff Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Sun, 4 Oct 2015 12:34:13 -0500 Subject: [PATCH] Bring over a bunch of stuff from hedge --- .gitignore | 9 + examples/advection/advection.py | 176 +++ examples/advection/space-dep.dat | Bin 0 -> 38912 bytes examples/advection/var-velocity.py | 258 ++++ examples/burgers/burgers.dat | Bin 0 -> 918528 bytes examples/burgers/burgers.py | 238 ++++ examples/conftest.py | 7 + examples/gas_dynamics/box-in-box.py | 235 ++++ examples/gas_dynamics/euler/sine-wave.py | 198 +++ examples/gas_dynamics/euler/sod-2d.py | 183 +++ .../euler/vortex-adaptive-grid.py | 258 ++++ examples/gas_dynamics/euler/vortex-sources.py | 334 +++++ examples/gas_dynamics/euler/vortex.py | 214 ++++ .../gas_dynamics/gas_dynamics_initials.py | 223 ++++ examples/gas_dynamics/lbm-simple.py | 153 +++ examples/gas_dynamics/naca.py | 268 ++++ .../gas_dynamics/navierstokes/shearflow.py | 198 +++ examples/gas_dynamics/square.py | 281 +++++ examples/gas_dynamics/wing.py | 232 ++++ examples/heat/heat.py | 174 +++ examples/maxwell/.gitignore | 2 + examples/maxwell/analytic_solutions.py | 457 +++++++ examples/maxwell/cavities.py | 259 ++++ examples/maxwell/dipole.py | 257 ++++ examples/maxwell/generate-bessel-zeros.py | 14 + examples/maxwell/inhom-cavity.py | 265 ++++ examples/maxwell/inhomogeneous_waveguide.mac | 39 + examples/maxwell/maxwell-2d.py | 155 +++ examples/maxwell/maxwell-2d/maxwell-3.dat | Bin 0 -> 46080 bytes examples/maxwell/maxwell-pml.py | 243 ++++ examples/maxwell/notes.tm | 403 ++++++ examples/poisson/helmholtz.py | 179 +++ examples/poisson/poisson.py | 139 +++ examples/wave/var-propagation-speed.py | 196 +++ examples/wave/wave-min.py | 95 ++ examples/wave/wave.py | 189 +++ examples/wave/wiggly.py | 222 ++++ grudge/models/__init__.py | 67 + grudge/models/advection.py | 409 ++++++ grudge/models/burgers.py | 154 +++ grudge/models/diffusion.py | 128 ++ grudge/models/em.py | 546 ++++++++ grudge/models/gas_dynamics/__init__.py | 918 ++++++++++++++ grudge/models/gas_dynamics/lbm.py | 210 ++++ grudge/models/nd_calculus.py | 138 ++ grudge/models/pml.py | 287 +++++ grudge/models/poisson.py | 265 ++++ grudge/models/wave.py | 423 +++++++ grudge/symbolic/__init__.py | 31 + grudge/symbolic/compiler.py | 1106 +++++++++++++++++ grudge/symbolic/operators.py | 780 ++++++++++++ grudge/symbolic/primitives.py | 283 +++++ grudge/symbolic/tools.py | 391 ++++++ 53 files changed, 12889 insertions(+) create mode 100644 examples/advection/advection.py create mode 100644 examples/advection/space-dep.dat create mode 100644 examples/advection/var-velocity.py create mode 100644 examples/burgers/burgers.dat create mode 100644 examples/burgers/burgers.py create mode 100644 examples/conftest.py create mode 100644 examples/gas_dynamics/box-in-box.py create mode 100644 examples/gas_dynamics/euler/sine-wave.py create mode 100644 examples/gas_dynamics/euler/sod-2d.py create mode 100644 examples/gas_dynamics/euler/vortex-adaptive-grid.py create mode 100644 examples/gas_dynamics/euler/vortex-sources.py create mode 100644 examples/gas_dynamics/euler/vortex.py create mode 100644 examples/gas_dynamics/gas_dynamics_initials.py create mode 100644 examples/gas_dynamics/lbm-simple.py create mode 100644 examples/gas_dynamics/naca.py create mode 100644 examples/gas_dynamics/navierstokes/shearflow.py create mode 100644 examples/gas_dynamics/square.py create mode 100644 examples/gas_dynamics/wing.py create mode 100644 examples/heat/heat.py create mode 100644 examples/maxwell/.gitignore create mode 100644 examples/maxwell/analytic_solutions.py create mode 100644 examples/maxwell/cavities.py create mode 100644 examples/maxwell/dipole.py create mode 100644 examples/maxwell/generate-bessel-zeros.py create mode 100644 examples/maxwell/inhom-cavity.py create mode 100644 examples/maxwell/inhomogeneous_waveguide.mac create mode 100644 examples/maxwell/maxwell-2d.py create mode 100644 examples/maxwell/maxwell-2d/maxwell-3.dat create mode 100644 examples/maxwell/maxwell-pml.py create mode 100644 examples/maxwell/notes.tm create mode 100644 examples/poisson/helmholtz.py create mode 100644 examples/poisson/poisson.py create mode 100644 examples/wave/var-propagation-speed.py create mode 100644 examples/wave/wave-min.py create mode 100644 examples/wave/wave.py create mode 100644 examples/wave/wiggly.py create mode 100644 grudge/models/__init__.py create mode 100644 grudge/models/advection.py create mode 100644 grudge/models/burgers.py create mode 100644 grudge/models/diffusion.py create mode 100644 grudge/models/em.py create mode 100644 grudge/models/gas_dynamics/__init__.py create mode 100644 grudge/models/gas_dynamics/lbm.py create mode 100644 grudge/models/nd_calculus.py create mode 100644 grudge/models/pml.py create mode 100644 grudge/models/poisson.py create mode 100644 grudge/models/wave.py create mode 100644 grudge/symbolic/__init__.py create mode 100644 grudge/symbolic/compiler.py create mode 100644 grudge/symbolic/operators.py create mode 100644 grudge/symbolic/primitives.py create mode 100644 grudge/symbolic/tools.py diff --git a/.gitignore b/.gitignore index 05e0c40e..e6efa6fa 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,12 @@ examples/*.pdf *.vtu *.vts + +run-debug-* + +*.vtu +*.pvtu +*.pvd +*.dot +*.vtk +*.silo diff --git a/examples/advection/advection.py b/examples/advection/advection.py new file mode 100644 index 00000000..e912531c --- /dev/null +++ b/examples/advection/advection.py @@ -0,0 +1,176 @@ +# Hedge - the Hybrid'n'Easy DG Environment +# Copyright (C) 2007 Andreas Kloeckner +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 this program. If not, see . + + +from __future__ import division +import numpy +import numpy.linalg as la + + + + +def main(write_output=True, flux_type_arg="upwind"): + from hedge.tools import mem_checkpoint + from math import sin, cos, pi, sqrt + from math import floor + + from hedge.backends import guess_run_context + rcon = guess_run_context() + + def f(x): + return sin(pi*x) + + def u_analytic(x, el, t): + return f((-numpy.dot(v, x)/norm_v+t*norm_v)) + + def boundary_tagger(vertices, el, face_nr, all_v): + if numpy.dot(el.face_normals[face_nr], v) < 0: + return ["inflow"] + else: + return ["outflow"] + + dim = 2 + + if dim == 1: + v = numpy.array([1]) + if rcon.is_head_rank: + from hedge.mesh.generator import make_uniform_1d_mesh + mesh = make_uniform_1d_mesh(0, 2, 10, periodic=True) + elif dim == 2: + v = numpy.array([2,0]) + if rcon.is_head_rank: + from hedge.mesh.generator import make_disk_mesh + mesh = make_disk_mesh(boundary_tagger=boundary_tagger) + elif dim == 3: + v = numpy.array([0,0,1]) + if rcon.is_head_rank: + from hedge.mesh.generator import make_cylinder_mesh, make_ball_mesh, make_box_mesh + + mesh = make_cylinder_mesh(max_volume=0.04, height=2, boundary_tagger=boundary_tagger, + periodic=False, radial_subdivisions=32) + else: + raise RuntimeError, "bad number of dimensions" + + norm_v = la.norm(v) + + if rcon.is_head_rank: + mesh_data = rcon.distribute_mesh(mesh) + else: + mesh_data = rcon.receive_mesh() + + if dim != 1: + mesh_data = mesh_data.reordered_by("cuthill") + + discr = rcon.make_discretization(mesh_data, order=4) + vis_discr = discr + + from hedge.visualization import VtkVisualizer + if write_output: + vis = VtkVisualizer(vis_discr, rcon, "fld") + + # operator setup ---------------------------------------------------------- + from hedge.data import \ + ConstantGivenFunction, \ + TimeConstantGivenFunction, \ + TimeDependentGivenFunction + from hedge.models.advection import StrongAdvectionOperator, WeakAdvectionOperator + op = WeakAdvectionOperator(v, + inflow_u=TimeDependentGivenFunction(u_analytic), + flux_type=flux_type_arg) + + u = discr.interpolate_volume_function(lambda x, el: u_analytic(x, el, 0)) + + # timestep setup ---------------------------------------------------------- + from hedge.timestep.runge_kutta import LSRK4TimeStepper + stepper = LSRK4TimeStepper() + + if rcon.is_head_rank: + print "%d elements" % len(discr.mesh.elements) + + # diagnostics setup ------------------------------------------------------- + from pytools.log import LogManager, \ + add_general_quantities, \ + add_simulation_quantities, \ + add_run_info + + if write_output: + log_file_name = "advection.dat" + else: + log_file_name = None + + logmgr = LogManager(log_file_name, "w", rcon.communicator) + add_run_info(logmgr) + add_general_quantities(logmgr) + add_simulation_quantities(logmgr) + discr.add_instrumentation(logmgr) + + stepper.add_instrumentation(logmgr) + + from hedge.log import Integral, LpNorm + u_getter = lambda: u + logmgr.add_quantity(Integral(u_getter, discr, name="int_u")) + logmgr.add_quantity(LpNorm(u_getter, discr, p=1, name="l1_u")) + logmgr.add_quantity(LpNorm(u_getter, discr, name="l2_u")) + + logmgr.add_watches(["step.max", "t_sim.max", "l2_u", "t_step.max"]) + + # timestep loop ----------------------------------------------------------- + rhs = op.bind(discr) + + try: + from hedge.timestep import times_and_steps + step_it = times_and_steps( + final_time=3, logmgr=logmgr, + max_dt_getter=lambda t: op.estimate_timestep(discr, + stepper=stepper, t=t, fields=u)) + + for step, t, dt in step_it: + if step % 5 == 0 and write_output: + visf = vis.make_file("fld-%04d" % step) + vis.add_data(visf, [ + ("u", discr.convert_volume(u, kind="numpy")), + ], time=t, step=step) + visf.close() + + u = stepper(u, t, dt, rhs) + + true_u = discr.interpolate_volume_function(lambda x, el: u_analytic(x, el, t)) + print discr.norm(u-true_u) + assert discr.norm(u-true_u) < 1e-2 + finally: + if write_output: + vis.close() + + logmgr.close() + discr.close() + + + + +if __name__ == "__main__": + main() + + + + +# entry points for py.test ---------------------------------------------------- +def test_advection(): + from pytools.test import mark_test + mark_long = mark_test.long + + for flux_type in ["upwind", "central", "lf"]: + yield "advection with %s flux" % flux_type, \ + mark_long(main), False, flux_type diff --git a/examples/advection/space-dep.dat b/examples/advection/space-dep.dat new file mode 100644 index 0000000000000000000000000000000000000000..78e1f83c1721d1491d9eb5fce605c90b5607b872 GIT binary patch literal 38912 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 literal 0 HcmV?d00001 diff --git a/examples/advection/var-velocity.py b/examples/advection/var-velocity.py new file mode 100644 index 00000000..81fb7e67 --- /dev/null +++ b/examples/advection/var-velocity.py @@ -0,0 +1,258 @@ +# Hedge - the Hybrid'n'Easy DG Environment +# Copyright (C) 2009 Andreas Stock +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 this program. If not, see . + + + + +from __future__ import division +import numpy + + + +def main(write_output=True, flux_type_arg="central", use_quadrature=True, + final_time=20): + from math import sin, cos, pi, sqrt + + from hedge.backends import guess_run_context + rcon = guess_run_context() + + # mesh setup -------------------------------------------------------------- + if rcon.is_head_rank: + #from hedge.mesh.generator import make_disk_mesh + #mesh = make_disk_mesh() + from hedge.mesh.generator import make_rect_mesh + mesh = make_rect_mesh(a=(-1,-1),b=(1,1),max_area=0.008) + + if rcon.is_head_rank: + mesh_data = rcon.distribute_mesh(mesh) + else: + mesh_data = rcon.receive_mesh() + + # space-time-dependent-velocity-field ------------------------------------- + # simple vortex + class TimeDependentVField: + """ `TimeDependentVField` is a callable expecting `(x, t)` representing space and time + + `x` is of the length of the spatial dimension and `t` is the time.""" + shape = (2,) + + def __call__(self, pt, el, t): + x, y = pt + # Correction-Factor to make the speed zero on the on the boundary + #fac = (1-x**2)*(1-y**2) + fac = 1. + return numpy.array([-y*fac, x*fac]) * cos(pi*t) + + class VField: + """ `VField` is a callable expecting `(x)` representing space + + `x` is of the length of the spatial dimension.""" + shape = (2,) + + def __call__(self, pt, el): + x, y = pt + # Correction-Factor to make the speed zero on the on the boundary + #fac = (1-x**2)*(1-y**2) + fac = 1. + return numpy.array([-y*fac, x*fac]) + + # space-time-dependent State BC (optional)----------------------------------- + class TimeDependentBc_u: + """ space and time dependent BC for state u""" + def __call__(self, pt, el, t): + x, y = pt + if t <= 0.5: + if x > 0: + return 1 + else: + return 0 + else: + return 0 + + class Bc_u: + """ Only space dependent BC for state u""" + def __call__(seld, pt, el): + x, y = pt + if x > 0: + return 1 + else: + return 0 + + + # operator setup ---------------------------------------------------------- + # In the operator setup it is possible to switch between a only space + # dependent velocity field `VField` or a time and space dependent + # `TimeDependentVField`. + # For `TimeDependentVField`: advec_v=TimeDependentGivenFunction(VField()) + # For `VField`: advec_v=TimeConstantGivenFunction(GivenFunction(VField())) + # Same for the Bc_u Function! If you don't define Bc_u then the BC for u = 0. + + from hedge.data import \ + ConstantGivenFunction, \ + TimeConstantGivenFunction, \ + TimeDependentGivenFunction, \ + GivenFunction + from hedge.models.advection import VariableCoefficientAdvectionOperator + op = VariableCoefficientAdvectionOperator(mesh.dimensions, + #advec_v=TimeDependentGivenFunction( + # TimeDependentVField()), + advec_v=TimeConstantGivenFunction( + GivenFunction(VField())), + #bc_u_f=TimeDependentGivenFunction( + # TimeDependentBc_u()), + bc_u_f=TimeConstantGivenFunction( + GivenFunction(Bc_u())), + flux_type=flux_type_arg) + + # discretization setup ---------------------------------------------------- + order = 5 + if use_quadrature: + quad_min_degrees = {"quad": 3*order} + else: + quad_min_degrees = {} + + discr = rcon.make_discretization(mesh_data, order=order, + default_scalar_type=numpy.float64, + debug=["cuda_no_plan"], + quad_min_degrees=quad_min_degrees, + tune_for=op.op_template(), + + ) + vis_discr = discr + + # visualization setup ----------------------------------------------------- + from hedge.visualization import VtkVisualizer + if write_output: + vis = VtkVisualizer(vis_discr, rcon, "fld") + + # initial condition ------------------------------------------------------- + if True: + def initial(pt, el): + # Gauss pulse + from math import exp + x = (pt-numpy.array([0.3, 0.5]))*8 + return exp(-numpy.dot(x, x)) + else: + def initial(pt, el): + # Rectangle + x, y = pt + if abs(x) < 0.5 and abs(y) < 0.2: + return 2 + else: + return 1 + + u = discr.interpolate_volume_function(initial) + + # timestep setup ---------------------------------------------------------- + from hedge.timestep.runge_kutta import LSRK4TimeStepper + stepper = LSRK4TimeStepper( + vector_primitive_factory=discr.get_vector_primitive_factory()) + + if rcon.is_head_rank: + print "%d elements" % len(discr.mesh.elements) + + # filter setup------------------------------------------------------------- + from hedge.discretization import ExponentialFilterResponseFunction + from hedge.optemplate.operators import FilterOperator + mode_filter = FilterOperator( + ExponentialFilterResponseFunction(min_amplification=0.9,order=4))\ + .bind(discr) + + # diagnostics setup ------------------------------------------------------- + from pytools.log import LogManager, \ + add_general_quantities, \ + add_simulation_quantities, \ + add_run_info + + if write_output: + log_file_name = "space-dep.dat" + else: + log_file_name = None + + logmgr = LogManager(log_file_name, "w", rcon.communicator) + add_run_info(logmgr) + add_general_quantities(logmgr) + add_simulation_quantities(logmgr) + discr.add_instrumentation(logmgr) + + stepper.add_instrumentation(logmgr) + + from hedge.log import Integral, LpNorm + u_getter = lambda: u + logmgr.add_quantity(Integral(u_getter, discr, name="int_u")) + logmgr.add_quantity(LpNorm(u_getter, discr, p=1, name="l1_u")) + logmgr.add_quantity(LpNorm(u_getter, discr, name="l2_u")) + + logmgr.add_watches(["step.max", "t_sim.max", "l2_u", "t_step.max"]) + + # Initialize v for data output: + v = op.advec_v.volume_interpolant(0, discr) + + # timestep loop ----------------------------------------------------------- + rhs = op.bind(discr) + try: + from hedge.timestep import times_and_steps + step_it = times_and_steps( + final_time=final_time, logmgr=logmgr, + max_dt_getter=lambda t: op.estimate_timestep(discr, + stepper=stepper, t=t, fields=u)) + + for step, t, dt in step_it: + if step % 10 == 0 and write_output: + visf = vis.make_file("fld-%04d" % step) + vis.add_data(visf, [ + ("u", discr.convert_volume(u, kind="numpy")), + ("v", discr.convert_volume(v, kind="numpy")) + ], time=t, step=step) + visf.close() + + u = stepper(u, t, dt, rhs) + + # We're feeding in a discontinuity through the BCs. + # Quadrature does not help with shock capturing-- + # therefore we do need to filter here, regardless + # of whether quadrature is enabled. + u = mode_filter(u) + + assert discr.norm(u) < 10 + + finally: + if write_output: + vis.close() + + logmgr.close() + discr.close() + + + +if __name__ == "__main__": + main() + + + + +# entry points for py.test ---------------------------------------------------- +def test_var_velocity_advection(): + from pytools.test import mark_test + mark_long = mark_test.long + + for flux_type in ["upwind", "central", "lf"]: + for use_quadrature in [False, True]: + descr = "variable-velocity-advection with %s flux" % flux_type + if use_quadrature: + descr += " and quadrature" + + yield descr, mark_long(main), False, flux_type, use_quadrature, 1 diff --git a/examples/burgers/burgers.dat b/examples/burgers/burgers.dat new file mode 100644 index 0000000000000000000000000000000000000000..baf59af8ed316993b4c2f5eaedea2b2c2538f17d GIT binary patch literal 918528 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

9>C0iG$6yadC;OnwrVd7wCHAg4`l_VUj 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%0DhGl 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. + + +from __future__ import division +import numpy +import numpy.linalg as la +from math import sin, cos, pi, sqrt +from pytools.test import mark_test + + + + +class ExactTestCase: + a = 0 + b = 150 + final_time = 1000 + + def u0(self, x): + return self.u_exact(x, 0) + + def u_exact(self, x, t): + # CAUTION: This gets the shock speed wrong as soon as the pulse + # starts interacting with itself. + + def f(x, shock_loc): + if x < (t-40)/4: + return 1/4 + else: + if t < 40: + if x < (3*t)/4: + return (x+15)/(t+20) + elif x < (t+80)/4: + return (x-30)/(t-40) + else: + return 1/4 + else: + if x < shock_loc: + return (x+15)/(t+20) + else: + return 1/4 + + from math import sqrt + + shock_loc = 30*sqrt(2*t+40)/sqrt(120) + t/4 - 10 + shock_win = (shock_loc + 20) // self.b + x += shock_win * 150 + + x -= 20 + + return max(f(x, shock_loc), f(x-self.b, shock_loc-self.b)) + +class OffCenterMigratingTestCase: + a = -pi + b = pi + final_time = 10 + + def u0(self, x): + return -0.4+sin(x+0.1) + + +class CenteredStationaryTestCase: + # does funny things to P-P + a = -pi + b = pi + final_time = 10 + + def u0(self, x): + return -sin(x) + +class OffCenterStationaryTestCase: + # does funny things to P-P + a = -pi + b = pi + final_time = 10 + + def u0(self, x): + return -sin(x+0.3) + + + +def main(write_output=True, flux_type_arg="upwind", + #case = CenteredStationaryTestCase(), + #case = OffCenterStationaryTestCase(), + #case = OffCenterMigratingTestCase(), + case = ExactTestCase(), + ): + from hedge.backends import guess_run_context + rcon = guess_run_context() + + order = 3 + if rcon.is_head_rank: + if True: + from hedge.mesh.generator import make_uniform_1d_mesh + mesh = make_uniform_1d_mesh(case.a, case.b, 20, periodic=True) + else: + from hedge.mesh.generator import make_rect_mesh + print (pi*2)/(11*5*2) + mesh = make_rect_mesh((-pi, -1), (pi, 1), + periodicity=(True, True), + subdivisions=(11,5), + max_area=(pi*2)/(11*5*2) + ) + + if rcon.is_head_rank: + mesh_data = rcon.distribute_mesh(mesh) + else: + mesh_data = rcon.receive_mesh() + + discr = rcon.make_discretization(mesh_data, order=order, + quad_min_degrees={"quad": 3*order}) + + if write_output: + from hedge.visualization import VtkVisualizer + vis = VtkVisualizer(discr, rcon, "fld") + + # operator setup ---------------------------------------------------------- + from hedge.second_order import IPDGSecondDerivative + + from hedge.models.burgers import BurgersOperator + op = BurgersOperator(mesh.dimensions, + viscosity_scheme=IPDGSecondDerivative()) + + if rcon.is_head_rank: + print "%d elements" % len(discr.mesh.elements) + + # exact solution ---------------------------------------------------------- + import pymbolic + var = pymbolic.var + + u = discr.interpolate_volume_function(lambda x, el: case.u0(x[0])) + + # diagnostics setup ------------------------------------------------------- + from pytools.log import LogManager, \ + add_general_quantities, \ + add_simulation_quantities, \ + add_run_info + + if write_output: + log_file_name = "burgers.dat" + else: + log_file_name = None + + logmgr = LogManager(log_file_name, "w", rcon.communicator) + add_run_info(logmgr) + add_general_quantities(logmgr) + add_simulation_quantities(logmgr) + discr.add_instrumentation(logmgr) + + from hedge.log import LpNorm + u_getter = lambda: u + logmgr.add_quantity(LpNorm(u_getter, discr, p=1, name="l1_u")) + + logmgr.add_watches(["step.max", "t_sim.max", "l1_u", "t_step.max"]) + + # timestep loop ----------------------------------------------------------- + rhs = op.bind(discr) + + from hedge.timestep.runge_kutta import ODE45TimeStepper, LSRK4TimeStepper + stepper = ODE45TimeStepper() + + stepper.add_instrumentation(logmgr) + + try: + from hedge.timestep import times_and_steps + # for visc=0.01 + #stab_fac = 0.1 # RK4 + #stab_fac = 1.6 # dumka3(3), central + #stab_fac = 3 # dumka3(4), central + + #stab_fac = 0.01 # RK4 + stab_fac = 0.2 # dumka3(3), central + #stab_fac = 3 # dumka3(4), central + + dt = stab_fac*op.estimate_timestep(discr, + stepper=LSRK4TimeStepper(), t=0, fields=u) + + step_it = times_and_steps( + final_time=case.final_time, logmgr=logmgr, max_dt_getter=lambda t: dt) + from hedge.optemplate import InverseVandermondeOperator + inv_vdm = InverseVandermondeOperator().bind(discr) + + for step, t, dt in step_it: + if step % 3 == 0 and write_output: + if hasattr(case, "u_exact"): + extra_fields = [ + ("u_exact", + discr.interpolate_volume_function( + lambda x, el: case.u_exact(x[0], t)))] + else: + extra_fields = [] + + visf = vis.make_file("fld-%04d" % step) + vis.add_data(visf, [ + ("u", u), + ] + extra_fields, + time=t, + step=step) + visf.close() + + u = stepper(u, t, dt, rhs) + + if isinstance(case, ExactTestCase): + assert discr.norm(u, 1) < 50 + + finally: + if write_output: + vis.close() + + logmgr.save() + + + + +if __name__ == "__main__": + main() + + + + +# entry points for py.test ---------------------------------------------------- +@mark_test.long +def test_stability(): + main(write_output=False) + diff --git a/examples/conftest.py b/examples/conftest.py new file mode 100644 index 00000000..ec99a4c7 --- /dev/null +++ b/examples/conftest.py @@ -0,0 +1,7 @@ +# This file tells py.test to scan for test_xxx() functions in +# any file below here, not just those named test_xxxx.py. + + +def pytest_collect_file(path, parent): + if "hedge/examples" in str(path.dirpath()) and path.ext == ".py": + return parent.Module(path, parent=parent) diff --git a/examples/gas_dynamics/box-in-box.py b/examples/gas_dynamics/box-in-box.py new file mode 100644 index 00000000..ac3be82f --- /dev/null +++ b/examples/gas_dynamics/box-in-box.py @@ -0,0 +1,235 @@ +# Hedge - the Hybrid'n'Easy DG Environment +# Copyright (C) 2008 Andreas Kloeckner +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 this program. If not, see . + + +from __future__ import division +import numpy + + +def make_boxmesh(): + from meshpy.tet import MeshInfo, build + from meshpy.geometry import GeometryBuilder, Marker, make_box + + geob = GeometryBuilder() + + box_marker = Marker.FIRST_USER_MARKER + extent_small = 0.1*numpy.ones(3, dtype=numpy.float64) + + geob.add_geometry(*make_box(-extent_small, extent_small)) + + # make small "separator box" for region attribute + + geob.add_geometry( + *make_box( + -extent_small*4, + numpy.array([4, 0.4, 0.4], dtype=numpy.float64))) + + geob.add_geometry( + *make_box( + numpy.array([-1, -1, -1], dtype=numpy.float64), + numpy.array([5, 1, 1], dtype=numpy.float64))) + + mesh_info = MeshInfo() + geob.set(mesh_info) + mesh_info.set_holes([(0, 0, 0)]) + + # region attributes + mesh_info.regions.resize(1) + mesh_info.regions[0] = ( + # point in region + list(extent_small*2) + [ + # region number + 1, + # max volume in region + #0.0001 + 0.005 + ]) + + mesh = build(mesh_info, max_volume=0.02, + volume_constraints=True, attributes=True) + print "%d elements" % len(mesh.elements) + #mesh.write_vtk("box-in-box.vtk") + #print "done writing" + + fvi2fm = mesh.face_vertex_indices_to_face_marker + + face_marker_to_tag = { + box_marker: "noslip", + Marker.MINUS_X: "inflow", + Marker.PLUS_X: "outflow", + Marker.MINUS_Y: "inflow", + Marker.PLUS_Y: "inflow", + Marker.PLUS_Z: "inflow", + Marker.MINUS_Z: "inflow" + } + + def bdry_tagger(fvi, el, fn, all_v): + face_marker = fvi2fm[fvi] + return [face_marker_to_tag[face_marker]] + + from hedge.mesh import make_conformal_mesh + return make_conformal_mesh( + mesh.points, mesh.elements, bdry_tagger) + + +def main(): + from hedge.backends import guess_run_context + rcon = guess_run_context(["cuda"]) + + if rcon.is_head_rank: + mesh = make_boxmesh() + #from hedge.mesh import make_rect_mesh + #mesh = make_rect_mesh( + # boundary_tagger=lambda fvi, el, fn, all_v: ["inflow"]) + mesh_data = rcon.distribute_mesh(mesh) + else: + mesh_data = rcon.receive_mesh() + + for order in [3]: + from pytools import add_python_path_relative_to_script + add_python_path_relative_to_script("..") + + from gas_dynamics_initials import UniformMachFlow + box = UniformMachFlow(angle_of_attack=0) + + from hedge.models.gas_dynamics import GasDynamicsOperator + op = GasDynamicsOperator(dimensions=3, + gamma=box.gamma, mu=box.mu, + prandtl=box.prandtl, spec_gas_const=box.spec_gas_const, + bc_inflow=box, bc_outflow=box, bc_noslip=box, + inflow_tag="inflow", outflow_tag="outflow", noslip_tag="noslip") + + discr = rcon.make_discretization(mesh_data, order=order, + debug=[ + #"cuda_no_plan", + #"cuda_dump_kernels", + #"dump_dataflow_graph", + #"dump_optemplate_stages", + #"dump_dataflow_graph", + #"print_op_code", + "cuda_no_plan_el_local", + ], + default_scalar_type=numpy.float32, + tune_for=op.op_template()) + + from hedge.visualization import SiloVisualizer, VtkVisualizer # noqa + #vis = VtkVisualizer(discr, rcon, "shearflow-%d" % order) + vis = SiloVisualizer(discr, rcon) + + fields = box.volume_interpolant(0, discr) + + navierstokes_ex = op.bind(discr) + + max_eigval = [0] + + def rhs(t, q): + ode_rhs, speed = navierstokes_ex(t, q) + max_eigval[0] = speed + return ode_rhs + + rhs(0, fields) + + if rcon.is_head_rank: + print "---------------------------------------------" + print "order %d" % order + print "---------------------------------------------" + print "#elements=", len(mesh.elements) + + from hedge.timestep import RK4TimeStepper + stepper = RK4TimeStepper() + + # diagnostics setup --------------------------------------------------- + from pytools.log import LogManager, add_general_quantities, \ + add_simulation_quantities, add_run_info + + logmgr = LogManager("navierstokes-%d.dat" % order, "w", rcon.communicator) + add_run_info(logmgr) + add_general_quantities(logmgr) + add_simulation_quantities(logmgr) + discr.add_instrumentation(logmgr) + stepper.add_instrumentation(logmgr) + + logmgr.add_watches(["step.max", "t_sim.max", "t_step.max"]) + + from pytools.log import LogQuantity + + class ChangeSinceLastStep(LogQuantity): + """Records the change of a variable between a time step and the previous + one""" + + def __init__(self, name="change"): + LogQuantity.__init__(self, name, "1", "Change since last time step") + + self.old_fields = 0 + + def __call__(self): + result = discr.norm(fields - self.old_fields) + self.old_fields = fields + return result + + logmgr.add_quantity(ChangeSinceLastStep()) + + # timestep loop ------------------------------------------------------- + try: + from hedge.timestep import times_and_steps + step_it = times_and_steps( + final_time=200, + #max_steps=500, + logmgr=logmgr, + max_dt_getter=lambda t: op.estimate_timestep(discr, + stepper=stepper, t=t, max_eigenvalue=max_eigval[0])) + + for step, t, dt in step_it: + if step % 200 == 0: + #if False: + visf = vis.make_file("box-%d-%06d" % (order, step)) + + #rhs_fields = rhs(t, fields) + + vis.add_data(visf, + [ + ("rho", discr.convert_volume( + op.rho(fields), kind="numpy")), + ("e", discr.convert_volume( + op.e(fields), kind="numpy")), + ("rho_u", discr.convert_volume( + op.rho_u(fields), kind="numpy")), + ("u", discr.convert_volume( + op.u(fields), kind="numpy")), + + # ("rhs_rho", discr.convert_volume( + # op.rho(rhs_fields), kind="numpy")), + # ("rhs_e", discr.convert_volume( + # op.e(rhs_fields), kind="numpy")), + # ("rhs_rho_u", discr.convert_volume( + # op.rho_u(rhs_fields), kind="numpy")), + ], + expressions=[ + ("p", "(0.4)*(e- 0.5*(rho_u*u))"), + ], + time=t, step=step + ) + visf.close() + + fields = stepper(fields, t, dt, rhs) + + finally: + vis.close() + logmgr.save() + discr.close() + +if __name__ == "__main__": + main() diff --git a/examples/gas_dynamics/euler/sine-wave.py b/examples/gas_dynamics/euler/sine-wave.py new file mode 100644 index 00000000..9f751a86 --- /dev/null +++ b/examples/gas_dynamics/euler/sine-wave.py @@ -0,0 +1,198 @@ +# Hedge - the Hybrid'n'Easy DG Environment +# Copyright (C) 2008 Andreas Kloeckner +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 this program. If not, see . + + + + +from __future__ import division +import numpy +import numpy.linalg as la + + + + +class SineWave: + def __init__(self): + self.gamma = 1.4 + self.mu = 0 + self.prandtl = 0.72 + self.spec_gas_const = 287.1 + + def __call__(self, t, x_vec): + rho = 2 + numpy.sin(x_vec[0] + x_vec[1] + x_vec[2] - 2 * t) + velocity = numpy.array([1, 1, 0]) + p = 1 + e = p/(self.gamma-1) + rho/2 * numpy.dot(velocity, velocity) + rho_u = rho * velocity[0] + rho_v = rho * velocity[1] + rho_w = rho * velocity[2] + + from hedge.tools import join_fields + return join_fields(rho, e, rho_u, rho_v, rho_w) + + def properties(self): + return(self.gamma, self.mu, self.prandtl, self.spec_gas_const) + + def volume_interpolant(self, t, discr): + return discr.convert_volume( + self(t, discr.nodes.T), + kind=discr.compute_kind) + + def boundary_interpolant(self, t, discr, tag): + return discr.convert_boundary( + self(t, discr.get_boundary(tag).nodes.T), + tag=tag, kind=discr.compute_kind) + + + + +def main(final_time=1, write_output=False): + from hedge.backends import guess_run_context + rcon = guess_run_context() + + from hedge.tools import EOCRecorder, to_obj_array + eoc_rec = EOCRecorder() + + if rcon.is_head_rank: + from hedge.mesh import make_box_mesh + mesh = make_box_mesh((0,0,0), (10,10,10), max_volume=0.5) + mesh_data = rcon.distribute_mesh(mesh) + else: + mesh_data = rcon.receive_mesh() + + for order in [3, 4, 5]: + discr = rcon.make_discretization(mesh_data, order=order, + default_scalar_type=numpy.float64) + + from hedge.visualization import SiloVisualizer, VtkVisualizer + vis = VtkVisualizer(discr, rcon, "sinewave-%d" % order) + #vis = SiloVisualizer(discr, rcon) + + sinewave = SineWave() + fields = sinewave.volume_interpolant(0, discr) + gamma, mu, prandtl, spec_gas_const = sinewave.properties() + + from hedge.mesh import TAG_ALL + from hedge.models.gas_dynamics import GasDynamicsOperator + op = GasDynamicsOperator(dimensions=mesh.dimensions, gamma=gamma, mu=mu, + prandtl=prandtl, spec_gas_const=spec_gas_const, + bc_inflow=sinewave, bc_outflow=sinewave, bc_noslip=sinewave, + inflow_tag=TAG_ALL, source=None) + + euler_ex = op.bind(discr) + + max_eigval = [0] + def rhs(t, q): + ode_rhs, speed = euler_ex(t, q) + max_eigval[0] = speed + return ode_rhs + rhs(0, fields) + + if rcon.is_head_rank: + print "---------------------------------------------" + print "order %d" % order + print "---------------------------------------------" + print "#elements=", len(mesh.elements) + + from hedge.timestep import RK4TimeStepper + stepper = RK4TimeStepper() + + # diagnostics setup --------------------------------------------------- + from pytools.log import LogManager, add_general_quantities, \ + add_simulation_quantities, add_run_info + + if write_output: + log_name = ("euler-sinewave-%(order)d-%(els)d.dat" + % {"order":order, "els":len(mesh.elements)}) + else: + log_name = False + logmgr = LogManager(log_name, "w", rcon.communicator) + add_run_info(logmgr) + add_general_quantities(logmgr) + add_simulation_quantities(logmgr) + discr.add_instrumentation(logmgr) + stepper.add_instrumentation(logmgr) + + logmgr.add_watches(["step.max", "t_sim.max", "t_step.max"]) + + # timestep loop ------------------------------------------------------- + try: + from hedge.timestep import times_and_steps + step_it = times_and_steps( + final_time=final_time, logmgr=logmgr, + max_dt_getter=lambda t: op.estimate_timestep(discr, + stepper=stepper, t=t, max_eigenvalue=max_eigval[0])) + + for step, t, dt in step_it: + #if step % 10 == 0: + if write_output: + visf = vis.make_file("sinewave-%d-%04d" % (order, step)) + + #from pyvisfile.silo import DB_VARTYPE_VECTOR + vis.add_data(visf, + [ + ("rho", discr.convert_volume(op.rho(fields), kind="numpy")), + ("e", discr.convert_volume(op.e(fields), kind="numpy")), + ("rho_u", discr.convert_volume(op.rho_u(fields), kind="numpy")), + ("u", discr.convert_volume(op.u(fields), kind="numpy")), + + #("true_rho", op.rho(true_fields)), + #("true_e", op.e(true_fields)), + #("true_rho_u", op.rho_u(true_fields)), + #("true_u", op.u(true_fields)), + + #("rhs_rho", op.rho(rhs_fields)), + #("rhs_e", op.e(rhs_fields)), + #("rhs_rho_u", op.rho_u(rhs_fields)), + ], + #expressions=[ + #("diff_rho", "rho-true_rho"), + #("diff_e", "e-true_e"), + #("diff_rho_u", "rho_u-true_rho_u", DB_VARTYPE_VECTOR), + + #("p", "0.4*(e- 0.5*(rho_u*u))"), + #], + time=t, step=step + ) + visf.close() + + fields = stepper(fields, t, dt, rhs) + + finally: + vis.close() + logmgr.close() + discr.close() + + true_fields = sinewave.volume_interpolant(t, discr) + eoc_rec.add_data_point(order, discr.norm(fields-true_fields)) + print + print eoc_rec.pretty_print("P.Deg.", "L2 Error") + + + + +if __name__ == "__main__": + main() + + + + +# entry points for py.test ---------------------------------------------------- +from pytools.test import mark_test +@mark_test.long +def test_euler_sine_wave(): + main(final_time=0.1, write_output=False) + diff --git a/examples/gas_dynamics/euler/sod-2d.py b/examples/gas_dynamics/euler/sod-2d.py new file mode 100644 index 00000000..352a855e --- /dev/null +++ b/examples/gas_dynamics/euler/sod-2d.py @@ -0,0 +1,183 @@ +from __future__ import division +import numpy +import numpy.linalg as la + + + + +class Sod: + def __init__(self, gamma): + self.gamma = gamma + self.prandtl = 0.72 + + def __call__(self, t, x_vec): + + from hedge.tools import heaviside + from hedge.tools import heaviside_a + + x_rel = x_vec[0] + y_rel = x_vec[1] + + from math import pi + r = numpy.sqrt(x_rel**2+y_rel**2) + r_shift=r-3.0 + u = 0.0 + v = 0.0 + from numpy import sign + rho = heaviside(-r_shift)+.125*heaviside_a(r_shift,1.0) + e = (1.0/(self.gamma-1.0))*(heaviside(-r_shift)+.1*heaviside_a(r_shift,1.0)) + p = (self.gamma-1.0)*e + + from hedge.tools import join_fields + return join_fields(rho, e, rho*u, rho*v) + + + def volume_interpolant(self, t, discr): + return discr.convert_volume( + self(t, discr.nodes.T), + kind=discr.compute_kind) + + def boundary_interpolant(self, t, discr, tag): + return discr.convert_boundary( + self(t, discr.get_boundary(tag).nodes.T), + tag=tag, kind=discr.compute_kind) + + + + +def main(): + from hedge.backends import guess_run_context + rcon = guess_run_context() + + from hedge.tools import to_obj_array + + if rcon.is_head_rank: + from hedge.mesh.generator import make_rect_mesh + mesh = make_rect_mesh((-5,-5), (5,5), max_area=0.01) + mesh_data = rcon.distribute_mesh(mesh) + else: + mesh_data = rcon.receive_mesh() + + for order in [1]: + discr = rcon.make_discretization(mesh_data, order=order, + default_scalar_type=numpy.float64) + + from hedge.visualization import SiloVisualizer, VtkVisualizer + vis = VtkVisualizer(discr, rcon, "Sod2D-%d" % order) + #vis = SiloVisualizer(discr, rcon) + + sod_field = Sod(gamma=1.4) + fields = sod_field.volume_interpolant(0, discr) + + from hedge.models.gas_dynamics import GasDynamicsOperator + from hedge.mesh import TAG_ALL + op = GasDynamicsOperator(dimensions=2, gamma=sod_field.gamma, mu=0.0, + prandtl=sod_field.prandtl, + bc_inflow=sod_field, + bc_outflow=sod_field, + bc_noslip=sod_field, + inflow_tag=TAG_ALL, + source=None) + + euler_ex = op.bind(discr) + + max_eigval = [0] + def rhs(t, q): + ode_rhs, speed = euler_ex(t, q) + max_eigval[0] = speed + return ode_rhs + rhs(0, fields) + + if rcon.is_head_rank: + print "---------------------------------------------" + print "order %d" % order + print "---------------------------------------------" + print "#elements=", len(mesh.elements) + + # limiter setup ------------------------------------------------------------ + from hedge.models.gas_dynamics import SlopeLimiter1NEuler + limiter = SlopeLimiter1NEuler(discr, sod_field.gamma, 2, op) + + # integrator setup--------------------------------------------------------- + from hedge.timestep import SSPRK3TimeStepper, RK4TimeStepper + stepper = SSPRK3TimeStepper(limiter=limiter) + #stepper = SSPRK3TimeStepper() + #stepper = RK4TimeStepper() + + # diagnostics setup --------------------------------------------------- + from pytools.log import LogManager, add_general_quantities, \ + add_simulation_quantities, add_run_info + + logmgr = LogManager("euler-%d.dat" % order, "w", rcon.communicator) + add_run_info(logmgr) + add_general_quantities(logmgr) + add_simulation_quantities(logmgr) + discr.add_instrumentation(logmgr) + stepper.add_instrumentation(logmgr) + + logmgr.add_watches(["step.max", "t_sim.max", "t_step.max"]) + + # filter setup------------------------------------------------------------- + from hedge.discretization import Filter, ExponentialFilterResponseFunction + mode_filter = Filter(discr, + ExponentialFilterResponseFunction(min_amplification=0.9,order=4)) + + # timestep loop ------------------------------------------------------- + try: + from hedge.timestep import times_and_steps + step_it = times_and_steps( + final_time=1.0, logmgr=logmgr, + max_dt_getter=lambda t: op.estimate_timestep(discr, + stepper=stepper, t=t, max_eigenvalue=max_eigval[0])) + + for step, t, dt in step_it: + if step % 5 == 0: + #if False: + visf = vis.make_file("vortex-%d-%04d" % (order, step)) + + #true_fields = vortex.volume_interpolant(t, discr) + + #from pyvisfile.silo import DB_VARTYPE_VECTOR + vis.add_data(visf, + [ + ("rho", discr.convert_volume(op.rho(fields), kind="numpy")), + ("e", discr.convert_volume(op.e(fields), kind="numpy")), + ("rho_u", discr.convert_volume(op.rho_u(fields), kind="numpy")), + ("u", discr.convert_volume(op.u(fields), kind="numpy")), + + #("true_rho", op.rho(true_fields)), + #("true_e", op.e(true_fields)), + #("true_rho_u", op.rho_u(true_fields)), + #("true_u", op.u(true_fields)), + + #("rhs_rho", op.rho(rhs_fields)), + #("rhs_e", op.e(rhs_fields)), + #("rhs_rho_u", op.rho_u(rhs_fields)), + ], + #expressions=[ + #("diff_rho", "rho-true_rho"), + #("diff_e", "e-true_e"), + #("diff_rho_u", "rho_u-true_rho_u", DB_VARTYPE_VECTOR), + + #("p", "0.4*(e- 0.5*(rho_u*u))"), + #], + time=t, step=step + ) + visf.close() + + fields = stepper(fields, t, dt, rhs) + # fields = limiter(fields) + # fields = mode_filter(fields) + + assert not numpy.isnan(numpy.sum(fields[0])) + finally: + vis.close() + logmgr.close() + discr.close() + + # not solution, just to check against when making code changes + true_fields = sod_field.volume_interpolant(t, discr) + print discr.norm(fields-true_fields) + +if __name__ == "__main__": + main() diff --git a/examples/gas_dynamics/euler/vortex-adaptive-grid.py b/examples/gas_dynamics/euler/vortex-adaptive-grid.py new file mode 100644 index 00000000..fb33c2ed --- /dev/null +++ b/examples/gas_dynamics/euler/vortex-adaptive-grid.py @@ -0,0 +1,258 @@ +# Hedge - the Hybrid'n'Easy DG Environment +# Copyright (C) 2008 Andreas Kloeckner +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 this program. If not, see . + + + + +from __future__ import division +import numpy +import numpy.linalg as la + + + + +def main(write_output=True): + from pytools import add_python_path_relative_to_script + add_python_path_relative_to_script("..") + + from hedge.backends import guess_run_context + rcon = guess_run_context() + + from hedge.tools import EOCRecorder + eoc_rec = EOCRecorder() + + + if rcon.is_head_rank: + from hedge.mesh.generator import \ + make_rect_mesh, \ + make_centered_regular_rect_mesh + + refine = 4 + mesh = make_centered_regular_rect_mesh((0,-5), (10,5), n=(9,9), + post_refine_factor=refine) + mesh_data = rcon.distribute_mesh(mesh) + else: + mesh_data = rcon.receive_mesh() + + # a second mesh to regrid to + if rcon.is_head_rank: + from hedge.mesh.generator import \ + make_rect_mesh, \ + make_centered_regular_rect_mesh + + refine = 4 + mesh2 = make_centered_regular_rect_mesh((0,-5), (10,5), n=(8,8), + post_refine_factor=refine) + mesh_data2 = rcon.distribute_mesh(mesh2) + else: + mesh_data2 = rcon.receive_mesh() + + + + for order in [3,4]: + discr = rcon.make_discretization(mesh_data, order=order, + default_scalar_type=numpy.float64, + quad_min_degrees={ + "gasdyn_vol": 3*order, + "gasdyn_face": 3*order, + }) + + discr2 = rcon.make_discretization(mesh_data2, order=order, + default_scalar_type=numpy.float64, + quad_min_degrees={ + "gasdyn_vol": 3*order, + "gasdyn_face": 3*order, + }) + + + from hedge.visualization import SiloVisualizer, VtkVisualizer + vis = VtkVisualizer(discr, rcon, "vortex-%d" % order) + #vis = SiloVisualizer(discr, rcon) + + from gas_dynamics_initials import Vortex + vortex = Vortex() + fields = vortex.volume_interpolant(0, discr) + + from hedge.models.gas_dynamics import GasDynamicsOperator + from hedge.mesh import TAG_ALL + + op = GasDynamicsOperator(dimensions=2, gamma=vortex.gamma, mu=vortex.mu, + prandtl=vortex.prandtl, spec_gas_const=vortex.spec_gas_const, + bc_inflow=vortex, bc_outflow=vortex, bc_noslip=vortex, + inflow_tag=TAG_ALL, source=None) + + euler_ex = op.bind(discr) + + max_eigval = [0] + def rhs(t, q): + ode_rhs, speed = euler_ex(t, q) + max_eigval[0] = speed + return ode_rhs + rhs(0, fields) + + + if rcon.is_head_rank: + print "---------------------------------------------" + print "order %d" % order + print "---------------------------------------------" + print "#elements for mesh 1 =", len(mesh.elements) + print "#elements for mesh 2 =", len(mesh2.elements) + + + # limiter ------------------------------------------------------------ + from hedge.models.gas_dynamics import SlopeLimiter1NEuler + limiter = SlopeLimiter1NEuler(discr, vortex.gamma, 2, op) + + from hedge.timestep import SSPRK3TimeStepper + #stepper = SSPRK3TimeStepper(limiter=limiter) + stepper = SSPRK3TimeStepper() + + #from hedge.timestep import RK4TimeStepper + #stepper = RK4TimeStepper() + + # diagnostics setup --------------------------------------------------- + from pytools.log import LogManager, add_general_quantities, \ + add_simulation_quantities, add_run_info + + if write_output: + log_file_name = "euler-%d.dat" % order + else: + log_file_name = None + + logmgr = LogManager(log_file_name, "w", rcon.communicator) + add_run_info(logmgr) + add_general_quantities(logmgr) + add_simulation_quantities(logmgr) + discr.add_instrumentation(logmgr) + stepper.add_instrumentation(logmgr) + + logmgr.add_watches(["step.max", "t_sim.max", "t_step.max"]) + + # timestep loop ------------------------------------------------------- + try: + final_time = 0.2 + from hedge.timestep import times_and_steps + step_it = times_and_steps( + final_time=final_time, logmgr=logmgr, + max_dt_getter=lambda t: op.estimate_timestep(discr, + stepper=stepper, t=t, max_eigenvalue=max_eigval[0])) + + for step, t, dt in step_it: + if step % 10 == 0 and write_output: + #if False: + visf = vis.make_file("vortex-%d-%04d" % (order, step)) + + #true_fields = vortex.volume_interpolant(t, discr) + + from pyvisfile.silo import DB_VARTYPE_VECTOR + vis.add_data(visf, + [ + ("rho", discr.convert_volume(op.rho(fields), kind="numpy")), + ("e", discr.convert_volume(op.e(fields), kind="numpy")), + ("rho_u", discr.convert_volume(op.rho_u(fields), kind="numpy")), + ("u", discr.convert_volume(op.u(fields), kind="numpy")), + + #("true_rho", discr.convert_volume(op.rho(true_fields), kind="numpy")), + #("true_e", discr.convert_volume(op.e(true_fields), kind="numpy")), + #("true_rho_u", discr.convert_volume(op.rho_u(true_fields), kind="numpy")), + #("true_u", discr.convert_volume(op.u(true_fields), kind="numpy")), + + #("rhs_rho", discr.convert_volume(op.rho(rhs_fields), kind="numpy")), + #("rhs_e", discr.convert_volume(op.e(rhs_fields), kind="numpy")), + #("rhs_rho_u", discr.convert_volume(op.rho_u(rhs_fields), kind="numpy")), + ], + #expressions=[ + #("diff_rho", "rho-true_rho"), + #("diff_e", "e-true_e"), + #("diff_rho_u", "rho_u-true_rho_u", DB_VARTYPE_VECTOR), + + #("p", "0.4*(e- 0.5*(rho_u*u))"), + #], + time=t, step=step + ) + visf.close() + + fields = stepper(fields, t, dt, rhs) + #fields = limiter(fields) + + #regrid to discr2 at some arbitrary time + if step == 21: + + #get interpolated fields + fields = discr.get_regrid_values(fields, discr2, dtype=None, use_btree=True, thresh=1e-8) + #get new stepper (old one has reference to discr + stepper = SSPRK3TimeStepper() + #new bind + euler_ex = op.bind(discr2) + #new rhs + max_eigval = [0] + def rhs(t, q): + ode_rhs, speed = euler_ex(t, q) + max_eigval[0] = speed + return ode_rhs + rhs(t+dt, fields) + #add logmanager + #discr2.add_instrumentation(logmgr) + #new step_it + step_it = times_and_steps( + final_time=final_time, logmgr=logmgr, + max_dt_getter=lambda t: op.estimate_timestep(discr2, + stepper=stepper, t=t, max_eigenvalue=max_eigval[0])) + + #new visualization + vis.close() + vis = VtkVisualizer(discr2, rcon, "vortexNewGrid-%d" % order) + discr=discr2 + + + + assert not numpy.isnan(numpy.sum(fields[0])) + + true_fields = vortex.volume_interpolant(final_time, discr) + l2_error = discr.norm(fields-true_fields) + l2_error_rho = discr.norm(op.rho(fields)-op.rho(true_fields)) + l2_error_e = discr.norm(op.e(fields)-op.e(true_fields)) + l2_error_rhou = discr.norm(op.rho_u(fields)-op.rho_u(true_fields)) + l2_error_u = discr.norm(op.u(fields)-op.u(true_fields)) + + eoc_rec.add_data_point(order, l2_error) + print + print eoc_rec.pretty_print("P.Deg.", "L2 Error") + + logmgr.set_constant("l2_error", l2_error) + logmgr.set_constant("l2_error_rho", l2_error_rho) + logmgr.set_constant("l2_error_e", l2_error_e) + logmgr.set_constant("l2_error_rhou", l2_error_rhou) + logmgr.set_constant("l2_error_u", l2_error_u) + logmgr.set_constant("refinement", refine) + + finally: + if write_output: + vis.close() + + logmgr.close() + discr.close() + + + + # after order loop + # assert eoc_rec.estimate_order_of_convergence()[0,1] > 6 + + + + +if __name__ == "__main__": + main() diff --git a/examples/gas_dynamics/euler/vortex-sources.py b/examples/gas_dynamics/euler/vortex-sources.py new file mode 100644 index 00000000..dbe5f84d --- /dev/null +++ b/examples/gas_dynamics/euler/vortex-sources.py @@ -0,0 +1,334 @@ +# Hedge - the Hybrid'n'Easy DG Environment +# Copyright (C) 2008 Andreas Kloeckner +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 this program. If not, see . + + + + +from __future__ import division +import numpy +import numpy.linalg as la + +# modifies isentropic vortex solution so that rho->Arho, P->A^gamma rho^gamma +# this will be analytic solution if appropriate source terms are added +# to the RHS. coded for vel_x=1, vel_y=0 + + +class Vortex: + def __init__(self, beta, gamma, center, velocity, densityA): + self.beta = beta + self.gamma = gamma + self.center = numpy.asarray(center) + self.velocity = numpy.asarray(velocity) + self.densityA = densityA + + def __call__(self, t, x_vec): + vortex_loc = self.center + t*self.velocity + + # coordinates relative to vortex center + x_rel = x_vec[0] - vortex_loc[0] + y_rel = x_vec[1] - vortex_loc[1] + + # Y.C. Zhou, G.W. Wei / Journal of Computational Physics 189 (2003) 159 + # also JSH/TW Nodal DG Methods, p. 209 + + from math import pi + r = numpy.sqrt(x_rel**2+y_rel**2) + expterm = self.beta*numpy.exp(1-r**2) + u = self.velocity[0] - expterm*y_rel/(2*pi) + v = self.velocity[1] + expterm*x_rel/(2*pi) + rho = self.densityA*(1-(self.gamma-1)/(16*self.gamma*pi**2)*expterm**2)**(1/(self.gamma-1)) + p = rho**self.gamma + + e = p/(self.gamma-1) + rho/2*(u**2+v**2) + + from hedge.tools import join_fields + return join_fields(rho, e, rho*u, rho*v) + + def volume_interpolant(self, t, discr): + return discr.convert_volume( + self(t, discr.nodes.T + .astype(discr.default_scalar_type)), + kind=discr.compute_kind) + + def boundary_interpolant(self, t, discr, tag): + return discr.convert_boundary( + self(t, discr.get_boundary(tag).nodes.T + .astype(discr.default_scalar_type)), + tag=tag, kind=discr.compute_kind) + + + +class SourceTerms: + def __init__(self, beta, gamma, center, velocity, densityA): + self.beta = beta + self.gamma = gamma + self.center = numpy.asarray(center) + self.velocity = numpy.asarray(velocity) + self.densityA = densityA + + + + def __call__(self,t,x_vec,q): + + vortex_loc = self.center + t*self.velocity + + # coordinates relative to vortex center + x_rel = x_vec[0] - vortex_loc[0] + y_rel = x_vec[1] - vortex_loc[1] + + # sources written in terms of A=1.0 solution + # (standard isentropic vortex) + + from math import pi + r = numpy.sqrt(x_rel**2+y_rel**2) + expterm = self.beta*numpy.exp(1-r**2) + u = self.velocity[0] - expterm*y_rel/(2*pi) + v = self.velocity[1] + expterm*x_rel/(2*pi) + rho = (1-(self.gamma-1)/(16*self.gamma*pi**2)*expterm**2)**(1/(self.gamma-1)) + p = rho**self.gamma + + #computed necessary derivatives + expterm_t = 2*expterm*x_rel + expterm_x = -2*expterm*x_rel + expterm_y = -2*expterm*y_rel + u_x = -expterm*y_rel/(2*pi)*(-2*x_rel) + v_y = expterm*x_rel/(2*pi)*(-2*y_rel) + + #derivatives for rho (A=1) + facG=self.gamma-1 + rho_t = (1/facG)*(1-(facG)/(16*self.gamma*pi**2)*expterm**2)**(1/facG-1)* \ + (-facG/(16*self.gamma*pi**2)*2*expterm*expterm_t) + rho_x = (1/facG)*(1-(facG)/(16*self.gamma*pi**2)*expterm**2)**(1/facG-1)* \ + (-facG/(16*self.gamma*pi**2)*2*expterm*expterm_x) + rho_y = (1/facG)*(1-(facG)/(16*self.gamma*pi**2)*expterm**2)**(1/facG-1)* \ + (-facG/(16*self.gamma*pi**2)*2*expterm*expterm_y) + + #derivatives for rho (A=1) to the power of gamma + rho_gamma_t = self.gamma*rho**(self.gamma-1)*rho_t + rho_gamma_x = self.gamma*rho**(self.gamma-1)*rho_x + rho_gamma_y = self.gamma*rho**(self.gamma-1)*rho_y + + + factorA=self.densityA**self.gamma-self.densityA + #construct source terms + source_rho = x_vec[0]-x_vec[0] + source_e = (factorA/(self.gamma-1))*(rho_gamma_t + self.gamma*(u_x*rho**self.gamma+u*rho_gamma_x)+ \ + self.gamma*(v_y*rho**self.gamma+v*rho_gamma_y)) + source_rhou = factorA*rho_gamma_x + source_rhov = factorA*rho_gamma_y + + from hedge.tools import join_fields + return join_fields(source_rho, source_e, source_rhou, source_rhov, x_vec[0]-x_vec[0]) + + def volume_interpolant(self,t,q,discr): + return discr.convert_volume( + self(t,discr.nodes.T,q), + kind=discr.compute_kind) + + +def main(write_output=True): + from hedge.backends import guess_run_context + rcon = guess_run_context( + #["cuda"] + ) + + gamma = 1.4 + + # at A=1 we have case of isentropic vortex, source terms + # arise for other values + densityA = 2.0 + + from hedge.tools import EOCRecorder, to_obj_array + eoc_rec = EOCRecorder() + + if rcon.is_head_rank: + from hedge.mesh import \ + make_rect_mesh, \ + make_centered_regular_rect_mesh + + refine = 1 + mesh = make_centered_regular_rect_mesh((0,-5), (10,5), n=(9,9), + post_refine_factor=refine) + mesh_data = rcon.distribute_mesh(mesh) + else: + mesh_data = rcon.receive_mesh() + + for order in [4,5]: + discr = rcon.make_discretization(mesh_data, order=order, + debug=[#"cuda_no_plan", + #"print_op_code" + ], + default_scalar_type=numpy.float64) + + from hedge.visualization import SiloVisualizer, VtkVisualizer + #vis = VtkVisualizer(discr, rcon, "vortex-%d" % order) + vis = SiloVisualizer(discr, rcon) + + vortex = Vortex(beta=5, gamma=gamma, + center=[5,0], + velocity=[1,0], densityA=densityA) + fields = vortex.volume_interpolant(0, discr) + sources=SourceTerms(beta=5, gamma=gamma, + center=[5,0], + velocity=[1,0], densityA=densityA) + + from hedge.models.gas_dynamics import ( + GasDynamicsOperator, GammaLawEOS) + from hedge.mesh import TAG_ALL + + op = GasDynamicsOperator(dimensions=2, + mu=0.0, prandtl=0.72, spec_gas_const=287.1, + equation_of_state=GammaLawEOS(vortex.gamma), + bc_inflow=vortex, bc_outflow=vortex, bc_noslip=vortex, + inflow_tag=TAG_ALL, source=sources) + + euler_ex = op.bind(discr) + + max_eigval = [0] + def rhs(t, q): + ode_rhs, speed = euler_ex(t, q) + max_eigval[0] = speed + return ode_rhs + rhs(0, fields) + + if rcon.is_head_rank: + print "---------------------------------------------" + print "order %d" % order + print "---------------------------------------------" + print "#elements=", len(mesh.elements) + + # limiter setup ------------------------------------------------------- + from hedge.models.gas_dynamics import SlopeLimiter1NEuler + limiter = SlopeLimiter1NEuler(discr, gamma, 2, op) + + # time stepper -------------------------------------------------------- + from hedge.timestep import SSPRK3TimeStepper, RK4TimeStepper + #stepper = SSPRK3TimeStepper(limiter=limiter) + #stepper = SSPRK3TimeStepper() + stepper = RK4TimeStepper() + + # diagnostics setup --------------------------------------------------- + from pytools.log import LogManager, add_general_quantities, \ + add_simulation_quantities, add_run_info + + if write_output: + log_file_name = "euler-%d.dat" % order + else: + log_file_name = None + + logmgr = LogManager(log_file_name, "w", rcon.communicator) + add_run_info(logmgr) + add_general_quantities(logmgr) + add_simulation_quantities(logmgr) + discr.add_instrumentation(logmgr) + stepper.add_instrumentation(logmgr) + + logmgr.add_watches(["step.max", "t_sim.max", "t_step.max"]) + + # timestep loop ------------------------------------------------------- + t = 0 + + #fields = limiter(fields) + + try: + from hedge.timestep import times_and_steps + step_it = times_and_steps( + final_time=.1, + #max_steps=500, + logmgr=logmgr, + max_dt_getter=lambda t: 0.4*op.estimate_timestep(discr, + stepper=stepper, t=t, max_eigenvalue=max_eigval[0])) + + for step, t, dt in step_it: + if step % 1 == 0 and write_output: + #if False: + visf = vis.make_file("vortex-%d-%04d" % (order, step)) + + true_fields = vortex.volume_interpolant(t, discr) + + #rhs_fields = rhs(t, fields) + + from pyvisfile.silo import DB_VARTYPE_VECTOR + vis.add_data(visf, + [ + ("rho", discr.convert_volume(op.rho(fields), kind="numpy")), + ("e", discr.convert_volume(op.e(fields), kind="numpy")), + ("rho_u", discr.convert_volume(op.rho_u(fields), kind="numpy")), + ("u", discr.convert_volume(op.u(fields), kind="numpy")), + + #("true_rho", discr.convert_volume(op.rho(true_fields), kind="numpy")), + #("true_e", discr.convert_volume(op.e(true_fields), kind="numpy")), + #("true_rho_u", discr.convert_volume(op.rho_u(true_fields), kind="numpy")), + #("true_u", discr.convert_volume(op.u(true_fields), kind="numpy")), + + #("rhs_rho", discr.convert_volume(op.rho(rhs_fields), kind="numpy")), + #("rhs_e", discr.convert_volume(op.e(rhs_fields), kind="numpy")), + #("rhs_rho_u", discr.convert_volume(op.rho_u(rhs_fields), kind="numpy")), + ], + expressions=[ + #("diff_rho", "rho-true_rho"), + #("diff_e", "e-true_e"), + #("diff_rho_u", "rho_u-true_rho_u", DB_VARTYPE_VECTOR), + + ("p", "0.4*(e- 0.5*(rho_u*u))"), + ], + time=t, step=step + ) + visf.close() + + fields = stepper(fields, t, dt, rhs) + + true_fields = vortex.volume_interpolant(t, discr) + l2_error = discr.norm(fields-true_fields) + l2_error_rho = discr.norm(op.rho(fields)-op.rho(true_fields)) + l2_error_e = discr.norm(op.e(fields)-op.e(true_fields)) + l2_error_rhou = discr.norm(op.rho_u(fields)-op.rho_u(true_fields)) + l2_error_u = discr.norm(op.u(fields)-op.u(true_fields)) + + eoc_rec.add_data_point(order, l2_error_rho) + print + print eoc_rec.pretty_print("P.Deg.", "L2 Error") + + logmgr.set_constant("l2_error", l2_error) + logmgr.set_constant("l2_error_rho", l2_error_rho) + logmgr.set_constant("l2_error_e", l2_error_e) + logmgr.set_constant("l2_error_rhou", l2_error_rhou) + logmgr.set_constant("l2_error_u", l2_error_u) + logmgr.set_constant("refinement", refine) + + finally: + if write_output: + vis.close() + + logmgr.close() + discr.close() + + # after order loop + #assert eoc_rec.estimate_order_of_convergence()[0,1] > 6 + + + + +if __name__ == "__main__": + main() + + + +# entry points for py.test ---------------------------------------------------- +from pytools.test import mark_test +@mark_test.long +def test_euler_vortex(): + main(write_output=False) diff --git a/examples/gas_dynamics/euler/vortex.py b/examples/gas_dynamics/euler/vortex.py new file mode 100644 index 00000000..ddbb4328 --- /dev/null +++ b/examples/gas_dynamics/euler/vortex.py @@ -0,0 +1,214 @@ +# Hedge - the Hybrid'n'Easy DG Environment +# Copyright (C) 2008 Andreas Kloeckner +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 this program. If not, see . + + + + +from __future__ import division +import numpy + + + + +def main(write_output=True): + from pytools import add_python_path_relative_to_script + add_python_path_relative_to_script("..") + + from hedge.backends import guess_run_context + rcon = guess_run_context() + + from hedge.tools import EOCRecorder + eoc_rec = EOCRecorder() + + if rcon.is_head_rank: + from hedge.mesh.generator import \ + make_rect_mesh, \ + make_centered_regular_rect_mesh + + refine = 4 + mesh = make_centered_regular_rect_mesh((0,-5), (10,5), n=(9,9), + post_refine_factor=refine) + mesh_data = rcon.distribute_mesh(mesh) + else: + mesh_data = rcon.receive_mesh() + + for order in [3, 4, 5]: + from gas_dynamics_initials import Vortex + flow = Vortex() + + from hedge.models.gas_dynamics import ( + GasDynamicsOperator, PolytropeEOS, GammaLawEOS) + + from hedge.mesh import TAG_ALL + # works equally well for GammaLawEOS + op = GasDynamicsOperator(dimensions=2, mu=flow.mu, + prandtl=flow.prandtl, spec_gas_const=flow.spec_gas_const, + equation_of_state=PolytropeEOS(flow.gamma), + bc_inflow=flow, bc_outflow=flow, bc_noslip=flow, + inflow_tag=TAG_ALL, source=None) + + discr = rcon.make_discretization(mesh_data, order=order, + default_scalar_type=numpy.float64, + quad_min_degrees={ + "gasdyn_vol": 3*order, + "gasdyn_face": 3*order, + }, + tune_for=op.op_template(), + debug=["cuda_no_plan"]) + + from hedge.visualization import SiloVisualizer, VtkVisualizer + vis = VtkVisualizer(discr, rcon, "vortex-%d" % order) + #vis = SiloVisualizer(discr, rcon) + + fields = flow.volume_interpolant(0, discr) + + euler_ex = op.bind(discr) + + max_eigval = [0] + def rhs(t, q): + ode_rhs, speed = euler_ex(t, q) + max_eigval[0] = speed + return ode_rhs + rhs(0, fields) + + if rcon.is_head_rank: + print "---------------------------------------------" + print "order %d" % order + print "---------------------------------------------" + print "#elements=", len(mesh.elements) + + + # limiter ------------------------------------------------------------ + from hedge.models.gas_dynamics import SlopeLimiter1NEuler + limiter = SlopeLimiter1NEuler(discr, flow.gamma, 2, op) + + from hedge.timestep.runge_kutta import SSP3TimeStepper + #stepper = SSP3TimeStepper(limiter=limiter) + stepper = SSP3TimeStepper( + vector_primitive_factory=discr.get_vector_primitive_factory()) + + #from hedge.timestep import RK4TimeStepper + #stepper = RK4TimeStepper() + + # diagnostics setup --------------------------------------------------- + from pytools.log import LogManager, add_general_quantities, \ + add_simulation_quantities, add_run_info + + if write_output: + log_file_name = "euler-%d.dat" % order + else: + log_file_name = None + + logmgr = LogManager(log_file_name, "w", rcon.communicator) + add_run_info(logmgr) + add_general_quantities(logmgr) + add_simulation_quantities(logmgr) + discr.add_instrumentation(logmgr) + stepper.add_instrumentation(logmgr) + + logmgr.add_watches(["step.max", "t_sim.max", "t_step.max"]) + + # timestep loop ------------------------------------------------------- + try: + final_time = flow.final_time + from hedge.timestep import times_and_steps + step_it = times_and_steps( + final_time=final_time, logmgr=logmgr, + max_dt_getter=lambda t: op.estimate_timestep(discr, + stepper=stepper, t=t, max_eigenvalue=max_eigval[0])) + + print "run until t=%g" % final_time + for step, t, dt in step_it: + if step % 10 == 0 and write_output: + #if False: + visf = vis.make_file("vortex-%d-%04d" % (order, step)) + + #true_fields = vortex.volume_interpolant(t, discr) + + from pyvisfile.silo import DB_VARTYPE_VECTOR + vis.add_data(visf, + [ + ("rho", discr.convert_volume(op.rho(fields), kind="numpy")), + ("e", discr.convert_volume(op.e(fields), kind="numpy")), + ("rho_u", discr.convert_volume(op.rho_u(fields), kind="numpy")), + ("u", discr.convert_volume(op.u(fields), kind="numpy")), + + #("true_rho", discr.convert_volume(op.rho(true_fields), kind="numpy")), + #("true_e", discr.convert_volume(op.e(true_fields), kind="numpy")), + #("true_rho_u", discr.convert_volume(op.rho_u(true_fields), kind="numpy")), + #("true_u", discr.convert_volume(op.u(true_fields), kind="numpy")), + + #("rhs_rho", discr.convert_volume(op.rho(rhs_fields), kind="numpy")), + #("rhs_e", discr.convert_volume(op.e(rhs_fields), kind="numpy")), + #("rhs_rho_u", discr.convert_volume(op.rho_u(rhs_fields), kind="numpy")), + ], + #expressions=[ + #("diff_rho", "rho-true_rho"), + #("diff_e", "e-true_e"), + #("diff_rho_u", "rho_u-true_rho_u", DB_VARTYPE_VECTOR), + + #("p", "0.4*(e- 0.5*(rho_u*u))"), + #], + time=t, step=step + ) + visf.close() + + fields = stepper(fields, t, dt, rhs) + #fields = limiter(fields) + + assert not numpy.isnan(numpy.sum(fields[0])) + + true_fields = flow.volume_interpolant(final_time, discr) + l2_error = discr.norm(fields-true_fields) + l2_error_rho = discr.norm(op.rho(fields)-op.rho(true_fields)) + l2_error_e = discr.norm(op.e(fields)-op.e(true_fields)) + l2_error_rhou = discr.norm(op.rho_u(fields)-op.rho_u(true_fields)) + l2_error_u = discr.norm(op.u(fields)-op.u(true_fields)) + + eoc_rec.add_data_point(order, l2_error) + print + print eoc_rec.pretty_print("P.Deg.", "L2 Error") + + logmgr.set_constant("l2_error", l2_error) + logmgr.set_constant("l2_error_rho", l2_error_rho) + logmgr.set_constant("l2_error_e", l2_error_e) + logmgr.set_constant("l2_error_rhou", l2_error_rhou) + logmgr.set_constant("l2_error_u", l2_error_u) + logmgr.set_constant("refinement", refine) + + finally: + if write_output: + vis.close() + + logmgr.close() + discr.close() + + # after order loop + assert eoc_rec.estimate_order_of_convergence()[0,1] > 6 + + + + +if __name__ == "__main__": + main() + + + +# entry points for py.test ---------------------------------------------------- +from pytools.test import mark_test +@mark_test.long +def test_euler_vortex(): + main(write_output=False) diff --git a/examples/gas_dynamics/gas_dynamics_initials.py b/examples/gas_dynamics/gas_dynamics_initials.py new file mode 100644 index 00000000..9e1bef7d --- /dev/null +++ b/examples/gas_dynamics/gas_dynamics_initials.py @@ -0,0 +1,223 @@ +# Hedge - the Hybrid'n'Easy DG Environment +# Copyright (C) 2008 Andreas Kloeckner +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 this program. If not, see . + + + + +from __future__ import division +import numpy +import numpy.linalg as la + + + + +class UniformMachFlow: + def __init__(self, mach=0.1, p=1, rho=1, reynolds=100, + gamma=1.4, prandtl=0.72, char_length=1, spec_gas_const=287.1, + angle_of_attack=None, direction=None, gaussian_pulse_at=None, + pulse_magnitude=0.1): + """ + :param direction: is a vector indicating the direction of the + flow. Only one of angle_of_attack and direction may be + specified. Only the direction, not the magnitude, of + direction is taken into account. + + :param angle_of_attack: if not None, specifies the angle of + the flow along the Y axis, where the flow is + directed along the X axis. + """ + if angle_of_attack is not None and direction is not None: + raise ValueError("Only one of angle_of_attack and " + "direction may be specified.") + + if angle_of_attack is None and direction is None: + angle_of_attack = 0 + + if direction is not None: + self.direction = direction/la.norm(direction) + else: + self.direction = None + + self.mach = mach + self.p = p + self.rho = rho + + self.gamma = gamma + self.prandtl = prandtl + self.reynolds = reynolds + self.length = char_length + self.spec_gas_const = spec_gas_const + + self.angle_of_attack = angle_of_attack + + self.gaussian_pulse_at = gaussian_pulse_at + self.pulse_magnitude = pulse_magnitude + + self.c = (self.gamma * p / rho)**0.5 + u = self.velocity = mach * self.c + self.e = p / (self.gamma - 1) + rho / 2 * u**2 + + if numpy.isinf(self.reynolds): + self.mu = 0 + else: + self.mu = u * self.length * rho / self.reynolds + + def direction_vector(self, dimensions): + # this must be done here because dimensions is not known above + if self.direction is None: + assert self.angle_of_attack is not None + direction = numpy.zeros(dimensions, dtype=numpy.float64) + direction[0] = numpy.cos( + self.angle_of_attack / 180. * numpy.pi) + direction[1] = numpy.sin( + self.angle_of_attack / 180. * numpy.pi) + return direction + else: + return self.direction + + def __call__(self, t, x_vec): + ones = numpy.ones_like(x_vec[0]) + rho_field = ones*self.rho + + if self.gaussian_pulse_at is not None: + rel_to_pulse = [x_vec[i] - self.gaussian_pulse_at[i] + for i in range(len(x_vec))] + rho_field += self.pulse_magnitude * self.rho * numpy.exp( + - sum(rtp_i**2 for rtp_i in rel_to_pulse)/2) + + direction = self.direction_vector(x_vec.shape[0]) + + from hedge.tools import make_obj_array + u_field = make_obj_array([ones*self.velocity*dir_i + for dir_i in direction]) + + from hedge.tools import join_fields + return join_fields(rho_field, self.e*ones, self.rho*u_field) + + def volume_interpolant(self, t, discr): + return discr.convert_volume( + self(t, discr.nodes.T), + kind=discr.compute_kind, + dtype=discr.default_scalar_type) + + def boundary_interpolant(self, t, discr, tag): + return discr.convert_boundary( + self(t, discr.get_boundary(tag).nodes.T), + tag=tag, kind=discr.compute_kind, + dtype=discr.default_scalar_type) + +class Vortex: + def __init__(self): + self.beta = 5 + self.gamma = 1.4 + self.center = numpy.array([5, 0]) + self.velocity = numpy.array([1, 0]) + + self.mu = 0 + self.prandtl = 0.72 + self.spec_gas_const = 287.1 + + def __call__(self, t, x_vec): + vortex_loc = self.center + t*self.velocity + + # coordinates relative to vortex center + x_rel = x_vec[0] - vortex_loc[0] + y_rel = x_vec[1] - vortex_loc[1] + + # Y.C. Zhou, G.W. Wei / Journal of Computational Physics 189 (2003) 159 + # also JSH/TW Nodal DG Methods, p. 209 + + from math import pi + r = numpy.sqrt(x_rel**2+y_rel**2) + expterm = self.beta*numpy.exp(1-r**2) + u = self.velocity[0] - expterm*y_rel/(2*pi) + v = self.velocity[1] + expterm*x_rel/(2*pi) + rho = (1-(self.gamma-1)/(16*self.gamma*pi**2)*expterm**2)**(1/(self.gamma-1)) + p = rho**self.gamma + + e = p/(self.gamma-1) + rho/2*(u**2+v**2) + + from hedge.tools import join_fields + return join_fields(rho, e, rho*u, rho*v) + + def volume_interpolant(self, t, discr): + return discr.convert_volume( + self(t, discr.nodes.T + .astype(discr.default_scalar_type)), + kind=discr.compute_kind) + + def boundary_interpolant(self, t, discr, tag): + return discr.convert_boundary( + self(t, discr.get_boundary(tag).nodes.T + .astype(discr.default_scalar_type)), + tag=tag, kind=discr.compute_kind) + + + + + + + +class Vortex: + def __init__(self): + self.beta = 5 + self.gamma = 1.4 + self.center = numpy.array([5, 0]) + self.velocity = numpy.array([1, 0]) + self.final_time = 0.5 + + self.mu = 0 + self.prandtl = 0.72 + self.spec_gas_const = 287.1 + + def __call__(self, t, x_vec): + vortex_loc = self.center + t*self.velocity + + # coordinates relative to vortex center + x_rel = x_vec[0] - vortex_loc[0] + y_rel = x_vec[1] - vortex_loc[1] + + # Y.C. Zhou, G.W. Wei / Journal of Computational Physics 189 (2003) 159 + # also JSH/TW Nodal DG Methods, p. 209 + + from math import pi + r = numpy.sqrt(x_rel**2+y_rel**2) + expterm = self.beta*numpy.exp(1-r**2) + u = self.velocity[0] - expterm*y_rel/(2*pi) + v = self.velocity[1] + expterm*x_rel/(2*pi) + rho = (1-(self.gamma-1)/(16*self.gamma*pi**2)*expterm**2)**(1/(self.gamma-1)) + p = rho**self.gamma + + e = p/(self.gamma-1) + rho/2*(u**2+v**2) + + from hedge.tools import join_fields + return join_fields(rho, e, rho*u, rho*v) + + def volume_interpolant(self, t, discr): + return discr.convert_volume( + self(t, discr.nodes.T + .astype(discr.default_scalar_type)), + kind=discr.compute_kind) + + def boundary_interpolant(self, t, discr, tag): + return discr.convert_boundary( + self(t, discr.get_boundary(tag).nodes.T + .astype(discr.default_scalar_type)), + tag=tag, kind=discr.compute_kind) + + + + diff --git a/examples/gas_dynamics/lbm-simple.py b/examples/gas_dynamics/lbm-simple.py new file mode 100644 index 00000000..4e5cf034 --- /dev/null +++ b/examples/gas_dynamics/lbm-simple.py @@ -0,0 +1,153 @@ +# Hedge - the Hybrid'n'Easy DG Environment +# Copyright (C) 2011 Andreas Kloeckner +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 this program. If not, see . + + + + +from __future__ import division +import numpy as np +import numpy.linalg as la + + + + +def main(write_output=True, dtype=np.float32): + from hedge.backends import guess_run_context + rcon = guess_run_context() + + from hedge.mesh.generator import make_rect_mesh + if rcon.is_head_rank: + h_fac = 1 + mesh = make_rect_mesh(a=(0,0),b=(1,1), max_area=h_fac**2*1e-4, + periodicity=(True,True), + subdivisions=(int(70/h_fac), int(70/h_fac))) + + from hedge.models.gas_dynamics.lbm import \ + D2Q9LBMMethod, LatticeBoltzmannOperator + + op = LatticeBoltzmannOperator( + D2Q9LBMMethod(), lbm_delta_t=0.001, nu=1e-4) + + if rcon.is_head_rank: + print "%d elements" % len(mesh.elements) + mesh_data = rcon.distribute_mesh(mesh) + else: + mesh_data = rcon.receive_mesh() + + discr = rcon.make_discretization(mesh_data, order=3, + default_scalar_type=dtype, + debug=["cuda_no_plan"]) + from hedge.timestep.runge_kutta import LSRK4TimeStepper + stepper = LSRK4TimeStepper(dtype=dtype, + #vector_primitive_factory=discr.get_vector_primitive_factory() + ) + + from hedge.visualization import VtkVisualizer + if write_output: + vis = VtkVisualizer(discr, rcon, "fld") + + from hedge.data import CompiledExpressionData + def ic_expr(t, x, fields): + from hedge.optemplate import CFunction + from pymbolic.primitives import IfPositive + from pytools.obj_array import make_obj_array + + tanh = CFunction("tanh") + sin = CFunction("sin") + + rho = 1 + u0 = 0.05 + w = 0.05 + delta = 0.05 + + from hedge.optemplate.primitives import make_common_subexpression as cse + u = cse(make_obj_array([ + IfPositive(x[1]-1/2, + u0*tanh(4*(3/4-x[1])/w), + u0*tanh(4*(x[1]-1/4)/w)), + u0*delta*sin(2*np.pi*(x[0]+1/4))]), + "u") + + return make_obj_array([ + op.method.f_equilibrium(rho, alpha, u) + for alpha in range(len(op.method)) + ]) + + + # timestep loop ----------------------------------------------------------- + stream_rhs = op.bind_rhs(discr) + collision_update = op.bind(discr, op.collision_update) + get_rho = op.bind(discr, op.rho) + get_rho_u = op.bind(discr, op.rho_u) + + + f_bar = CompiledExpressionData(ic_expr).volume_interpolant(0, discr) + + from hedge.discretization import ExponentialFilterResponseFunction + from hedge.optemplate.operators import FilterOperator + mode_filter = FilterOperator( + ExponentialFilterResponseFunction(min_amplification=0.9, order=4))\ + .bind(discr) + + final_time = 1000 + try: + lbm_dt = op.lbm_delta_t + dg_dt = op.estimate_timestep(discr, stepper=stepper) + print dg_dt + + dg_steps_per_lbm_step = int(np.ceil(lbm_dt / dg_dt)) + dg_dt = lbm_dt / dg_steps_per_lbm_step + + lbm_steps = int(final_time // op.lbm_delta_t) + for step in xrange(lbm_steps): + t = step*lbm_dt + + if step % 100 == 0 and write_output: + visf = vis.make_file("fld-%04d" % step) + + rho = get_rho(f_bar) + rho_u = get_rho_u(f_bar) + vis.add_data(visf, + [ ("fbar%d" %i, + discr.convert_volume(f_bar_i, "numpy")) for i, f_bar_i in enumerate(f_bar)]+ + [ + ("rho", discr.convert_volume(rho, "numpy")), + ("rho_u", discr.convert_volume(rho_u, "numpy")), + ], + time=t, + step=step) + visf.close() + + print "step=%d, t=%f" % (step, t) + + f_bar = collision_update(f_bar) + + for substep in range(dg_steps_per_lbm_step): + f_bar = stepper(f_bar, t + substep*dg_dt, dg_dt, stream_rhs) + + #f_bar = mode_filter(f_bar) + + finally: + if write_output: + vis.close() + + discr.close() + + + + +if __name__ == "__main__": + main(True) diff --git a/examples/gas_dynamics/naca.py b/examples/gas_dynamics/naca.py new file mode 100644 index 00000000..9439deee --- /dev/null +++ b/examples/gas_dynamics/naca.py @@ -0,0 +1,268 @@ +# Hedge - the Hybrid'n'Easy DG Environment +# Copyright (C) 2008 Andreas Kloeckner +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 this program. If not, see . + + + + +from __future__ import division +import numpy +import numpy.linalg as la + + + + +def make_nacamesh(): + def round_trip_connect(seq): + result = [] + for i in range(len(seq)): + result.append((i, (i+1)%len(seq))) + return result + + pt_back = numpy.array([1,0]) + + #def max_area(pt): + #max_area_front = 1e-2*la.norm(pt)**2 + 1e-5 + #max_area_back = 1e-2*la.norm(pt-pt_back)**2 + 1e-4 + #return min(max_area_front, max_area_back) + + def max_area(pt): + x = pt[0] + + if x < 0: + return 1e-2*la.norm(pt)**2 + 1e-5 + elif x > 1: + return 1e-2*la.norm(pt-pt_back)**2 + 1e-5 + else: + return 1e-2*pt[1]**2 + 1e-5 + + def needs_refinement(vertices, area): + barycenter = sum(numpy.array(v) for v in vertices)/3 + return bool(area > max_area(barycenter)) + + from meshpy.naca import get_naca_points + points = get_naca_points(naca_digits="2412", number_of_points=80) + + from meshpy.geometry import GeometryBuilder, Marker + from meshpy.triangle import write_gnuplot_mesh + + profile_marker = Marker.FIRST_USER_MARKER + builder = GeometryBuilder() + builder.add_geometry(points=points, + facets=round_trip_connect(points), + facet_markers=profile_marker) + builder.wrap_in_box(4, (10, 8)) + + from meshpy.triangle import MeshInfo, build + mi = MeshInfo() + builder.set(mi) + mi.set_holes([builder.center()]) + + mesh = build(mi, refinement_func=needs_refinement, + #allow_boundary_steiner=False, + generate_faces=True) + + write_gnuplot_mesh("mesh.dat", mesh) + + print "%d elements" % len(mesh.elements) + + fvi2fm = mesh.face_vertex_indices_to_face_marker + + face_marker_to_tag = { + profile_marker: "noslip", + Marker.MINUS_X: "inflow", + Marker.PLUS_X: "outflow", + Marker.MINUS_Y: "inflow", + Marker.PLUS_Y: "inflow" + #Marker.MINUS_Y: "minus_y", + #Marker.PLUS_Y: "plus_y" + } + + def bdry_tagger(fvi, el, fn, all_v): + face_marker = fvi2fm[fvi] + return [face_marker_to_tag[face_marker]] + + from hedge.mesh import make_conformal_mesh_ext + + vertices = numpy.asarray(mesh.points, order="C") + from hedge.mesh.element import Triangle + return make_conformal_mesh_ext( + vertices, + [Triangle(i, el_idx, vertices) + for i, el_idx in enumerate(mesh.elements)], + bdry_tagger, + #periodicity=[None, ("minus_y", "plus_y")] + ) + + + + +def main(): + from hedge.backends import guess_run_context + rcon = guess_run_context() + + if rcon.is_head_rank: + mesh = make_nacamesh() + mesh_data = rcon.distribute_mesh(mesh) + else: + mesh_data = rcon.receive_mesh() + + from pytools import add_python_path_relative_to_script + add_python_path_relative_to_script("..") + + for order in [4]: + from gas_dynamics_initials import UniformMachFlow + uniform_flow = UniformMachFlow() + + from hedge.models.gas_dynamics import GasDynamicsOperator, GammaLawEOS + op = GasDynamicsOperator(dimensions=2, + equation_of_state=GammaLawEOS(uniform_flow.gamma), + prandtl=uniform_flow.prandtl, + spec_gas_const=uniform_flow.spec_gas_const, mu=uniform_flow.mu, + bc_inflow=uniform_flow, bc_outflow=uniform_flow, bc_noslip=uniform_flow, + inflow_tag="inflow", outflow_tag="outflow", noslip_tag="noslip") + + discr = rcon.make_discretization(mesh_data, order=order, + debug=[ + "cuda_no_plan", + #"cuda_dump_kernels", + #"dump_optemplate_stages", + #"dump_dataflow_graph", + #"print_op_code" + ], + default_scalar_type=numpy.float32, + tune_for=op.op_template()) + + from hedge.visualization import SiloVisualizer, VtkVisualizer + #vis = VtkVisualizer(discr, rcon, "shearflow-%d" % order) + vis = SiloVisualizer(discr, rcon) + + fields = uniform_flow.volume_interpolant(0, discr) + + navierstokes_ex = op.bind(discr) + + max_eigval = [0] + def rhs(t, q): + ode_rhs, speed = navierstokes_ex(t, q) + max_eigval[0] = speed + return ode_rhs + rhs(0, fields) + + if rcon.is_head_rank: + print "---------------------------------------------" + print "order %d" % order + print "---------------------------------------------" + print "#elements=", len(mesh.elements) + + from hedge.timestep.runge_kutta import \ + ODE23TimeStepper, LSRK4TimeStepper + stepper = ODE23TimeStepper(dtype=discr.default_scalar_type, + rtol=1e-6, + vector_primitive_factory=discr.get_vector_primitive_factory()) + #stepper = LSRK4TimeStepper(dtype=discr.default_scalar_type) + + # diagnostics setup --------------------------------------------------- + from pytools.log import LogManager, add_general_quantities, \ + add_simulation_quantities, add_run_info + + logmgr = LogManager("cns-naca-%d.dat" % order, "w", rcon.communicator) + + add_run_info(logmgr) + add_general_quantities(logmgr) + add_simulation_quantities(logmgr) + discr.add_instrumentation(logmgr) + stepper.add_instrumentation(logmgr) + + from pytools.log import LogQuantity + class ChangeSinceLastStep(LogQuantity): + """Records the change of a variable between a time step and the previous + one""" + + def __init__(self, name="change"): + LogQuantity.__init__(self, name, "1", "Change since last time step") + + self.old_fields = 0 + + def __call__(self): + result = discr.norm(fields - self.old_fields) + self.old_fields = fields + return result + + #logmgr.add_quantity(ChangeSinceLastStep()) + + # filter setup------------------------------------------------------------- + from hedge.discretization import Filter, ExponentialFilterResponseFunction + mode_filter = Filter(discr, + ExponentialFilterResponseFunction(min_amplification=0.9,order=4)) + # timestep loop ------------------------------------------------------- + + logmgr.add_watches(["step.max", "t_sim.max", "t_step.max"]) + + try: + from hedge.timestep import times_and_steps + step_it = times_and_steps( + final_time=200, + #max_steps=500, + logmgr=logmgr, + max_dt_getter=lambda t: next_dt, + taken_dt_getter=lambda: taken_dt) + + model_stepper = LSRK4TimeStepper() + next_dt = op.estimate_timestep(discr, + stepper=model_stepper, t=0, + max_eigenvalue=max_eigval[0]) + + for step, t, dt in step_it: + if step % 10 == 0: + visf = vis.make_file("naca-%d-%06d" % (order, step)) + + from pyvisfile.silo import DB_VARTYPE_VECTOR + vis.add_data(visf, + [ + ("rho", discr.convert_volume(op.rho(fields), kind="numpy")), + ("e", discr.convert_volume(op.e(fields), kind="numpy")), + ("rho_u", discr.convert_volume(op.rho_u(fields), kind="numpy")), + ("u", discr.convert_volume(op.u(fields), kind="numpy")), + + #("true_rho", op.rho(true_fields)), + #("true_e", op.e(true_fields)), + #("true_rho_u", op.rho_u(true_fields)), + #("true_u", op.u(true_fields)), + + #("rhs_rho", discr.convert_volume(op.rho(rhs_fields), kind="numpy")), + #("rhs_e", discr.convert_volume(op.e(rhs_fields), kind="numpy")), + #("rhs_rho_u", discr.convert_volume(op.rho_u(rhs_fields), kind="numpy")), + ], + expressions=[ + #("diff_rho", "rho-true_rho"), + #("diff_e", "e-true_e"), + #("diff_rho_u", "rho_u-true_rho_u", DB_VARTYPE_VECTOR), + + ("p", "(0.4)*(e- 0.5*(rho_u*u))"), + ], + time=t, step=step + ) + visf.close() + + fields, t, taken_dt, next_dt = stepper(fields, t, dt, rhs) + fields = mode_filter(fields) + + finally: + vis.close() + logmgr.save() + discr.close() + +if __name__ == "__main__": + main() diff --git a/examples/gas_dynamics/navierstokes/shearflow.py b/examples/gas_dynamics/navierstokes/shearflow.py new file mode 100644 index 00000000..597c2b0b --- /dev/null +++ b/examples/gas_dynamics/navierstokes/shearflow.py @@ -0,0 +1,198 @@ +# Hedge - the Hybrid'n'Easy DG Environment +# Copyright (C) 2008 Andreas Kloeckner +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 this program. If not, see . + + + + +from __future__ import division +import numpy +import numpy.linalg as la + + + + +class SteadyShearFlow: + def __init__(self): + self.gamma = 1.5 + self.mu = 0.01 + self.prandtl = 0.72 + self.spec_gas_const = 287.1 + + def __call__(self, t, x_vec): + # JSH/TW Nodal DG Methods, p.326 + + rho = numpy.ones_like(x_vec[0]) + rho_u = x_vec[1] * x_vec[1] + rho_v = numpy.zeros_like(x_vec[0]) + e = (2 * self.mu * x_vec[0] + 10) / (self.gamma - 1) + x_vec[1]**4 / 2 + + from hedge.tools import join_fields + return join_fields(rho, e, rho_u, rho_v) + + def properties(self): + return(self.gamma, self.mu, self.prandtl, self.spec_gas_const) + + def volume_interpolant(self, t, discr): + return discr.convert_volume( + self(t, discr.nodes.T + .astype(discr.default_scalar_type)), + kind=discr.compute_kind) + + def boundary_interpolant(self, t, discr, tag): + result = discr.convert_boundary( + self(t, discr.get_boundary(tag).nodes.T + .astype(discr.default_scalar_type)), + tag=tag, kind=discr.compute_kind) + return result + + + + +def main(): + from hedge.backends import guess_run_context + rcon = guess_run_context( + #["cuda"] + ) + + from hedge.tools import EOCRecorder, to_obj_array + eoc_rec = EOCRecorder() + + def boundary_tagger(vertices, el, face_nr, all_v): + return ["inflow"] + + if rcon.is_head_rank: + from hedge.mesh import make_rect_mesh, \ + make_centered_regular_rect_mesh + #mesh = make_rect_mesh((0,0), (10,1), max_area=0.01) + refine = 1 + mesh = make_centered_regular_rect_mesh((0,0), (10,1), n=(20,4), + #periodicity=(True, False), + post_refine_factor=refine, + boundary_tagger=boundary_tagger) + mesh_data = rcon.distribute_mesh(mesh) + else: + mesh_data = rcon.receive_mesh() + + for order in [3]: + discr = rcon.make_discretization(mesh_data, order=order, + default_scalar_type=numpy.float64) + + from hedge.visualization import SiloVisualizer, VtkVisualizer + #vis = VtkVisualizer(discr, rcon, "shearflow-%d" % order) + vis = SiloVisualizer(discr, rcon) + + shearflow = SteadyShearFlow() + fields = shearflow.volume_interpolant(0, discr) + gamma, mu, prandtl, spec_gas_const = shearflow.properties() + + from hedge.models.gas_dynamics import GasDynamicsOperator + op = GasDynamicsOperator(dimensions=2, gamma=gamma, mu=mu, + prandtl=prandtl, spec_gas_const=spec_gas_const, + bc_inflow=shearflow, bc_outflow=shearflow, bc_noslip=shearflow, + inflow_tag="inflow", outflow_tag="outflow", noslip_tag="noslip") + + navierstokes_ex = op.bind(discr) + + max_eigval = [0] + def rhs(t, q): + ode_rhs, speed = navierstokes_ex(t, q) + max_eigval[0] = speed + return ode_rhs + + # needed to get first estimate of maximum eigenvalue + rhs(0, fields) + + if rcon.is_head_rank: + print "---------------------------------------------" + print "order %d" % order + print "---------------------------------------------" + print "#elements=", len(mesh.elements) + + from hedge.timestep import RK4TimeStepper + stepper = RK4TimeStepper() + + # diagnostics setup --------------------------------------------------- + from pytools.log import LogManager, add_general_quantities, \ + add_simulation_quantities, add_run_info + + logmgr = LogManager("navierstokes-cpu-%d-%d.dat" % (order, refine), + "w", rcon.communicator) + add_run_info(logmgr) + add_general_quantities(logmgr) + add_simulation_quantities(logmgr) + discr.add_instrumentation(logmgr) + stepper.add_instrumentation(logmgr) + + logmgr.add_watches(["step.max", "t_sim.max", "t_step.max"]) + + # timestep loop ------------------------------------------------------- + try: + from hedge.timestep import times_and_steps + step_it = times_and_steps( + final_time=0.3, + #max_steps=500, + logmgr=logmgr, + max_dt_getter=lambda t: op.estimate_timestep(discr, + stepper=stepper, t=t, max_eigenvalue=max_eigval[0])) + + for step, t, dt in step_it: + if step % 10 == 0: + #if False: + visf = vis.make_file("shearflow-%d-%04d" % (order, step)) + + #true_fields = shearflow.volume_interpolant(t, discr) + + from pyvisfile.silo import DB_VARTYPE_VECTOR + vis.add_data(visf, + [ + ("rho", discr.convert_volume(op.rho(fields), kind="numpy")), + ("e", discr.convert_volume(op.e(fields), kind="numpy")), + ("rho_u", discr.convert_volume(op.rho_u(fields), kind="numpy")), + ("u", discr.convert_volume(op.u(fields), kind="numpy")), + + #("true_rho", discr.convert_volume(op.rho(true_fields), kind="numpy")), + #("true_e", discr.convert_volume(op.e(true_fields), kind="numpy")), + #("true_rho_u", discr.convert_volume(op.rho_u(true_fields), kind="numpy")), + #("true_u", discr.convert_volume(op.u(true_fields), kind="numpy")), + ], + expressions=[ + #("diff_rho", "rho-true_rho"), + #("diff_e", "e-true_e"), + #("diff_rho_u", "rho_u-true_rho_u", DB_VARTYPE_VECTOR), + + ("p", "0.4*(e- 0.5*(rho_u*u))"), + ], + time=t, step=step + ) + visf.close() + + fields = stepper(fields, t, dt, rhs) + + true_fields = shearflow.volume_interpolant(t, discr) + l2_error = discr.norm(op.u(fields)-op.u(true_fields)) + eoc_rec.add_data_point(order, l2_error) + print + print eoc_rec.pretty_print("P.Deg.", "L2 Error") + + logmgr.set_constant("l2_error", l2_error) + + finally: + vis.close() + logmgr.save() + discr.close() + +if __name__ == "__main__": + main() diff --git a/examples/gas_dynamics/square.py b/examples/gas_dynamics/square.py new file mode 100644 index 00000000..f1e63e62 --- /dev/null +++ b/examples/gas_dynamics/square.py @@ -0,0 +1,281 @@ +# Hedge - the Hybrid'n'Easy DG Environment +# Copyright (C) 2008 Andreas Kloeckner +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 this program. If not, see . + + + + +from __future__ import division +import numpy +import numpy.linalg as la + + + + +def make_squaremesh(): + def round_trip_connect(seq): + result = [] + for i in range(len(seq)): + result.append((i, (i+1)%len(seq))) + return result + + def needs_refinement(vertices, area): + x = sum(numpy.array(v) for v in vertices)/3 + + max_area_volume = 0.7e-2 + 0.03*(0.05*x[1]**2 + 0.3*min(x[0]+1,0)**2) + + max_area_corners = 1e-3 + 0.001*max( + la.norm(x-corner)**4 for corner in obstacle_corners) + + return bool(area > 2.5*min(max_area_volume, max_area_corners)) + + from meshpy.geometry import make_box + points, facets, _, _ = make_box((-0.5,-0.5), (0.5,0.5)) + obstacle_corners = points[:] + + from meshpy.geometry import GeometryBuilder, Marker + + profile_marker = Marker.FIRST_USER_MARKER + builder = GeometryBuilder() + builder.add_geometry(points=points, facets=facets, + facet_markers=profile_marker) + + points, facets, _, facet_markers = make_box((-16, -22), (25, 22)) + builder.add_geometry(points=points, facets=facets, + facet_markers=facet_markers) + + from meshpy.triangle import MeshInfo, build + mi = MeshInfo() + builder.set(mi) + mi.set_holes([(0,0)]) + + mesh = build(mi, refinement_func=needs_refinement, + allow_boundary_steiner=True, + generate_faces=True) + + print "%d elements" % len(mesh.elements) + + from meshpy.triangle import write_gnuplot_mesh + write_gnuplot_mesh("mesh.dat", mesh) + + fvi2fm = mesh.face_vertex_indices_to_face_marker + + face_marker_to_tag = { + profile_marker: "noslip", + Marker.MINUS_X: "inflow", + Marker.PLUS_X: "outflow", + Marker.MINUS_Y: "inflow", + Marker.PLUS_Y: "inflow" + } + + def bdry_tagger(fvi, el, fn, all_v): + face_marker = fvi2fm[fvi] + return [face_marker_to_tag[face_marker]] + + from hedge.mesh import make_conformal_mesh_ext + vertices = numpy.asarray(mesh.points, dtype=float, order="C") + from hedge.mesh.element import Triangle + return make_conformal_mesh_ext( + vertices, + [Triangle(i, el_idx, vertices) + for i, el_idx in enumerate(mesh.elements)], + bdry_tagger) + + + + +def main(): + import logging + logging.basicConfig(level=logging.INFO) + + from hedge.backends import guess_run_context + rcon = guess_run_context() + + if rcon.is_head_rank: + if True: + mesh = make_squaremesh() + else: + from hedge.mesh import make_rect_mesh + mesh = make_rect_mesh( + boundary_tagger=lambda fvi, el, fn, all_v: ["inflow"], + max_area=0.1) + + mesh_data = rcon.distribute_mesh(mesh) + else: + mesh_data = rcon.receive_mesh() + + from pytools import add_python_path_relative_to_script + add_python_path_relative_to_script(".") + + for order in [3]: + from gas_dynamics_initials import UniformMachFlow + square = UniformMachFlow(gaussian_pulse_at=numpy.array([-2, 2]), + pulse_magnitude=0.003) + + from hedge.models.gas_dynamics import ( + GasDynamicsOperator, + GammaLawEOS) + + op = GasDynamicsOperator(dimensions=2, + equation_of_state=GammaLawEOS(square.gamma), mu=square.mu, + prandtl=square.prandtl, spec_gas_const=square.spec_gas_const, + bc_inflow=square, bc_outflow=square, bc_noslip=square, + inflow_tag="inflow", outflow_tag="outflow", noslip_tag="noslip") + + discr = rcon.make_discretization(mesh_data, order=order, + debug=["cuda_no_plan", + "cuda_dump_kernels", + #"dump_dataflow_graph", + #"dump_optemplate_stages", + #"dump_dataflow_graph", + #"dump_op_code" + #"cuda_no_plan_el_local" + ], + default_scalar_type=numpy.float64, + tune_for=op.op_template(), + quad_min_degrees={ + "gasdyn_vol": 3*order, + "gasdyn_face": 3*order, + } + ) + + from hedge.visualization import SiloVisualizer, VtkVisualizer + #vis = VtkVisualizer(discr, rcon, "shearflow-%d" % order) + vis = SiloVisualizer(discr, rcon) + + from hedge.timestep.runge_kutta import ( + LSRK4TimeStepper, ODE23TimeStepper, ODE45TimeStepper) + from hedge.timestep.dumka3 import Dumka3TimeStepper + #stepper = LSRK4TimeStepper(dtype=discr.default_scalar_type, + #vector_primitive_factory=discr.get_vector_primitive_factory()) + + stepper = ODE23TimeStepper(dtype=discr.default_scalar_type, + rtol=1e-6, + vector_primitive_factory=discr.get_vector_primitive_factory()) + # Dumka works kind of poorly + #stepper = Dumka3TimeStepper(dtype=discr.default_scalar_type, + #rtol=1e-7, pol_index=2, + #vector_primitive_factory=discr.get_vector_primitive_factory()) + + #from hedge.timestep.dumka3 import Dumka3TimeStepper + #stepper = Dumka3TimeStepper(3, rtol=1e-7) + + # diagnostics setup --------------------------------------------------- + from pytools.log import LogManager, add_general_quantities, \ + add_simulation_quantities, add_run_info + + logmgr = LogManager("cns-square-sp-%d.dat" % order, "w", rcon.communicator) + + add_run_info(logmgr) + add_general_quantities(logmgr) + discr.add_instrumentation(logmgr) + stepper.add_instrumentation(logmgr) + + from pytools.log import LogQuantity + class ChangeSinceLastStep(LogQuantity): + """Records the change of a variable between a time step and the previous + one""" + + def __init__(self, name="change"): + LogQuantity.__init__(self, name, "1", "Change since last time step") + + self.old_fields = 0 + + def __call__(self): + result = discr.norm(fields - self.old_fields) + self.old_fields = fields + return result + + #logmgr.add_quantity(ChangeSinceLastStep()) + + add_simulation_quantities(logmgr) + logmgr.add_watches(["step.max", "t_sim.max", "t_step.max"]) + + # filter setup ------------------------------------------------------------ + from hedge.discretization import Filter, ExponentialFilterResponseFunction + mode_filter = Filter(discr, + ExponentialFilterResponseFunction(min_amplification=0.95, order=6)) + + # timestep loop ------------------------------------------------------- + fields = square.volume_interpolant(0, discr) + + navierstokes_ex = op.bind(discr) + + max_eigval = [0] + def rhs(t, q): + ode_rhs, speed = navierstokes_ex(t, q) + max_eigval[0] = speed + return ode_rhs + rhs(0, fields) + + if rcon.is_head_rank: + print "---------------------------------------------" + print "order %d" % order + print "---------------------------------------------" + print "#elements=", len(mesh.elements) + + try: + from hedge.timestep import times_and_steps + step_it = times_and_steps( + final_time=1000, + #max_steps=500, + logmgr=logmgr, + max_dt_getter=lambda t: next_dt, + taken_dt_getter=lambda: taken_dt) + + model_stepper = LSRK4TimeStepper() + next_dt = op.estimate_timestep(discr, + stepper=model_stepper, t=0, + max_eigenvalue=max_eigval[0]) + + for step, t, dt in step_it: + #if (step % 10000 == 0): #and step < 950000) or (step % 500 == 0 and step > 950000): + #if False: + if step % 5 == 0: + visf = vis.make_file("square-%d-%06d" % (order, step)) + + #from pyvisfile.silo import DB_VARTYPE_VECTOR + vis.add_data(visf, + [ + ("rho", discr.convert_volume(op.rho(fields), kind="numpy")), + ("e", discr.convert_volume(op.e(fields), kind="numpy")), + ("rho_u", discr.convert_volume(op.rho_u(fields), kind="numpy")), + ("u", discr.convert_volume(op.u(fields), kind="numpy")), + ], + expressions=[ + ("p", "(0.4)*(e- 0.5*(rho_u*u))"), + ], + time=t, step=step + ) + visf.close() + + if stepper.adaptive: + fields, t, taken_dt, next_dt = stepper(fields, t, dt, rhs) + else: + taken_dt = dt + fields = stepper(fields, t, dt, rhs) + dt = op.estimate_timestep(discr, + stepper=model_stepper, t=0, + max_eigenvalue=max_eigval[0]) + + #fields = mode_filter(fields) + + finally: + vis.close() + logmgr.save() + discr.close() + +if __name__ == "__main__": + main() diff --git a/examples/gas_dynamics/wing.py b/examples/gas_dynamics/wing.py new file mode 100644 index 00000000..de824324 --- /dev/null +++ b/examples/gas_dynamics/wing.py @@ -0,0 +1,232 @@ +# Hedge - the Hybrid'n'Easy DG Environment +# Copyright (C) 2008 Andreas Kloeckner +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 this program. If not, see . + + + + +from __future__ import division +import numpy +import numpy.linalg as la + + + + +def make_wingmesh(): + import numpy + from math import pi, cos, sin + from meshpy.tet import MeshInfo, build + from meshpy.geometry import GeometryBuilder, Marker, \ + generate_extrusion, make_box + + geob = GeometryBuilder() + + profile_marker = Marker.FIRST_USER_MARKER + + wing_length = 2 + wing_subdiv = 5 + + rz_points = [ + (0, -wing_length*1.05), + (0.7, -wing_length*1.05), + ] + [ + (r, x) for x, r in zip( + numpy.linspace(-wing_length, 0, wing_subdiv, endpoint=False), + numpy.linspace(0.8, 1, wing_subdiv, endpoint=False)) + ] + [(1,0)] + [ + (r, x) for x, r in zip( + numpy.linspace(wing_length, 0, wing_subdiv, endpoint=False), + numpy.linspace(0.8, 1, wing_subdiv, endpoint=False)) + ][::-1] + [ + (0.7, wing_length*1.05), + (0, wing_length*1.05) + ] + + from meshpy.naca import get_naca_points + geob.add_geometry(*generate_extrusion( + rz_points=rz_points, + base_shape=get_naca_points("0012", number_of_points=20), + ring_markers=(wing_subdiv*2+4)*[profile_marker])) + + def deform_wing(p): + x, y, z = p + return numpy.array([ + x + 0.8*abs(z/wing_length)** 1.2, + y + 0.1*abs(z/wing_length)**2, + z]) + + geob.apply_transform(deform_wing) + + points, facets, facet_markers = make_box( + numpy.array([-1.5,-1,-wing_length-1], dtype=numpy.float64), + numpy.array([3,1,wing_length+1], dtype=numpy.float64)) + + geob.add_geometry(points, facets, facet_markers=facet_markers) + + mesh_info = MeshInfo() + geob.set(mesh_info) + mesh_info.set_holes([(0.5,0,0)]) + + mesh = build(mesh_info) + print "%d elements" % len(mesh.elements) + + fvi2fm = mesh.face_vertex_indices_to_face_marker + + face_marker_to_tag = { + profile_marker: "noslip", + Marker.MINUS_X: "inflow", + Marker.PLUS_X: "outflow", + Marker.MINUS_Y: "inflow", + Marker.PLUS_Y: "inflow", + Marker.PLUS_Z: "inflow", + Marker.MINUS_Z: "inflow" + } + + def bdry_tagger(fvi, el, fn, all_v): + face_marker = fvi2fm[fvi] + return [face_marker_to_tag[face_marker]] + + from hedge.mesh import make_conformal_mesh + return make_conformal_mesh(mesh.points, mesh.elements, bdry_tagger) + + + + +def main(): + from hedge.backends import guess_run_context + rcon = guess_run_context( ["cuda", "mpi"]) + + if rcon.is_head_rank: + mesh = make_wingmesh() + #from hedge.mesh import make_rect_mesh + #mesh = make_rect_mesh( + # boundary_tagger=lambda fvi, el, fn, all_v: ["inflow"]) + mesh_data = rcon.distribute_mesh(mesh) + else: + mesh_data = rcon.receive_mesh() + + for order in [3]: + from pytools import add_python_path_relative_to_script + add_python_path_relative_to_script("..") + + from gas_dynamics_initials import UniformMachFlow + wing = UniformMachFlow(angle_of_attack=0) + + from hedge.models.gas_dynamics import GasDynamicsOperator + op = GasDynamicsOperator(dimensions=3, + gamma=wing.gamma, mu=wing.mu, + prandtl=wing.prandtl, spec_gas_const=wing.spec_gas_const, + bc_inflow=wing, bc_outflow=wing, bc_noslip=wing, + inflow_tag="inflow", outflow_tag="outflow", noslip_tag="noslip") + + discr = rcon.make_discretization(mesh_data, order=order, + debug=["cuda_no_plan", + #"cuda_dump_kernels", + #"dump_dataflow_graph", + #"dump_optemplate_stages", + #"dump_dataflow_graph", + #"print_op_code" + "cuda_no_metis", + ], + default_scalar_type=numpy.float64, + tune_for=op.op_template()) + + from hedge.visualization import SiloVisualizer, VtkVisualizer + #vis = VtkVisualizer(discr, rcon, "shearflow-%d" % order) + vis = SiloVisualizer(discr, rcon) + + fields = wing.volume_interpolant(0, discr) + + navierstokes_ex = op.bind(discr) + + max_eigval = [0] + def rhs(t, q): + ode_rhs, speed = navierstokes_ex(t, q) + max_eigval[0] = speed + return ode_rhs + rhs(0, fields) + + if rcon.is_head_rank: + print "---------------------------------------------" + print "order %d" % order + print "---------------------------------------------" + print "#elements=", len(mesh.elements) + + from hedge.timestep import RK4TimeStepper + stepper = RK4TimeStepper() + + # diagnostics setup --------------------------------------------------- + from pytools.log import LogManager, add_general_quantities, \ + add_simulation_quantities, add_run_info + + logmgr = LogManager("navierstokes-%d.dat" % order, "w", rcon.communicator) + add_run_info(logmgr) + add_general_quantities(logmgr) + add_simulation_quantities(logmgr) + discr.add_instrumentation(logmgr) + stepper.add_instrumentation(logmgr) + + logmgr.add_watches(["step.max", "t_sim.max", "t_step.max"]) + + # timestep loop ------------------------------------------------------- + try: + from hedge.timestep import times_and_steps + step_it = times_and_steps( + final_time=200, + #max_steps=500, + logmgr=logmgr, + max_dt_getter=lambda t: 0.6 * op.estimate_timestep(discr, + stepper=stepper, t=t, max_eigenvalue=max_eigval[0])) + + for step, t, dt in step_it: + if step % 200 == 0: + #if False: + visf = vis.make_file("wing-%d-%06d" % (order, step)) + + #rhs_fields = rhs(t, fields) + + from pyvisfile.silo import DB_VARTYPE_VECTOR + from hedge.discretization import ones_on_boundary + vis.add_data(visf, + [ + ("rho", discr.convert_volume(op.rho(fields), kind="numpy")), + ("e", discr.convert_volume(op.e(fields), kind="numpy")), + ("rho_u", discr.convert_volume(op.rho_u(fields), kind="numpy")), + ("u", discr.convert_volume(op.u(fields), kind="numpy")), + + #("rhs_rho", discr.convert_volume(op.rho(rhs_fields), kind="numpy")), + #("rhs_e", discr.convert_volume(op.e(rhs_fields), kind="numpy")), + #("rhs_rho_u", discr.convert_volume(op.rho_u(rhs_fields), kind="numpy")), + ], + expressions=[ + ("p", "(0.4)*(e- 0.5*(rho_u*u))"), + ], + time=t, step=step + ) + visf.close() + + fields = stepper(fields, t, dt, rhs) + t += dt + + finally: + vis.close() + logmgr.save() + discr.close() + + + + +if __name__ == "__main__": + main() diff --git a/examples/heat/heat.py b/examples/heat/heat.py new file mode 100644 index 00000000..6cd193f7 --- /dev/null +++ b/examples/heat/heat.py @@ -0,0 +1,174 @@ +# Hedge - the Hybrid'n'Easy DG Environment +# Copyright (C) 2007 Andreas Kloeckner +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 this program. If not, see . + + + + +import numpy +import numpy.linalg as la + + + + +def main(write_output=True) : + from math import sin, cos, pi, exp, sqrt + from hedge.data import TimeConstantGivenFunction, \ + ConstantGivenFunction + + from hedge.backends import guess_run_context + rcon = guess_run_context() + + dim = 2 + + def boundary_tagger(fvi, el, fn, all_v): + if el.face_normals[fn][0] > 0: + return ["dirichlet"] + else: + return ["neumann"] + + if dim == 2: + if rcon.is_head_rank: + from hedge.mesh.generator import make_disk_mesh + mesh = make_disk_mesh(r=0.5, boundary_tagger=boundary_tagger) + elif dim == 3: + if rcon.is_head_rank: + from hedge.mesh.generator import make_ball_mesh + mesh = make_ball_mesh(max_volume=0.001) + else: + raise RuntimeError, "bad number of dimensions" + + if rcon.is_head_rank: + print "%d elements" % len(mesh.elements) + mesh_data = rcon.distribute_mesh(mesh) + else: + mesh_data = rcon.receive_mesh() + + discr = rcon.make_discretization(mesh_data, order=3, + debug=["cuda_no_plan"], + default_scalar_type=numpy.float64) + + if write_output: + from hedge.visualization import VtkVisualizer + vis = VtkVisualizer(discr, rcon, "fld") + + def u0(x, el): + if la.norm(x) < 0.2: + return 1 + else: + return 0 + + def coeff(x, el): + if x[0] < 0: + return 0.25 + else: + return 1 + + def dirichlet_bc(t, x): + return 0 + + def neumann_bc(t, x): + return 2 + + from hedge.models.diffusion import DiffusionOperator + op = DiffusionOperator(discr.dimensions, + #coeff=coeff, + dirichlet_tag="dirichlet", + dirichlet_bc=TimeConstantGivenFunction(ConstantGivenFunction(0)), + neumann_tag="neumann", + neumann_bc=TimeConstantGivenFunction(ConstantGivenFunction(1)) + ) + u = discr.interpolate_volume_function(u0) + + # diagnostics setup ------------------------------------------------------- + from pytools.log import LogManager, \ + add_general_quantities, \ + add_simulation_quantities, \ + add_run_info + + if write_output: + log_file_name = "heat.dat" + else: + log_file_name = None + + logmgr = LogManager(log_file_name, "w", rcon.communicator) + add_run_info(logmgr) + add_general_quantities(logmgr) + add_simulation_quantities(logmgr) + discr.add_instrumentation(logmgr) + + from hedge.log import LpNorm + u_getter = lambda: u + logmgr.add_quantity(LpNorm(u_getter, discr, 1, name="l1_u")) + logmgr.add_quantity(LpNorm(u_getter, discr, name="l2_u")) + + logmgr.add_watches(["step.max", "t_sim.max", "l2_u", "t_step.max"]) + + # timestep loop ----------------------------------------------------------- + from hedge.timestep.runge_kutta import LSRK4TimeStepper, ODE45TimeStepper + from hedge.timestep.dumka3 import Dumka3TimeStepper + #stepper = LSRK4TimeStepper() + stepper = Dumka3TimeStepper(3, rtol=1e-6, rcon=rcon, + vector_primitive_factory=discr.get_vector_primitive_factory(), + dtype=discr.default_scalar_type) + #stepper = ODE45TimeStepper(rtol=1e-6, rcon=rcon, + #vector_primitive_factory=discr.get_vector_primitive_factory(), + #dtype=discr.default_scalar_type) + stepper.add_instrumentation(logmgr) + + rhs = op.bind(discr) + try: + next_dt = op.estimate_timestep(discr, + stepper=LSRK4TimeStepper(), t=0, fields=u) + + from hedge.timestep import times_and_steps + step_it = times_and_steps( + final_time=0.1, logmgr=logmgr, + max_dt_getter=lambda t: next_dt, + taken_dt_getter=lambda: taken_dt) + + for step, t, dt in step_it: + if step % 10 == 0 and write_output: + visf = vis.make_file("fld-%04d" % step) + vis.add_data(visf, [ + ("u", discr.convert_volume(u, kind="numpy")), + ], time=t, step=step) + visf.close() + + u, t, taken_dt, next_dt = stepper(u, t, next_dt, rhs) + #u = stepper(u, t, dt, rhs) + + assert discr.norm(u) < 1 + finally: + if write_output: + vis.close() + + logmgr.close() + discr.close() + + + + +if __name__ == "__main__": + main() + + + + +# entry points for py.test ---------------------------------------------------- +from pytools.test import mark_test +@mark_test.long +def test_heat(): + main(write_output=False) diff --git a/examples/maxwell/.gitignore b/examples/maxwell/.gitignore new file mode 100644 index 00000000..64116385 --- /dev/null +++ b/examples/maxwell/.gitignore @@ -0,0 +1,2 @@ +bessel_zeros.py +2d_cavity diff --git a/examples/maxwell/analytic_solutions.py b/examples/maxwell/analytic_solutions.py new file mode 100644 index 00000000..64505c94 --- /dev/null +++ b/examples/maxwell/analytic_solutions.py @@ -0,0 +1,457 @@ +# Hedge - the Hybrid'n'Easy DG Environment +# Copyright (C) 2007 Andreas Kloeckner +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 this program. If not, see . + + + + +from __future__ import division +from hedge.tools import \ + cyl_bessel_j, \ + cyl_bessel_j_prime +from math import sqrt, pi, sin, cos, atan2, acos +import numpy +import numpy.linalg as la +import cmath + + + + +# solution adapters ----------------------------------------------------------- +class RealPartAdapter: + def __init__(self, adaptee): + self.adaptee = adaptee + + @property + def shape(self): + return self.adaptee.shape + + def __call__(self, x, el): + return [xi.real for xi in self.adaptee(x, el)] + +class SplitComplexAdapter: + def __init__(self, adaptee): + self.adaptee = adaptee + + @property + def shape(self): + (n,) = self.adaptee.shape + return (n*2,) + + def __call__(self, x, el): + ad_x = self.adaptee(x, el) + return [xi.real for xi in ad_x] + [xi.imag for xi in ad_x] + + + + +class CylindricalFieldAdapter: + """Turns a field returned as (r, phi, z) components into + (x,y,z) components. + """ + def __init__(self, adaptee): + self.adaptee = adaptee + + @property + def shape(self): + return self.adaptee.shape + + def __call__(self, x, el): + xy = x[:2] + r = la.norm(xy) + phi = atan2(x[1], x[0]) + + prev_result = self.adaptee(x, el) + result = [] + i = 0 + while i < len(prev_result): + fr, fphi, fz = prev_result[i:i+3] + result.extend([ + cos(phi)*fr - sin(phi)*fphi, # ex + sin(phi)*fr + cos(phi)*fphi, # ey + fz, + ]) + i += 3 + + return result + + + + +class SphericalFieldAdapter: + """Turns the adaptee's field C{(Er, Etheta, Ephi)} components into + C{(Ex,Ey,Ez)} components. + + Conventions are those of + http://mathworld.wolfram.com/SphericalCoordinates.html + """ + def __init__(self, adaptee): + self.adaptee = adaptee + + @property + def shape(self): + return self.adaptee.shape + + @staticmethod + def r_theta_phi_from_xyz(x): + r = la.norm(x) + return r, atan2(x[1], x[0]), acos(x[2]/r) + + def __call__(self, x, el): + r, theta, phi = self.r_theta_phi_from_xyz(x) + + er = numpy.array([ + cos(theta)*sin(phi), + sin(theta)*sin(phi), + cos(phi)]) + etheta = numpy.array([ + -sin(theta), + cos(theta), + 0]) + ephi = numpy.array([ + cos(theta)*cos(phi), + sin(theta)*cos(phi), + -sin(phi)]) + + adaptee_result = self.adaptee(x, el) + result = numpy.empty_like(adaptee_result) + i = 0 + while i < len(adaptee_result): + fr, ftheta, fphi = adaptee_result[i:i+3] + result[i:i+3] = fr*er + ftheta*etheta + fphi*ephi + i += 3 + + return result + + + + +# actual solutions ------------------------------------------------------------ +class CylindricalCavityMode: + """A cylindrical TM cavity mode. + + Taken from: + J.D. Jackson, Classical Electrodynamics, Wiley. + 3rd edition, 2001. + ch 8.7, p. 368f. + """ + + def __init__(self, m, n, p, radius, height, epsilon, mu): + try: + from bessel_zeros import bessel_zeros + except ImportError: + print "*** You need to generate the bessel root data file." + print "*** Execute generate-bessel-zeros.py at the command line." + raise + + assert m >= 0 and m == int(m) + assert n >= 1 and n == int(n) + assert p >= 0 and p == int(p) + self.m = m + self.n = n + self.p = p + self.phi_sign = 1 + + R = self.radius = radius + d = self.height = height + + self.epsilon = epsilon + self.mu = mu + + self.t = 0 + + x_mn = bessel_zeros[m][n-1] + + self.omega = 1 / sqrt(mu*epsilon) * sqrt( + x_mn**2 / R**2 + + p**2 * pi**2 / d**2) + + self.gamma_mn = x_mn/R + + def set_time(self, t): + self.t = t + + shape = (6,) + + def __call__(self, x, el): + # coordinates ----------------------------------------------------- + xy = x[:2] + r = sqrt(xy*xy) + phi = atan2(x[1], x[0]) + z = x[2] + + # copy instance variables for easier access ----------------------- + m = self.m + p = self.p + gamma = self.gamma_mn + phi_sign = self.phi_sign + omega = self.omega + d = self.height + epsilon = self.epsilon + + # common subexpressions ------------------------------------------- + tdep = cmath.exp(-1j * omega * self.t) + phi_factor = cmath.exp(phi_sign * 1j * m * phi) + + # psi and derivatives --------------------------------------------- + psi = cyl_bessel_j(m, gamma * r) * phi_factor + psi_dr = gamma*cyl_bessel_j_prime(m, gamma*r) * phi_factor + psi_dphi = (cyl_bessel_j(m, gamma * r) + * 1/r * phi_sign*1j*m * phi_factor) + + # field components in polar coordinates --------------------------- + ez = tdep * cos(p * pi * z / d) * psi + + e_transverse_factor = (tdep + * (-p*pi/(d*gamma**2)) + * sin(p * pi * z / d)) + + er = e_transverse_factor * psi_dr + ephi = e_transverse_factor * psi_dphi + + hz = 0j + + # z x grad psi = z x (psi_x, psi_y) = (-psi_y, psi_x) + # z x grad psi = z x (psi_r, psi_phi) = (-psi_phi, psi_r) + h_transverse_factor = (tdep + * 1j*epsilon*omega/gamma**2 + * cos(p * pi * z / d)) + + hr = h_transverse_factor * (-psi_dphi) + hphi = h_transverse_factor * psi_dr + + return [er, ephi, ez, hr, hphi, hz] + + + + +class RectangularWaveguideMode: + """A rectangular TM cavity mode.""" + + def __init__(self, epsilon, mu, mode_indices, + dimensions=(1,1,1), coefficients=(1,0,0), + forward_coeff=1, backward_coeff=0): + for n in mode_indices: + assert n >= 0 and n == int(n) + self.mode_indices = mode_indices + self.dimensions = dimensions + self.coefficients = coefficients + self.forward_coeff = forward_coeff + self.backward_coeff = backward_coeff + + self.epsilon = epsilon + self.mu = mu + + self.t = 0 + + self.factors = [n*pi/a for n, a in zip(self.mode_indices, self.dimensions)] + + c = 1/sqrt(mu*epsilon) + self.k = sqrt(sum(f**2 for f in self.factors)) + self.omega = self.k*c + + def set_time(self, t): + self.t = t + + def __call__(self, discr): + f,g,h = self.factors + omega = self.omega + k = self.k + + x = discr.nodes[:,0] + y = discr.nodes[:,1] + if discr.dimensions > 2: + z = discr.nodes[:,2] + else: + z = discr.volume_zeros() + + sx = numpy.sin(f*x) + sy = numpy.sin(g*y) + cx = numpy.cos(f*x) + cy = numpy.cos(g*y) + + zdep_add = numpy.exp(1j*h*z)+numpy.exp(-1j*h*z) + zdep_sub = numpy.exp(1j*h*z)-numpy.exp(-1j*h*z) + + tdep = numpy.exp(-1j * omega * self.t) + + C = 1j/(f**2+g**2) + + result = numpy.zeros((6,len(x)), dtype=zdep_add.dtype) + + result[0] = C*f*h*cx*sy*zdep_sub*tdep # ex + result[1] = C*g*h*sx*cy*zdep_sub*tdep # ey + result[2] = sx*sy*zdep_add*tdep # ez + result[3] = -C*g*self.epsilon*omega*sx*cy*zdep_add*tdep # hx + result[4] = C*f*self.epsilon*omega*cx*sy*zdep_add*tdep + + return result + + + + +class RectangularCavityMode(RectangularWaveguideMode): + """A rectangular TM cavity mode.""" + + def __init__(self, *args, **kwargs): + if "scale" in kwargs: + kwargs["forward_coeff"] = scale + kwargs["backward_coeff"] = scale + else: + kwargs["forward_coeff"] = 1 + kwargs["backward_coeff"] = 1 + RectangularWaveguideMode.__init__(self, *args, **kwargs) + + + + + +# dipole solution ------------------------------------------------------------- +class DipoleFarField: + """Dipole EM field. + + See U{http://piki.tiker.net/wiki/Dipole}. + """ + def __init__(self, q, d, omega, epsilon, mu): + self.q = q + self.d = d + + self.epsilon = epsilon + self.mu = mu + self.c = c = 1/sqrt(mu*epsilon) + + self.omega = omega + + freq = omega/(2*pi) + self.wavelength = c/freq + + def far_field_mask(self, x, el): + if la.norm(x) > 0.5*self.wavelength: + return 1 + else: + return 0 + + def set_time(self, t): + self.t = t + + shape = (6,) + + def __call__(self, x, el): + # coordinates --------------------------------------------------------- + r, theta, phi = SphericalFieldAdapter.r_theta_phi_from_xyz(x) + + # copy instance variables for easier access --------------------------- + q = self.q + d = self.d + + epsilon = self.epsilon + mu = self.mu + c = self.c + + omega = self.omega + t = self.t + + # field components ---------------------------------------------------- + tdep = omega*t-omega*r/c + e_r = (2*cos(phi)*q*d) / (4*pi*epsilon) * ( + 1/r**3 * sin(tdep) + +omega/(c*r**2) * cos(tdep)) + e_phi = (sin(phi)*q*d) / (4*pi*epsilon) * ( + (1/r**3 - omega**2/(c**2*r)*sin(tdep)) + + omega/(c*r**2)*cos(tdep)) + h_theta = (omega*sin(phi)*q*d)/(4*pi) * ( + - omega/(c*r)*sin(tdep) + + 1/r**2*cos(tdep)) + + return [e_r, 0, e_phi, 0, h_theta, 0] + + def source_modulation(self, t): + j0 = self.q*self.d*self.omega/self.epsilon + return j0*cos(self.omega*t) + + + + +# analytic solution tools ----------------------------------------------------- +def check_time_harmonic_solution(discr, mode, c_sol): + from hedge.discretization import bind_nabla, bind_mass_matrix + from hedge.visualization import SiloVisualizer + from hedge.silo import SiloFile + from hedge.tools import dot, cross + from hedge.silo import DB_VARTYPE_VECTOR + + def curl(field): + return cross(nabla, field) + + vis = SiloVisualizer(discr) + + nabla = bind_nabla(discr) + mass = bind_mass_matrix(discr) + + def rel_l2_error(err, base): + def l2_norm(field): + return sqrt(dot(field, mass*field)) + + base_l2 = l2_norm(base) + err_l2 = l2_norm(err) + if base_l2 == 0: + if err_l2 == 0: + return 0. + else: + return float("inf") + else: + return err_l2/base_l2 + + dt = 0.1 + + for step in range(10): + t = step*dt + mode.set_time(t) + fields = discr.interpolate_volume_function(c_sol) + + er = fields[0:3] + hr = fields[3:6] + ei = fields[6:9] + hi = fields[9:12] + + silo = SiloFile("em-complex-%04d.silo" % step) + vis.add_to_silo(silo, + vectors=[ + ("curl_er", curl(er)), + ("om_hi", -mode.mu*mode.omega*hi), + ("curl_hr", curl(hr)), + ("om_ei", mode.epsilon*mode.omega*hi), + ], + expressions=[ + ("diff_er", "curl_er-om_hi", DB_VARTYPE_VECTOR), + ("diff_hr", "curl_hr-om_ei", DB_VARTYPE_VECTOR), + ], + write_coarse_mesh=True, + time=t, step=step + ) + + er_res = curl(er) + mode.mu *mode.omega*hi + ei_res = curl(ei) - mode.mu *mode.omega*hr + hr_res = curl(hr) - mode.epsilon*mode.omega*ei + hi_res = curl(hi) + mode.epsilon*mode.omega*er + + print "time=%f, rel l2 residual in Re[E]=%g\tIm[E]=%g\tRe[H]=%g\tIm[H]=%g" % ( + t, + rel_l2_error(er_res, er), + rel_l2_error(ei_res, ei), + rel_l2_error(hr_res, hr), + rel_l2_error(hi_res, hi), + ) + diff --git a/examples/maxwell/cavities.py b/examples/maxwell/cavities.py new file mode 100644 index 00000000..b1e2dfea --- /dev/null +++ b/examples/maxwell/cavities.py @@ -0,0 +1,259 @@ +# Hedge - the Hybrid'n'Easy DG Environment +# Copyright (C) 2007 Andreas Kloeckner +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 this program. If not, see . + + +from __future__ import division +import numpy as np + +import logging +logger = logging.getLogger(__name__) + + +def main(write_output=True, allow_features=None, flux_type_arg=1, + bdry_flux_type_arg=None, extra_discr_args={}): + from hedge.mesh.generator import make_cylinder_mesh, make_box_mesh + from hedge.tools import EOCRecorder, to_obj_array + from math import sqrt, pi # noqa + from analytic_solutions import ( # noqa + RealPartAdapter, + SplitComplexAdapter, + CylindricalFieldAdapter, + CylindricalCavityMode, + RectangularWaveguideMode, + RectangularCavityMode) + from hedge.models.em import MaxwellOperator + + logging.basicConfig(level=logging.DEBUG) + + from hedge.backends import guess_run_context + rcon = guess_run_context(allow_features) + + epsilon0 = 8.8541878176e-12 # C**2 / (N m**2) + mu0 = 4*pi*1e-7 # N/A**2. + epsilon = 1*epsilon0 + mu = 1*mu0 + + eoc_rec = EOCRecorder() + + cylindrical = False + periodic = False + + if cylindrical: + R = 1 + d = 2 + mode = CylindricalCavityMode(m=1, n=1, p=1, + radius=R, height=d, + epsilon=epsilon, mu=mu) + # r_sol = CylindricalFieldAdapter(RealPartAdapter(mode)) + # c_sol = SplitComplexAdapter(CylindricalFieldAdapter(mode)) + + if rcon.is_head_rank: + mesh = make_cylinder_mesh(radius=R, height=d, max_volume=0.01) + else: + if periodic: + mode = RectangularWaveguideMode(epsilon, mu, (3, 2, 1)) + periodicity = (False, False, True) + else: + periodicity = None + mode = RectangularCavityMode(epsilon, mu, (1, 2, 2)) + + if rcon.is_head_rank: + mesh = make_box_mesh(max_volume=0.001, periodicity=periodicity) + + if rcon.is_head_rank: + mesh_data = rcon.distribute_mesh(mesh) + else: + mesh_data = rcon.receive_mesh() + + for order in [4, 5, 6]: + #for order in [1,2,3,4,5,6]: + extra_discr_args.setdefault("debug", []).extend([ + "cuda_no_plan", "cuda_dump_kernels"]) + + op = MaxwellOperator(epsilon, mu, + flux_type=flux_type_arg, + bdry_flux_type=bdry_flux_type_arg) + + discr = rcon.make_discretization(mesh_data, order=order, + tune_for=op.op_template(), + **extra_discr_args) + + from hedge.visualization import VtkVisualizer + if write_output: + vis = VtkVisualizer(discr, rcon, "em-%d" % order) + + mode.set_time(0) + + def get_true_field(): + return discr.convert_volume( + to_obj_array(mode(discr) + .real.astype(discr.default_scalar_type).copy()), + kind=discr.compute_kind) + + fields = get_true_field() + + if rcon.is_head_rank: + print "---------------------------------------------" + print "order %d" % order + print "---------------------------------------------" + print "#elements=", len(mesh.elements) + + from hedge.timestep.runge_kutta import LSRK4TimeStepper + stepper = LSRK4TimeStepper(dtype=discr.default_scalar_type, rcon=rcon) + #from hedge.timestep.dumka3 import Dumka3TimeStepper + #stepper = Dumka3TimeStepper(3, dtype=discr.default_scalar_type, rcon=rcon) + + # {{{ diagnostics setup + + from pytools.log import LogManager, add_general_quantities, \ + add_simulation_quantities, add_run_info + + if write_output: + log_file_name = "maxwell-%d.dat" % order + else: + log_file_name = None + + logmgr = LogManager(log_file_name, "w", rcon.communicator) + + add_run_info(logmgr) + add_general_quantities(logmgr) + add_simulation_quantities(logmgr) + discr.add_instrumentation(logmgr) + stepper.add_instrumentation(logmgr) + + from pytools.log import IntervalTimer + vis_timer = IntervalTimer("t_vis", "Time spent visualizing") + logmgr.add_quantity(vis_timer) + + from hedge.log import EMFieldGetter, add_em_quantities + field_getter = EMFieldGetter(discr, op, lambda: fields) + add_em_quantities(logmgr, op, field_getter) + + logmgr.add_watches( + ["step.max", "t_sim.max", + ("W_field", "W_el+W_mag"), + "t_step.max"] + ) + + # }}} + + # {{{ timestep loop + + rhs = op.bind(discr) + final_time = 0.5e-9 + + try: + from hedge.timestep import times_and_steps + step_it = times_and_steps( + final_time=final_time, logmgr=logmgr, + max_dt_getter=lambda t: op.estimate_timestep(discr, + stepper=stepper, t=t, fields=fields)) + + for step, t, dt in step_it: + if step % 50 == 0 and write_output: + sub_timer = vis_timer.start_sub_timer() + e, h = op.split_eh(fields) + visf = vis.make_file("em-%d-%04d" % (order, step)) + vis.add_data( + visf, + [ + ("e", + discr.convert_volume(e, kind="numpy")), + ("h", + discr.convert_volume(h, kind="numpy")), + ], + time=t, step=step) + visf.close() + sub_timer.stop().submit() + + fields = stepper(fields, t, dt, rhs) + + mode.set_time(final_time) + + eoc_rec.add_data_point(order, + discr.norm(fields-get_true_field())) + + finally: + if write_output: + vis.close() + + logmgr.close() + discr.close() + + if rcon.is_head_rank: + print + print eoc_rec.pretty_print("P.Deg.", "L2 Error") + + # }}} + + assert eoc_rec.estimate_order_of_convergence()[0, 1] > 6 + + +# {{{ entry points for py.test + +from pytools.test import mark_test + + +@mark_test.long +def test_maxwell_cavities(): + main(write_output=False) + + +@mark_test.long +def test_maxwell_cavities_lf(): + main(write_output=False, flux_type_arg="lf", bdry_flux_type_arg=1) + + +@mark_test.mpi +@mark_test.long +def test_maxwell_cavities_mpi(): + from pytools.mpi import run_with_mpi_ranks + run_with_mpi_ranks(__file__, 2, main, + write_output=False, allow_features=["mpi"]) + + +def test_cuda(): + try: + from pycuda.tools import mark_cuda_test + except ImportError: + pass + + yield "SP CUDA Maxwell", mark_cuda_test( + do_test_maxwell_cavities_cuda), np.float32 + yield "DP CUDA Maxwell", mark_cuda_test( + do_test_maxwell_cavities_cuda), np.float64 + + +def do_test_maxwell_cavities_cuda(dtype): + import pytest # noqa + + main(write_output=False, allow_features=["cuda"], + extra_discr_args=dict( + debug=["cuda_no_plan"], + default_scalar_type=dtype, + )) + +# }}} + + +# entry point ----------------------------------------------------------------- + +if __name__ == "__main__": + from pytools.mpi import in_mpi_relaunch + if in_mpi_relaunch(): + test_maxwell_cavities_mpi() + else: + do_test_maxwell_cavities_cuda(np.float32) diff --git a/examples/maxwell/dipole.py b/examples/maxwell/dipole.py new file mode 100644 index 00000000..261434c1 --- /dev/null +++ b/examples/maxwell/dipole.py @@ -0,0 +1,257 @@ +# Hedge - the Hybrid'n'Easy DG Environment +# Copyright (C) 2007 Andreas Kloeckner +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 this program. If not, see . + +# FIXME: This example doesn't quite do what it should any more. + + + + +from __future__ import division +import numpy +import numpy.linalg as la + + + + +def main(write_output=True, allow_features=None): + from hedge.timestep import RK4TimeStepper + from hedge.mesh import make_ball_mesh, make_cylinder_mesh, make_box_mesh + from hedge.visualization import \ + VtkVisualizer, \ + SiloVisualizer, \ + get_rank_partition + from math import sqrt, pi + + from hedge.backends import guess_run_context + rcon = guess_run_context(allow_features) + + epsilon0 = 8.8541878176e-12 # C**2 / (N m**2) + mu0 = 4*pi*1e-7 # N/A**2. + epsilon = 1*epsilon0 + mu = 1*mu0 + + dims = 3 + + if rcon.is_head_rank: + if dims == 2: + from hedge.mesh import make_rect_mesh + mesh = make_rect_mesh( + a=(-10.5,-1.5), + b=(10.5,1.5), + max_area=0.1 + ) + elif dims == 3: + from hedge.mesh import make_box_mesh + mesh = make_box_mesh( + a=(-10.5,-1.5,-1.5), + b=(10.5,1.5,1.5), + max_volume=0.1) + + if rcon.is_head_rank: + mesh_data = rcon.distribute_mesh(mesh) + else: + mesh_data = rcon.receive_mesh() + + #for order in [1,2,3,4,5,6]: + discr = rcon.make_discretization(mesh_data, order=3) + + if write_output: + vis = VtkVisualizer(discr, rcon, "dipole") + + from analytic_solutions import DipoleFarField, SphericalFieldAdapter + from hedge.data import ITimeDependentGivenFunction + + sph_dipole = DipoleFarField( + q=1, #C + d=1/39, + omega=2*pi*1e8, + epsilon=epsilon0, + mu=mu0, + ) + cart_dipole = SphericalFieldAdapter(sph_dipole) + + class PointDipoleSource(ITimeDependentGivenFunction): + def __init__(self): + from pyrticle.tools import CInfinityShapeFunction + sf = CInfinityShapeFunction( + 0.1*sph_dipole.wavelength, + discr.dimensions) + self.num_sf = discr.interpolate_volume_function( + lambda x, el: sf(x)) + self.vol_0 = discr.volume_zeros() + + def volume_interpolant(self, t, discr): + from hedge.tools import make_obj_array + return make_obj_array([ + self.vol_0, + self.vol_0, + sph_dipole.source_modulation(t)*self.num_sf + ]) + + from hedge.mesh import TAG_ALL, TAG_NONE + if dims == 2: + from hedge.models.em import TMMaxwellOperator as MaxwellOperator + else: + from hedge.models.em import MaxwellOperator + + op = MaxwellOperator( + epsilon, mu, + flux_type=1, + pec_tag=TAG_NONE, + absorb_tag=TAG_ALL, + current=PointDipoleSource(), + ) + + fields = op.assemble_eh(discr=discr) + + if rcon.is_head_rank: + print "#elements=", len(mesh.elements) + + stepper = RK4TimeStepper() + + # diagnostics setup --------------------------------------------------- + from pytools.log import LogManager, add_general_quantities, \ + add_simulation_quantities, add_run_info + + if write_output: + log_file_name = "dipole.dat" + else: + log_file_name = None + + logmgr = LogManager(log_file_name, "w", rcon.communicator) + add_run_info(logmgr) + add_general_quantities(logmgr) + add_simulation_quantities(logmgr) + discr.add_instrumentation(logmgr) + stepper.add_instrumentation(logmgr) + + from pytools.log import IntervalTimer + vis_timer = IntervalTimer("t_vis", "Time spent visualizing") + logmgr.add_quantity(vis_timer) + + from hedge.log import EMFieldGetter, add_em_quantities + field_getter = EMFieldGetter(discr, op, lambda: fields) + add_em_quantities(logmgr, op, field_getter) + + from pytools.log import PushLogQuantity + relerr_e_q = PushLogQuantity("relerr_e", "1", "Relative error in masked E-field") + relerr_h_q = PushLogQuantity("relerr_h", "1", "Relative error in masked H-field") + logmgr.add_quantity(relerr_e_q) + logmgr.add_quantity(relerr_h_q) + + logmgr.add_watches(["step.max", "t_sim.max", + ("W_field", "W_el+W_mag"), "t_step.max", + "relerr_e", "relerr_h"]) + + if write_output: + point_timeseries = [ + (open("b-x%d-vs-time.dat" % i, "w"), + open("b-x%d-vs-time-true.dat" % i, "w"), + discr.get_point_evaluator(numpy.array([i,0,0][:dims], + dtype=discr.default_scalar_type))) + for i in range(1,5) + ] + + # timestep loop ------------------------------------------------------- + mask = discr.interpolate_volume_function(sph_dipole.far_field_mask) + + def apply_mask(field): + from hedge.tools import log_shape + ls = log_shape(field) + result = discr.volume_empty(ls) + from pytools import indices_in_shape + for i in indices_in_shape(ls): + result[i] = mask * field[i] + + return result + + rhs = op.bind(discr) + + t = 0 + try: + from hedge.timestep import times_and_steps + step_it = times_and_steps( + final_time=1e-8, logmgr=logmgr, + max_dt_getter=lambda t: op.estimate_timestep(discr, + stepper=stepper, t=t, fields=fields)) + + for step, t, dt in step_it: + if write_output and step % 10 == 0: + sub_timer = vis_timer.start_sub_timer() + e, h = op.split_eh(fields) + sph_dipole.set_time(t) + true_e, true_h = op.split_eh( + discr.interpolate_volume_function(cart_dipole)) + visf = vis.make_file("dipole-%04d" % step) + + mask_e = apply_mask(e) + mask_h = apply_mask(h) + mask_true_e = apply_mask(true_e) + mask_true_h = apply_mask(true_h) + + from pyvisfile.silo import DB_VARTYPE_VECTOR + vis.add_data(visf, + [ + ("e", e), + ("h", h), + ("true_e", true_e), + ("true_h", true_h), + ("mask_e", mask_e), + ("mask_h", mask_h), + ("mask_true_e", mask_true_e), + ("mask_true_h", mask_true_h)], + time=t, step=step) + visf.close() + sub_timer.stop().submit() + + from hedge.tools import relative_error + relerr_e_q.push_value( + relative_error( + discr.norm(mask_e-mask_true_e), + discr.norm(mask_true_e))) + relerr_h_q.push_value( + relative_error( + discr.norm(mask_h-mask_true_h), + discr.norm(mask_true_h))) + + if write_output: + for outf_num, outf_true, evaluator in point_timeseries: + for outf, ev_h in zip([outf_num, outf_true], + [h, true_h]): + outf.write("%g\t%g\n" % (t, op.mu*evaluator(ev_h[1]))) + outf.flush() + + fields = stepper(fields, t, dt, rhs) + + finally: + if write_output: + vis.close() + + logmgr.save() + discr.close() + + + +if __name__ == "__main__": + main() + + + + +from pytools.test import mark_test +@mark_test.long +def test_run(): + main(write_output=False) diff --git a/examples/maxwell/generate-bessel-zeros.py b/examples/maxwell/generate-bessel-zeros.py new file mode 100644 index 00000000..9c3b11b6 --- /dev/null +++ b/examples/maxwell/generate-bessel-zeros.py @@ -0,0 +1,14 @@ +def main(): + import scipy.special + + maxnu = 10 + n_zeros = 20 + + zeros = [] + for n in range(0,maxnu+1): + zeros.append(list(scipy.special.jn_zeros(n, n_zeros))) + + outf = open("bessel_zeros.py", "w").write("bessel_zeros = %s" % zeros) + +if __name__ == "__main__": + main() diff --git a/examples/maxwell/inhom-cavity.py b/examples/maxwell/inhom-cavity.py new file mode 100644 index 00000000..70f4a8e4 --- /dev/null +++ b/examples/maxwell/inhom-cavity.py @@ -0,0 +1,265 @@ +# Hedge - the Hybrid'n'Easy DG Environment +# Copyright (C) 2007 Andreas Kloeckner +# Adapted 2010 by David Powell +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 this program. If not, see . + +"""This example is for maxwell's equations in a 2D rectangular cavity +with inhomogeneous dielectric filling""" + +from __future__ import division +import numpy + +CAVITY_GEOMETRY = """ +// a rectangular cavity with a dielectric in one region + +lc = 10e-3; +height = 50e-3; +air_width = 100e-3; +dielectric_width = 50e-3; + +Point(1) = {0, 0, 0, lc}; +Point(2) = {air_width, 0, 0, lc}; +Point(3) = {air_width+dielectric_width, 0, 0, lc}; +Point(4) = {air_width+dielectric_width, height, 0, lc}; +Point(5) = {air_width, height, 0, lc}; +Point(6) = {0, height, 0, lc}; + +Line(1) = {1, 2}; +Line(2) = {2, 3}; +Line(3) = {3, 4}; +Line(4) = {4, 5}; +Line(5) = {5, 6}; +Line(6) = {6, 1}; +Line(7) = {2, 5}; + +Line Loop(1) = {1, 7, 5, 6}; +Line Loop(2) = {2, 3, 4, -7}; + +Plane Surface(1) = {1}; +Plane Surface(2) = {2}; + +Physical Surface("vacuum") = {1}; +Physical Surface("dielectric") = {2}; +""" + + +def main(write_output=True, allow_features=None, flux_type_arg=1, + bdry_flux_type_arg=None, extra_discr_args={}): + from math import sqrt, pi + from hedge.models.em import TEMaxwellOperator + + from hedge.backends import guess_run_context + rcon = guess_run_context(allow_features) + + epsilon0 = 8.8541878176e-12 # C**2 / (N m**2) + mu0 = 4*pi*1e-7 # N/A**2. + c = 1/sqrt(mu0*epsilon0) + + materials = {"vacuum" : (epsilon0, mu0), + "dielectric" : (2*epsilon0, mu0)} + + output_dir = "2d_cavity" + + import os + if not os.access(output_dir, os.F_OK): + os.makedirs(output_dir) + + # should no tag raise an error or default to free space? + def eps_val(x, el): + for key in materials.keys(): + if el in material_elements[key]: + return materials[key][0] + raise ValueError, "Element does not belong to any material" + + def mu_val(x, el): + for key in materials.keys(): + if el in material_elements[key]: + return materials[key][1] + raise ValueError, "Element does not belong to any material" + + # geometry of cavity + d = 100e-3 + a = 150e-3 + + # analytical frequency and transverse wavenumbers of resonance + f0 = 9.0335649907522321e8 + h = 2*pi*f0/c + l = -h*sqrt(2) + + # substitute the following and change materials for a homogeneous cavity + #h = pi/a + #l =-h + + def initial_val(discr): + # the initial solution for the TE_10-like mode + def initial_Hz(x, el): + from math import cos, sin + if el in material_elements["vacuum"]: + return h*cos(h*x[0]) + else: + return -l*sin(h*d)/sin(l*(a-d))*cos(l*(a-x[0])) + + from hedge.tools import make_obj_array + result_zero = discr.volume_zeros(kind="numpy", dtype=numpy.float64) + H_z = make_tdep_given(initial_Hz).volume_interpolant(0, discr) + return make_obj_array([result_zero, result_zero, H_z]) + + if rcon.is_head_rank: + from hedge.mesh.reader.gmsh import generate_gmsh + mesh = generate_gmsh(CAVITY_GEOMETRY, 2, force_dimension=2) + mesh_data = rcon.distribute_mesh(mesh) + else: + mesh_data = rcon.receive_mesh() + + # Work out which elements belong to each material + material_elements = {} + for key in materials.keys(): + material_elements[key] = set(mesh_data.tag_to_elements[key]) + + order = 3 + #extra_discr_args.setdefault("debug", []).append("cuda_no_plan") + #extra_discr_args.setdefault("debug", []).append("dump_optemplate_stages") + + from hedge.data import make_tdep_given + from hedge.mesh import TAG_ALL + + op = TEMaxwellOperator(epsilon=make_tdep_given(eps_val), mu=make_tdep_given(mu_val), \ + flux_type=flux_type_arg, \ + bdry_flux_type=bdry_flux_type_arg, dimensions=2, pec_tag=TAG_ALL) + # op = TEMaxwellOperator(epsilon=epsilon0, mu=mu0, + # flux_type=flux_type_arg, \ + # bdry_flux_type=bdry_flux_type_arg, dimensions=2, pec_tag=TAG_ALL) + + discr = rcon.make_discretization(mesh_data, order=order, + tune_for=op.op_template(), + **extra_discr_args) + + # create the initial solution + fields = initial_val(discr) + + from hedge.visualization import VtkVisualizer + if write_output: + from os.path import join + vis = VtkVisualizer(discr, rcon, join(output_dir, "cav-%d" % order)) + + # monitor the solution at a point to find the resonant frequency + try: + point_getter = discr.get_point_evaluator(numpy.array([75e-3, 25e-3, 0])) #[0.25, 0.25, 0.25])) + except RuntimeError: + point_getter = None + + if rcon.is_head_rank: + print "---------------------------------------------" + print "order %d" % order + print "---------------------------------------------" + print "#elements=", len(mesh.elements) + + from hedge.timestep.runge_kutta import LSRK4TimeStepper + stepper = LSRK4TimeStepper(dtype=discr.default_scalar_type, rcon=rcon) + #from hedge.timestep.dumka3 import Dumka3TimeStepper + #stepper = Dumka3TimeStepper(3, dtype=discr.default_scalar_type, rcon=rcon) + + # diagnostics setup --------------------------------------------------- + from pytools.log import LogManager, add_general_quantities, \ + add_simulation_quantities, add_run_info + + if write_output: + from os.path import join + log_file_name = join(output_dir, "cavity-%d.dat" % order) + else: + log_file_name = None + + logmgr = LogManager(log_file_name, "w", rcon.communicator) + + add_run_info(logmgr) + add_general_quantities(logmgr) + add_simulation_quantities(logmgr) + discr.add_instrumentation(logmgr) + stepper.add_instrumentation(logmgr) + + from pytools.log import IntervalTimer + vis_timer = IntervalTimer("t_vis", "Time spent visualizing") + logmgr.add_quantity(vis_timer) + + #from hedge.log import EMFieldGetter, add_em_quantities + #field_getter = EMFieldGetter(discr, op, lambda: fields) + #add_em_quantities(logmgr, op, field_getter) + + logmgr.add_watches( + ["step.max", "t_sim.max", + #("W_field", "W_el+W_mag"), + "t_step.max"] + ) + + # timestep loop ------------------------------------------------------- + rhs = op.bind(discr) + final_time = 10e-9 + + if point_getter is not None: + from os.path import join + pointfile = open(join(output_dir, "point.txt"), "wt") + done_dt = False + try: + from hedge.timestep import times_and_steps + from os.path import join + step_it = times_and_steps( + final_time=final_time, logmgr=logmgr, + max_dt_getter=lambda t: op.estimate_timestep(discr, + stepper=stepper, t=t, fields=fields)) + + for step, t, dt in step_it: + if step % 10 == 0 and write_output: + sub_timer = vis_timer.start_sub_timer() + e, h = op.split_eh(fields) + visf = vis.make_file(join(output_dir, "cav-%d-%04d") % (order, step)) + vis.add_data(visf, + [ + ("e", + discr.convert_volume(e, kind="numpy")), + ("h", + discr.convert_volume(h, kind="numpy")),], + time=t, step=step + ) + visf.close() + sub_timer.stop().submit() + + fields = stepper(fields, t, dt, rhs) + if point_getter is not None: + val = point_getter(fields) + #print val + if not done_dt: + pointfile.write("#%g\n" % dt) + done_dt = True + pointfile.write("%g\n" %val[0]) + + finally: + if write_output: + vis.close() + + logmgr.close() + discr.close() + + if point_getter is not None: + pointfile.close() + + + + + + + +# entry point ----------------------------------------------------------------- +if __name__ == "__main__": + main(write_output=True) diff --git a/examples/maxwell/inhomogeneous_waveguide.mac b/examples/maxwell/inhomogeneous_waveguide.mac new file mode 100644 index 00000000..28a582f4 --- /dev/null +++ b/examples/maxwell/inhomogeneous_waveguide.mac @@ -0,0 +1,39 @@ + +/* +From Robert E. Collin, Field Theory of Guided Waves, p. 413, chaper 6 + +Find the cut-off frequency of a rectangular waveguide partially filled with a dielectric slab, +in order to find the resonant frequency of an inhomogeneous 2D cavity. + +Take (5a), the transcendental equation for h and l, and substitute for their definitions in terms of gamma +Then solve for the condition that gamma is 0, for the mode with m=0. +t - width of dielectric section +d - width of air section +kappa - relative permittivity +k_0 - free space wavenumber +gamma - waveguide wavenumber +l - transverse wavenumber in dielectric +h - transverse wavenumber in air +*/ + +trans_eq : h*tan(l*t) + l*tan(h*d); +l_gamma : sqrt(gamma^2 - (m*pi/b)^2 + kappa*k_0^2); +h_gamma : sqrt(gamma^2 - (m*pi/b)^2 + k_0^2); +l_simp : l_gamma, gamma=0, m=0; +h_simp : h_gamma, gamma=0, m=0; + +subst(h_gamma, h, trans_eq)$ +subst(l_gamma, l, %)$ +subst(0, m, %)$ +trans_eq2 : subst(0, gamma, %); + +c : 2.99792458e8$ +plot2d([trans_eq2], [f,0.1e9,1.4e9], [y, -1000, 1000]), t = 50e-3, d=100e-3, kappa=2, k_0 = 2*%pi*f/c$ +f_sol : find_root(trans_eq2, f, 0.8e9, 1e9), t = 50e-3, d = 100e-3, kappa = 2, k_0 = 2*%pi*f/c; +h_simp: float(2*%pi*f_sol/c); +sqrt(kappa)*2*%pi*f_sol/c, kappa=2$ +l_simp: float(%); + +%pi*a/(a-d-sqrt(kappa)), a=150e-3, d=100e-3, kappa=2; +float(%); + diff --git a/examples/maxwell/maxwell-2d.py b/examples/maxwell/maxwell-2d.py new file mode 100644 index 00000000..33681ebc --- /dev/null +++ b/examples/maxwell/maxwell-2d.py @@ -0,0 +1,155 @@ +# Hedge - the Hybrid'n'Easy DG Environment +# Copyright (C) 2007 Andreas Kloeckner +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 this program. If not, see . + +"Maxwell's equation example with fixed material coefficients" + + +from __future__ import division +import numpy.linalg as la + + +def main(write_output=True): + from math import sqrt, pi, exp + from os.path import join + + from hedge.backends import guess_run_context + rcon = guess_run_context() + + epsilon0 = 8.8541878176e-12 # C**2 / (N m**2) + mu0 = 4*pi*1e-7 # N/A**2. + epsilon = 1*epsilon0 + mu = 1*mu0 + + output_dir = "maxwell-2d" + import os + if not os.access(output_dir, os.F_OK): + os.makedirs(output_dir) + + from hedge.mesh.generator import make_disk_mesh + mesh = make_disk_mesh(r=0.5, max_area=1e-3) + + if rcon.is_head_rank: + mesh_data = rcon.distribute_mesh(mesh) + else: + mesh_data = rcon.receive_mesh() + + class CurrentSource: + shape = (3,) + + def __call__(self, x, el): + return [0,0,exp(-80*la.norm(x))] + + order = 3 + final_time = 1e-8 + discr = rcon.make_discretization(mesh_data, order=order, + debug=["cuda_no_plan"]) + + from hedge.visualization import VtkVisualizer + if write_output: + vis = VtkVisualizer(discr, rcon, join(output_dir, "em-%d" % order)) + + if rcon.is_head_rank: + print "order %d" % order + print "#elements=", len(mesh.elements) + + from hedge.mesh import TAG_ALL, TAG_NONE + from hedge.models.em import TMMaxwellOperator + from hedge.data import make_tdep_given, TimeIntervalGivenFunction + op = TMMaxwellOperator(epsilon, mu, flux_type=1, + current=TimeIntervalGivenFunction( + make_tdep_given(CurrentSource()), off_time=final_time/10), + absorb_tag=TAG_ALL, pec_tag=TAG_NONE) + fields = op.assemble_eh(discr=discr) + + from hedge.timestep import LSRK4TimeStepper + stepper = LSRK4TimeStepper() + from time import time + last_tstep = time() + t = 0 + + # diagnostics setup --------------------------------------------------- + from pytools.log import LogManager, add_general_quantities, \ + add_simulation_quantities, add_run_info + + if write_output: + log_file_name = join(output_dir, "maxwell-%d.dat" % order) + else: + log_file_name = None + + logmgr = LogManager(log_file_name, "w", rcon.communicator) + add_run_info(logmgr) + add_general_quantities(logmgr) + add_simulation_quantities(logmgr) + discr.add_instrumentation(logmgr) + stepper.add_instrumentation(logmgr) + + from pytools.log import IntervalTimer + vis_timer = IntervalTimer("t_vis", "Time spent visualizing") + logmgr.add_quantity(vis_timer) + + from hedge.log import EMFieldGetter, add_em_quantities + field_getter = EMFieldGetter(discr, op, lambda: fields) + add_em_quantities(logmgr, op, field_getter) + + logmgr.add_watches(["step.max", "t_sim.max", + ("W_field", "W_el+W_mag"), "t_step.max"]) + + # timestep loop ------------------------------------------------------- + rhs = op.bind(discr) + + try: + from hedge.timestep import times_and_steps + step_it = times_and_steps( + final_time=final_time, logmgr=logmgr, + max_dt_getter=lambda t: op.estimate_timestep(discr, + stepper=stepper, t=t, fields=fields)) + + for step, t, dt in step_it: + if step % 10 == 0 and write_output: + e, h = op.split_eh(fields) + visf = vis.make_file(join(output_dir, "em-%d-%04d" % (order, step))) + vis.add_data(visf, + [ + ("e", discr.convert_volume(e, "numpy")), + ("h", discr.convert_volume(h, "numpy")), + ], + time=t, step=step + ) + visf.close() + + fields = stepper(fields, t, dt, rhs) + + assert discr.norm(fields) < 0.03 + finally: + if write_output: + vis.close() + + logmgr.close() + discr.close() + +if __name__ == "__main__": + import cProfile as profile + #profile.run("main()", "wave2d.prof") + main() + + + + +# entry points for py.test ---------------------------------------------------- +from pytools.test import mark_test +@mark_test.long +def test_maxwell_2d(): + main(write_output=False) diff --git a/examples/maxwell/maxwell-2d/maxwell-3.dat b/examples/maxwell/maxwell-2d/maxwell-3.dat new file mode 100644 index 0000000000000000000000000000000000000000..e428446bf3a175943f62f70e73317555571f8993 GIT binary patch literal 46080 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 literal 0 HcmV?d00001 diff --git a/examples/maxwell/maxwell-pml.py b/examples/maxwell/maxwell-pml.py new file mode 100644 index 00000000..e365d804 --- /dev/null +++ b/examples/maxwell/maxwell-pml.py @@ -0,0 +1,243 @@ +"""Hedge is the Hybrid'n'Easy Discontinuous Galerkin Environment.""" + +from __future__ import division + +__copyright__ = "Copyright (C) 2007 Andreas Kloeckner" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + + + + +import numpy as np + + + + +def make_mesh(a, b, pml_width=0.25, **kwargs): + from meshpy.geometry import GeometryBuilder, make_circle + geob = GeometryBuilder() + + circle_centers = [(-1.5, 0), (1.5, 0)] + for cent in circle_centers: + geob.add_geometry(*make_circle(1, cent)) + + geob.wrap_in_box(1) + geob.wrap_in_box(pml_width) + + mesh_mod = geob.mesher_module() + mi = mesh_mod.MeshInfo() + geob.set(mi) + + mi.set_holes(circle_centers) + + built_mi = mesh_mod.build(mi, **kwargs) + + def boundary_tagger(fvi, el, fn, points): + return [] + + from hedge.mesh import make_conformal_mesh_ext + from hedge.mesh.element import Triangle + pts = np.asarray(built_mi.points, dtype=np.float64) + return make_conformal_mesh_ext( + pts, + [Triangle(i, el, pts) + for i, el in enumerate(built_mi.elements)], + boundary_tagger) + + + + +def main(write_output=True): + from hedge.timestep.runge_kutta import LSRK4TimeStepper + from math import sqrt, pi, exp + + from hedge.backends import guess_run_context + rcon = guess_run_context() + + epsilon0 = 8.8541878176e-12 # C**2 / (N m**2) + mu0 = 4*pi*1e-7 # N/A**2. + epsilon = 1*epsilon0 + mu = 1*mu0 + + c = 1/sqrt(mu*epsilon) + + pml_width = 0.5 + #mesh = make_mesh(a=np.array((-1,-1,-1)), b=np.array((1,1,1)), + #mesh = make_mesh(a=np.array((-3,-3)), b=np.array((3,3)), + mesh = make_mesh(a=np.array((-1,-1)), b=np.array((1,1)), + #mesh = make_mesh(a=np.array((-2,-2)), b=np.array((2,2)), + pml_width=pml_width, max_volume=0.01) + + if rcon.is_head_rank: + mesh_data = rcon.distribute_mesh(mesh) + else: + mesh_data = rcon.receive_mesh() + + class Current: + def volume_interpolant(self, t, discr): + from hedge.tools import make_obj_array + + result = discr.volume_zeros(kind="numpy", dtype=np.float64) + + omega = 6*c + if omega*t > 2*pi: + return make_obj_array([result, result, result]) + + x = make_obj_array(discr.nodes.T) + r = np.sqrt(np.dot(x, x)) + + idx = r<0.3 + result[idx] = (1+np.cos(pi*r/0.3))[idx] \ + *np.sin(omega*t)**3 + + result = discr.convert_volume(result, kind=discr.compute_kind, + dtype=discr.default_scalar_type) + return make_obj_array([-result, result, result]) + + order = 3 + discr = rcon.make_discretization(mesh_data, order=order, + debug=["cuda_no_plan"]) + + from hedge.visualization import VtkVisualizer + if write_output: + vis = VtkVisualizer(discr, rcon, "em-%d" % order) + + from hedge.mesh import TAG_ALL, TAG_NONE + from hedge.data import GivenFunction, TimeHarmonicGivenFunction, TimeIntervalGivenFunction + from hedge.models.em import MaxwellOperator + from hedge.models.pml import \ + AbarbanelGottliebPMLMaxwellOperator, \ + AbarbanelGottliebPMLTMMaxwellOperator, \ + AbarbanelGottliebPMLTEMaxwellOperator + + op = AbarbanelGottliebPMLTEMaxwellOperator(epsilon, mu, flux_type=1, + current=Current(), + pec_tag=TAG_ALL, + absorb_tag=TAG_NONE, + add_decay=True + ) + + fields = op.assemble_ehpq(discr=discr) + + stepper = LSRK4TimeStepper() + + if rcon.is_head_rank: + print "order %d" % order + print "#elements=", len(mesh.elements) + + # diagnostics setup --------------------------------------------------- + from pytools.log import LogManager, add_general_quantities, \ + add_simulation_quantities, add_run_info + + if write_output: + log_file_name = "maxwell-%d.dat" % order + else: + log_file_name = None + + logmgr = LogManager(log_file_name, "w", rcon.communicator) + add_run_info(logmgr) + add_general_quantities(logmgr) + add_simulation_quantities(logmgr) + discr.add_instrumentation(logmgr) + stepper.add_instrumentation(logmgr) + + from pytools.log import IntervalTimer + vis_timer = IntervalTimer("t_vis", "Time spent visualizing") + logmgr.add_quantity(vis_timer) + + from hedge.log import EMFieldGetter, add_em_quantities + field_getter = EMFieldGetter(discr, op, lambda: fields) + add_em_quantities(logmgr, op, field_getter) + + logmgr.add_watches(["step.max", "t_sim.max", ("W_field", "W_el+W_mag"), "t_step.max"]) + + from hedge.log import LpNorm + class FieldIdxGetter: + def __init__(self, whole_getter, idx): + self.whole_getter = whole_getter + self.idx = idx + + def __call__(self): + return self.whole_getter()[self.idx] + + # timestep loop ------------------------------------------------------- + + t = 0 + pml_coeff = op.coefficients_from_width(discr, width=pml_width) + rhs = op.bind(discr, pml_coeff) + + try: + from hedge.timestep import times_and_steps + step_it = times_and_steps( + final_time=4/c, logmgr=logmgr, + max_dt_getter=lambda t: op.estimate_timestep(discr, + stepper=stepper, t=t, fields=fields)) + + for step, t, dt in step_it: + if step % 10 == 0 and write_output: + e, h, p, q = op.split_ehpq(fields) + visf = vis.make_file("em-%d-%04d" % (order, step)) + #pml_rhs_e, pml_rhs_h, pml_rhs_p, pml_rhs_q = \ + #op.split_ehpq(rhs(t, fields)) + j = Current().volume_interpolant(t, discr) + vis.add_data(visf, [ + ("e", discr.convert_volume(e, "numpy")), + ("h", discr.convert_volume(h, "numpy")), + ("p", discr.convert_volume(p, "numpy")), + ("q", discr.convert_volume(q, "numpy")), + ("j", discr.convert_volume(j, "numpy")), + #("pml_rhs_e", pml_rhs_e), + #("pml_rhs_h", pml_rhs_h), + #("pml_rhs_p", pml_rhs_p), + #("pml_rhs_q", pml_rhs_q), + #("max_rhs_e", max_rhs_e), + #("max_rhs_h", max_rhs_h), + #("max_rhs_p", max_rhs_p), + #("max_rhs_q", max_rhs_q), + ], + time=t, step=step) + visf.close() + + fields = stepper(fields, t, dt, rhs) + + _, _, energies_data = logmgr.get_expr_dataset("W_el+W_mag") + energies = [value for tick_nbr, value in energies_data] + + assert energies[-1] < max(energies) * 1e-2 + + finally: + logmgr.close() + + if write_output: + vis.close() + +if __name__ == "__main__": + main() + + + + +# entry points for py.test ---------------------------------------------------- +from pytools.test import mark_test +@mark_test.long +def test_maxwell_pml(): + main(write_output=False) diff --git a/examples/maxwell/notes.tm b/examples/maxwell/notes.tm new file mode 100644 index 00000000..bd159b24 --- /dev/null +++ b/examples/maxwell/notes.tm @@ -0,0 +1,403 @@ + + +> + +<\body> + + + + <\input|> >> + )clear all + + + <\output> + \ \ \ All user variables and function definitions have been cleared. + + + <\input|> >> + )library )dir "/home/andreas/axiom" + + + <\output> + \ \ \ TexFormat is already explicitly exposed in frame initial\ + + \ \ \ TexFormat will be automatically loaded when needed from\ + + \ \ \ \ \ \ /home/andreas/axiom/TEX.NRLIB/code + + \ \ \ TexFormat1 is already explicitly exposed in frame initial\ + + \ \ \ TexFormat1 will be automatically loaded when needed from\ + + \ \ \ \ \ \ /home/andreas/axiom/TEX1.NRLIB/code + + + <\input|> >> + J:=operator 'J + + + <\output> + (1)> + + + + + <\input|> >> + psi(rho,phi) == J(gamma*rho)*exp(PP*%i*m*phi) + + + <\output> + + + + <\input|> >> + psiglob:=psi(sqrt(x^2+y^2),atan(y/x)) + + + <\output> + \ \ \ Compiling function psi with type (Expression Integer,Expression\ + + \ \ \ \ \ \ Integer) -\ Expression Complex Integer\ + + \+x>ei*P*P*m*arctan + >(3)> + + + + + <\input|> >> + D(psi(rho,phi),rho) + + + <\output> + \ \ \ Compiling function psi with type (Variable rho,Variable phi) + -\\ + + \ \ \ \ \ \ Expression Complex Integer\ + + ei*P*P*m\>J\\(5)> + + + + + <\input|> >> + cross(vector [0,0,1], vector [x,y,0]) + + + <\output> + -y,x,0(7)> + + + + + <\input|> >> + \; + + > + + + + According to Jackson, p. 357, (8.17), we need to solve the Helmholtz + equation + + <\eqnarray*> + |+\\\)>>|>>>>>>||,>>>> + + + subject to \=0> and + \=0>. The ansatz is + + <\equation*> + \=(x)E(y)E(z)>>|(x)E(y)E(z)>>|(x)E(y)E(z)>>>>> + + + and likewise for >. The boundary conditions are + + <\eqnarray*> + (x,>|>>>>>,z)>||>|(x,y,>|>>>>>)>||>>> + + + and so on, as well as + + <\eqnarray*> + (>|>>>>>,y,z)>||>>> + + + So + + <\equation*> + E=\exp(i\x)siny|b>sinz|c>exp(-i\t)=\ess + + + and analogous terms for > and + > satisfy the first batch of boundary conditions. + Because of the Helmholtz equation, we find that + =m\/a>; otherwise, not all vector + components would share the same eigenvalue, which would not solve the + equation. + + + <\input|> >> + )clear all + + + <\output> + \ \ \ All user variables and function definitions have been cleared. + + + <\input|> >> + )library )dir "/home/andreas/axiom" + + + <\output> + \ \ \ TexFormat is already explicitly exposed in frame initial\ + + \ \ \ TexFormat will be automatically loaded when needed from\ + + \ \ \ \ \ \ /home/andreas/axiom/TEX.NRLIB/code + + \ \ \ TexFormat1 is already explicitly exposed in frame initial\ + + \ \ \ TexFormat1 will be automatically loaded when needed from\ + + \ \ \ \ \ \ /home/andreas/axiom/TEX1.NRLIB/code + + + <\input|> >> + factors:=[f,g,h]; + + + <\output> + + + + <\input|> >> + coord := [x,y,z]; + + + <\output> + + + + <\input|> >> + curl(v)== vector [D(v.3,y)-D(v.2,z),D(v.1,z)-D(v.3,x),D(v.2,x)-D(v.1,y)]; + + + <\output> + + + + <\input|> >> + c:=1/sqrt(epsilon*mu); + + + <\output> + + + + <\input|> >> + sines(i) == sin(factors.i*coord.i); + + + <\output> + + + + <\input|> >> + cosines(i) == cos(factors.i*coord.i); + + + <\output> + + + + <\input|> >> + k:=sqrt(f^2+g^2+h^2);omega:=k*c; + + + <\output> + + + + <\input|> >> + zdep1:=exp(%i*h*z); zdep2:=exp(-%i*h*z); + + + <\output> + + + + <\input|> >> + zf1:=1; zf2:=-1; + + + <\output> + + + + <\input|> >> + zdep:=zf1*zdep1 + zf2*zdep2; + + + <\output> + + + + <\input|> >> + C:=%i/(f^2+g^2); + + + <\output> + + + + <\input|> >> + efield := vector [ + + C*f*h*cosines(1)* \ sines(2)*(zf1*zdep1-zf2*zdep2), + + C*g*h* \ sines(1)*cosines(2)*(zf1*zdep1-zf2*zdep2), + + \ \ \ \ \ \ \ \ sines(1)* \ sines(2)*zdep]; + + + <\output> + + + + <\input|> >> + hfield:=1/(-%i*omega*mu)*(-curl efield); + + + <\output> + + + + <\input|> >> + efield2:=1/(-%i*omega*epsilon)*(curl hfield); + + + <\output> + + + + <\input|> >> + efield2-efield + + + <\output> + 0,0,0(71)> + + + + + <\input|> >> + hfield + + + <\output> + -i*g*h-i*g-i*fgcos + g*yei*h*z>+i*g*h+i*g+i*fgcos + g*ye-i*h*z>sin + f*x\>|g+f\+g+f>>,i*f*h+i*f*g+i*fcos + f*xei*h*z>+-i*f*h-i*f*g-i*fcos + f*xe-i*h*z>sin + g*y\>|g+f\+g+f>>,0(72)> + + + + + <\input|> >> + hfield2:=vector [ + + -%i*g/(f^2+g^2)*epsilon*omega*sines(1)*cosines(2)*(zf1*zdep1+zf2*zdep2), + + \ %i*f/(f^2+g^2)*epsilon*omega*cosines(1)*sines(2)*(zf1*zdep1+zf2*zdep2), + + 0] + + + <\output> + -i\g*cos + g*yei*h*z>+i\g*cos + g*ye-i*h*z>sin + f*x+g+f>|g+f\>>,i\f*cos + f*xei*h*z>-i\f*cos + f*xe-i*h*z>sin + g*y+g+f>|g+f\>>,0(73)> + + + + + <\input|> >> + hfield-hfield2 + + + <\output> + 0,0,0(74)> + + + + + <\input|> >> + bcs:=[ + + eval(efield.1, z=0), + + eval(efield.2, z=0), + + eval(efield.3, z=0), + + eval(hfield.1, x=0), + + eval(hfield.2, y=0), + + eval(hfield.3, z=0) + + ] + + + <\output> + 0,g*ysin f*x|g+f>,f*xsin g*y|g+f>,0,0,0(76)> + + + + + <\input|> >> + \; + + > + + \; + + +<\initial> + <\collection> + + + + +<\references> + <\collection> + > + > + > + + + +<\auxiliary> + <\collection> + <\associate|toc> + |math-font-series||Cylindrical + TM Maxwell Cavity Mode> |.>>>>|> + + + |math-font-series||Rectangular + Cavity Mode> |.>>>>|> + + + + \ No newline at end of file diff --git a/examples/poisson/helmholtz.py b/examples/poisson/helmholtz.py new file mode 100644 index 00000000..ac4ce519 --- /dev/null +++ b/examples/poisson/helmholtz.py @@ -0,0 +1,179 @@ +# Hedge - the Hybrid'n'Easy DG Environment +# Copyright (C) 2007 Andreas Kloeckner +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 this program. If not, see . + + + + +from __future__ import division +import numpy +import numpy.linalg as la +from hedge.tools import Reflection, Rotation + + + +class ResidualPrinter: + def __init__(self, compute_resid=None): + self.count = 0 + self.compute_resid = compute_resid + + def __call__(self, cur_sol): + import sys + + if cur_sol is not None: + if self.count % 20 == 0: + sys.stdout.write("IT %8d %g \r" % ( + self.count, la.norm(self.compute_resid(cur_sol)))) + else: + sys.stdout.write("IT %8d \r" % self.count) + self.count += 1 + sys.stdout.flush() + + + + +def main(write_output=True): + from hedge.data import GivenFunction, ConstantGivenFunction + + from hedge.backends import guess_run_context + rcon = guess_run_context() + + dim = 2 + + def boundary_tagger(fvi, el, fn, points): + from math import atan2, pi + normal = el.face_normals[fn] + if -90/180*pi < atan2(normal[1], normal[0]) < 90/180*pi: + return ["neumann"] + else: + return ["dirichlet"] + + def dirichlet_boundary_tagger(fvi, el, fn, points): + return ["dirichlet"] + + if dim == 2: + if rcon.is_head_rank: + from hedge.mesh.generator import make_disk_mesh + mesh = make_disk_mesh(r=0.5, + boundary_tagger=dirichlet_boundary_tagger, + max_area=1e-3) + elif dim == 3: + if rcon.is_head_rank: + from hedge.mesh.generator import make_ball_mesh + mesh = make_ball_mesh(max_volume=0.0001, + boundary_tagger=lambda fvi, el, fn, points: + ["dirichlet"]) + else: + raise RuntimeError, "bad number of dimensions" + + if rcon.is_head_rank: + print "%d elements" % len(mesh.elements) + mesh_data = rcon.distribute_mesh(mesh) + else: + mesh_data = rcon.receive_mesh() + + discr = rcon.make_discretization(mesh_data, order=5, + debug=[]) + + def dirichlet_bc(x, el): + from math import sin + return sin(10*x[0]) + + def rhs_c(x, el): + if la.norm(x) < 0.1: + return 1000 + else: + return 0 + + def my_diff_tensor(): + result = numpy.eye(dim) + result[0,0] = 0.1 + return result + + try: + from hedge.models.poisson import ( + PoissonOperator, + HelmholtzOperator) + from hedge.second_order import \ + IPDGSecondDerivative, LDGSecondDerivative, \ + StabilizedCentralSecondDerivative + + k = 1 + + from hedge.mesh import TAG_NONE, TAG_ALL + op = HelmholtzOperator(k, discr.dimensions, + #diffusion_tensor=my_diff_tensor(), + + #dirichlet_tag="dirichlet", + #neumann_tag="neumann", + + dirichlet_tag=TAG_ALL, + neumann_tag=TAG_NONE, + + #dirichlet_tag=TAG_ALL, + #neumann_tag=TAG_NONE, + + #dirichlet_bc=GivenFunction(dirichlet_bc), + dirichlet_bc=ConstantGivenFunction(0), + neumann_bc=ConstantGivenFunction(-10), + + scheme=StabilizedCentralSecondDerivative(), + #scheme=LDGSecondDerivative(), + #scheme=IPDGSecondDerivative(), + ) + bound_op = op.bind(discr) + + if False: + from hedge.iterative import parallel_cg + u = -parallel_cg(rcon, -bound_op, + bound_op.prepare_rhs(discr.interpolate_volume_function(rhs_c)), + debug=20, tol=5e-4, + dot=discr.nodewise_dot_product, + x=discr.volume_zeros()) + else: + rhs = bound_op.prepare_rhs(discr.interpolate_volume_function(rhs_c)) + def compute_resid(x): + return bound_op(x)-rhs + + from scipy.sparse.linalg import minres, LinearOperator + u, info = minres( + LinearOperator( + (len(discr), len(discr)), + matvec=bound_op, dtype=bound_op.dtype), + rhs, + callback=ResidualPrinter(compute_resid), + tol=1e-5) + print + if info != 0: + raise RuntimeError("gmres reported error %d" % info) + print "finished gmres" + + print la.norm(bound_op(u)-rhs)/la.norm(rhs) + + if write_output: + from hedge.visualization import SiloVisualizer, VtkVisualizer + vis = VtkVisualizer(discr, rcon) + visf = vis.make_file("fld") + vis.add_data(visf, [ ("sol", discr.convert_volume(u, kind="numpy")), ]) + visf.close() + finally: + discr.close() + + + + + +if __name__ == "__main__": + main() diff --git a/examples/poisson/poisson.py b/examples/poisson/poisson.py new file mode 100644 index 00000000..d521c745 --- /dev/null +++ b/examples/poisson/poisson.py @@ -0,0 +1,139 @@ +# Hedge - the Hybrid'n'Easy DG Environment +# Copyright (C) 2007 Andreas Kloeckner +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 this program. If not, see . + + + + +from __future__ import division +import numpy +import numpy.linalg as la +from hedge.tools import Reflection, Rotation + + + + +def main(write_output=True): + from hedge.data import GivenFunction, ConstantGivenFunction + + from hedge.backends import guess_run_context + rcon = guess_run_context() + + dim = 2 + + def boundary_tagger(fvi, el, fn, points): + from math import atan2, pi + normal = el.face_normals[fn] + if -90/180*pi < atan2(normal[1], normal[0]) < 90/180*pi: + return ["neumann"] + else: + return ["dirichlet"] + + if dim == 2: + if rcon.is_head_rank: + from hedge.mesh.generator import make_disk_mesh + mesh = make_disk_mesh(r=0.5, boundary_tagger=boundary_tagger, + max_area=1e-2) + elif dim == 3: + if rcon.is_head_rank: + from hedge.mesh.generator import make_ball_mesh + mesh = make_ball_mesh(max_volume=0.0001, + boundary_tagger=lambda fvi, el, fn, points: + ["dirichlet"]) + else: + raise RuntimeError, "bad number of dimensions" + + if rcon.is_head_rank: + print "%d elements" % len(mesh.elements) + mesh_data = rcon.distribute_mesh(mesh) + else: + mesh_data = rcon.receive_mesh() + + discr = rcon.make_discretization(mesh_data, order=5, + debug=[]) + + def dirichlet_bc(x, el): + from math import sin + return sin(10*x[0]) + + def rhs_c(x, el): + if la.norm(x) < 0.1: + return 1000 + else: + return 0 + + def my_diff_tensor(): + result = numpy.eye(dim) + result[0,0] = 0.1 + return result + + try: + from hedge.models.poisson import PoissonOperator + from hedge.second_order import \ + IPDGSecondDerivative, LDGSecondDerivative, \ + StabilizedCentralSecondDerivative + from hedge.mesh import TAG_NONE, TAG_ALL + op = PoissonOperator(discr.dimensions, + diffusion_tensor=my_diff_tensor(), + + #dirichlet_tag="dirichlet", + #neumann_tag="neumann", + + dirichlet_tag=TAG_ALL, + neumann_tag=TAG_NONE, + + #dirichlet_tag=TAG_ALL, + #neumann_tag=TAG_NONE, + + dirichlet_bc=GivenFunction(dirichlet_bc), + neumann_bc=ConstantGivenFunction(-10), + + scheme=StabilizedCentralSecondDerivative(), + #scheme=LDGSecondDerivative(), + #scheme=IPDGSecondDerivative(), + ) + bound_op = op.bind(discr) + + from hedge.iterative import parallel_cg + u = -parallel_cg(rcon, -bound_op, + bound_op.prepare_rhs(discr.interpolate_volume_function(rhs_c)), + debug=20, tol=5e-4, + dot=discr.nodewise_dot_product, + x=discr.volume_zeros()) + + if write_output: + from hedge.visualization import SiloVisualizer, VtkVisualizer + vis = VtkVisualizer(discr, rcon) + visf = vis.make_file("fld") + vis.add_data(visf, [ ("sol", discr.convert_volume(u, kind="numpy")), ]) + visf.close() + finally: + discr.close() + + + + + +if __name__ == "__main__": + main() + + + + +# entry points for py.test ---------------------------------------------------- +from pytools.test import mark_test +@mark_test.long +def test_poisson(): + main(write_output=False) diff --git a/examples/wave/var-propagation-speed.py b/examples/wave/var-propagation-speed.py new file mode 100644 index 00000000..48b7f910 --- /dev/null +++ b/examples/wave/var-propagation-speed.py @@ -0,0 +1,196 @@ +"""Variable-coefficient wave propagation.""" + +from __future__ import division + +__copyright__ = "Copyright (C) 2009 Andreas Kloeckner" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + + +import numpy as np +from hedge.mesh import TAG_ALL, TAG_NONE + + +def main(write_output=True, + dir_tag=TAG_NONE, + neu_tag=TAG_NONE, + rad_tag=TAG_ALL, + flux_type_arg="upwind"): + from math import sin, cos, pi, exp, sqrt # noqa + + from hedge.backends import guess_run_context + rcon = guess_run_context() + + dim = 2 + + if dim == 1: + if rcon.is_head_rank: + from hedge.mesh.generator import make_uniform_1d_mesh + mesh = make_uniform_1d_mesh(-10, 10, 500) + elif dim == 2: + from hedge.mesh.generator import make_rect_mesh + if rcon.is_head_rank: + mesh = make_rect_mesh(a=(-1, -1), b=(1, 1), max_area=0.003) + elif dim == 3: + if rcon.is_head_rank: + from hedge.mesh.generator import make_ball_mesh + mesh = make_ball_mesh(max_volume=0.0005) + else: + raise RuntimeError("bad number of dimensions") + + if rcon.is_head_rank: + print "%d elements" % len(mesh.elements) + mesh_data = rcon.distribute_mesh(mesh) + else: + mesh_data = rcon.receive_mesh() + + discr = rcon.make_discretization(mesh_data, order=4) + + from hedge.timestep.runge_kutta import LSRK4TimeStepper + stepper = LSRK4TimeStepper() + + from hedge.visualization import VtkVisualizer + if write_output: + vis = VtkVisualizer(discr, rcon, "fld") + + source_center = np.array([0.7, 0.4]) + source_width = 1/16 + source_omega = 3 + + import hedge.optemplate as sym + sym_x = sym.nodes(2) + sym_source_center_dist = sym_x - source_center + + from hedge.models.wave import VariableVelocityStrongWaveOperator + op = VariableVelocityStrongWaveOperator( + c=sym.If(sym.Comparison( + np.dot(sym_x, sym_x), "<", 0.4**2), + 1, 0.5), + dimensions=discr.dimensions, + source= + sym.CFunction("sin")(source_omega*sym.ScalarParameter("t")) + * sym.CFunction("exp")( + -np.dot(sym_source_center_dist, sym_source_center_dist) + / source_width**2), + dirichlet_tag=dir_tag, + neumann_tag=neu_tag, + radiation_tag=rad_tag, + flux_type=flux_type_arg + ) + + from hedge.tools import join_fields + fields = join_fields(discr.volume_zeros(), + [discr.volume_zeros() for i in range(discr.dimensions)]) + + # {{{ diagnostics setup + + from pytools.log import LogManager, \ + add_general_quantities, \ + add_simulation_quantities, \ + add_run_info + + if write_output: + log_file_name = "wave.dat" + else: + log_file_name = None + + logmgr = LogManager(log_file_name, "w", rcon.communicator) + add_run_info(logmgr) + add_general_quantities(logmgr) + add_simulation_quantities(logmgr) + discr.add_instrumentation(logmgr) + + from pytools.log import IntervalTimer + vis_timer = IntervalTimer("t_vis", "Time spent visualizing") + logmgr.add_quantity(vis_timer) + stepper.add_instrumentation(logmgr) + + from hedge.log import LpNorm + u_getter = lambda: fields[0] + logmgr.add_quantity(LpNorm(u_getter, discr, 1, name="l1_u")) + logmgr.add_quantity(LpNorm(u_getter, discr, name="l2_u")) + + logmgr.add_watches(["step.max", "t_sim.max", "l2_u", "t_step.max"]) + + # }}} + + # {{{ timestep loop + + rhs = op.bind(discr) + try: + from hedge.timestep.stability import \ + approximate_rk4_relative_imag_stability_region + max_dt = ( + 1/discr.compile(op.max_eigenvalue_expr())() + * discr.dt_non_geometric_factor() + * discr.dt_geometric_factor() + * approximate_rk4_relative_imag_stability_region(stepper)) + if flux_type_arg == "central": + max_dt *= 0.25 + + from hedge.timestep import times_and_steps + step_it = times_and_steps(final_time=3, logmgr=logmgr, + max_dt_getter=lambda t: max_dt) + + for step, t, dt in step_it: + if step % 10 == 0 and write_output: + visf = vis.make_file("fld-%04d" % step) + + vis.add_data(visf, + [ + ("u", fields[0]), + ("v", fields[1:]), + ], + time=t, + step=step) + visf.close() + + fields = stepper(fields, t, dt, rhs) + + assert discr.norm(fields) < 1 + finally: + if write_output: + vis.close() + + logmgr.close() + discr.close() + + # }}} + +if __name__ == "__main__": + main(flux_type_arg="upwind") + + +# entry points for py.test ---------------------------------------------------- +def test_var_velocity_wave(): + from pytools.test import mark_test + mark_long = mark_test.long + + for flux_type in ["upwind", "central"]: + yield ("dirichlet var-v wave equation with %s flux" % flux_type, + mark_long(main), + False, TAG_ALL, TAG_NONE, TAG_NONE, flux_type) + yield ("neumann var-v wave equation", mark_long(main), + False, TAG_NONE, TAG_ALL, TAG_NONE) + yield ("radiation-bc var-v wave equation", mark_long(main), + False, TAG_NONE, TAG_NONE, TAG_ALL) + +# vim: foldmethod=marker diff --git a/examples/wave/wave-min.py b/examples/wave/wave-min.py new file mode 100644 index 00000000..2d0d91c6 --- /dev/null +++ b/examples/wave/wave-min.py @@ -0,0 +1,95 @@ +"""Minimal example of a hedge driver.""" + +from __future__ import division + +__copyright__ = "Copyright (C) 2009 Andreas Kloeckner" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + + +import numpy as np + + +def main(write_output=True): + from math import sin, exp, sqrt # noqa + + from hedge.mesh.generator import make_rect_mesh + mesh = make_rect_mesh(a=(-0.5, -0.5), b=(0.5, 0.5), max_area=0.008) + + from hedge.backends.jit import Discretization + + discr = Discretization(mesh, order=4) + + from hedge.visualization import VtkVisualizer + vis = VtkVisualizer(discr, None, "fld") + + source_center = np.array([0.1, 0.22]) + source_width = 0.05 + source_omega = 3 + + import hedge.optemplate as sym + sym_x = sym.nodes(2) + sym_source_center_dist = sym_x - source_center + + from hedge.models.wave import StrongWaveOperator + from hedge.mesh import TAG_ALL, TAG_NONE + op = StrongWaveOperator(-0.1, discr.dimensions, + source_f= + sym.CFunction("sin")(source_omega*sym.ScalarParameter("t")) + * sym.CFunction("exp")( + -np.dot(sym_source_center_dist, sym_source_center_dist) + / source_width**2), + dirichlet_tag=TAG_NONE, + neumann_tag=TAG_NONE, + radiation_tag=TAG_ALL, + flux_type="upwind") + + from hedge.tools import join_fields + fields = join_fields(discr.volume_zeros(), + [discr.volume_zeros() for i in range(discr.dimensions)]) + + from hedge.timestep.runge_kutta import LSRK4TimeStepper + stepper = LSRK4TimeStepper() + dt = op.estimate_timestep(discr, stepper=stepper, fields=fields) + + nsteps = int(10/dt) + print "dt=%g nsteps=%d" % (dt, nsteps) + + rhs = op.bind(discr) + for step in range(nsteps): + t = step*dt + + if step % 10 == 0 and write_output: + print step, t, discr.norm(fields[0]) + visf = vis.make_file("fld-%04d" % step) + + vis.add_data(visf, + [("u", fields[0]), ("v", fields[1:]), ], + time=t, step=step) + visf.close() + + fields = stepper(fields, t, dt, rhs) + + vis.close() + + +if __name__ == "__main__": + main() diff --git a/examples/wave/wave.py b/examples/wave/wave.py new file mode 100644 index 00000000..70463ece --- /dev/null +++ b/examples/wave/wave.py @@ -0,0 +1,189 @@ +# Hedge - the Hybrid'n'Easy DG Environment +# Copyright (C) 2007 Andreas Kloeckner +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 this program. If not, see . + + +from __future__ import division +import numpy as np +from hedge.mesh import TAG_ALL, TAG_NONE + + +def main(write_output=True, + dir_tag=TAG_NONE, neu_tag=TAG_NONE, rad_tag=TAG_ALL, + flux_type_arg="upwind", dtype=np.float64, debug=[]): + from math import sin, cos, pi, exp, sqrt # noqa + + from hedge.backends import guess_run_context + rcon = guess_run_context() + + dim = 2 + + if dim == 1: + if rcon.is_head_rank: + from hedge.mesh.generator import make_uniform_1d_mesh + mesh = make_uniform_1d_mesh(-10, 10, 500) + elif dim == 2: + from hedge.mesh.generator import make_rect_mesh + if rcon.is_head_rank: + mesh = make_rect_mesh(a=(-0.5, -0.5), b=(0.5, 0.5), max_area=0.008) + elif dim == 3: + if rcon.is_head_rank: + from hedge.mesh.generator import make_ball_mesh + mesh = make_ball_mesh(max_volume=0.0005) + else: + raise RuntimeError("bad number of dimensions") + + if rcon.is_head_rank: + print "%d elements" % len(mesh.elements) + mesh_data = rcon.distribute_mesh(mesh) + else: + mesh_data = rcon.receive_mesh() + + from hedge.timestep.runge_kutta import LSRK4TimeStepper + stepper = LSRK4TimeStepper(dtype=dtype) + + from hedge.models.wave import StrongWaveOperator + from hedge.mesh import TAG_ALL, TAG_NONE # noqa + + source_center = np.array([0.1, 0.22]) + source_width = 0.05 + source_omega = 3 + + import hedge.optemplate as sym + sym_x = sym.nodes(2) + sym_source_center_dist = sym_x - source_center + + op = StrongWaveOperator(-1, dim, + source_f= + sym.CFunction("sin")(source_omega*sym.ScalarParameter("t")) + * sym.CFunction("exp")( + -np.dot(sym_source_center_dist, sym_source_center_dist) + / source_width**2), + dirichlet_tag=dir_tag, + neumann_tag=neu_tag, + radiation_tag=rad_tag, + flux_type=flux_type_arg + ) + + discr = rcon.make_discretization(mesh_data, order=4, debug=debug, + default_scalar_type=dtype, + tune_for=op.op_template()) + + from hedge.visualization import VtkVisualizer + if write_output: + vis = VtkVisualizer(discr, rcon, "fld") + + from hedge.tools import join_fields + fields = join_fields(discr.volume_zeros(dtype=dtype), + [discr.volume_zeros(dtype=dtype) for i in range(discr.dimensions)]) + + # {{{ diagnostics setup + + from pytools.log import LogManager, \ + add_general_quantities, \ + add_simulation_quantities, \ + add_run_info + + if write_output: + log_file_name = "wave.dat" + else: + log_file_name = None + + logmgr = LogManager(log_file_name, "w", rcon.communicator) + add_run_info(logmgr) + add_general_quantities(logmgr) + add_simulation_quantities(logmgr) + discr.add_instrumentation(logmgr) + + from pytools.log import IntervalTimer + vis_timer = IntervalTimer("t_vis", "Time spent visualizing") + logmgr.add_quantity(vis_timer) + stepper.add_instrumentation(logmgr) + + from hedge.log import LpNorm + u_getter = lambda: fields[0] + logmgr.add_quantity(LpNorm(u_getter, discr, 1, name="l1_u")) + logmgr.add_quantity(LpNorm(u_getter, discr, name="l2_u")) + + logmgr.add_watches(["step.max", "t_sim.max", "l2_u", "t_step.max"]) + + # }}} + + # {{{ timestep loop + + rhs = op.bind(discr) + try: + from hedge.timestep import times_and_steps + step_it = times_and_steps( + final_time=4, logmgr=logmgr, + max_dt_getter=lambda t: op.estimate_timestep(discr, + stepper=stepper, t=t, fields=fields)) + + for step, t, dt in step_it: + if step % 10 == 0 and write_output: + visf = vis.make_file("fld-%04d" % step) + + vis.add_data(visf, + [ + ("u", discr.convert_volume(fields[0], kind="numpy")), + ("v", discr.convert_volume(fields[1:], kind="numpy")), + ], + time=t, + step=step) + visf.close() + + fields = stepper(fields, t, dt, rhs) + + assert discr.norm(fields) < 1 + assert fields[0].dtype == dtype + + finally: + if write_output: + vis.close() + + logmgr.close() + discr.close() + + # }}} + +if __name__ == "__main__": + main(True, TAG_ALL, TAG_NONE, TAG_NONE, "upwind", np.float64, + debug=["cuda_no_plan", "dump_optemplate_stages"]) + + +# {{{ entry points for py.test + +def test_wave(): + from pytools.test import mark_test + mark_long = mark_test.long + + yield ("dirichlet wave equation with SP data", mark_long(main), + False, TAG_ALL, TAG_NONE, TAG_NONE, "upwind", np.float64) + yield ("dirichlet wave equation with SP complex data", mark_long(main), + False, TAG_ALL, TAG_NONE, TAG_NONE, "upwind", np.complex64) + yield ("dirichlet wave equation with DP complex data", mark_long(main), + False, TAG_ALL, TAG_NONE, TAG_NONE, "upwind", np.complex128) + for flux_type in ["upwind", "central"]: + yield ("dirichlet wave equation with %s flux" % flux_type, + mark_long(main), + False, TAG_ALL, TAG_NONE, TAG_NONE, flux_type) + yield ("neumann wave equation", mark_long(main), + False, TAG_NONE, TAG_ALL, TAG_NONE) + yield ("radiation-bc wave equation", mark_long(main), + False, TAG_NONE, TAG_NONE, TAG_ALL) + +# }}} + +# ij diff --git a/examples/wave/wiggly.py b/examples/wave/wiggly.py new file mode 100644 index 00000000..22eaeb04 --- /dev/null +++ b/examples/wave/wiggly.py @@ -0,0 +1,222 @@ +"""Wiggly geometry wave propagation.""" + +from __future__ import division + +__copyright__ = "Copyright (C) 2009 Andreas Kloeckner" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + + +import numpy as np +from hedge.mesh import TAG_ALL, TAG_NONE # noqa + + +def main(write_output=True, + flux_type_arg="upwind", dtype=np.float64, debug=[]): + from math import sin, cos, pi, exp, sqrt # noqa + + from hedge.backends import guess_run_context + rcon = guess_run_context() + + if rcon.is_head_rank: + from hedge.mesh.reader.gmsh import generate_gmsh + mesh = generate_gmsh(GEOMETRY, 2, + allow_internal_boundaries=True, + force_dimension=2) + + print "%d elements" % len(mesh.elements) + mesh_data = rcon.distribute_mesh(mesh) + else: + mesh_data = rcon.receive_mesh() + + discr = rcon.make_discretization(mesh_data, order=4, debug=debug, + default_scalar_type=dtype) + from hedge.timestep.runge_kutta import LSRK4TimeStepper + stepper = LSRK4TimeStepper(dtype=dtype) + + from hedge.visualization import VtkVisualizer + if write_output: + vis = VtkVisualizer(discr, rcon, "fld") + + source_center = 0 + source_width = 0.05 + source_omega = 3 + + import hedge.optemplate as sym + sym_x = sym.nodes(2) + sym_source_center_dist = sym_x - source_center + + from hedge.models.wave import StrongWaveOperator + op = StrongWaveOperator(-1, discr.dimensions, + source_f= + sym.CFunction("sin")(source_omega*sym.ScalarParameter("t")) + * sym.CFunction("exp")( + -np.dot(sym_source_center_dist, sym_source_center_dist) + / source_width**2), + dirichlet_tag="boundary", + neumann_tag=TAG_NONE, + radiation_tag=TAG_NONE, + flux_type=flux_type_arg + ) + + from hedge.tools import join_fields + fields = join_fields(discr.volume_zeros(dtype=dtype), + [discr.volume_zeros(dtype=dtype) for i in range(discr.dimensions)]) + + # diagnostics setup ------------------------------------------------------- + from pytools.log import LogManager, \ + add_general_quantities, \ + add_simulation_quantities, \ + add_run_info + + if write_output: + log_file_name = "wiggly.dat" + else: + log_file_name = None + + logmgr = LogManager(log_file_name, "w", rcon.communicator) + add_run_info(logmgr) + add_general_quantities(logmgr) + add_simulation_quantities(logmgr) + discr.add_instrumentation(logmgr) + + stepper.add_instrumentation(logmgr) + + logmgr.add_watches(["step.max", "t_sim.max", "t_step.max"]) + + # timestep loop ----------------------------------------------------------- + rhs = op.bind(discr) + try: + from hedge.timestep import times_and_steps + step_it = times_and_steps( + final_time=4, logmgr=logmgr, + max_dt_getter=lambda t: op.estimate_timestep(discr, + stepper=stepper, t=t, fields=fields)) + + for step, t, dt in step_it: + if step % 10 == 0 and write_output: + visf = vis.make_file("fld-%04d" % step) + + vis.add_data(visf, + [ + ("u", fields[0]), + ("v", fields[1:]), + ], + time=t, + step=step) + visf.close() + + fields = stepper(fields, t, dt, rhs) + + assert discr.norm(fields) < 1 + assert fields[0].dtype == dtype + + finally: + if write_output: + vis.close() + + logmgr.close() + discr.close() + +GEOMETRY = """ +w = 1; +dx = 0.2; +ch_width = 0.2; +rows = 4; + +Point(0) = {0,0,0}; +Point(1) = {w,0,0}; + +bottom_line = newl; +Line(bottom_line) = {0,1}; + +left_pts[] = { 0 }; +right_pts[] = { 1 }; + +left_pts[] = { }; +emb_lines[] = {}; + +For row In {1:rows} + If (row % 2 == 0) + // left + rp = newp; Point(rp) = {w,dx*row, 0}; + right_pts[] += {rp}; + + mp = newp; Point(mp) = {ch_width,dx*row, 0}; + emb_line = newl; Line(emb_line) = {mp,rp}; + emb_lines[] += {emb_line}; + EndIf + If (row % 2) + // right + lp = newp; Point(lp) = {0,dx*row, 0}; + left_pts[] += {lp}; + + mp = newp; Point(mp) = { w-ch_width,dx*row, 0}; + emb_line = newl; Line(emb_line) = {mp,lp}; + emb_lines[] += {emb_line}; + EndIf +EndFor + +lep = newp; Point(lep) = {0,(rows+1)*dx,0}; +rep = newp; Point(rep) = {w,(rows+1)*dx,0}; +top_line = newl; Line(top_line) = {lep,rep}; + +left_pts[] += { lep }; +right_pts[] += { rep }; + +lines[] = {bottom_line}; + +For i In {0:#right_pts[]-2} + l = newl; Line(l) = {right_pts[i], right_pts[i+1]}; + lines[] += {l}; +EndFor + +lines[] += {-top_line}; + +For i In {#left_pts[]-1:0:-1} + l = newl; Line(l) = {left_pts[i], left_pts[i-1]}; + lines[] += {l}; +EndFor + +Line Loop (1) = lines[]; + +Plane Surface (1) = {1}; +Physical Surface(1) = {1}; + +For i In {0:#emb_lines[]-1} + Line { emb_lines[i] } In Surface { 1 }; +EndFor + +boundary_lines[] = {}; +boundary_lines[] += lines[]; +boundary_lines[] += emb_lines[]; +Physical Line ("boundary") = boundary_lines[]; + +Mesh.CharacteristicLengthFactor = 0.4; +""" + +if __name__ == "__main__": + main() + + + + + diff --git a/grudge/models/__init__.py b/grudge/models/__init__.py new file mode 100644 index 00000000..d9142d67 --- /dev/null +++ b/grudge/models/__init__.py @@ -0,0 +1,67 @@ +"""Base classes for operators.""" + +from __future__ import division + +__copyright__ = "Copyright (C) 2007 Andreas Kloeckner" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + + +class Operator(object): + """A base class for Discontinuous Galerkin operators. + + You may derive your own operators from this class, but, at present + this class provides no functionality. Its function is merely as + documentation, to group related classes together in an inheritance + tree. + """ + pass + + +class TimeDependentOperator(Operator): + """A base class for time-dependent Discontinuous Galerkin operators. + + You may derive your own operators from this class, but, at present + this class provides no functionality. Its function is merely as + documentation, to group related classes together in an inheritance + tree. + """ + pass + + +class HyperbolicOperator(Operator): + """A base class for hyperbolic Discontinuous Galerkin operators.""" + + def estimate_timestep(self, discr, + stepper=None, stepper_class=None, stepper_args=None, + t=None, fields=None): + u"""Estimate the largest stable timestep, given a time stepper + `stepper_class`. If none is given, RK4 is assumed. + """ + + rk4_dt = 1 / self.max_eigenvalue(t, fields, discr) \ + * (discr.dt_non_geometric_factor() + * discr.dt_geometric_factor()) + + from hedge.timestep.stability import \ + approximate_rk4_relative_imag_stability_region + return rk4_dt * approximate_rk4_relative_imag_stability_region( + stepper, stepper_class, stepper_args) diff --git a/grudge/models/advection.py b/grudge/models/advection.py new file mode 100644 index 00000000..e5679518 --- /dev/null +++ b/grudge/models/advection.py @@ -0,0 +1,409 @@ +# -*- coding: utf8 -*- +"""Operators modeling advective phenomena.""" + +from __future__ import division + +__copyright__ = "Copyright (C) 2009 Andreas Kloeckner" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + + + + +import numpy +import numpy.linalg as la + +import hedge.data +from hedge.models import HyperbolicOperator +from hedge.second_order import CentralSecondDerivative + + + + +# {{{ constant-coefficient advection ------------------------------------------ +class AdvectionOperatorBase(HyperbolicOperator): + flux_types = [ + "central", + "upwind", + "lf" + ] + + def __init__(self, v, + inflow_tag="inflow", + inflow_u=hedge.data.make_tdep_constant(0), + outflow_tag="outflow", + flux_type="central" + ): + self.dimensions = len(v) + self.v = v + self.inflow_tag = inflow_tag + self.inflow_u = inflow_u + self.outflow_tag = outflow_tag + self.flux_type = flux_type + + def weak_flux(self): + from hedge.flux import make_normal, FluxScalarPlaceholder + from pymbolic.primitives import IfPositive + + u = FluxScalarPlaceholder(0) + normal = make_normal(self.dimensions) + + if self.flux_type == "central": + return u.avg*numpy.dot(normal, self.v) + elif self.flux_type == "lf": + return u.avg*numpy.dot(normal, self.v) \ + + 0.5*la.norm(self.v)*(u.int - u.ext) + elif self.flux_type == "upwind": + return (numpy.dot(normal, self.v)* + IfPositive(numpy.dot(normal, self.v), + u.int, # outflow + u.ext, # inflow + )) + else: + raise ValueError, "invalid flux type" + + def max_eigenvalue(self, t=None, fields=None, discr=None): + return la.norm(self.v) + + def bind(self, discr): + compiled_op_template = discr.compile(self.op_template()) + + from hedge.mesh import check_bc_coverage + check_bc_coverage(discr.mesh, [self.inflow_tag, self.outflow_tag]) + + def rhs(t, u): + bc_in = self.inflow_u.boundary_interpolant(t, discr, self.inflow_tag) + return compiled_op_template(u=u, bc_in=bc_in) + + return rhs + + def bind_interdomain(self, + my_discr, my_part_data, + nb_discr, nb_part_data): + from hedge.partition import compile_interdomain_flux + compiled_op_template, from_nb_indices = compile_interdomain_flux( + self.op_template(), "u", "nb_bdry_u", + my_discr, my_part_data, nb_discr, nb_part_data, + use_stupid_substitution=True) + + from hedge.tools import with_object_array_or_scalar, is_zero + + def nb_bdry_permute(fld): + if is_zero(fld): + return 0 + else: + return fld[from_nb_indices] + + def rhs(t, u, u_neighbor): + return compiled_op_template(u=u, + nb_bdry_u=with_object_array_or_scalar(nb_bdry_permute, u_neighbor)) + + return rhs + + + + +class StrongAdvectionOperator(AdvectionOperatorBase): + def flux(self): + from hedge.flux import make_normal, FluxScalarPlaceholder + + u = FluxScalarPlaceholder(0) + normal = make_normal(self.dimensions) + + return u.int * numpy.dot(normal, self.v) - self.weak_flux() + + def op_template(self): + from hedge.optemplate import Field, BoundaryPair, \ + get_flux_operator, make_nabla, InverseMassOperator + + u = Field("u") + bc_in = Field("bc_in") + + nabla = make_nabla(self.dimensions) + m_inv = InverseMassOperator() + + flux_op = get_flux_operator(self.flux()) + + return ( + -numpy.dot(self.v, nabla*u) + + m_inv( + flux_op(u) + + flux_op(BoundaryPair(u, bc_in, self.inflow_tag)))) + + + + +class WeakAdvectionOperator(AdvectionOperatorBase): + def flux(self): + return self.weak_flux() + + def op_template(self): + from hedge.optemplate import ( + Field, + BoundaryPair, + get_flux_operator, + make_stiffness_t, + InverseMassOperator, + BoundarizeOperator, + QuadratureGridUpsampler, + QuadratureInteriorFacesGridUpsampler) + + u = Field("u") + + to_quad = QuadratureGridUpsampler("quad") + to_int_face_quad = QuadratureInteriorFacesGridUpsampler("quad") + + # boundary conditions ------------------------------------------------- + bc_in = Field("bc_in") + bc_out = BoundarizeOperator(self.outflow_tag)*u + + stiff_t = make_stiffness_t(self.dimensions) + m_inv = InverseMassOperator() + + flux_op = get_flux_operator(self.flux()) + + return m_inv(numpy.dot(self.v, stiff_t*u) - ( + flux_op(u) + + flux_op(BoundaryPair(u, bc_in, self.inflow_tag)) + + flux_op(BoundaryPair(u, bc_out, self.outflow_tag)) + )) + +# }}} + + + + +# {{{ variable-coefficient advection ------------------------------------------ +class VariableCoefficientAdvectionOperator(HyperbolicOperator): + """A class for space- and time-dependent DG advection operators. + + :param advec_v: Adheres to the :class:`hedge.data.ITimeDependentGivenFunction` + interfacer and is an n-dimensional vector representing the velocity. + :param bc_u_f: Adheres to the :class:`hedge.data.ITimeDependentGivenFunction` + interface and is a scalar representing the boundary condition at all + boundary faces. + + Optionally allows diffusion. + """ + + flux_types = [ + "central", + "upwind", + "lf" + ] + + def __init__(self, + dimensions, + advec_v, + bc_u_f="None", + flux_type="central", + diffusion_coeff=None, + diffusion_scheme=CentralSecondDerivative()): + self.dimensions = dimensions + self.advec_v = advec_v + self.bc_u_f = bc_u_f + self.flux_type = flux_type + self.diffusion_coeff = diffusion_coeff + self.diffusion_scheme = diffusion_scheme + + # {{{ flux ---------------------------------------------------------------- + def flux(self): + from hedge.flux import ( + make_normal, + FluxVectorPlaceholder, + flux_max) + from pymbolic.primitives import IfPositive + + d = self.dimensions + + w = FluxVectorPlaceholder((1+d)+1) + u = w[0] + v = w[1:d+1] + c = w[1+d] + + normal = make_normal(self.dimensions) + + if self.flux_type == "central": + return (u.int*numpy.dot(v.int, normal ) + + u.ext*numpy.dot(v.ext, normal)) * 0.5 + elif self.flux_type == "lf": + n_vint = numpy.dot(normal, v.int) + n_vext = numpy.dot(normal, v.ext) + return 0.5 * (n_vint * u.int + n_vext * u.ext) \ + - 0.5 * (u.ext - u.int) \ + * flux_max(c.int, c.ext) + + elif self.flux_type == "upwind": + return ( + IfPositive(numpy.dot(normal, v.avg), + numpy.dot(normal, v.int) * u.int, # outflow + numpy.dot(normal, v.ext) * u.ext, # inflow + )) + else: + raise ValueError, "invalid flux type" + # }}} + + def bind_characteristic_velocity(self, discr): + from hedge.optemplate.operators import ( + ElementwiseMaxOperator) + from hedge.optemplate import make_sym_vector + velocity_vec = make_sym_vector("v", self.dimensions) + velocity = ElementwiseMaxOperator()( + numpy.dot(velocity_vec, velocity_vec)**0.5) + + compiled = discr.compile(velocity) + + def do(t, u): + return compiled(v=self.advec_v.volume_interpolant(t, discr)) + + return do + + def op_template(self, with_sensor=False): + # {{{ operator preliminaries ------------------------------------------ + from hedge.optemplate import (Field, BoundaryPair, get_flux_operator, + make_stiffness_t, InverseMassOperator, make_sym_vector, + ElementwiseMaxOperator, BoundarizeOperator) + + from hedge.optemplate.primitives import make_common_subexpression as cse + + from hedge.optemplate.operators import ( + QuadratureGridUpsampler, + QuadratureInteriorFacesGridUpsampler) + + to_quad = QuadratureGridUpsampler("quad") + to_if_quad = QuadratureInteriorFacesGridUpsampler("quad") + + from hedge.tools import join_fields, \ + ptwise_dot + + u = Field("u") + v = make_sym_vector("v", self.dimensions) + c = ElementwiseMaxOperator()(ptwise_dot(1, 1, v, v)) + + quad_u = cse(to_quad(u)) + quad_v = cse(to_quad(v)) + + w = join_fields(u, v, c) + quad_face_w = to_if_quad(w) + # }}} + + # {{{ boundary conditions --------------------------------------------- + + from hedge.mesh import TAG_ALL + bc_c = to_quad(BoundarizeOperator(TAG_ALL)(c)) + bc_u = to_quad(Field("bc_u")) + bc_v = to_quad(BoundarizeOperator(TAG_ALL)(v)) + + if self.bc_u_f is "None": + bc_w = join_fields(0, bc_v, bc_c) + else: + bc_w = join_fields(bc_u, bc_v, bc_c) + + minv_st = make_stiffness_t(self.dimensions) + m_inv = InverseMassOperator() + + flux_op = get_flux_operator(self.flux()) + # }}} + + # {{{ diffusion ------------------------------------------------------- + if with_sensor or ( + self.diffusion_coeff is not None and self.diffusion_coeff != 0): + if self.diffusion_coeff is None: + diffusion_coeff = 0 + else: + diffusion_coeff = self.diffusion_coeff + + if with_sensor: + diffusion_coeff += Field("sensor") + + from hedge.second_order import SecondDerivativeTarget + + # strong_form here allows IPDG to reuse the value of grad u. + grad_tgt = SecondDerivativeTarget( + self.dimensions, strong_form=True, + operand=u) + + self.diffusion_scheme.grad(grad_tgt, bc_getter=None, + dirichlet_tags=[], neumann_tags=[]) + + div_tgt = SecondDerivativeTarget( + self.dimensions, strong_form=False, + operand=diffusion_coeff*grad_tgt.minv_all) + + self.diffusion_scheme.div(div_tgt, + bc_getter=None, + dirichlet_tags=[], neumann_tags=[]) + + diffusion_part = div_tgt.minv_all + else: + diffusion_part = 0 + + # }}} + + to_quad = QuadratureGridUpsampler("quad") + quad_u = cse(to_quad(u)) + quad_v = cse(to_quad(v)) + + return m_inv(numpy.dot(minv_st, cse(quad_v*quad_u)) + - (flux_op(quad_face_w) + + flux_op(BoundaryPair(quad_face_w, bc_w, TAG_ALL)))) \ + + diffusion_part + + def bind(self, discr, sensor=None): + compiled_op_template = discr.compile( + self.op_template(with_sensor=sensor is not None)) + + from hedge.mesh import check_bc_coverage, TAG_ALL + check_bc_coverage(discr.mesh, [TAG_ALL]) + + def rhs(t, u): + kwargs = {} + if sensor is not None: + kwargs["sensor"] = sensor(t, u) + + if self.bc_u_f is not "None": + kwargs["bc_u"] = \ + self.bc_u_f.boundary_interpolant(t, discr, tag=TAG_ALL) + + return compiled_op_template( + u=u, + v=self.advec_v.volume_interpolant(t, discr), + **kwargs) + + return rhs + + def max_eigenvalue(self, t, fields=None, discr=None): + # Gives the max eigenvalue of a vector of eigenvalues. + # As the velocities of each node is stored in the velocity-vector-field + # a pointwise dot product of this vector has to be taken to get the + # magnitude of the velocity at each node. From this vector the maximum + # values limits the timestep. + + from hedge.tools import ptwise_dot + v = self.advec_v.volume_interpolant(t, discr) + return discr.nodewise_max(ptwise_dot(1, 1, v, v)**0.5) + +# }}} + + + + +# vim: foldmethod=marker diff --git a/grudge/models/burgers.py b/grudge/models/burgers.py new file mode 100644 index 00000000..7c38eb5b --- /dev/null +++ b/grudge/models/burgers.py @@ -0,0 +1,154 @@ +# -*- coding: utf8 -*- +"""Burgers operator.""" + +from __future__ import division + +__copyright__ = "Copyright (C) 2009 Andreas Kloeckner" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + + + + +from hedge.models import HyperbolicOperator +import numpy +from hedge.second_order import CentralSecondDerivative + + + + +class BurgersOperator(HyperbolicOperator): + def __init__(self, dimensions, viscosity=None, + viscosity_scheme=CentralSecondDerivative()): + # yes, you read that right--no BCs, 1D only. + # (well--you can run the operator on a 2D grid. If you must.) + self.dimensions = dimensions + self.viscosity = viscosity + self.viscosity_scheme = viscosity_scheme + + def characteristic_velocity_optemplate(self, field): + from hedge.optemplate.operators import ( + ElementwiseMaxOperator) + return ElementwiseMaxOperator()(field**2)**0.5 + + def bind_characteristic_velocity(self, discr): + from hedge.optemplate import Field + compiled = discr.compile( + self.characteristic_velocity_optemplate( + Field("u"))) + + def do(u): + return compiled(u=u) + + return do + + def op_template(self, with_sensor): + from hedge.optemplate import ( + Field, + make_stiffness_t, + make_nabla, + InverseMassOperator, + ElementwiseMaxOperator, + get_flux_operator) + + from hedge.optemplate.operators import ( + QuadratureGridUpsampler, + QuadratureInteriorFacesGridUpsampler) + + to_quad = QuadratureGridUpsampler("quad") + to_if_quad = QuadratureInteriorFacesGridUpsampler("quad") + + u = Field("u") + u0 = Field("u0") + + # boundary conditions ------------------------------------------------- + minv_st = make_stiffness_t(self.dimensions) + nabla = make_nabla(self.dimensions) + m_inv = InverseMassOperator() + + def flux(u): + return u**2/2 + #return u0*u + + emax_u = self.characteristic_velocity_optemplate(u) + from hedge.flux.tools import make_lax_friedrichs_flux + from pytools.obj_array import make_obj_array + num_flux = make_lax_friedrichs_flux( + #u0, + to_if_quad(emax_u), + make_obj_array([to_if_quad(u)]), + [make_obj_array([flux(to_if_quad(u))])], + [], strong=False)[0] + + from hedge.second_order import SecondDerivativeTarget + + if self.viscosity is not None or with_sensor: + viscosity_coeff = 0 + if with_sensor: + viscosity_coeff += Field("sensor") + + if isinstance(self.viscosity, float): + viscosity_coeff += self.viscosity + elif self.viscosity is None: + pass + else: + raise TypeError("unsupported type of viscosity coefficient") + + # strong_form here allows IPDG to reuse the value of grad u. + grad_tgt = SecondDerivativeTarget( + self.dimensions, strong_form=True, + operand=u) + + self.viscosity_scheme.grad(grad_tgt, bc_getter=None, + dirichlet_tags=[], neumann_tags=[]) + + div_tgt = SecondDerivativeTarget( + self.dimensions, strong_form=False, + operand=viscosity_coeff*grad_tgt.minv_all) + + self.viscosity_scheme.div(div_tgt, + bc_getter=None, + dirichlet_tags=[], neumann_tags=[]) + + viscosity_bit = div_tgt.minv_all + else: + viscosity_bit = 0 + + return m_inv((minv_st[0](flux(to_quad(u)))) - num_flux) \ + + viscosity_bit + + def bind(self, discr, u0=1, sensor=None): + compiled_op_template = discr.compile( + self.op_template(with_sensor=sensor is not None)) + + from hedge.mesh import check_bc_coverage + check_bc_coverage(discr.mesh, []) + + def rhs(t, u): + kwargs = {} + if sensor is not None: + kwargs["sensor"] = sensor(u) + return compiled_op_template(u=u, u0=u0, **kwargs) + + return rhs + + def max_eigenvalue(self, t=None, fields=None, discr=None): + return discr.nodewise_max(fields) diff --git a/grudge/models/diffusion.py b/grudge/models/diffusion.py new file mode 100644 index 00000000..a6930f72 --- /dev/null +++ b/grudge/models/diffusion.py @@ -0,0 +1,128 @@ +# -*- coding: utf8 -*- +"""Operators modeling diffusive phenomena.""" + +from __future__ import division + +__copyright__ = "Copyright (C) 2009 Andreas Kloeckner" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + + + +import numpy + +import hedge.data +from hedge.models import TimeDependentOperator +from hedge.models.poisson import LaplacianOperatorBase +from hedge.second_order import CentralSecondDerivative + + + + +class DiffusionOperator(TimeDependentOperator, LaplacianOperatorBase): + def __init__(self, dimensions, diffusion_tensor=None, + dirichlet_bc=hedge.data.make_tdep_constant(0), dirichlet_tag="dirichlet", + neumann_bc=hedge.data.make_tdep_constant(0), neumann_tag="neumann", + scheme=CentralSecondDerivative()): + self.dimensions = dimensions + + self.scheme = scheme + + self.dirichlet_bc = dirichlet_bc + self.dirichlet_tag = dirichlet_tag + self.neumann_bc = neumann_bc + self.neumann_tag = neumann_tag + + if diffusion_tensor is None: + diffusion_tensor = numpy.eye(dimensions) + self.diffusion_tensor = diffusion_tensor + + def bind(self, discr): + """Return a :class:`BoundPoissonOperator`.""" + + assert self.dimensions == discr.dimensions + + from hedge.mesh import check_bc_coverage + check_bc_coverage(discr.mesh, [self.dirichlet_tag, self.neumann_tag]) + + return BoundDiffusionOperator(self, discr) + + def estimate_timestep(self, discr, + stepper=None, stepper_class=None, stepper_args=None, + t=None, fields=None): + u"""Estimate the largest stable timestep, given a time stepper + `stepper_class`. If none is given, RK4 is assumed. + """ + + rk4_dt = 0.2 \ + * (discr.dt_non_geometric_factor() + * discr.dt_geometric_factor())**2 + + from hedge.timestep.stability import \ + approximate_rk4_relative_imag_stability_region + return rk4_dt * approximate_rk4_relative_imag_stability_region( + stepper, stepper_class, stepper_args) + + + + +class BoundDiffusionOperator(hedge.iterative.OperatorBase): + """Returned by :meth:`DiffusionOperator.bind`.""" + + def __init__(self, diffusion_op, discr): + hedge.iterative.OperatorBase.__init__(self) + self.discr = discr + + dop = self.diffusion_op = diffusion_op + + op = dop.op_template(apply_minv=True) + + self.compiled_op = discr.compile(op) + + # Check whether use of Poincaré mean-value method is required. + # (for pure Neumann or pure periodic) + + from hedge.mesh import TAG_ALL + self.poincare_mean_value_hack = ( + len(self.discr.get_boundary(TAG_ALL).nodes) + == len(self.discr.get_boundary(diffusion_op.neumann_tag).nodes)) + + def __call__(self, t, u): + dop = self.diffusion_op + + context = {"u": u} + + if not isinstance(self.diffusion_op.diffusion_tensor, numpy.ndarray): + self.diffusion = dop.diffusion_tensor.volume_interpolant(t, self.discr) + self.neu_diff = dop.diffusion_tensor.boundary_interpolant( + t, self.discr, dop.neumann_tag) + + context["diffusion"] = self.diffusion + context["neumann_diffusion"] = self.neu_diff + + if not self.discr.get_boundary(dop.dirichlet_tag).is_empty(): + context["dir_bc"] = dop.dirichlet_bc.boundary_interpolant( + t, self.discr, dop.dirichlet_tag) + if not self.discr.get_boundary(dop.neumann_tag).is_empty(): + context["neu_bc"] = dop.neumann_bc.boundary_interpolant( + t, self.discr, dop.neumann_tag) + + return self.compiled_op(**context) diff --git a/grudge/models/em.py b/grudge/models/em.py new file mode 100644 index 00000000..23598708 --- /dev/null +++ b/grudge/models/em.py @@ -0,0 +1,546 @@ +# -*- coding: utf8 -*- +"""Hedge operators modelling electromagnetic phenomena.""" + +from __future__ import division + +__copyright__ = "Copyright (C) 2007, 2010 Andreas Kloeckner, David Powell" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + +from pytools import memoize_method + +import hedge.mesh +from hedge.models import HyperbolicOperator +from hedge.optemplate.primitives import make_common_subexpression as cse +from hedge.tools import make_obj_array + +# TODO: Check PML + + +class MaxwellOperator(HyperbolicOperator): + """A 3D Maxwell operator which supports fixed or variable + isotropic, non-dispersive, positive epsilon and mu. + + Field order is [Ex Ey Ez Hx Hy Hz]. + """ + + _default_dimensions = 3 + + def __init__(self, epsilon, mu, + flux_type, + bdry_flux_type=None, + pec_tag=hedge.mesh.TAG_ALL, + pmc_tag=hedge.mesh.TAG_NONE, + absorb_tag=hedge.mesh.TAG_NONE, + incident_tag=hedge.mesh.TAG_NONE, + incident_bc=lambda maxwell_op, e, h: 0, current=0, dimensions=None): + """ + :arg flux_type: can be in [0,1] for anything between central and upwind, + or "lf" for Lax-Friedrichs + :arg epsilon: can be a number, for fixed material throughout the + computation domain, or a TimeConstantGivenFunction for spatially + variable material coefficients + :arg mu: can be a number, for fixed material throughout the computation + domain, or a TimeConstantGivenFunction for spatially variable material + coefficients + :arg incident_bc_getter: a function of signature *(maxwell_op, e, h)* that + accepts *e* and *h* as a symbolic object arrays + returns a symbolic expression for the incident + boundary condition + """ + + self.dimensions = dimensions or self._default_dimensions + + space_subset = [True]*self.dimensions + [False]*(3-self.dimensions) + + e_subset = self.get_eh_subset()[0:3] + h_subset = self.get_eh_subset()[3:6] + + from hedge.tools import SubsettableCrossProduct + self.space_cross_e = SubsettableCrossProduct( + op1_subset=space_subset, + op2_subset=e_subset, + result_subset=h_subset) + self.space_cross_h = SubsettableCrossProduct( + op1_subset=space_subset, + op2_subset=h_subset, + result_subset=e_subset) + + self.epsilon = epsilon + self.mu = mu + + from pymbolic.primitives import is_constant + self.fixed_material = is_constant(epsilon) and is_constant(mu) + + self.flux_type = flux_type + if bdry_flux_type is None: + self.bdry_flux_type = flux_type + else: + self.bdry_flux_type = bdry_flux_type + + self.pec_tag = pec_tag + self.pmc_tag = pmc_tag + self.absorb_tag = absorb_tag + self.incident_tag = incident_tag + + self.current = current + self.incident_bc_data = incident_bc + + @property + def c(self): + from warnings import warn + warn("MaxwellOperator.c is deprecated", DeprecationWarning) + if not self.fixed_material: + raise RuntimeError("Cannot compute speed of light " + "for non-constant material") + + return 1/(self.mu*self.epsilon)**0.5 + + def flux(self, flux_type): + """The template for the numerical flux for variable coefficients. + + :param flux_type: can be in [0,1] for anything between central and upwind, + or "lf" for Lax-Friedrichs. + + As per Hesthaven and Warburton page 433. + """ + from hedge.flux import (make_normal, FluxVectorPlaceholder, + FluxConstantPlaceholder) + from hedge.tools import join_fields + + normal = make_normal(self.dimensions) + + if self.fixed_material: + from hedge.tools import count_subset + w = FluxVectorPlaceholder(count_subset(self.get_eh_subset())) + + e, h = self.split_eh(w) + epsilon = FluxConstantPlaceholder(self.epsilon) + mu = FluxConstantPlaceholder(self.mu) + + else: + from hedge.tools import count_subset + w = FluxVectorPlaceholder(count_subset(self.get_eh_subset())+2) + + epsilon, mu, e, h = self.split_eps_mu_eh(w) + + Z_int = (mu.int/epsilon.int)**0.5 + Y_int = 1/Z_int + Z_ext = (mu.ext/epsilon.ext)**0.5 + Y_ext = 1/Z_ext + + if flux_type == "lf": + if self.fixed_material: + max_c = (self.epsilon*self.mu)**(-0.5) + else: + from hedge.flux import Max + c_int = (epsilon.int*mu.int)**(-0.5) + c_ext = (epsilon.ext*mu.ext)**(-0.5) + max_c = Max(c_int, c_ext) # noqa + + return join_fields( + # flux e, + 1/2*( + -self.space_cross_h(normal, h.int-h.ext) + # multiplication by epsilon undoes material divisor below + #-max_c*(epsilon.int*e.int - epsilon.ext*e.ext) + ), + # flux h + 1/2*( + self.space_cross_e(normal, e.int-e.ext) + # multiplication by mu undoes material divisor below + #-max_c*(mu.int*h.int - mu.ext*h.ext) + )) + elif isinstance(flux_type, (int, float)): + # see doc/maxima/maxwell.mac + return join_fields( + # flux e, + ( + -1/(Z_int+Z_ext)*self.space_cross_h(normal, + Z_ext*(h.int-h.ext) + - flux_type*self.space_cross_e(normal, e.int-e.ext)) + ), + # flux h + ( + 1/(Y_int + Y_ext)*self.space_cross_e(normal, + Y_ext*(e.int-e.ext) + + flux_type*self.space_cross_h(normal, h.int-h.ext)) + ), + ) + else: + raise ValueError("maxwell: invalid flux_type (%s)" + % self.flux_type) + + def local_derivatives(self, w=None): + """Template for the spatial derivatives of the relevant components of + :math:`E` and :math:`H` + """ + + e, h = self.split_eh(self.field_placeholder(w)) + + def e_curl(field): + return self.space_cross_e(nabla, field) + + def h_curl(field): + return self.space_cross_h(nabla, field) + + from hedge.optemplate import make_nabla + from hedge.tools import join_fields + + nabla = make_nabla(self.dimensions) + + # in conservation form: u_t + A u_x = 0 + return join_fields( + (self.current - h_curl(h)), + e_curl(e) + ) + + def field_placeholder(self, w=None): + "A placeholder for E and H." + from hedge.tools import count_subset + fld_cnt = count_subset(self.get_eh_subset()) + if w is None: + from hedge.optemplate import make_sym_vector + w = make_sym_vector("w", fld_cnt) + + return w + + def pec_bc(self, w=None): + "Construct part of the flux operator template for PEC boundary conditions" + e, h = self.split_eh(self.field_placeholder(w)) + + from hedge.tools import join_fields + from hedge.optemplate import BoundarizeOperator + pec_e = BoundarizeOperator(self.pec_tag)(e) + pec_h = BoundarizeOperator(self.pec_tag)(h) + + return join_fields(-pec_e, pec_h) + + def pmc_bc(self, w=None): + "Construct part of the flux operator template for PMC boundary conditions" + e, h = self.split_eh(self.field_placeholder(w)) + + from hedge.tools import join_fields + from hedge.optemplate import BoundarizeOperator + pmc_e = BoundarizeOperator(self.pmc_tag)(e) + pmc_h = BoundarizeOperator(self.pmc_tag)(h) + + return join_fields(pmc_e, -pmc_h) + + def absorbing_bc(self, w=None): + """Construct part of the flux operator template for 1st order + absorbing boundary conditions. + """ + + from hedge.optemplate import normal + absorb_normal = normal(self.absorb_tag, self.dimensions) + + from hedge.optemplate import BoundarizeOperator, Field + from hedge.tools import join_fields + + e, h = self.split_eh(self.field_placeholder(w)) + + if self.fixed_material: + epsilon = self.epsilon + mu = self.mu + else: + epsilon = cse( + BoundarizeOperator(self.absorb_tag)(Field("epsilon"))) + mu = cse( + BoundarizeOperator(self.absorb_tag)(Field("mu"))) + + absorb_Z = (mu/epsilon)**0.5 + absorb_Y = 1/absorb_Z + + absorb_e = BoundarizeOperator(self.absorb_tag)(e) + absorb_h = BoundarizeOperator(self.absorb_tag)(h) + + bc = join_fields( + absorb_e + 1/2*(self.space_cross_h(absorb_normal, self.space_cross_e( + absorb_normal, absorb_e)) + - absorb_Z*self.space_cross_h(absorb_normal, absorb_h)), + absorb_h + 1/2*( + self.space_cross_e(absorb_normal, self.space_cross_h( + absorb_normal, absorb_h)) + + absorb_Y*self.space_cross_e(absorb_normal, absorb_e))) + + return bc + + def incident_bc(self, w=None): + "Flux terms for incident boundary conditions" + # NOTE: Untested for inhomogeneous materials, but would usually be + # physically meaningless anyway (are there exceptions to this?) + + e, h = self.split_eh(self.field_placeholder(w)) + if not self.fixed_material: + from warnings import warn + if self.incident_tag != hedge.mesh.TAG_NONE: + warn("Incident boundary conditions assume homogeneous" + " background material, results may be unphysical") + + from hedge.tools import count_subset + fld_cnt = count_subset(self.get_eh_subset()) + + from hedge.tools import is_zero + incident_bc_data = self.incident_bc_data(self, e, h) + if is_zero(incident_bc_data): + return make_obj_array([0]*fld_cnt) + else: + return cse(-incident_bc_data) + + def op_template(self, w=None): + """The full operator template - the high level description of + the Maxwell operator. + + Combines the relevant operator templates for spatial + derivatives, flux, boundary conditions etc. + """ + from hedge.tools import join_fields + w = self.field_placeholder(w) + + if self.fixed_material: + flux_w = w + else: + epsilon = self.epsilon + mu = self.mu + + flux_w = join_fields(epsilon, mu, w) + + from hedge.optemplate import BoundaryPair, \ + InverseMassOperator, get_flux_operator + + flux_op = get_flux_operator(self.flux(self.flux_type)) + bdry_flux_op = get_flux_operator(self.flux(self.bdry_flux_type)) + + from hedge.tools.indexing import count_subset + elec_components = count_subset(self.get_eh_subset()[0:3]) + mag_components = count_subset(self.get_eh_subset()[3:6]) + + if self.fixed_material: + # need to check this + material_divisor = ( + [self.epsilon]*elec_components+[self.mu]*mag_components) + else: + material_divisor = join_fields( + [epsilon]*elec_components, + [mu]*mag_components) + + tags_and_bcs = [ + (self.pec_tag, self.pec_bc(w)), + (self.pmc_tag, self.pmc_bc(w)), + (self.absorb_tag, self.absorbing_bc(w)), + (self.incident_tag, self.incident_bc(w)), + ] + + def make_flux_bc_vector(tag, bc): + if self.fixed_material: + return bc + else: + from hedge.optemplate import BoundarizeOperator + return join_fields( + cse(BoundarizeOperator(tag)(epsilon)), + cse(BoundarizeOperator(tag)(mu)), + bc) + + return ( + - self.local_derivatives(w) + + InverseMassOperator()( + flux_op(flux_w) + + sum( + bdry_flux_op(BoundaryPair( + flux_w, make_flux_bc_vector(tag, bc), tag)) + for tag, bc in tags_and_bcs)) + ) / material_divisor + + def bind(self, discr): + "Convert the abstract operator template into compiled code." + from hedge.mesh import check_bc_coverage + check_bc_coverage(discr.mesh, [ + self.pec_tag, self.absorb_tag, self.incident_tag]) + + compiled_op_template = discr.compile(self.op_template()) + + def rhs(t, w, **extra_context): + kwargs = {} + kwargs.update(extra_context) + + return compiled_op_template(w=w, t=t, **kwargs) + + return rhs + + def assemble_eh(self, e=None, h=None, discr=None): + "Combines separate E and H vectors into a single array." + if discr is None: + def zero(): + return 0 + else: + def zero(): + return discr.volume_zeros() + + from hedge.tools import count_subset + e_components = count_subset(self.get_eh_subset()[0:3]) + h_components = count_subset(self.get_eh_subset()[3:6]) + + def default_fld(fld, comp): + if fld is None: + return [zero() for i in xrange(comp)] + else: + return fld + + e = default_fld(e, e_components) + h = default_fld(h, h_components) + + from hedge.tools import join_fields + return join_fields(e, h) + + assemble_fields = assemble_eh + + @memoize_method + def partial_to_eh_subsets(self): + """Helps find the indices of the E and H components, which can vary + depending on number of dimensions and whether we have a full/TE/TM + operator. + """ + + e_subset = self.get_eh_subset()[0:3] + h_subset = self.get_eh_subset()[3:6] + + from hedge.tools import partial_to_all_subset_indices + return tuple(partial_to_all_subset_indices( + [e_subset, h_subset])) + + def split_eps_mu_eh(self, w): + """Splits an array into epsilon, mu, E and H components. + + Only used for fluxes. + """ + e_idx, h_idx = self.partial_to_eh_subsets() + epsilon, mu, e, h = w[[0]], w[[1]], w[e_idx+2], w[h_idx+2] + + from hedge.flux import FluxVectorPlaceholder as FVP + if isinstance(w, FVP): + return ( + FVP(scalars=epsilon), + FVP(scalars=mu), + FVP(scalars=e), + FVP(scalars=h)) + else: + return epsilon, mu, make_obj_array(e), make_obj_array(h) + + def split_eh(self, w): + "Splits an array into E and H components" + e_idx, h_idx = self.partial_to_eh_subsets() + e, h = w[e_idx], w[h_idx] + + from hedge.flux import FluxVectorPlaceholder as FVP + if isinstance(w, FVP): + return FVP(scalars=e), FVP(scalars=h) + else: + return make_obj_array(e), make_obj_array(h) + + def get_eh_subset(self): + """Return a 6-tuple of :class:`bool` objects indicating whether field + components are to be computed. The fields are numbered in the order + specified in the class documentation. + """ + return 6*(True,) + + def max_eigenvalue_expr(self): + """Return the largest eigenvalue of Maxwell's equations as a hyperbolic + system. + """ + from math import sqrt + if self.fixed_material: + return 1/sqrt(self.epsilon*self.mu) # a number + else: + import hedge.optemplate as sym + return sym.NodalMax()(1/sym.CFunction("sqrt")(self.epsilon*self.mu)) + + def max_eigenvalue(self, t, fields=None, discr=None, context={}): + if self.fixed_material: + return self.max_eigenvalue_expr() + else: + raise ValueError("max_eigenvalue is no longer supported for " + "variable-coefficient problems--use max_eigenvalue_expr") + + +class TMMaxwellOperator(MaxwellOperator): + """A 2D TM Maxwell operator with PEC boundaries. + + Field order is [Ez Hx Hy]. + """ + + _default_dimensions = 2 + + def get_eh_subset(self): + return ( + (False, False, True) # only ez + + + (True, True, False) # hx and hy + ) + + +class TEMaxwellOperator(MaxwellOperator): + """A 2D TE Maxwell operator. + + Field order is [Ex Ey Hz]. + """ + + _default_dimensions = 2 + + def get_eh_subset(self): + return ( + (True, True, False) # ex and ey + + + (False, False, True) # only hz + ) + + +class TE1DMaxwellOperator(MaxwellOperator): + """A 1D TE Maxwell operator. + + Field order is [Ex Ey Hz]. + """ + + _default_dimensions = 1 + + def get_eh_subset(self): + return ( + (True, True, False) + + + (False, False, True) + ) + + +class SourceFree1DMaxwellOperator(MaxwellOperator): + """A 1D TE Maxwell operator. + + Field order is [Ey Hz]. + """ + + _default_dimensions = 1 + + def get_eh_subset(self): + return ( + (False, True, False) + + + (False, False, True) + ) diff --git a/grudge/models/gas_dynamics/__init__.py b/grudge/models/gas_dynamics/__init__.py new file mode 100644 index 00000000..8392a4dd --- /dev/null +++ b/grudge/models/gas_dynamics/__init__.py @@ -0,0 +1,918 @@ +"""Operator for compressible Navier-Stokes and Euler equations.""" + +from __future__ import division + +__copyright__ = "Copyright (C) 2007 Hendrik Riedmann, Andreas Kloeckner" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + + + + +import numpy +import hedge.tools +import hedge.mesh +import hedge.data +from hedge.models import TimeDependentOperator +from pytools import Record +from hedge.tools import is_zero +from hedge.second_order import ( + StabilizedCentralSecondDerivative, + CentralSecondDerivative, + IPDGSecondDerivative) +from hedge.optemplate.primitives import make_common_subexpression as cse +from pytools import memoize_method +from hedge.optemplate.tools import make_sym_vector +from pytools.obj_array import make_obj_array, join_fields + +AXES = ["x", "y", "z", "w"] + +from hedge.optemplate.operators import ( + QuadratureGridUpsampler, + QuadratureInteriorFacesGridUpsampler) + +to_vol_quad = QuadratureGridUpsampler("gasdyn_vol") + +# It is recommended (though not required) that these two +# remain the same so that they can be computed together +# by the CUDA backend + +to_int_face_quad = QuadratureInteriorFacesGridUpsampler("gasdyn_face") +to_bdry_quad = QuadratureGridUpsampler("gasdyn_face") + + + + +# {{{ equations of state +class EquationOfState(object): + def q_to_p(self, op, q): + raise NotImplementedError + + def p_to_e(self, p, rho, u): + raise NotImplementedError + +class GammaLawEOS(EquationOfState): + # FIXME Shouldn't gamma only occur in the equation of state? + # I.e. shouldn't all uses of gamma go through the EOS? + + def __init__(self, gamma): + self.gamma = gamma + + def q_to_p(self, op, q): + return (self.gamma-1)*(op.e(q)-0.5*numpy.dot(op.rho_u(q), op.u(q))) + + def p_to_e(self, p, rho, u): + return p / (self.gamma - 1) + rho / 2 * numpy.dot(u, u) + +class PolytropeEOS(GammaLawEOS): + # inverse is same as superclass + + def q_to_p(self, op, q): + return op.rho(q)**self.gamma + +# }}} + + + + + +class GasDynamicsOperator(TimeDependentOperator): + """An nD Navier-Stokes and Euler operator. + + see JSH, TW: Nodal Discontinuous Galerkin Methods p.320 and p.206 + + dq/dt = d/dx * (-F + tau_:1) + d/dy * (-G + tau_:2) + + where e.g. in 2D + + q = (rho, rho_u_x, rho_u_y, E) + F = (rho_u_x, rho_u_x^2 + p, rho_u_x * rho_u_y / rho, u_x * (E + p)) + G = (rho_u_y, rho_u_x * rho_u_y / rho, rho_u_y^2 + p, u_y * (E + p)) + + tau_11 = mu * (2 * du/dx - 2/3 * (du/dx + dv/dy)) + tau_12 = mu * (du/dy + dv/dx) + tau_21 = tau_12 + tau_22 = mu * (2 * dv/dy - 2/3 * (du/dx + dv/dy)) + tau_31 = u * tau_11 + v * tau_12 + tau_32 = u * tau_21 + v * tau_22 + + For the heat flux: + + q = -k * nabla * T + k = c_p * mu / Pr + + Field order is [rho E rho_u_x rho_u_y ...]. + """ + + # {{{ initialization ------------------------------------------------------ + def __init__(self, dimensions, + gamma=None, mu=0, + bc_inflow=None, + bc_outflow=None, + bc_noslip=None, + bc_supersonic_inflow=None, + prandtl=None, spec_gas_const=1.0, + equation_of_state=None, + inflow_tag="inflow", + outflow_tag="outflow", + noslip_tag="noslip", + wall_tag="wall", + supersonic_inflow_tag="supersonic_inflow", + supersonic_outflow_tag="supersonic_outflow", + source=None, + second_order_scheme=CentralSecondDerivative(), + artificial_viscosity_mode=None, + ): + """ + :param source: should implement + :class:`hedge.data.IFieldDependentGivenFunction` + or be None. + + :param artificial_viscosity_mode: + """ + from hedge.data import ( + TimeConstantGivenFunction, + ConstantGivenFunction) + + if gamma is not None: + if equation_of_state is not None: + raise ValueError("can only specify one of gamma and equation_of_state") + + from warnings import warn + warn("argument gamma is deprecated in favor of equation_of_state", + DeprecationWarning, stacklevel=2) + + equation_of_state = GammaLawEOS(gamma) + + dull_bc = TimeConstantGivenFunction( + ConstantGivenFunction(make_obj_array( + [1, 1] + [0]*dimensions))) + if bc_inflow is None: + bc_inflow = dull_bc + if bc_outflow is None: + bc_outflow = dull_bc + if bc_noslip is None: + bc_noslip = dull_bc + if bc_supersonic_inflow is None: + bc_supersonic_inflow = dull_bc + + self.dimensions = dimensions + + self.prandtl = prandtl + self.spec_gas_const = spec_gas_const + self.mu = mu + + self.bc_inflow = bc_inflow + self.bc_outflow = bc_outflow + self.bc_noslip = bc_noslip + self.bc_supersonic_inflow = bc_supersonic_inflow + + self.inflow_tag = inflow_tag + self.outflow_tag = outflow_tag + self.noslip_tag = noslip_tag + self.wall_tag = wall_tag + self.supersonic_inflow_tag = supersonic_inflow_tag + self.supersonic_outflow_tag = supersonic_outflow_tag + + self.source = source + self.equation_of_state = equation_of_state + + self.second_order_scheme = second_order_scheme + + if artificial_viscosity_mode not in [ + "cns", "diffusion", "blended", None]: + raise ValueError("artificial_viscosity_mode has an invalid value") + + self.artificial_viscosity_mode = artificial_viscosity_mode + + + + # }}} + + # {{{ conversions --------------------------------------------------------- + def state(self): + return make_sym_vector("q", self.dimensions+2) + + @memoize_method + def volq_state(self): + return cse(to_vol_quad(self.state()), "vol_quad_state") + + @memoize_method + def faceq_state(self): + return cse(to_int_face_quad(self.state()), "face_quad_state") + + @memoize_method + def sensor(self): + from hedge.optemplate.primitives import Field + sensor = Field("sensor") + + def rho(self, q): + return q[0] + + def e(self, q): + return q[1] + + def rho_u(self, q): + return q[2:2+self.dimensions] + + def u(self, q): + return make_obj_array([ + rho_u_i/self.rho(q) + for rho_u_i in self.rho_u(q)]) + + def p(self,q): + return self.equation_of_state.q_to_p(self, q) + + def cse_u(self, q): + return cse(self.u(q), "u") + + def cse_rho(self, q): + return cse(self.rho(q), "rho") + + def cse_rho_u(self, q): + return cse(self.rho_u(q), "rho_u") + + def cse_p(self, q): + return cse(self.p(q), "p") + + def temperature(self, q): + c_v = 1 / (self.equation_of_state.gamma - 1) * self.spec_gas_const + return (self.e(q)/self.rho(q) - 0.5 * numpy.dot(self.u(q), self.u(q))) / c_v + + def cse_temperature(self, q): + return cse(self.temperature(q), "temperature") + + def get_mu(self, q, to_quad_op): + """ + :param to_quad_op: If not *None*, represents an operator which transforms + nodal values onto a quadrature grid on which the returned :math:`\mu` + needs to be represented. In that case, *q* is assumed to already be on the + same quadrature grid. + """ + + if to_quad_op is None: + def to_quad_op(x): + return x + + if self.mu == "sutherland": + # Sutherland's law: !!!not tested!!! + t_s = 110.4 + mu_inf = 1.735e-5 + result = cse( + mu_inf * self.cse_temperature(q) ** 1.5 * (1 + t_s) + / (self.cse_temperature(q) + t_s), + "sutherland_mu") + else: + result = self.mu + + if self.artificial_viscosity_mode == "cns": + mapped_sensor = self.sensor() + else: + mapped_sensor = None + + if mapped_sensor is not None: + result = result + cse(to_quad_op(mapped_sensor), "quad_sensor") + + return cse(result, "mu") + + def primitive_to_conservative(self, prims, use_cses=True): + if not use_cses: + from hedge.optemplate.primitives import make_common_subexpression as cse + else: + def cse(x, name): return x + + rho = prims[0] + p = prims[1] + u = prims[2:] + e = self.equation_of_state.p_to_e(p, rho, u) + + return join_fields( + rho, + cse(e, "e"), + cse(rho * u, "rho_u")) + + def conservative_to_primitive(self, q, use_cses=True): + if use_cses: + from hedge.optemplate.primitives import make_common_subexpression as cse + else: + def cse(x, name): return x + + return join_fields( + self.rho(q), + self.p(q), + self.u(q)) + + def characteristic_velocity_optemplate(self, state): + from hedge.optemplate.operators import ElementwiseMaxOperator + + from hedge.optemplate.primitives import CFunction + sqrt = CFunction("sqrt") + + sound_speed = cse(sqrt( + self.equation_of_state.gamma*self.cse_p(state)/self.cse_rho(state)), + "sound_speed") + u = self.cse_u(state) + speed = cse(sqrt(numpy.dot(u, u)), "norm_u") + sound_speed + return ElementwiseMaxOperator()(speed) + + def bind_characteristic_velocity(self, discr): + state = make_sym_vector("q", self.dimensions+2) + + compiled = discr.compile( + self.characteristic_velocity_optemplate(state)) + + def do(q): + return compiled(q=q) + + return do + + # }}} + + # {{{ helpers for second-order part --------------------------------------- + + # {{{ compute gradient of state --------------------------------------- + def grad_of(self, var, faceq_var): + from hedge.second_order import SecondDerivativeTarget + grad_tgt = SecondDerivativeTarget( + self.dimensions, strong_form=False, + operand=var, + int_flux_operand=faceq_var, + bdry_flux_int_operand=faceq_var) + + self.second_order_scheme.grad(grad_tgt, + bc_getter=self.get_boundary_condition_for, + dirichlet_tags=self.get_boundary_tags(), + neumann_tags=[]) + + return grad_tgt.minv_all + + def grad_of_state(self): + dimensions = self.dimensions + + state = self.state() + + dq = numpy.zeros((len(state), dimensions), dtype=object) + + for i in range(len(state)): + dq[i,:] = self.grad_of( + state[i], self.faceq_state()[i]) + + return dq + + def grad_of_state_func(self, func, of_what_descr): + return cse(self.grad_of( + func(self.volq_state()), + func(self.faceq_state())), + "grad_"+of_what_descr) + + # }}} + + # {{{ viscous stress tensor + + def tau(self, to_quad_op, state, mu=None): + faceq_state = self.faceq_state() + + dimensions = self.dimensions + + # {{{ compute gradient of u --------------------------------------- + # Use the product rule to compute the gradient of + # u from the gradient of (rho u). This ensures we don't + # compute the derivatives twice. + + from pytools.obj_array import with_object_array_or_scalar + dq = with_object_array_or_scalar( + to_quad_op, self.grad_of_state()) + + q = cse(to_quad_op(state)) + + du = numpy.zeros((dimensions, dimensions), dtype=object) + for i in range(dimensions): + for j in range(dimensions): + du[i,j] = cse( + (dq[i+2,j] - self.cse_u(q)[i] * dq[0,j]) / self.rho(q), + "du%d_d%s" % (i, AXES[j])) + + # }}} + + # {{{ put together viscous stress tau ----------------------------- + from pytools import delta + + if mu is None: + mu = self.get_mu(q, to_quad_op) + + tau = numpy.zeros((dimensions, dimensions), dtype=object) + for i in range(dimensions): + for j in range(dimensions): + tau[i,j] = cse(mu * cse(du[i,j] + du[j,i] - + 2/self.dimensions * delta(i,j) * numpy.trace(du)), + "tau_%d%d" % (i, j)) + + return tau + + # }}} + + # }}} + + # }}} + + # {{{ heat conduction + + def heat_conduction_coefficient(self, to_quad_op): + mu = self.get_mu(self.state(), to_quad_op) + if self.prandtl is None or numpy.isinf(self.prandtl): + return 0 + + eos = self.equation_of_state + return (mu / self.prandtl) * (eos.gamma / (eos.gamma-1)) + + def heat_conduction_grad(self, to_quad_op): + grad_p_over_rho = self.grad_of_state_func( + lambda state: self.p(state)/self.rho(state), + "p_over_rho") + + return (self.heat_conduction_coefficient(to_quad_op) + * to_quad_op(grad_p_over_rho)) + + # }}} + + # {{{ flux + + def flux(self, q): + from pytools import delta + + return [ # one entry for each flux direction + cse(join_fields( + # flux rho + self.rho_u(q)[i], + + # flux E + cse(self.e(q)+self.cse_p(q))*self.cse_u(q)[i], + + # flux rho_u + make_obj_array([ + self.rho_u(q)[i]*self.cse_u(q)[j] + + delta(i,j) * self.cse_p(q) + for j in range(self.dimensions) + ]) + ), "%s_flux" % AXES[i]) + for i in range(self.dimensions)] + + # }}} + + # {{{ boundary conditions --------------------------------------------- + + def make_bc_info(self, bc_name, tag, state, state0=None): + """ + :param state0: The boundary 'free-stream' state around which the + BC is linearized. + """ + if state0 is None: + state0 = make_sym_vector(bc_name, self.dimensions+2) + + state0 = cse(to_bdry_quad(state0)) + + rho0 = self.rho(state0) + p0 = self.cse_p(state0) + u0 = self.cse_u(state0) + + c0 = (self.equation_of_state.gamma * p0 / rho0)**0.5 + + from hedge.optemplate import BoundarizeOperator + bdrize_op = BoundarizeOperator(tag) + + class SingleBCInfo(Record): + pass + + return SingleBCInfo( + rho0=rho0, p0=p0, u0=u0, c0=c0, + + # notation: suffix "m" for "minus", i.e. "interior" + drhom=cse(self.rho(cse(to_bdry_quad(bdrize_op(state)))) + - rho0, "drhom"), + dumvec=cse(self.cse_u(cse(to_bdry_quad(bdrize_op(state)))) + - u0, "dumvec"), + dpm=cse(self.cse_p(cse(to_bdry_quad(bdrize_op(state)))) + - p0, "dpm")) + + def outflow_state(self, state): + from hedge.optemplate import make_normal + normal = make_normal(self.outflow_tag, self.dimensions) + bc = self.make_bc_info("bc_q_out", self.outflow_tag, state) + + # see hedge/doc/maxima/euler.mac + return join_fields( + # bc rho + cse(bc.rho0 + + bc.drhom + numpy.dot(normal, bc.dumvec)*bc.rho0/(2*bc.c0) + - bc.dpm/(2*bc.c0*bc.c0), "bc_rho_outflow"), + + # bc p + cse(bc.p0 + + bc.c0*bc.rho0*numpy.dot(normal, bc.dumvec)/2 + bc.dpm/2, "bc_p_outflow"), + + # bc u + cse(bc.u0 + + bc.dumvec - normal*numpy.dot(normal, bc.dumvec)/2 + + bc.dpm*normal/(2*bc.c0*bc.rho0), "bc_u_outflow")) + + def inflow_state_inner(self, normal, bc, name): + # see hedge/doc/maxima/euler.mac + return join_fields( + # bc rho + cse(bc.rho0 + + numpy.dot(normal, bc.dumvec)*bc.rho0/(2*bc.c0) + bc.dpm/(2*bc.c0*bc.c0), "bc_rho_"+name), + + # bc p + cse(bc.p0 + + bc.c0*bc.rho0*numpy.dot(normal, bc.dumvec)/2 + bc.dpm/2, "bc_p_"+name), + + # bc u + cse(bc.u0 + + normal*numpy.dot(normal, bc.dumvec)/2 + bc.dpm*normal/(2*bc.c0*bc.rho0), "bc_u_"+name)) + + def inflow_state(self, state): + from hedge.optemplate import make_normal + normal = make_normal(self.inflow_tag, self.dimensions) + bc = self.make_bc_info("bc_q_in", self.inflow_tag, state) + return self.inflow_state_inner(normal, bc, "inflow") + + def noslip_state(self, state): + from hedge.optemplate import make_normal + state0 = join_fields( + make_sym_vector("bc_q_noslip", 2), + [0]*self.dimensions) + normal = make_normal(self.noslip_tag, self.dimensions) + bc = self.make_bc_info("bc_q_noslip", self.noslip_tag, state, state0) + return self.inflow_state_inner(normal, bc, "noslip") + + def wall_state(self, state): + from hedge.optemplate import BoundarizeOperator + bc = BoundarizeOperator(self.wall_tag)(state) + wall_rho = self.rho(bc) + wall_e = self.e(bc) # <3 eve + wall_rho_u = self.rho_u(bc) + + from hedge.optemplate import make_normal + normal = make_normal(self.wall_tag, self.dimensions) + + return join_fields( + wall_rho, + wall_e, + wall_rho_u - 2*numpy.dot(wall_rho_u, normal) * normal) + + @memoize_method + def get_primitive_boundary_conditions(self): + state = self.state() + + return { + self.outflow_tag: self.outflow_state(state), + self.inflow_tag: self.inflow_state(state), + self.noslip_tag: self.noslip_state(state) + } + + + @memoize_method + def get_conservative_boundary_conditions(self): + state = self.state() + + from hedge.optemplate import BoundarizeOperator + return { + self.supersonic_inflow_tag: + make_sym_vector("bc_q_supersonic_in", self.dimensions+2), + self.supersonic_outflow_tag: + BoundarizeOperator(self.supersonic_outflow_tag)( + (state)), + self.wall_tag: self.wall_state(state), + } + + @memoize_method + def get_boundary_tags(self): + return (set(self.get_primitive_boundary_conditions().keys()) + | set(self.get_conservative_boundary_conditions().keys())) + + @memoize_method + def _normalize_expr(self, expr): + """Normalize expressions for use as hash keys.""" + from hedge.optemplate.mappers import ( + QuadratureUpsamplerRemover, + CSERemover) + + return CSERemover()( + QuadratureUpsamplerRemover({}, do_warn=False)(expr)) + + @memoize_method + def _get_norm_primitive_exprs(self): + return [ + self._normalize_expr(expr) for expr in + self.conservative_to_primitive(self.state()) + ] + + @memoize_method + def get_boundary_condition_for(self, tag, expr): + prim_bcs = self.get_primitive_boundary_conditions() + cons_bcs = self.get_conservative_boundary_conditions() + + if tag in prim_bcs: + # BC is given in primitive variables, avoid converting + # to conservative and back. + try: + norm_expr = self._normalize_expr(expr) + prim_idx = self._get_norm_primitive_exprs().index(norm_expr) + except ValueError: + cbstate = self.primitive_to_conservative( + prim_bcs[tag]) + else: + return prim_bcs[tag][prim_idx] + else: + # BC is given in conservative variables, no potential + # for optimization. + + cbstate = to_bdry_quad(cons_bcs[tag]) + + # 'cbstate' is the boundary state in conservative variables. + + from hedge.optemplate.mappers import QuadratureUpsamplerRemover + expr = QuadratureUpsamplerRemover({}, do_warn=False)(expr) + + def subst_func(expr): + from pymbolic.primitives import Subscript, Variable + + if isinstance(expr, Subscript): + assert (isinstance(expr.aggregate, Variable) + and expr.aggregate.name == "q") + + return cbstate[expr.index] + elif isinstance(expr, Variable) and expr.name =="sensor": + from hedge.optemplate import BoundarizeOperator + result = BoundarizeOperator(tag)(self.sensor()) + return cse(to_bdry_quad(result), "bdry_sensor") + + from hedge.optemplate import SubstitutionMapper + return SubstitutionMapper(subst_func)(expr) + + # }}} + + # {{{ second order part + def div(self, vol_operand, int_face_operand): + from hedge.second_order import SecondDerivativeTarget + div_tgt = SecondDerivativeTarget( + self.dimensions, strong_form=False, + operand=vol_operand, + int_flux_operand=int_face_operand) + + self.second_order_scheme.div(div_tgt, + bc_getter=self.get_boundary_condition_for, + dirichlet_tags=list(self.get_boundary_tags()), + neumann_tags=[]) + + return div_tgt.minv_all + + def make_second_order_part(self): + state = self.state() + faceq_state = self.faceq_state() + volq_state = self.volq_state() + + volq_tau_mat = self.tau(to_vol_quad, state) + faceq_tau_mat = self.tau(to_int_face_quad, state) + + return join_fields( + 0, + self.div( + numpy.sum(volq_tau_mat*self.cse_u(volq_state), axis=1) + + self.heat_conduction_grad(to_vol_quad) + , + numpy.sum(faceq_tau_mat*self.cse_u(faceq_state), axis=1) + + self.heat_conduction_grad(to_int_face_quad) + , + ), + [ + self.div(volq_tau_mat[i], faceq_tau_mat[i]) + for i in range(self.dimensions)] + ) + + # }}} + + # {{{ operator template --------------------------------------------------- + def make_extra_terms(self): + return 0 + + def op_template(self, sensor_scaling=None, viscosity_only=False): + u = self.cse_u + rho = self.cse_rho + rho_u = self.rho_u + p = self.p + e = self.e + + # {{{ artificial diffusion + def make_artificial_diffusion(): + if self.artificial_viscosity_mode not in ["diffusion"]: + return 0 + + dq = self.grad_of_state() + + return make_obj_array([ + self.div( + to_vol_quad(self.sensor())*to_vol_quad(dq[i]), + to_int_face_quad(self.sensor())*to_int_face_quad(dq[i])) + for i in range(dq.shape[0])]) + # }}} + + # {{{ state setup + + volq_flux = self.flux(self.volq_state()) + faceq_flux = self.flux(self.faceq_state()) + + from hedge.optemplate.primitives import CFunction + sqrt = CFunction("sqrt") + + speed = self.characteristic_velocity_optemplate(self.state()) + + has_viscosity = not is_zero(self.get_mu(self.state(), to_quad_op=None)) + + # }}} + + # {{{ operator assembly ----------------------------------------------- + from hedge.flux.tools import make_lax_friedrichs_flux + from hedge.optemplate.operators import InverseMassOperator + + from hedge.optemplate.tools import make_stiffness_t + + primitive_bcs_as_quad_conservative = dict( + (tag, self.primitive_to_conservative(to_bdry_quad(bc))) + for tag, bc in + self.get_primitive_boundary_conditions().iteritems()) + + def get_bc_tuple(tag): + state = self.state() + bc = make_obj_array([ + self.get_boundary_condition_for(tag, s_i) for s_i in state]) + return tag, bc, self.flux(bc) + + first_order_part = InverseMassOperator()( + numpy.dot(make_stiffness_t(self.dimensions), volq_flux) + - make_lax_friedrichs_flux( + wave_speed=cse(to_int_face_quad(speed), "emax_c"), + + state=self.faceq_state(), fluxes=faceq_flux, + bdry_tags_states_and_fluxes=[ + get_bc_tuple(tag) for tag in self.get_boundary_tags()], + strong=False)) + + if viscosity_only: + first_order_part = 0*first_order_part + + result = join_fields( + first_order_part + + self.make_second_order_part() + + make_artificial_diffusion() + + self.make_extra_terms(), + speed) + + if self.source is not None: + result = result + join_fields( + make_sym_vector("source_vect", len(self.state())), + # extra field for speed + 0) + + return result + + # }}} + + # }}} + + # {{{ operator binding ---------------------------------------------------- + def bind(self, discr, sensor=None, sensor_scaling=None, viscosity_only=False): + if (sensor is None and + self.artificial_viscosity_mode is not None): + raise ValueError("must specify a sensor if using " + "artificial viscosity") + + bound_op = discr.compile(self.op_template( + sensor_scaling=sensor_scaling, + viscosity_only=False)) + + from hedge.mesh import check_bc_coverage + check_bc_coverage(discr.mesh, [ + self.inflow_tag, + self.outflow_tag, + self.noslip_tag, + self.wall_tag, + self.supersonic_inflow_tag, + self.supersonic_outflow_tag, + ]) + + if self.mu == 0 and not discr.get_boundary(self.noslip_tag).is_empty(): + raise RuntimeError("no-slip BCs only make sense for " + "viscous problems") + + def rhs(t, q): + extra_kwargs = {} + if self.source is not None: + extra_kwargs["source_vect"] = self.source.volume_interpolant( + t, q, discr) + + if sensor is not None: + extra_kwargs["sensor"] = sensor(q) + + opt_result = bound_op(q=q, + bc_q_in=self.bc_inflow.boundary_interpolant( + t, discr, self.inflow_tag), + bc_q_out=self.bc_outflow.boundary_interpolant( + t, discr, self.outflow_tag), + bc_q_noslip=self.bc_noslip.boundary_interpolant( + t, discr, self.noslip_tag), + bc_q_supersonic_in=self.bc_supersonic_inflow + .boundary_interpolant(t, discr, + self.supersonic_inflow_tag), + **extra_kwargs + ) + + max_speed = opt_result[-1] + ode_rhs = opt_result[:-1] + return ode_rhs, discr.nodewise_max(max_speed) + + return rhs + + # }}} + + # {{{ timestep estimation ------------------------------------------------- + + def estimate_timestep(self, discr, + stepper=None, stepper_class=None, stepper_args=None, + t=None, max_eigenvalue=None): + u"""Estimate the largest stable timestep, given a time stepper + `stepper_class`. If none is given, RK4 is assumed. + """ + + dg_factor = (discr.dt_non_geometric_factor() + * discr.dt_geometric_factor()) + + # see JSH/TW, eq. (7.32) + rk4_dt = dg_factor / (max_eigenvalue + self.mu / dg_factor) + + from hedge.timestep.stability import \ + approximate_rk4_relative_imag_stability_region + return rk4_dt * approximate_rk4_relative_imag_stability_region( + stepper, stepper_class, stepper_args) + + # }}} + + + + +# {{{ limiter (unfinished, deprecated) +class SlopeLimiter1NEuler: + def __init__(self, discr, gamma, dimensions, op): + """Construct a limiter from Jan's book page 225 + """ + self.discr = discr + self.gamma=gamma + self.dimensions=dimensions + self.op=op + + from hedge.optemplate.operators import AveragingOperator + self.get_average = AveragingOperator().bind(discr) + + def __call__(self, fields): + from hedge.tools import join_fields + + #get conserved fields + rho=self.op.rho(fields) + e=self.op.e(fields) + rho_velocity=self.op.rho_u(fields) + + #get primitive fields + #to do + + #reset field values to cell average + rhoLim=self.get_average(rho) + eLim=self.get_average(e) + temp=join_fields([self.get_average(rho_vel) + for rho_vel in rho_velocity]) + + #should do for primitive fields too + + return join_fields(rhoLim, eLim, temp) + +# }}} + + +# vim: foldmethod=marker diff --git a/grudge/models/gas_dynamics/lbm.py b/grudge/models/gas_dynamics/lbm.py new file mode 100644 index 00000000..84446f8b --- /dev/null +++ b/grudge/models/gas_dynamics/lbm.py @@ -0,0 +1,210 @@ +# -*- coding: utf8 -*- +"""Lattice-Boltzmann operator.""" + +from __future__ import division + +__copyright__ = "Copyright (C) 2011 Andreas Kloeckner" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + +import numpy as np +import numpy.linalg as la +from hedge.models import HyperbolicOperator +from pytools.obj_array import make_obj_array + + + + + +class LBMMethodBase(object): + def __len__(self): + return len(self.direction_vectors) + + def find_opposites(self): + self.opposites = np.zeros(len(self)) + + for alpha in xrange(len(self)): + if self.opposites[alpha]: + continue + + found = False + for alpha_2 in xrange(alpha, len(self)): + if la.norm( + self.direction_vectors[alpha] + + self.direction_vectors[alpha_2]) < 1e-12: + self.opposites[alpha] = alpha_2 + self.opposites[alpha_2] = alpha + found = True + + if not found: + raise RuntimeError( + "direction %s had no opposite" + % self.direction_vectors[alpha]) + + + + +class D2Q9LBMMethod(LBMMethodBase): + def __init__(self): + self.dimensions = 2 + + alphas = np.arange(0, 9) + thetas = (alphas-1)*np.pi/2 + thetas[5:9] += np.pi/4 + + direction_vectors = np.vstack([ + np.cos(thetas), np.sin(thetas)]).T + + direction_vectors[0] *= 0 + direction_vectors[5:9] *= np.sqrt(2) + + direction_vectors[np.abs(direction_vectors) < 1e-12] = 0 + + self.direction_vectors = direction_vectors + + self.weights = np.array([4/9] + [1/9]*4 + [1/36]*4) + + self.speed_of_sound = 1/np.sqrt(3) + self.find_opposites() + + def f_equilibrium(self, rho, alpha, u): + e_alpha = self.direction_vectors[alpha] + c_s = self.speed_of_sound + return self.weights[alpha]*rho*( + 1 + + np.dot(e_alpha, u)/c_s**2 + + 1/2*np.dot(e_alpha, u)**2/c_s**4 + - 1/2*np.dot(u, u)/c_s**2) + + + + +class LatticeBoltzmannOperator(HyperbolicOperator): + def __init__(self, method, lbm_delta_t, nu, flux_type="upwind"): + self.method = method + self.lbm_delta_t = lbm_delta_t + self.nu = nu + + self.flux_type = flux_type + + @property + def tau(self): + return (self.nu + / + (self.lbm_delta_t*self.method.speed_of_sound**2)) + + def get_advection_flux(self, velocity): + from hedge.flux import make_normal, FluxScalarPlaceholder + from pymbolic.primitives import IfPositive + + u = FluxScalarPlaceholder(0) + normal = make_normal(self.method.dimensions) + + if self.flux_type == "central": + return u.avg*np.dot(normal, velocity) + elif self.flux_type == "lf": + return u.avg*np.dot(normal, velocity) \ + + 0.5*la.norm(v)*(u.int - u.ext) + elif self.flux_type == "upwind": + return (np.dot(normal, velocity)* + IfPositive(np.dot(normal, velocity), + u.int, # outflow + u.ext, # inflow + )) + else: + raise ValueError, "invalid flux type" + + def get_advection_op(self, q, velocity): + from hedge.optemplate import ( + BoundaryPair, + get_flux_operator, + make_stiffness_t, + InverseMassOperator) + + stiff_t = make_stiffness_t(self.method.dimensions) + + flux_op = get_flux_operator(self.get_advection_flux(velocity)) + return InverseMassOperator()( + np.dot(velocity, stiff_t*q) - flux_op(q)) + + def f_bar(self): + from hedge.optemplate import make_sym_vector + return make_sym_vector("f_bar", len(self.method)) + + def rho(self, f_bar): + return sum(f_bar) + + def rho_u(self, f_bar): + return sum( + dv_i * field_i + for dv_i, field_i in + zip(self.method.direction_vectors, f_bar)) + + def stream_rhs(self, f_bar): + return make_obj_array([ + self.get_advection_op(f_bar_alpha, e_alpha) + for e_alpha, f_bar_alpha in + zip(self.method.direction_vectors, f_bar)]) + + def collision_update(self, f_bar): + from hedge.optemplate.primitives import make_common_subexpression as cse + rho = cse(self.rho(f_bar), "rho") + rho_u = self.rho_u(f_bar) + u = cse(rho_u/rho, "u") + + f_eq_func = self.method.f_equilibrium + f_eq = make_obj_array([ + f_eq_func(rho, alpha, u) for alpha in range(len(self.method))]) + + return f_bar - 1/(self.tau+1/2)*(f_bar - f_eq) + + def bind_rhs(self, discr): + compiled_op_template = discr.compile( + self.stream_rhs(self.f_bar())) + + #from hedge.mesh import check_bc_coverage, TAG_ALL + #check_bc_coverage(discr.mesh, [TAG_ALL]) + + def rhs(t, f_bar): + return compiled_op_template(f_bar=f_bar) + + return rhs + + def bind(self, discr, what): + f_bar_sym = self.f_bar() + + from hedge.optemplate.mappers.type_inference import ( + type_info, NodalRepresentation) + + type_hints = dict( + (f_bar_i, type_info.VolumeVector(NodalRepresentation())) + for f_bar_i in f_bar_sym) + + compiled_op_template = discr.compile(what(f_bar_sym), type_hints=type_hints) + + def rhs(f_bar): + return compiled_op_template(f_bar=f_bar) + + return rhs + + def max_eigenvalue(self, t=None, fields=None, discr=None): + return max( + la.norm(v) for v in self.method.direction_vectors) diff --git a/grudge/models/nd_calculus.py b/grudge/models/nd_calculus.py new file mode 100644 index 00000000..5674fc9f --- /dev/null +++ b/grudge/models/nd_calculus.py @@ -0,0 +1,138 @@ +# -*- coding: utf8 -*- +"""Canned operators for multivariable calculus.""" + +from __future__ import division + +__copyright__ = "Copyright (C) 2009 Andreas Kloeckner" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + + + + +from hedge.models import Operator + + + + +class GradientOperator(Operator): + def __init__(self, dimensions): + self.dimensions = dimensions + + def flux(self): + from hedge.flux import make_normal, FluxScalarPlaceholder + u = FluxScalarPlaceholder() + + normal = make_normal(self.dimensions) + return u.int*normal - u.avg*normal + + def op_template(self): + from hedge.mesh import TAG_ALL + from hedge.optemplate import Field, BoundaryPair, \ + make_nabla, InverseMassOperator, get_flux_operator + + u = Field("u") + bc = Field("bc") + + nabla = make_nabla(self.dimensions) + flux_op = get_flux_operator(self.flux()) + + return nabla*u - InverseMassOperator()( + flux_op(u) + + flux_op(BoundaryPair(u, bc, TAG_ALL))) + + def bind(self, discr): + compiled_op_template = discr.compile(self.op_template()) + + def op(u): + from hedge.mesh import TAG_ALL + + return compiled_op_template(u=u, + bc=discr.boundarize_volume_field(u, TAG_ALL)) + + return op + + + + +class DivergenceOperator(Operator): + def __init__(self, dimensions, subset=None): + self.dimensions = dimensions + + if subset is None: + self.subset = dimensions * [True,] + else: + # chop off any extra dimensions + self.subset = subset[:dimensions] + + from hedge.tools import count_subset + self.arg_count = count_subset(self.subset) + + def flux(self): + from hedge.flux import make_normal, FluxVectorPlaceholder + + v = FluxVectorPlaceholder(self.arg_count) + + normal = make_normal(self.dimensions) + + flux = 0 + idx = 0 + + for i, i_enabled in enumerate(self.subset): + if i_enabled and i < self.dimensions: + flux += (v.int-v.avg)[idx]*normal[i] + idx += 1 + + return flux + + def op_template(self): + from hedge.mesh import TAG_ALL + from hedge.optemplate import make_sym_vector, BoundaryPair, \ + get_flux_operator, make_nabla, InverseMassOperator + + nabla = make_nabla(self.dimensions) + m_inv = InverseMassOperator() + + v = make_sym_vector("v", self.arg_count) + bc = make_sym_vector("bc", self.arg_count) + + local_op_result = 0 + idx = 0 + for i, i_enabled in enumerate(self.subset): + if i_enabled and i < self.dimensions: + local_op_result += nabla[i]*v[idx] + idx += 1 + + flux_op = get_flux_operator(self.flux()) + + return local_op_result - m_inv( + flux_op(v) + + flux_op(BoundaryPair(v, bc, TAG_ALL))) + + def bind(self, discr): + compiled_op_template = discr.compile(self.op_template()) + + def op(v): + from hedge.mesh import TAG_ALL + return compiled_op_template(v=v, + bc=discr.boundarize_volume_field(v, TAG_ALL)) + + return op diff --git a/grudge/models/pml.py b/grudge/models/pml.py new file mode 100644 index 00000000..b8cb6216 --- /dev/null +++ b/grudge/models/pml.py @@ -0,0 +1,287 @@ +# -*- coding: utf8 -*- +"""Models describing absorbing boundary layers.""" + +from __future__ import division + +__copyright__ = "Copyright (C) 2007 Andreas Kloeckner" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + + + + +import numpy + +from pytools import memoize_method, Record +from hedge.models.em import \ + MaxwellOperator, \ + TMMaxwellOperator, \ + TEMaxwellOperator + + + + +class AbarbanelGottliebPMLMaxwellOperator(MaxwellOperator): + """Implements a PML as in + + [1] S. Abarbanel and D. Gottlieb, "On the construction and analysis of absorbing + layers in CEM," Applied Numerical Mathematics, vol. 27, 1998, S. 331-340. + (eq 3.7-3.11) + + [2] E. Turkel and A. Yefet, "Absorbing PML + boundary layers for wave-like equations," + Applied Numerical Mathematics, vol. 27, + 1998, S. 533-557. + (eq. 4.10) + + [3] Abarbanel, D. Gottlieb, and J.S. Hesthaven, "Long Time Behavior of the + Perfectly Matched Layer Equations in Computational Electromagnetics," + Journal of Scientific Computing, vol. 17, Dez. 2002, S. 405-422. + + Generalized to 3D in doc/maxima/abarbanel-pml.mac. + """ + + class PMLCoefficients(Record): + __slots__ = ["sigma", "sigma_prime", "tau"] + # (tau=mu in [3] , to avoid confusion with permeability) + + def map(self, f): + return self.__class__( + **dict((name, f(getattr(self, name))) + for name in self.fields)) + + def __init__(self, *args, **kwargs): + self.add_decay = kwargs.pop("add_decay", True) + MaxwellOperator.__init__(self, *args, **kwargs) + + def pml_local_op(self, w): + sub_e, sub_h, sub_p, sub_q = self.split_ehpq(w) + + e_subset = self.get_eh_subset()[0:3] + h_subset = self.get_eh_subset()[3:6] + dim_subset = (True,) * self.dimensions + (False,) * (3-self.dimensions) + + def pad_vec(v, subset): + result = numpy.zeros((3,), dtype=object) + result[numpy.array(subset, dtype=bool)] = v + return result + + from hedge.optemplate import make_sym_vector + sig = pad_vec( + make_sym_vector("sigma", self.dimensions), + dim_subset) + sig_prime = pad_vec( + make_sym_vector("sigma_prime", self.dimensions), + dim_subset) + if self.add_decay: + tau = pad_vec( + make_sym_vector("tau", self.dimensions), + dim_subset) + else: + tau = numpy.zeros((3,)) + + e = pad_vec(sub_e, e_subset) + h = pad_vec(sub_h, h_subset) + p = pad_vec(sub_p, dim_subset) + q = pad_vec(sub_q, dim_subset) + + rhs = numpy.zeros(12, dtype=object) + + for mx in range(3): + my = (mx+1) % 3 + mz = (mx+2) % 3 + + from hedge.tools.mathematics import levi_civita + assert levi_civita((mx,my,mz)) == 1 + + rhs[mx] += -sig[my]/self.epsilon*(2*e[mx]+p[mx]) - 2*tau[my]/self.epsilon*e[mx] + rhs[my] += -sig[mx]/self.epsilon*(2*e[my]+p[my]) - 2*tau[mx]/self.epsilon*e[my] + rhs[3+mz] += 1/(self.epsilon*self.mu) * ( + sig_prime[mx] * q[mx] - sig_prime[my] * q[my]) + + rhs[6+mx] += sig[my]/self.epsilon*e[mx] + rhs[6+my] += sig[mx]/self.epsilon*e[my] + rhs[9+mx] += -sig[mx]/self.epsilon*q[mx] - (e[my] + e[mz]) + + from hedge.tools import full_to_subset_indices + sub_idx = full_to_subset_indices(e_subset+h_subset+dim_subset+dim_subset) + + return rhs[sub_idx] + + def op_template(self, w=None): + from hedge.tools import count_subset + fld_cnt = count_subset(self.get_eh_subset()) + if w is None: + from hedge.optemplate import make_sym_vector + w = make_sym_vector("w", fld_cnt+2*self.dimensions) + + from hedge.tools import join_fields + return join_fields( + MaxwellOperator.op_template(self, w[:fld_cnt]), + numpy.zeros((2*self.dimensions,), dtype=object) + ) + self.pml_local_op(w) + + def bind(self, discr, coefficients): + return MaxwellOperator.bind(self, discr, + sigma=coefficients.sigma, + sigma_prime=coefficients.sigma_prime, + tau=coefficients.tau) + + def assemble_ehpq(self, e=None, h=None, p=None, q=None, discr=None): + if discr is None: + def zero(): + return 0 + else: + def zero(): + return discr.volume_zeros() + + from hedge.tools import count_subset + e_components = count_subset(self.get_eh_subset()[0:3]) + h_components = count_subset(self.get_eh_subset()[3:6]) + + def default_fld(fld, comp): + if fld is None: + return [zero() for i in xrange(comp)] + else: + return fld + + e = default_fld(e, e_components) + h = default_fld(h, h_components) + p = default_fld(p, self.dimensions) + q = default_fld(q, self.dimensions) + + from hedge.tools import join_fields + return join_fields(e, h, p, q) + + @memoize_method + def partial_to_ehpq_subsets(self): + e_subset = self.get_eh_subset()[0:3] + h_subset = self.get_eh_subset()[3:6] + + dim_subset = [True] * self.dimensions + [False] * (3-self.dimensions) + + from hedge.tools import partial_to_all_subset_indices + return tuple(partial_to_all_subset_indices( + [e_subset, h_subset, dim_subset, dim_subset])) + + def split_ehpq(self, w): + e_idx, h_idx, p_idx, q_idx = self.partial_to_ehpq_subsets() + e, h, p, q = w[e_idx], w[h_idx], w[p_idx], w[q_idx] + + from hedge.flux import FluxVectorPlaceholder as FVP + if isinstance(w, FVP): + return FVP(scalars=e), FVP(scalars=h) + else: + from hedge.tools import make_obj_array as moa + return moa(e), moa(h), moa(p), moa(q) + + # sigma business ---------------------------------------------------------- + def _construct_scalar_coefficients(self, discr, node_coord, + i_min, i_max, o_min, o_max, exponent): + assert o_min < i_min <= i_max < o_max + + if o_min != i_min: + l_dist = (i_min - node_coord) / (i_min-o_min) + l_dist_prime = discr.volume_zeros(kind="numpy", dtype=node_coord.dtype) + l_dist_prime[l_dist >= 0] = -1 / (i_min-o_min) + l_dist[l_dist < 0] = 0 + else: + l_dist = l_dist_prime = numpy.zeros_like(node_coord) + + if i_max != o_max: + r_dist = (node_coord - i_max) / (o_max-i_max) + r_dist_prime = discr.volume_zeros(kind="numpy", dtype=node_coord.dtype) + r_dist_prime[r_dist >= 0] = 1 / (o_max-i_max) + r_dist[r_dist < 0] = 0 + else: + r_dist = r_dist_prime = numpy.zeros_like(node_coord) + + l_plus_r = l_dist+r_dist + return l_plus_r**exponent, \ + (l_dist_prime+r_dist_prime)*exponent*l_plus_r**(exponent-1), \ + l_plus_r + + def coefficients_from_boxes(self, discr, + inner_bbox, outer_bbox=None, + magnitude=None, tau_magnitude=None, + exponent=None, dtype=None): + if outer_bbox is None: + outer_bbox = discr.mesh.bounding_box() + + if exponent is None: + exponent = 2 + + if magnitude is None: + magnitude = 20 + + if tau_magnitude is None: + tau_magnitude = 0.4 + + # scale by free space conductivity + from math import sqrt + magnitude = magnitude*sqrt(self.epsilon/self.mu) + tau_magnitude = tau_magnitude*sqrt(self.epsilon/self.mu) + + i_min, i_max = inner_bbox + o_min, o_max = outer_bbox + + from hedge.tools import make_obj_array + + nodes = discr.nodes + if dtype is not None: + nodes = nodes.astype(dtype) + + sigma, sigma_prime, tau = zip(*[self._construct_scalar_coefficients( + discr, nodes[:,i], + i_min[i], i_max[i], o_min[i], o_max[i], + exponent) + for i in range(discr.dimensions)]) + + def conv(f): + return discr.convert_volume(f, kind=discr.compute_kind, + dtype=discr.default_scalar_type) + + return self.PMLCoefficients( + sigma=conv(magnitude*make_obj_array(sigma)), + sigma_prime=conv(magnitude*make_obj_array(sigma_prime)), + tau=conv(tau_magnitude*make_obj_array(tau))) + + def coefficients_from_width(self, discr, width, + magnitude=None, tau_magnitude=None, exponent=None, + dtype=None): + o_min, o_max = discr.mesh.bounding_box() + return self.coefficients_from_boxes(discr, + (o_min+width, o_max-width), + (o_min, o_max), + magnitude, tau_magnitude, exponent, dtype) + + + + +class AbarbanelGottliebPMLTEMaxwellOperator( + TEMaxwellOperator, AbarbanelGottliebPMLMaxwellOperator): + # not unimplemented--this IS the implementation. + pass + +class AbarbanelGottliebPMLTMMaxwellOperator( + TMMaxwellOperator, AbarbanelGottliebPMLMaxwellOperator): + # not unimplemented--this IS the implementation. + pass diff --git a/grudge/models/poisson.py b/grudge/models/poisson.py new file mode 100644 index 00000000..f60091d5 --- /dev/null +++ b/grudge/models/poisson.py @@ -0,0 +1,265 @@ +# -*- coding: utf8 -*- +"""Operators for Poisson problems.""" + +from __future__ import division + +__copyright__ = "Copyright (C) 2007 Andreas Kloeckner" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + + +import numpy as np + +from hedge.models import Operator +from hedge.second_order import LDGSecondDerivative +import hedge.data +import hedge.iterative + + +class LaplacianOperatorBase(object): + def op_template(self, apply_minv, u=None, dir_bc=None, neu_bc=None): + """ + :param apply_minv: :class:`bool` specifying whether to compute a complete + divergence operator. If False, the final application of the inverse + mass operator is skipped. This is used in :meth:`op` in order to + reduce the scheme :math:`M^{-1} S u = f` to :math:`S u = M f`, so + that the mass operator only needs to be applied once, when preparing + the right hand side in :meth:`prepare_rhs`. + + :class:`hedge.models.diffusion.DiffusionOperator` needs this. + """ + + from hedge.optemplate import Field, make_sym_vector + from hedge.second_order import SecondDerivativeTarget + + if u is None: + u = Field("u") + if dir_bc is None: + dir_bc = Field("dir_bc") + if neu_bc is None: + neu_bc = Field("neu_bc") + + # strong_form here allows IPDG to reuse the value of grad u. + grad_tgt = SecondDerivativeTarget( + self.dimensions, strong_form=True, + operand=u) + + def grad_bc_getter(tag, expr): + assert tag == self.dirichlet_tag + return dir_bc + self.scheme.grad(grad_tgt, + bc_getter=grad_bc_getter, + dirichlet_tags=[self.dirichlet_tag], + neumann_tags=[self.neumann_tag]) + + def apply_diff_tensor(v): + if isinstance(self.diffusion_tensor, np.ndarray): + sym_diff_tensor = self.diffusion_tensor + else: + sym_diff_tensor = (make_sym_vector( + "diffusion", self.dimensions**2) + .reshape(self.dimensions, self.dimensions)) + + return np.dot(sym_diff_tensor, v) + + div_tgt = SecondDerivativeTarget( + self.dimensions, strong_form=False, + operand=apply_diff_tensor(grad_tgt.minv_all)) + + def div_bc_getter(tag, expr): + if tag == self.dirichlet_tag: + return dir_bc + elif tag == self.neumann_tag: + return neu_bc + else: + assert False, "divergence bc getter " \ + "asked for '%s' BC for '%s'" % (tag, expr) + + self.scheme.div(div_tgt, + div_bc_getter, + dirichlet_tags=[self.dirichlet_tag], + neumann_tags=[self.neumann_tag]) + + if apply_minv: + return div_tgt.minv_all + else: + return div_tgt.all + + +class PoissonOperator(Operator, LaplacianOperatorBase): + """Implements the Local Discontinuous Galerkin (LDG) Method for elliptic + operators. + + See P. Castillo et al., + Local discontinuous Galerkin methods for elliptic problems", + Communications in Numerical Methods in Engineering 18, no. 1 (2002): 69-75. + """ + + def __init__(self, dimensions, diffusion_tensor=None, + dirichlet_bc=hedge.data.ConstantGivenFunction(), + dirichlet_tag="dirichlet", + neumann_bc=hedge.data.ConstantGivenFunction(), + neumann_tag="neumann", + scheme=LDGSecondDerivative()): + self.dimensions = dimensions + + self.scheme = scheme + + self.dirichlet_bc = dirichlet_bc + self.dirichlet_tag = dirichlet_tag + self.neumann_bc = neumann_bc + self.neumann_tag = neumann_tag + + if diffusion_tensor is None: + diffusion_tensor = np.eye(dimensions) + self.diffusion_tensor = diffusion_tensor + + # bound operator ---------------------------------------------------------- + def bind(self, discr): + """Return a :class:`BoundPoissonOperator`.""" + + assert self.dimensions == discr.dimensions + + from hedge.mesh import check_bc_coverage + check_bc_coverage(discr.mesh, [self.dirichlet_tag, self.neumann_tag]) + + return BoundPoissonOperator(self, discr) + + +class BoundPoissonOperator(hedge.iterative.OperatorBase): + """Returned by :meth:`PoissonOperator.bind`.""" + + def __init__(self, poisson_op, discr): + hedge.iterative.OperatorBase.__init__(self) + self.discr = discr + + pop = self.poisson_op = poisson_op + + op = pop.op_template( + apply_minv=False, dir_bc=0, neu_bc=0) + bc_op = pop.op_template(apply_minv=False) + + self.compiled_op = discr.compile(op) + self.compiled_bc_op = discr.compile(bc_op) + + if not isinstance(pop.diffusion_tensor, np.ndarray): + self.diffusion = pop.diffusion_tensor.volume_interpolant(discr) + + # Check whether use of Poincaré mean-value method is required. + # (for pure Neumann or pure periodic) + + from hedge.mesh import TAG_ALL + self.poincare_mean_value_hack = ( + len(self.discr.get_boundary(TAG_ALL).nodes) + == len(self.discr.get_boundary(poisson_op.neumann_tag).nodes)) + + @property + def dtype(self): + return self.discr.default_scalar_type + + @property + def shape(self): + nodes = len(self.discr) + return nodes, nodes + + def op(self, u): + context = {"u": u} + if not isinstance(self.poisson_op.diffusion_tensor, np.ndarray): + context["diffusion"] = self.diffusion + + result = self.compiled_op(**context) + + if self.poincare_mean_value_hack: + state_int = self.discr.integral(u) + mean_state = state_int / self.discr.mesh_volume() + return result - mean_state * self.discr._mass_ones() + else: + return result + + __call__ = op + + def prepare_rhs(self, rhs): + """Prepare the right-hand side for the linear system op(u)=rhs(f). + + In matrix form, LDG looks like this: + + .. math:: + Mv = Cu + g + Mf = Av + Bu + h + + where v is the auxiliary vector, u is the argument of the operator, f + is the result of the grad operator, g and h are inhom boundary data, and + A,B,C are some operator+lifting matrices. + + .. math:: + + M f = A M^{-1}(Cu + g) + Bu + h + + so the linear system looks like + + .. math:: + + M f = A M^{-1} Cu + A M^{-1} g + Bu + h + M f - A M^{-1} g - h = (A M^{-1} C + B)u (*) + + So the right hand side we're putting together here is really + + .. math:: + + M f - A M^{-1} g - h + + .. note:: + + Resist the temptation to left-multiply by the inverse + mass matrix, as this will result in a non-symmetric + matrix, which (e.g.) a conjugate gradient Krylov + solver will not take well. + """ + pop = self.poisson_op + + from hedge.optemplate import MassOperator + return (MassOperator().apply(self.discr, rhs) + - self.compiled_bc_op( + u=self.discr.volume_zeros(), + dir_bc=pop.dirichlet_bc.boundary_interpolant( + self.discr, pop.dirichlet_tag), + neu_bc=pop.neumann_bc.boundary_interpolant( + self.discr, pop.neumann_tag))) + + +class HelmholtzOperator(PoissonOperator): + def __init__(self, k, *args, **kwargs): + PoissonOperator.__init__(self, *args, **kwargs) + self.k = k + + def op_template(self, apply_minv, u=None, dir_bc=None, neu_bc=None): + from hedge.optemplate import Field + if u is None: + u = Field("u") + + result = PoissonOperator.op_template(self, + apply_minv, u, dir_bc, neu_bc) + + if apply_minv: + return result + self.k**2 * u + else: + from hedge.optemplate import MassOperator + return result + self.k**2 * MassOperator()(u) diff --git a/grudge/models/wave.py b/grudge/models/wave.py new file mode 100644 index 00000000..400a489d --- /dev/null +++ b/grudge/models/wave.py @@ -0,0 +1,423 @@ +# -*- coding: utf8 -*- +"""Wave equation operators.""" + +from __future__ import division + +__copyright__ = "Copyright (C) 2009 Andreas Kloeckner" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + + +import numpy as np +import hedge.mesh +from hedge.models import HyperbolicOperator +from hedge.second_order import CentralSecondDerivative + + +# {{{ constant-velocity + +class StrongWaveOperator(HyperbolicOperator): + """This operator discretizes the wave equation + :math:`\\partial_t^2 u = c^2 \\Delta u`. + + To be precise, we discretize the hyperbolic system + + .. math:: + + \partial_t u - c \\nabla \\cdot v = 0 + + \partial_t v - c \\nabla u = 0 + + The sign of :math:`v` determines whether we discretize the forward or the + backward wave equation. + + :math:`c` is assumed to be constant across all space. + """ + + def __init__(self, c, dimensions, source_f=0, + flux_type="upwind", + dirichlet_tag=hedge.mesh.TAG_ALL, + dirichlet_bc_f=0, + neumann_tag=hedge.mesh.TAG_NONE, + radiation_tag=hedge.mesh.TAG_NONE): + assert isinstance(dimensions, int) + + self.c = c + self.dimensions = dimensions + self.source_f = source_f + + if self.c > 0: + self.sign = 1 + else: + self.sign = -1 + + self.dirichlet_tag = dirichlet_tag + self.neumann_tag = neumann_tag + self.radiation_tag = radiation_tag + + self.dirichlet_bc_f = dirichlet_bc_f + + self.flux_type = flux_type + + def flux(self): + from hedge.flux import FluxVectorPlaceholder, make_normal + + dim = self.dimensions + w = FluxVectorPlaceholder(1+dim) + u = w[0] + v = w[1:] + normal = make_normal(dim) + + from hedge.tools import join_fields + flux_weak = join_fields( + np.dot(v.avg, normal), + u.avg * normal) + + if self.flux_type == "central": + pass + elif self.flux_type == "upwind": + # see doc/notes/hedge-notes.tm + flux_weak -= self.sign*join_fields( + 0.5*(u.int-u.ext), + 0.5*(normal * np.dot(normal, v.int-v.ext))) + else: + raise ValueError("invalid flux type '%s'" % self.flux_type) + + flux_strong = join_fields( + np.dot(v.int, normal), + u.int * normal) - flux_weak + + return -self.c*flux_strong + + def op_template(self): + from hedge.optemplate import \ + make_sym_vector, \ + BoundaryPair, \ + get_flux_operator, \ + make_nabla, \ + InverseMassOperator, \ + BoundarizeOperator + + d = self.dimensions + + w = make_sym_vector("w", d+1) + u = w[0] + v = w[1:] + + # boundary conditions ------------------------------------------------- + from hedge.tools import join_fields + + # dirichlet BCs ------------------------------------------------------- + from hedge.optemplate import normal, Field + + dir_u = BoundarizeOperator(self.dirichlet_tag) * u + dir_v = BoundarizeOperator(self.dirichlet_tag) * v + if self.dirichlet_bc_f: + # FIXME + from warnings import warn + warn("Inhomogeneous Dirichlet conditions on the wave equation " + "are still having issues.") + + dir_g = Field("dir_bc_u") + dir_bc = join_fields(2*dir_g - dir_u, dir_v) + else: + dir_bc = join_fields(-dir_u, dir_v) + + # neumann BCs --------------------------------------------------------- + neu_u = BoundarizeOperator(self.neumann_tag) * u + neu_v = BoundarizeOperator(self.neumann_tag) * v + neu_bc = join_fields(neu_u, -neu_v) + + # radiation BCs ------------------------------------------------------- + rad_normal = normal(self.radiation_tag, d) + + rad_u = BoundarizeOperator(self.radiation_tag) * u + rad_v = BoundarizeOperator(self.radiation_tag) * v + + rad_bc = join_fields( + 0.5*(rad_u - self.sign*np.dot(rad_normal, rad_v)), + 0.5*rad_normal*(np.dot(rad_normal, rad_v) - self.sign*rad_u) + ) + + # entire operator ----------------------------------------------------- + nabla = make_nabla(d) + flux_op = get_flux_operator(self.flux()) + + from hedge.tools import join_fields + result = ( + - join_fields( + -self.c*np.dot(nabla, v), + -self.c*(nabla*u) + ) + + + InverseMassOperator() * ( + flux_op(w) + + flux_op(BoundaryPair(w, dir_bc, self.dirichlet_tag)) + + flux_op(BoundaryPair(w, neu_bc, self.neumann_tag)) + + flux_op(BoundaryPair(w, rad_bc, self.radiation_tag)) + )) + + result[0] += self.source_f + + return result + + def bind(self, discr): + from hedge.mesh import check_bc_coverage + check_bc_coverage(discr.mesh, [ + self.dirichlet_tag, + self.neumann_tag, + self.radiation_tag]) + + compiled_op_template = discr.compile(self.op_template()) + + def rhs(t, w, **extra_context): + return compiled_op_template(t=t, w=w, **extra_context) + + return rhs + + def max_eigenvalue(self, t, fields=None, discr=None): + return abs(self.c) + +# }}} + + +# {{{ variable-velocity + +class VariableVelocityStrongWaveOperator(HyperbolicOperator): + r"""This operator discretizes the wave equation + :math:`\partial_t^2 u = c^2 \Delta u`. + + To be precise, we discretize the hyperbolic system + + .. math:: + + \partial_t u - c \nabla \cdot v = 0 + + \partial_t v - c \nabla u = 0 + """ + + def __init__( + self, c, dimensions, source=0, + flux_type="upwind", + dirichlet_tag=hedge.mesh.TAG_ALL, + neumann_tag=hedge.mesh.TAG_NONE, + radiation_tag=hedge.mesh.TAG_NONE, + time_sign=1, + diffusion_coeff=None, + diffusion_scheme=CentralSecondDerivative() + ): + """*c* and *source* are optemplate expressions. + """ + assert isinstance(dimensions, int) + + self.c = c + self.time_sign = time_sign + self.dimensions = dimensions + self.source = source + + self.dirichlet_tag = dirichlet_tag + self.neumann_tag = neumann_tag + self.radiation_tag = radiation_tag + + self.flux_type = flux_type + + self.diffusion_coeff = diffusion_coeff + self.diffusion_scheme = diffusion_scheme + + # {{{ flux ---------------------------------------------------------------- + def flux(self): + from hedge.flux import FluxVectorPlaceholder, make_normal + + dim = self.dimensions + w = FluxVectorPlaceholder(2+dim) + c = w[0] + u = w[1] + v = w[2:] + normal = make_normal(dim) + + from hedge.tools import join_fields + flux = self.time_sign*1/2*join_fields( + c.ext * np.dot(v.ext, normal) + - c.int * np.dot(v.int, normal), + normal*(c.ext*u.ext - c.int*u.int)) + + if self.flux_type == "central": + pass + elif self.flux_type == "upwind": + flux += join_fields( + c.ext*u.ext - c.int*u.int, + c.ext*normal*np.dot(normal, v.ext) + - c.int*normal*np.dot(normal, v.int) + ) + else: + raise ValueError("invalid flux type '%s'" % self.flux_type) + + return flux + + # }}} + + def bind_characteristic_velocity(self, discr): + from hedge.optemplate.operators import ElementwiseMaxOperator + + compiled = discr.compile(ElementwiseMaxOperator()(self.c)) + + def do(t, w, **extra_context): + return compiled(t=t, w=w, **extra_context) + + return do + + def op_template(self, with_sensor=False): + from hedge.optemplate import \ + Field, \ + make_sym_vector, \ + BoundaryPair, \ + get_flux_operator, \ + make_nabla, \ + InverseMassOperator, \ + BoundarizeOperator + + d = self.dimensions + + w = make_sym_vector("w", d+1) + u = w[0] + v = w[1:] + + from hedge.tools import join_fields + flux_w = join_fields(self.c, w) + + # {{{ boundary conditions + from hedge.tools import join_fields + + # Dirichlet + dir_c = BoundarizeOperator(self.dirichlet_tag) * self.c + dir_u = BoundarizeOperator(self.dirichlet_tag) * u + dir_v = BoundarizeOperator(self.dirichlet_tag) * v + + dir_bc = join_fields(dir_c, -dir_u, dir_v) + + # Neumann + neu_c = BoundarizeOperator(self.neumann_tag) * self.c + neu_u = BoundarizeOperator(self.neumann_tag) * u + neu_v = BoundarizeOperator(self.neumann_tag) * v + + neu_bc = join_fields(neu_c, neu_u, -neu_v) + + # Radiation + from hedge.optemplate import make_normal + rad_normal = make_normal(self.radiation_tag, d) + + rad_c = BoundarizeOperator(self.radiation_tag) * self.c + rad_u = BoundarizeOperator(self.radiation_tag) * u + rad_v = BoundarizeOperator(self.radiation_tag) * v + + rad_bc = join_fields( + rad_c, + 0.5*(rad_u - self.time_sign*np.dot(rad_normal, rad_v)), + 0.5*rad_normal*(np.dot(rad_normal, rad_v) - self.time_sign*rad_u) + ) + + # }}} + + # {{{ diffusion ------------------------------------------------------- + from pytools.obj_array import with_object_array_or_scalar + + def make_diffusion(arg): + if with_sensor or ( + self.diffusion_coeff is not None and self.diffusion_coeff != 0): + if self.diffusion_coeff is None: + diffusion_coeff = 0 + else: + diffusion_coeff = self.diffusion_coeff + + if with_sensor: + diffusion_coeff += Field("sensor") + + from hedge.second_order import SecondDerivativeTarget + + # strong_form here allows the reuse the value of grad u. + grad_tgt = SecondDerivativeTarget( + self.dimensions, strong_form=True, + operand=arg) + + self.diffusion_scheme.grad(grad_tgt, bc_getter=None, + dirichlet_tags=[], neumann_tags=[]) + + div_tgt = SecondDerivativeTarget( + self.dimensions, strong_form=False, + operand=diffusion_coeff*grad_tgt.minv_all) + + self.diffusion_scheme.div(div_tgt, + bc_getter=None, + dirichlet_tags=[], neumann_tags=[]) + + return div_tgt.minv_all + else: + return 0 + + # }}} + + # entire operator ----------------------------------------------------- + nabla = make_nabla(d) + flux_op = get_flux_operator(self.flux()) + + return ( + - join_fields( + - self.time_sign*self.c*np.dot(nabla, v) - make_diffusion(u) + + self.source, + + -self.time_sign*self.c*(nabla*u) - with_object_array_or_scalar( + make_diffusion, v) + ) + + + InverseMassOperator() * ( + flux_op(flux_w) + + flux_op(BoundaryPair(flux_w, dir_bc, self.dirichlet_tag)) + + flux_op(BoundaryPair(flux_w, neu_bc, self.neumann_tag)) + + flux_op(BoundaryPair(flux_w, rad_bc, self.radiation_tag)) + )) + + def bind(self, discr, sensor=None): + from hedge.mesh import check_bc_coverage + check_bc_coverage(discr.mesh, [ + self.dirichlet_tag, + self.neumann_tag, + self.radiation_tag]) + + compiled_op_template = discr.compile(self.op_template( + with_sensor=sensor is not None)) + + def rhs(t, w): + kwargs = {} + if sensor is not None: + kwargs["sensor"] = sensor(t, w) + + return compiled_op_template(t=t, w=w, **kwargs) + + return rhs + + def max_eigenvalue_expr(self): + import hedge.optemplate as sym + return sym.NodalMax()(sym.CFunction("fabs")(self.c)) + +# }}} + + +# vim: foldmethod=marker diff --git a/grudge/symbolic/__init__.py b/grudge/symbolic/__init__.py new file mode 100644 index 00000000..a72aa713 --- /dev/null +++ b/grudge/symbolic/__init__.py @@ -0,0 +1,31 @@ +"""Building blocks and mappers for operator expression trees.""" + +from __future__ import division + +__copyright__ = "Copyright (C) 2008 Andreas Kloeckner" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + + +from hedge.optemplate.primitives import * # noqa +from hedge.optemplate.operators import * # noqa +from hedge.optemplate.mappers import * # noqa +from hedge.optemplate.tools import * # noqa diff --git a/grudge/symbolic/compiler.py b/grudge/symbolic/compiler.py new file mode 100644 index 00000000..58619e48 --- /dev/null +++ b/grudge/symbolic/compiler.py @@ -0,0 +1,1106 @@ +"""Compiler to turn operator expression tree into (imperative) bytecode.""" + +from __future__ import division + +__copyright__ = "Copyright (C) 2008 Andreas Kloeckner" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + + +from pytools import Record, memoize_method +from hedge.optemplate import IdentityMapper + + +# {{{ instructions + +class Instruction(Record): + __slots__ = ["dep_mapper_factory"] + priority = 0 + + def get_assignees(self): + raise NotImplementedError("no get_assignees in %s" % self.__class__) + + def get_dependencies(self): + raise NotImplementedError("no get_dependencies in %s" % self.__class__) + + def __str__(self): + raise NotImplementedError + + def get_executor_method(self, executor): + raise NotImplementedError + + +class Assign(Instruction): + """ + .. attribute:: names + .. attribute:: exprs + .. attribute:: do_not_return + + a list of bools indicating whether the corresponding entry in names and + exprs describes an expression that is not needed beyond this assignment + + .. attribute:: priority + .. attribute:: is_scalar_valued + """ + + comment = "" + + def __init__(self, names, exprs, **kwargs): + Instruction.__init__(self, names=names, exprs=exprs, **kwargs) + + if not hasattr(self, "do_not_return"): + self.do_not_return = [False] * len(names) + + @memoize_method + def flop_count(self): + from hedge.optemplate import FlopCounter + return sum(FlopCounter()(expr) for expr in self.exprs) + + def get_assignees(self): + return set(self.names) + + def get_dependencies(self, each_vector=False): + try: + if each_vector: + raise AttributeError + else: + return self._dependencies + except: + # arg is include_subscripts + dep_mapper = self.dep_mapper_factory(each_vector) + + from operator import or_ + deps = reduce( + or_, (dep_mapper(expr) + for expr in self.exprs)) + + from pymbolic.primitives import Variable + deps -= set(Variable(name) for name in self.names) + + if not each_vector: + self._dependencies = deps + + return deps + + def __str__(self): + comment = self.comment + if len(self.names) == 1: + if comment: + comment = "/* %s */ " % comment + + return "%s <- %s%s" % (self.names[0], comment, self.exprs[0]) + else: + if comment: + comment = " /* %s */" % comment + + lines = [] + lines.append("{" + comment) + for n, e, dnr in zip(self.names, self.exprs, self.do_not_return): + if dnr: + dnr_indicator = "-#" + else: + dnr_indicator = "" + + lines.append(" %s <%s- %s" % (n, dnr_indicator, e)) + lines.append("}") + return "\n".join(lines) + + def get_executor_method(self, executor): + return executor.exec_assign + + +class FluxBatchAssign(Instruction): + __slots__ = ["names", "expressions", "repr_op"] + """ + :ivar names: + :ivar expressions: + + A list of :class:`hedge.optemplate.primitives.OperatorBinding` + instances bound to flux operators. + + .. note :: + + All operators in :attr:`expressions` are guaranteed to + yield the same operator from + :meth:`hedge.optemplate.operators.FluxOperatorBase.repr_op`. + + :ivar repr_op: The `repr_op` on which all operators agree. + """ + + def get_assignees(self): + return set(self.names) + + def __str__(self): + from hedge.flux import PrettyFluxStringifyMapper as PFSM + flux_strifier = PFSM() + from hedge.optemplate import StringifyMapper as OSM + op_strifier = OSM(flux_stringify_mapper=flux_strifier) + + from pymbolic.mapper.stringifier import PREC_NONE + + lines = [] + lines.append("{ /* %s */" % self.repr_op) + + lines_expr = [] + for n, f in zip(self.names, self.expressions): + lines_expr.append(" %s <- %s" % (n, op_strifier(f, PREC_NONE))) + + for n, str_f in getattr(flux_strifier, "cse_name_list", []): + lines.append(" (flux-local) %s <- %s" % (n, str_f)) + + lines.extend(lines_expr) + lines.append("}") + return "\n".join(lines) + + def get_executor_method(self, executor): + return executor.exec_flux_batch_assign + + +class DiffBatchAssign(Instruction): + """ + :ivar names: + :ivar operators: + + .. note :: + + All operators here are guaranteed to satisfy + :meth:`hedge.optemplate.operators.DiffOperatorBase. + equal_except_for_axis`. + + :ivar field: + """ + + def get_assignees(self): + return set(self.names) + + @memoize_method + def get_dependencies(self): + return self.dep_mapper_factory()(self.field) + + def __str__(self): + lines = [] + + if len(self.names) > 1: + lines.append("{") + for n, d in zip(self.names, self.operators): + lines.append(" %s <- %s(%s)" % (n, d, self.field)) + lines.append("}") + else: + for n, d in zip(self.names, self.operators): + lines.append("%s <- %s(%s)" % (n, d, self.field)) + + return "\n".join(lines) + + def get_executor_method(self, executor): + return executor.exec_diff_batch_assign + + +class QuadratureDiffBatchAssign(DiffBatchAssign): + def get_executor_method(self, executor): + return executor.exec_quad_diff_batch_assign + + +class FluxExchangeBatchAssign(Instruction): + __slots__ = [ + "names", "indices_and_ranks", + "rank_to_index_and_name", "arg_fields"] + + priority = 1 + + def __init__(self, names, indices_and_ranks, arg_fields, dep_mapper_factory): + rank_to_index_and_name = {} + for name, (index, rank) in zip( + names, indices_and_ranks): + rank_to_index_and_name.setdefault(rank, []).append( + (index, name)) + + Instruction.__init__(self, + names=names, + indices_and_ranks=indices_and_ranks, + rank_to_index_and_name=rank_to_index_and_name, + arg_fields=arg_fields, + dep_mapper_factory=dep_mapper_factory) + + def get_assignees(self): + return set(self.names) + + def get_dependencies(self): + dep_mapper = self.dep_mapper_factory() + result = set() + for fld in self.arg_fields: + result |= dep_mapper(fld) + return result + + def __str__(self): + lines = [] + + lines.append("{") + for n, (index, rank) in zip(self.names, self.indices_and_ranks): + lines.append(" %s <- receive index %s from rank %d [%s]" % ( + n, index, rank, self.arg_fields)) + lines.append("}") + + return "\n".join(lines) + + def get_executor_method(self, executor): + return executor.exec_flux_exchange_batch_assign + +# }}} + + +# {{{ graphviz/dot dataflow graph drawing + +def dot_dataflow_graph(code, max_node_label_length=30, + label_wrap_width=50): + origins = {} + node_names = {} + + result = [ + "initial [label=\"initial\"]" + "result [label=\"result\"]"] + + for num, insn in enumerate(code.instructions): + node_name = "node%d" % num + node_names[insn] = node_name + node_label = str(insn) + + if max_node_label_length is not None: + node_label = node_label[:max_node_label_length] + + if label_wrap_width is not None: + from pytools import word_wrap + node_label = word_wrap(node_label, label_wrap_width, + wrap_using="\n ") + + node_label = node_label.replace("\n", "\\l") + "\\l" + + result.append("%s [ label=\"p%d: %s\" shape=box ];" % ( + node_name, insn.priority, node_label)) + + for assignee in insn.get_assignees(): + origins[assignee] = node_name + + def get_orig_node(expr): + from pymbolic.primitives import Variable + if isinstance(expr, Variable): + return origins.get(expr.name, "initial") + else: + return "initial" + + def gen_expr_arrow(expr, target_node): + result.append("%s -> %s [label=\"%s\"];" + % (get_orig_node(expr), target_node, expr)) + + for insn in code.instructions: + for dep in insn.get_dependencies(): + gen_expr_arrow(dep, node_names[insn]) + + from hedge.tools import is_obj_array + + if is_obj_array(code.result): + for subexp in code.result: + gen_expr_arrow(subexp, "result") + else: + gen_expr_arrow(code.result, "result") + + return "digraph dataflow {\n%s\n}\n" % "\n".join(result) + +# }}} + + +# {{{ code representation + +class Code(object): + def __init__(self, instructions, result): + self.instructions = instructions + self.result = result + self.last_schedule = None + self.static_schedule_attempts = 5 + + def dump_dataflow_graph(self): + from hedge.tools import open_unique_debug_file + + open_unique_debug_file("dataflow", ".dot")\ + .write(dot_dataflow_graph(self, max_node_label_length=None)) + + def __str__(self): + lines = [] + for insn in self.instructions: + lines.extend(str(insn).split("\n")) + lines.append("RESULT: " + str(self.result)) + + return "\n".join(lines) + + # {{{ dynamic scheduler (generates static schedules by self-observation) + class NoInstructionAvailable(Exception): + pass + + @memoize_method + def get_next_step(self, available_names, done_insns): + from pytools import all, argmax2 + available_insns = [ + (insn, insn.priority) for insn in self.instructions + if insn not in done_insns + and all(dep.name in available_names + for dep in insn.get_dependencies())] + + if not available_insns: + raise self.NoInstructionAvailable + + from pytools import flatten + discardable_vars = set(available_names) - set(flatten( + [dep.name for dep in insn.get_dependencies()] + for insn in self.instructions + if insn not in done_insns)) + + # {{{ make sure results do not get discarded + from hedge.tools import with_object_array_or_scalar + + from hedge.optemplate.mappers import DependencyMapper + dm = DependencyMapper(composite_leaves=False) + + def remove_result_variable(result_expr): + # The extra dependency mapper run is necessary + # because, for instance, subscripts can make it + # into the result expression, which then does + # not consist of just variables. + + for var in dm(result_expr): + from pymbolic.primitives import Variable + assert isinstance(var, Variable) + discardable_vars.discard(var.name) + + with_object_array_or_scalar(remove_result_variable, self.result) + # }}} + + return argmax2(available_insns), discardable_vars + + def execute_dynamic(self, exec_mapper, pre_assign_check=None): + """Execute the instruction stream, make all scheduling decisions + dynamically. Record the schedule in *self.last_schedule*. + """ + schedule = [] + + context = exec_mapper.context + + next_future_id = 0 + futures = [] + done_insns = set() + + force_future = False + + while True: + insn = None + discardable_vars = [] + + # check futures for completion + + i = 0 + while i < len(futures): + future = futures[i] + if force_future or future.is_ready(): + futures.pop(i) + + insn = self.EvaluateFuture(future.id) + + assignments, new_futures = future() + force_future = False + break + else: + i += 1 + + del future + + # if no future got processed, pick the next insn + if insn is None: + try: + insn, discardable_vars = self.get_next_step( + frozenset(context.keys()), + frozenset(done_insns)) + + except self.NoInstructionAvailable: + if futures: + # no insn ready: we need a future to complete to continue + force_future = True + else: + # no futures, no available instructions: we're done + break + else: + for name in discardable_vars: + del context[name] + + done_insns.add(insn) + assignments, new_futures = \ + insn.get_executor_method(exec_mapper)(insn) + + if insn is not None: + for target, value in assignments: + if pre_assign_check is not None: + pre_assign_check(target, value) + + context[target] = value + + futures.extend(new_futures) + + schedule.append((discardable_vars, insn, len(new_futures))) + + for future in new_futures: + future.id = next_future_id + next_future_id += 1 + + if len(done_insns) < len(self.instructions): + print "Unreachable instructions:" + for insn in set(self.instructions) - done_insns: + print " ", insn + + raise RuntimeError("not all instructions are reachable" + "--did you forget to pass a value for a placeholder?") + + if self.static_schedule_attempts: + self.last_schedule = schedule + + from hedge.tools import with_object_array_or_scalar + return with_object_array_or_scalar(exec_mapper, self.result) + + # }}} + + # {{{ static schedule execution + class EvaluateFuture(object): + """A fake 'instruction' that represents evaluation of a future.""" + def __init__(self, future_id): + self.future_id = future_id + + def execute(self, exec_mapper, pre_assign_check=None): + """If we have a saved, static schedule for this instruction stream, + execute it. Otherwise, punt to the dynamic scheduler below. + """ + + if self.last_schedule is None: + return self.execute_dynamic(exec_mapper, pre_assign_check) + + context = exec_mapper.context + id_to_future = {} + next_future_id = 0 + + schedule_is_delay_free = True + + for discardable_vars, insn, new_future_count in self.last_schedule: + for name in discardable_vars: + del context[name] + + if isinstance(insn, self.EvaluateFuture): + future = id_to_future.pop(insn.future_id) + if not future.is_ready(): + schedule_is_delay_free = False + assignments, new_futures = future() + del future + else: + assignments, new_futures = \ + insn.get_executor_method(exec_mapper)(insn) + + for target, value in assignments: + if pre_assign_check is not None: + pre_assign_check(target, value) + + context[target] = value + + if len(new_futures) != new_future_count: + raise RuntimeError("static schedule got an unexpected number " + "of futures") + + for future in new_futures: + id_to_future[next_future_id] = future + next_future_id += 1 + + if not schedule_is_delay_free: + self.last_schedule = None + self.static_schedule_attempts -= 1 + + from hedge.tools import with_object_array_or_scalar + return with_object_array_or_scalar(exec_mapper, self.result) + + # }}} + +# }}} + + +# {{{ compiler + +class OperatorCompilerBase(IdentityMapper): + class FluxRecord(Record): + __slots__ = ["flux_expr", "dependencies", "repr_op"] + + class FluxBatch(Record): + __slots__ = ["flux_exprs", "repr_op"] + + def __init__(self, prefix="_expr", max_vectors_in_batch_expr=None): + IdentityMapper.__init__(self) + self.prefix = prefix + + self.max_vectors_in_batch_expr = max_vectors_in_batch_expr + + self.code = [] + self.expr_to_var = {} + + self.assigned_names = set() + + @memoize_method + def dep_mapper_factory(self, include_subscripts=False): + from hedge.optemplate import DependencyMapper + self.dep_mapper = DependencyMapper( + include_operator_bindings=False, + include_subscripts=include_subscripts, + include_calls="descend_args") + + return self.dep_mapper + + # {{{ collecting various optemplate components ---------------------------- + def get_contained_fluxes(self, expr): + """Recursively enumerate all flux expressions in the expression tree + `expr`. The returned list consists of `ExecutionPlanner.FluxRecord` + instances with fields `flux_expr` and `dependencies`. + """ + + # overridden by subclasses + raise NotImplementedError + + def collect_diff_ops(self, expr): + from hedge.optemplate.operators import ReferenceDiffOperatorBase + from hedge.optemplate.mappers import BoundOperatorCollector + return BoundOperatorCollector(ReferenceDiffOperatorBase)(expr) + + def collect_flux_exchange_ops(self, expr): + from hedge.optemplate.mappers import FluxExchangeCollector + return FluxExchangeCollector()(expr) + + # }}} + + # {{{ top-level driver ---------------------------------------------------- + def __call__(self, expr, type_hints={}): + # Put the result expressions into variables as well. + from hedge.optemplate import make_common_subexpression as cse + expr = cse(expr, "_result") + + from hedge.optemplate.mappers.type_inference import TypeInferrer + self.typedict = TypeInferrer()(expr, type_hints) + + # {{{ flux batching + # Fluxes can be evaluated faster in batches. Here, we find flux + # batches that we can evaluate together. + + # For each FluxRecord, find the other fluxes its flux depends on. + flux_queue = self.get_contained_fluxes(expr) + for fr in flux_queue: + fr.dependencies = set(sf.flux_expr + for sf in self.get_contained_fluxes(fr.flux_expr)) \ + - set([fr.flux_expr]) + + # Then figure out batches of fluxes to evaluate + self.flux_batches = [] + admissible_deps = set() + while flux_queue: + present_batch = set() + i = 0 + while i < len(flux_queue): + fr = flux_queue[i] + if fr.dependencies <= admissible_deps: + present_batch.add(fr) + flux_queue.pop(i) + else: + i += 1 + + if present_batch: + # bin batched operators by representative operator + batches_by_repr_op = {} + for fr in present_batch: + batches_by_repr_op[fr.repr_op] = \ + batches_by_repr_op.get(fr.repr_op, set()) \ + | set([fr.flux_expr]) + + for repr_op, batch in batches_by_repr_op.iteritems(): + self.flux_batches.append( + self.FluxBatch( + repr_op=repr_op, + flux_exprs=list(batch))) + + admissible_deps |= set(fr.flux_expr for fr in present_batch) + else: + raise RuntimeError("cannot resolve flux evaluation order") + + # }}} + + # Used for diff batching + + self.diff_ops = self.collect_diff_ops(expr) + + # Flux exchange also works better when batched. + self.flux_exchange_ops = self.collect_flux_exchange_ops(expr) + + # Finally, walk the expression and build the code. + result = IdentityMapper.__call__(self, expr) + + return Code(self.aggregate_assignments(self.code, result), result) + + # }}} + + # {{{ variables and names ------------------------------------------------- + def get_var_name(self, prefix=None): + def generate_suffixes(): + yield "" + i = 2 + while True: + yield "_%d" % i + i += 1 + + def generate_plain_names(): + i = 0 + while True: + yield self.prefix + str(i) + i += 1 + + if prefix is None: + for name in generate_plain_names(): + if name not in self.assigned_names: + break + else: + for suffix in generate_suffixes(): + name = prefix + suffix + if name not in self.assigned_names: + break + + self.assigned_names.add(name) + return name + + def assign_to_new_var(self, expr, priority=0, prefix=None, + is_scalar_valued=False): + from pymbolic.primitives import Variable, Subscript + + # Observe that the only things that can be legally subscripted in + # hedge are variables. All other expressions are broken down into + # their scalar components. + if isinstance(expr, (Variable, Subscript)): + return expr + + new_name = self.get_var_name(prefix) + self.code.append(self.make_assign( + new_name, expr, priority, is_scalar_valued)) + + return Variable(new_name) + + # }}} + + # {{{ map_xxx routines + + def map_common_subexpression(self, expr): + try: + return self.expr_to_var[expr.child] + except KeyError: + priority = getattr(expr, "priority", 0) + + from hedge.optemplate.mappers.type_inference import type_info + is_scalar_valued = isinstance(self.typedict[expr], type_info.Scalar) + + from hedge.optemplate import OperatorBinding + if isinstance(expr.child, OperatorBinding): + # We need to catch operator bindings here and + # treat them specially. They get assigned to their + # own variable by default, which would mean the + # CSE prefix would be omitted, making the resulting + # code less readable. + rec_child = self.map_operator_binding( + expr.child, name_hint=expr.prefix) + else: + rec_child = self.rec(expr.child) + + cse_var = self.assign_to_new_var(rec_child, + priority=priority, prefix=expr.prefix, + is_scalar_valued=is_scalar_valued) + + self.expr_to_var[expr.child] = cse_var + return cse_var + + def map_operator_binding(self, expr, name_hint=None): + from hedge.optemplate.operators import ( + ReferenceDiffOperatorBase, + FluxOperatorBase) + + if isinstance(expr.op, ReferenceDiffOperatorBase): + return self.map_ref_diff_op_binding(expr) + elif isinstance(expr.op, FluxOperatorBase): + raise RuntimeError("OperatorCompiler encountered a flux operator.\n\n" + "We are expecting flux operators to be converted to custom " + "flux assignment instructions, but the subclassed compiler " + "does not seem to have done this.") + else: + # make sure operator assignments stand alone and don't get muddled + # up in vector math + field_var = self.assign_to_new_var( + self.rec(expr.field)) + result_var = self.assign_to_new_var( + expr.op(field_var), + prefix=name_hint) + return result_var + + def map_ones(self, expr): + # make sure expression stands alone and doesn't get + # muddled up in vector math + return self.assign_to_new_var(expr, prefix="ones") + + def map_node_coordinate_component(self, expr): + # make sure expression stands alone and doesn't get + # muddled up in vector math + return self.assign_to_new_var(expr, prefix="nodes%d" % expr.axis) + + def map_normal_component(self, expr): + # make sure expression stands alone and doesn't get + # muddled up in vector math + return self.assign_to_new_var(expr, prefix="normal%d" % expr.axis) + + def map_call(self, expr): + from hedge.optemplate.primitives import CFunction + if isinstance(expr.function, CFunction): + return IdentityMapper.map_call(self, expr) + else: + # If it's not a C-level function, it shouldn't get muddled up into + # a vector math expression. + + return self.assign_to_new_var( + type(expr)( + expr.function, + [self.assign_to_new_var(self.rec(par)) + for par in expr.parameters])) + + def map_ref_diff_op_binding(self, expr): + try: + return self.expr_to_var[expr] + except KeyError: + all_diffs = [diff + for diff in self.diff_ops + if diff.op.equal_except_for_axis(expr.op) + and diff.field == expr.field] + + names = [self.get_var_name() for d in all_diffs] + + from pytools import single_valued + op_class = single_valued(type(d.op) for d in all_diffs) + + from hedge.optemplate.operators import \ + ReferenceQuadratureStiffnessTOperator + if isinstance(op_class, ReferenceQuadratureStiffnessTOperator): + assign_class = QuadratureDiffBatchAssign + else: + assign_class = DiffBatchAssign + + self.code.append( + assign_class( + names=names, + op_class=op_class, + operators=[d.op for d in all_diffs], + field=self.rec( + single_valued(d.field for d in all_diffs)), + dep_mapper_factory=self.dep_mapper_factory)) + + from pymbolic import var + for n, d in zip(names, all_diffs): + self.expr_to_var[d] = var(n) + + return self.expr_to_var[expr] + + def map_flux_exchange(self, expr): + try: + return self.expr_to_var[expr] + except KeyError: + from hedge.tools import is_field_equal + all_flux_xchgs = [fe + for fe in self.flux_exchange_ops + if is_field_equal(fe.arg_fields, expr.arg_fields)] + + assert len(all_flux_xchgs) > 0 + + names = [self.get_var_name() for d in all_flux_xchgs] + self.code.append( + FluxExchangeBatchAssign( + names=names, + indices_and_ranks=[ + (fe.index, fe.rank) + for fe in all_flux_xchgs], + arg_fields=[ + self.rec(arg_field) + for arg_field in fe.arg_fields], + dep_mapper_factory=self.dep_mapper_factory)) + + from pymbolic import var + for n, d in zip(names, all_flux_xchgs): + self.expr_to_var[d] = var(n) + + return self.expr_to_var[expr] + + def map_planned_flux(self, expr): + try: + return self.expr_to_var[expr] + except KeyError: + for fb in self.flux_batches: + try: + idx = fb.flux_exprs.index(expr) + except ValueError: + pass + else: + # found at idx + mapped_fluxes = [ + self.internal_map_flux(f) + for f in fb.flux_exprs] + + names = [self.get_var_name() for f in mapped_fluxes] + self.code.append( + self.make_flux_batch_assign( + names, mapped_fluxes, fb.repr_op)) + + from pymbolic import var + for n, f in zip(names, fb.flux_exprs): + self.expr_to_var[f] = var(n) + + return var(names[idx]) + + raise RuntimeError("flux '%s' not in any flux batch" % expr) + + # }}} + + # {{{ instruction producers + + def make_assign(self, name, expr, priority, is_scalar_valued=False): + return Assign(names=[name], exprs=[expr], + dep_mapper_factory=self.dep_mapper_factory, + priority=priority, + is_scalar_valued=is_scalar_valued) + + def make_flux_batch_assign(self, names, expressions, repr_op): + return FluxBatchAssign(names=names, expressions=expressions, repr_op=repr_op) + + # }}} + + # {{{ assignment aggregration pass + + def aggregate_assignments(self, instructions, result): + from pymbolic.primitives import Variable + + # {{{ aggregation helpers + + def get_complete_origins_set(insn, skip_levels=0): + if skip_levels < 0: + skip_levels = 0 + + result = set() + for dep in insn.get_dependencies(): + if isinstance(dep, Variable): + dep_origin = origins_map.get(dep.name, None) + if dep_origin is not None: + if skip_levels <= 0: + result.add(dep_origin) + result |= get_complete_origins_set( + dep_origin, skip_levels-1) + + return result + + var_assignees_cache = {} + + def get_var_assignees(insn): + try: + return var_assignees_cache[insn] + except KeyError: + result = set(Variable(assignee) + for assignee in insn.get_assignees()) + var_assignees_cache[insn] = result + return result + + def aggregate_two_assignments(ass_1, ass_2): + names = ass_1.names + ass_2.names + + from pymbolic.primitives import Variable + deps = (ass_1.get_dependencies() | ass_2.get_dependencies()) \ + - set(Variable(name) for name in names) + + return Assign( + names=names, exprs=ass_1.exprs + ass_2.exprs, + _dependencies=deps, + dep_mapper_factory=self.dep_mapper_factory, + priority=max(ass_1.priority, ass_2.priority)) + + # }}} + + # {{{ main aggregation pass + + origins_map = dict( + (assignee, insn) + for insn in instructions + for assignee in insn.get_assignees()) + + from pytools import partition + unprocessed_assigns, other_insns = partition( + lambda insn: isinstance(insn, Assign) and not insn.is_scalar_valued, + instructions) + + # filter out zero-flop-count assigns--no need to bother with those + processed_assigns, unprocessed_assigns = partition( + lambda ass: ass.flop_count() == 0, + unprocessed_assigns) + + # filter out zero assignments + from pytools import any + from hedge.tools import is_zero + + i = 0 + + while i < len(unprocessed_assigns): + my_assign = unprocessed_assigns[i] + if any(is_zero(expr) for expr in my_assign.exprs): + processed_assigns.append(unprocessed_assigns.pop()) + else: + i += 1 + + # greedy aggregation + while unprocessed_assigns: + my_assign = unprocessed_assigns.pop() + + my_deps = my_assign.get_dependencies() + my_assignees = get_var_assignees(my_assign) + + agg_candidates = [] + for i, other_assign in enumerate(unprocessed_assigns): + other_deps = other_assign.get_dependencies() + other_assignees = get_var_assignees(other_assign) + + if ((my_deps & other_deps + or my_deps & other_assignees + or other_deps & my_assignees) + and my_assign.priority == other_assign.priority): + agg_candidates.append((i, other_assign)) + + did_work = False + + if agg_candidates: + my_indirect_origins = get_complete_origins_set( + my_assign, skip_levels=1) + + for other_assign_index, other_assign in agg_candidates: + if self.max_vectors_in_batch_expr is not None: + new_assignee_count = len( + set(my_assign.get_assignees()) + | set(other_assign.get_assignees())) + new_dep_count = len( + my_assign.get_dependencies( + each_vector=True) + | other_assign.get_dependencies( + each_vector=True)) + + if (new_assignee_count + new_dep_count + > self.max_vectors_in_batch_expr): + continue + + other_indirect_origins = get_complete_origins_set( + other_assign, skip_levels=1) + + if (my_assign not in other_indirect_origins and + other_assign not in my_indirect_origins): + did_work = True + + # aggregate the two assignments + new_assignment = aggregate_two_assignments( + my_assign, other_assign) + del unprocessed_assigns[other_assign_index] + unprocessed_assigns.append(new_assignment) + for assignee in new_assignment.get_assignees(): + origins_map[assignee] = new_assignment + + break + + if not did_work: + processed_assigns.append(my_assign) + + externally_used_names = set( + expr + for insn in processed_assigns + other_insns + for expr in insn.get_dependencies()) + + from hedge.tools import is_obj_array + if is_obj_array(result): + externally_used_names |= set(expr for expr in result) + else: + externally_used_names |= set([result]) + + def schedule_and_finalize_assignment(ass): + dep_mapper = self.dep_mapper_factory() + + names_exprs = zip(ass.names, ass.exprs) + + my_assignees = set(name for name, expr in names_exprs) + names_exprs_deps = [ + (name, expr, + set(dep.name for dep in dep_mapper(expr) if + isinstance(dep, Variable)) & my_assignees) + for name, expr in names_exprs] + + ordered_names_exprs = [] + available_names = set() + + while names_exprs_deps: + schedulable = [] + + i = 0 + while i < len(names_exprs_deps): + name, expr, deps = names_exprs_deps[i] + + unsatisfied_deps = deps - available_names + + if not unsatisfied_deps: + schedulable.append((str(expr), name, expr)) + del names_exprs_deps[i] + else: + i += 1 + + # make sure these come out in a constant order + schedulable.sort() + + if schedulable: + for key, name, expr in schedulable: + ordered_names_exprs.append((name, expr)) + available_names.add(name) + else: + raise RuntimeError("aggregation resulted in an " + "impossible assignment") + + return self.finalize_multi_assign( + names=[name for name, expr in ordered_names_exprs], + exprs=[expr for name, expr in ordered_names_exprs], + do_not_return=[Variable(name) not in externally_used_names + for name, expr in ordered_names_exprs], + priority=ass.priority) + + return [schedule_and_finalize_assignment(ass) + for ass in processed_assigns] + other_insns + + # }}} + + # }}} + +# }}} + + +# vim: foldmethod=marker diff --git a/grudge/symbolic/operators.py b/grudge/symbolic/operators.py new file mode 100644 index 00000000..4c7bafdf --- /dev/null +++ b/grudge/symbolic/operators.py @@ -0,0 +1,780 @@ +"""Building blocks and mappers for operator expression trees.""" + +from __future__ import division + +__copyright__ = "Copyright (C) 2008 Andreas Kloeckner" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + + +import numpy as np +import numpy.linalg as la +import pymbolic.primitives +from pytools import Record, memoize_method + + +# {{{ base classes + +class Operator(pymbolic.primitives.Leaf): + def stringifier(self): + from hedge.optemplate import StringifyMapper + return StringifyMapper + + def __call__(self, expr): + from pytools.obj_array import with_object_array_or_scalar + from hedge.tools import is_zero + + def bind_one(subexpr): + if is_zero(subexpr): + return subexpr + else: + from hedge.optemplate.primitives import OperatorBinding + return OperatorBinding(self, subexpr) + + return with_object_array_or_scalar(bind_one, expr) + + @memoize_method + def bind(self, discr): + from hedge.optemplate import Field + bound_op = discr.compile(self(Field("f"))) + + def apply_op(field): + from hedge.tools import with_object_array_or_scalar + return with_object_array_or_scalar(lambda f: bound_op(f=f), field) + + return apply_op + + def apply(self, discr, field): + return self.bind(discr)(field) + + def get_hash(self): + return hash((self.__class__,) + (self.__getinitargs__())) + + def is_equal(self, other): + return self.__class__ == other.__class__ and \ + self.__getinitargs__() == other.__getinitargs__() + + +class StatelessOperator(Operator): + def __getinitargs__(self): + return () + +# }}} + + +# {{{ sum, integral, max + +class NodalReductionOperator(StatelessOperator): + pass + + +class NodalSum(NodalReductionOperator): + mapper_method = intern("map_nodal_sum") + + +class NodalMax(NodalReductionOperator): + mapper_method = intern("map_nodal_max") + + +class NodalMin(NodalReductionOperator): + mapper_method = intern("map_nodal_min") + +# }}} + + +# {{{ differentiation operators + +# {{{ global differentiation + +class DiffOperatorBase(Operator): + def __init__(self, xyz_axis): + Operator.__init__(self) + + self.xyz_axis = xyz_axis + + def __getinitargs__(self): + return (self.xyz_axis,) + + def preimage_ranges(self, eg): + return eg.ranges + + def equal_except_for_axis(self, other): + return (type(self) == type(other) + # first argument is always the axis + and self.__getinitargs__()[1:] == other.__getinitargs__()[1:]) + + +class StrongFormDiffOperatorBase(DiffOperatorBase): + pass + + +class WeakFormDiffOperatorBase(DiffOperatorBase): + pass + + +class StiffnessOperator(StrongFormDiffOperatorBase): + mapper_method = intern("map_stiffness") + + +class DifferentiationOperator(StrongFormDiffOperatorBase): + mapper_method = intern("map_diff") + + +class StiffnessTOperator(WeakFormDiffOperatorBase): + mapper_method = intern("map_stiffness_t") + + +class MInvSTOperator(WeakFormDiffOperatorBase): + mapper_method = intern("map_minv_st") + + +class QuadratureStiffnessTOperator(DiffOperatorBase): + """ + .. note:: + + This operator is purely for internal use. It is inserted + by :class:`hedge.optemplate.mappers.OperatorSpecializer` + when a :class:`StiffnessTOperator` is applied to a quadrature + field, and then eliminated by + :class:`hedge.optemplate.mappers.GlobalToReferenceMapper` + in favor of operators on the reference element. + """ + + def __init__(self, xyz_axis, quadrature_tag): + DiffOperatorBase.__init__(self, xyz_axis) + self.quadrature_tag = quadrature_tag + + def __getinitargs__(self): + return (self.xyz_axis, self.quadrature_tag) + + mapper_method = intern("map_quad_stiffness_t") + + +def DiffOperatorVector(els): + from hedge.tools import join_fields + return join_fields(*els) + +# }}} + + +# {{{ reference-element differentiation + +class ReferenceDiffOperatorBase(Operator): + def __init__(self, rst_axis): + Operator.__init__(self) + + self.rst_axis = rst_axis + + def __getinitargs__(self): + return (self.rst_axis,) + + def preimage_ranges(self, eg): + return eg.ranges + + def equal_except_for_axis(self, other): + return (type(self) == type(other) + # first argument is always the axis + and self.__getinitargs__()[1:] == other.__getinitargs__()[1:]) + + +class ReferenceDifferentiationOperator(ReferenceDiffOperatorBase): + @staticmethod + def matrices(element_group): + return element_group.differentiation_matrices + + mapper_method = intern("map_ref_diff") + + +class ReferenceStiffnessTOperator(ReferenceDiffOperatorBase): + @staticmethod + def matrices(element_group): + return element_group.stiffness_t_matrices + + mapper_method = intern("map_ref_stiffness_t") + + +class ReferenceQuadratureStiffnessTOperator(ReferenceDiffOperatorBase): + """ + .. note:: + + This operator is purely for internal use. It is inserted + by :class:`hedge.optemplate.mappers.OperatorSpecializer` + when a :class:`StiffnessTOperator` is applied to a quadrature field. + """ + + def __init__(self, rst_axis, quadrature_tag): + ReferenceDiffOperatorBase.__init__(self, rst_axis) + self.quadrature_tag = quadrature_tag + + def __getinitargs__(self): + return (self.rst_axis, self.quadrature_tag) + + mapper_method = intern("map_ref_quad_stiffness_t") + + def preimage_ranges(self, eg): + return eg.quadrature_info[self.quadrature_tag].ranges + + def matrices(self, element_group): + return element_group.quadrature_info[self.quadrature_tag] \ + .ldis_quad_info.stiffness_t_matrices() + +# }}} + +# }}} + + +# {{{ elementwise operators + +class ElementwiseLinearOperator(Operator): + def matrix(self, element_group): + raise NotImplementedError + + def coefficients(self, element_group): + return None + + mapper_method = intern("map_elementwise_linear") + + +class ElementwiseMaxOperator(StatelessOperator): + mapper_method = intern("map_elementwise_max") + + +# {{{ quadrature upsamplers + +class QuadratureGridUpsampler(Operator): + """In a user-specified optemplate, this operator can be used to interpolate + volume and boundary data to their corresponding quadrature grids. + + In pre-processing, the boundary quad interpolation is specialized to + a separate operator, :class:`QuadratureBoundaryGridUpsampler`. + """ + def __init__(self, quadrature_tag): + self.quadrature_tag = quadrature_tag + + def __getinitargs__(self): + return (self.quadrature_tag,) + + mapper_method = intern("map_quad_grid_upsampler") + + +class QuadratureInteriorFacesGridUpsampler(Operator): + """Interpolates nodal volume data to interior face data on a quadrature + grid. + + Note that the "interior faces" grid includes faces lying opposite to the + boundary. + """ + def __init__(self, quadrature_tag): + self.quadrature_tag = quadrature_tag + + def __getinitargs__(self): + return (self.quadrature_tag,) + + mapper_method = intern("map_quad_int_faces_grid_upsampler") + + +class QuadratureBoundaryGridUpsampler(Operator): + """ + .. note:: + + This operator is purely for internal use. It is inserted + by :class:`hedge.optemplate.mappers.OperatorSpecializer` + when a :class:`MassOperator` is applied to a quadrature field. + """ + def __init__(self, quadrature_tag, boundary_tag): + self.quadrature_tag = quadrature_tag + self.boundary_tag = boundary_tag + + def __getinitargs__(self): + return (self.quadrature_tag, self.boundary_tag) + + mapper_method = intern("map_quad_bdry_grid_upsampler") + +# }}} + + +# {{{ various elementwise linear operators + +class FilterOperator(ElementwiseLinearOperator): + def __init__(self, mode_response_func): + """ + :param mode_response_func: A function mapping + ``(mode_tuple, local_discretization)`` to a float indicating the + factor by which this mode is to be multiplied after filtering. + (For example an instance of + :class:`ExponentialFilterResponseFunction`. + """ + self.mode_response_func = mode_response_func + + def __getinitargs__(self): + return (self.mode_response_func,) + + def matrix(self, eg): + ldis = eg.local_discretization + + filter_coeffs = [self.mode_response_func(mid, ldis) + for mid in ldis.generate_mode_identifiers()] + + # build filter matrix + vdm = ldis.vandermonde() + from hedge.tools import leftsolve + mat = np.asarray( + leftsolve(vdm, + np.dot(vdm, np.diag(filter_coeffs))), + order="C") + + return mat + + +class OnesOperator(ElementwiseLinearOperator, StatelessOperator): + def matrix(self, eg): + ldis = eg.local_discretization + + node_count = ldis.node_count() + return np.ones((node_count, node_count), dtype=np.float64) + + +class AveragingOperator(ElementwiseLinearOperator, StatelessOperator): + def matrix(self, eg): + # average matrix, so that AVE*fields = cellaverage(fields) + # see Hesthaven and Warburton page 227 + + mmat = eg.local_discretization.mass_matrix() + standard_el_vol = np.sum(np.dot(mmat, np.ones(mmat.shape[0]))) + avg_mat_row = np.sum(mmat, 0)/standard_el_vol + + avg_mat = np.zeros((np.size(avg_mat_row), np.size(avg_mat_row))) + avg_mat[:] = avg_mat_row + return avg_mat + + +class InverseVandermondeOperator(ElementwiseLinearOperator, StatelessOperator): + def matrix(self, eg): + return np.asarray( + la.inv(eg.local_discretization.vandermonde()), + order="C") + + +class VandermondeOperator(ElementwiseLinearOperator, StatelessOperator): + def matrix(self, eg): + return np.asarray( + eg.local_discretization.vandermonde(), + order="C") + +# }}} + +# }}} + + +# {{{ mass operators + +class MassOperatorBase(ElementwiseLinearOperator, StatelessOperator): + pass + + +class MassOperator(MassOperatorBase): + @staticmethod + def matrix(element_group): + return element_group.mass_matrix + + @staticmethod + def coefficients(element_group): + return element_group.jacobians + + mapper_method = intern("map_mass") + + +class InverseMassOperator(MassOperatorBase): + @staticmethod + def matrix(element_group): + return element_group.inverse_mass_matrix + + @staticmethod + def coefficients(element_group): + return element_group.inverse_jacobians + + mapper_method = intern("map_inverse_mass") + + +class QuadratureMassOperator(Operator): + """ + .. note:: + + This operator is purely for internal use. It is inserted + by :class:`hedge.optemplate.mappers.OperatorSpecializer` + when a :class:`StiffnessTOperator` is applied to a quadrature + field, and then eliminated by + :class:`hedge.optemplate.mappers.GlobalToReferenceMapper` + in favor of operators on the reference element. + """ + + def __init__(self, quadrature_tag): + self.quadrature_tag = quadrature_tag + + def __getinitargs__(self): + return (self.quadrature_tag,) + + mapper_method = intern("map_quad_mass") + + +class ReferenceQuadratureMassOperator(Operator): + """ + .. note:: + + This operator is purely for internal use. It is inserted + by :class:`hedge.optemplate.mappers.OperatorSpecializer` + when a :class:`MassOperator` is applied to a quadrature field. + """ + + def __init__(self, quadrature_tag): + self.quadrature_tag = quadrature_tag + + def __getinitargs__(self): + return (self.quadrature_tag,) + + mapper_method = intern("map_ref_quad_mass") + + +class ReferenceMassOperatorBase(MassOperatorBase): + pass + + +class ReferenceMassOperator(ReferenceMassOperatorBase): + @staticmethod + def matrix(element_group): + return element_group.mass_matrix + + @staticmethod + def coefficients(element_group): + return None + + mapper_method = intern("map_ref_mass") + + +class ReferenceInverseMassOperator(ReferenceMassOperatorBase): + @staticmethod + def matrix(element_group): + return element_group.inverse_mass_matrix + + @staticmethod + def coefficients(element_group): + return None + + mapper_method = intern("map_ref_inverse_mass") + +# }}} + + +# {{{ boundary-related operators + +class BoundarizeOperator(Operator): + def __init__(self, tag): + self.tag = tag + + def __getinitargs__(self): + return (self.tag,) + + mapper_method = intern("map_boundarize") + + +class FluxExchangeOperator(pymbolic.primitives.AlgebraicLeaf): + """An operator that results in the sending and receiving of + boundary information for its argument fields. + """ + + def __init__(self, idx, rank, arg_fields): + self.index = idx + self.rank = rank + self.arg_fields = arg_fields + + # only tuples are hashable + if not isinstance(arg_fields, tuple): + raise TypeError("FluxExchangeOperator: arg_fields must be a tuple") + + def __getinitargs__(self): + return (self.index, self.rank, self.arg_fields) + + def get_hash(self): + return hash((self.__class__, self.index, self.rank, self.arg_fields)) + + mapper_method = intern("map_flux_exchange") + + def is_equal(self, other): + return self.__class__ == other.__class__ and \ + self.__getinitargs__() == other.__getinitargs__() + +# }}} + + +# {{{ flux-like operators + +class FluxOperatorBase(Operator): + def __init__(self, flux, is_lift=False): + Operator.__init__(self) + self.flux = flux + self.is_lift = is_lift + + def get_flux_or_lift_text(self): + if self.is_lift: + return "Lift" + else: + return "Flux" + + def repr_op(self): + """Return an equivalent operator with the flux expression set to 0.""" + return type(self)(0, *self.__getinitargs__()[1:]) + + def __call__(self, arg): + # override to suppress apply-operator-to-each-operand + # behavior from superclass + + from hedge.optemplate.primitives import OperatorBinding + return OperatorBinding(self, arg) + + def __mul__(self, arg): + from warnings import warn + warn("Multiplying by a flux operator is deprecated. " + "Use the less ambiguous parenthesized syntax instead.", + DeprecationWarning, stacklevel=2) + return self.__call__(arg) + + +class QuadratureFluxOperatorBase(FluxOperatorBase): + pass + + +class BoundaryFluxOperatorBase(FluxOperatorBase): + pass + + +class FluxOperator(FluxOperatorBase): + def __getinitargs__(self): + return (self.flux, self.is_lift) + + mapper_method = intern("map_flux") + + +class BoundaryFluxOperator(BoundaryFluxOperatorBase): + """ + .. note:: + + This operator is purely for internal use. It is inserted + by :class:`hedge.optemplate.mappers.OperatorSpecializer` + when a :class:`FluxOperator` is applied to a boundary field. + """ + def __init__(self, flux, boundary_tag, is_lift=False): + FluxOperatorBase.__init__(self, flux, is_lift) + self.boundary_tag = boundary_tag + + def __getinitargs__(self): + return (self.flux, self.boundary_tag, self.is_lift) + + mapper_method = intern("map_bdry_flux") + + +class QuadratureFluxOperator(QuadratureFluxOperatorBase): + """ + .. note:: + + This operator is purely for internal use. It is inserted + by :class:`hedge.optemplate.mappers.OperatorSpecializer` + when a :class:`FluxOperator` is applied to a quadrature field. + """ + + def __init__(self, flux, quadrature_tag): + FluxOperatorBase.__init__(self, flux, is_lift=False) + + self.quadrature_tag = quadrature_tag + + def __getinitargs__(self): + return (self.flux, self.quadrature_tag) + + mapper_method = intern("map_quad_flux") + + +class QuadratureBoundaryFluxOperator( + QuadratureFluxOperatorBase, BoundaryFluxOperatorBase): + """ + .. note:: + + This operator is purely for internal use. It is inserted + by :class:`hedge.optemplate.mappers.OperatorSpecializer` + when a :class:`FluxOperator` is applied to a quadrature + boundary field. + """ + def __init__(self, flux, quadrature_tag, boundary_tag): + FluxOperatorBase.__init__(self, flux, is_lift=False) + self.quadrature_tag = quadrature_tag + self.boundary_tag = boundary_tag + + def __getinitargs__(self): + return (self.flux, self.quadrature_tag, self.boundary_tag) + + mapper_method = intern("map_quad_bdry_flux") + + +class VectorFluxOperator(object): + """Note that this isn't an actual operator. It's just a placeholder that pops + out a vector of FluxOperators when applied to an operand. + """ + def __init__(self, fluxes): + self.fluxes = fluxes + + def __call__(self, arg): + if isinstance(arg, int) and arg == 0: + return 0 + from pytools.obj_array import make_obj_array + from hedge.optemplate.primitives import OperatorBinding + + return make_obj_array( + [OperatorBinding(FluxOperator(f), arg) + for f in self.fluxes]) + + def __mul__(self, arg): + from warnings import warn + warn("Multiplying by a vector flux operator is deprecated. " + "Use the less ambiguous parenthesized syntax instead.", + DeprecationWarning, stacklevel=2) + return self.__call__(arg) + + +class WholeDomainFluxOperator(pymbolic.primitives.AlgebraicLeaf): + """Used by the CUDA backend to represent a flux computation on the + whole domain--interior and boundary. + + Unlike other operators, :class:`WholeDomainFluxOperator` instances + are not bound. + """ + + class FluxInfo(Record): + __slots__ = [] + + def __repr__(self): + # override because we want flux_expr in infix + return "%s(%s)" % ( + self.__class__.__name__, + ", ".join("%s=%s" % (fld, getattr(self, fld)) + for fld in self.__class__.fields + if hasattr(self, fld))) + + class InteriorInfo(FluxInfo): + # attributes: flux_expr, field_expr, + + @property + @memoize_method + def dependencies(self): + from hedge.optemplate.tools import get_flux_dependencies + return set(get_flux_dependencies( + self.flux_expr, self.field_expr)) + + class BoundaryInfo(FluxInfo): + # attributes: flux_expr, bpair + + @property + @memoize_method + def int_dependencies(self): + from hedge.optemplate.tools import get_flux_dependencies + return set(get_flux_dependencies( + self.flux_expr, self.bpair, bdry="int")) + + @property + @memoize_method + def ext_dependencies(self): + from hedge.optemplate.tools import get_flux_dependencies + return set(get_flux_dependencies( + self.flux_expr, self.bpair, bdry="ext")) + + def __init__(self, is_lift, interiors, boundaries, + quadrature_tag): + from hedge.optemplate.tools import get_flux_dependencies + + self.is_lift = is_lift + + self.interiors = tuple(interiors) + self.boundaries = tuple(boundaries) + self.quadrature_tag = quadrature_tag + + from pytools import set_sum + interior_deps = set_sum(iflux.dependencies + for iflux in interiors) + boundary_int_deps = set_sum(bflux.int_dependencies + for bflux in boundaries) + boundary_ext_deps = set_sum(bflux.ext_dependencies + for bflux in boundaries) + + self.interior_deps = list(interior_deps) + self.boundary_int_deps = list(boundary_int_deps) + self.boundary_ext_deps = list(boundary_ext_deps) + self.boundary_deps = list(boundary_int_deps | boundary_ext_deps) + + self.dep_to_tag = {} + for bflux in boundaries: + for dep in get_flux_dependencies( + bflux.flux_expr, bflux.bpair, bdry="ext"): + self.dep_to_tag[dep] = bflux.bpair.tag + + def stringifier(self): + from hedge.optemplate import StringifyMapper + return StringifyMapper + + def repr_op(self): + return type(self)(False, [], [], self.quadrature_tag) + + @memoize_method + def rebuild_optemplate(self): + def generate_summands(): + for i in self.interiors: + if self.quadrature_tag is None: + yield FluxOperator( + i.flux_expr, self.is_lift)(i.field_expr) + else: + yield QuadratureFluxOperator( + i.flux_expr, self.quadrature_tag)(i.field_expr) + for b in self.boundaries: + if self.quadrature_tag is None: + yield BoundaryFluxOperator( + b.flux_expr, b.bpair.tag, self.is_lift)(b.bpair) + else: + yield QuadratureBoundaryFluxOperator( + b.flux_expr, self.quadrature_tag, + b.bpair.tag)(b.bpair) + + from pymbolic.primitives import flattened_sum + return flattened_sum(generate_summands()) + + # infrastructure interaction + def get_hash(self): + return hash((self.__class__, self.rebuild_optemplate())) + + def is_equal(self, other): + return (other.__class__ == WholeDomainFluxOperator + and self.rebuild_optemplate() == other.rebuild_optemplate()) + + def __getinitargs__(self): + return (self.is_lift, self.interiors, self.boundaries, + self.quadrature_tag) + + mapper_method = intern("map_whole_domain_flux") + +# }}} + + +# vim: foldmethod=marker diff --git a/grudge/symbolic/primitives.py b/grudge/symbolic/primitives.py new file mode 100644 index 00000000..af1f3593 --- /dev/null +++ b/grudge/symbolic/primitives.py @@ -0,0 +1,283 @@ +"""Operator template language: primitives.""" + +from __future__ import division + +__copyright__ = "Copyright (C) 2008 Andreas Kloeckner" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + + +import numpy +import pymbolic.primitives +import hedge.mesh + +from pymbolic.primitives import ( # noqa + make_common_subexpression, If, Comparison) + +from pytools import MovedFunctionDeprecationWrapper + + +class LeafBase(pymbolic.primitives.AlgebraicLeaf): + def stringifier(self): + from hedge.optemplate import StringifyMapper + return StringifyMapper + + +# {{{ variables + +Field = pymbolic.primitives.Variable + +make_sym_vector = pymbolic.primitives.make_sym_vector +make_sym_array = pymbolic.primitives.make_sym_array + + +def make_field(var_or_string): + if not isinstance(var_or_string, pymbolic.primitives.Expression): + return Field(var_or_string) + else: + return var_or_string + + +class ScalarParameter(pymbolic.primitives.Variable): + """A placeholder for a user-supplied scalar variable.""" + + def stringifier(self): + from hedge.optemplate import StringifyMapper + return StringifyMapper + + mapper_method = intern("map_scalar_parameter") + + +class CFunction(pymbolic.primitives.Variable): + """A symbol representing a C-level function, to be used as the function + argument of :class:`pymbolic.primitives.Call`. + """ + def stringifier(self): + from hedge.optemplate import StringifyMapper + return StringifyMapper + + def __call__(self, expr): + from pytools.obj_array import with_object_array_or_scalar + from functools import partial + return with_object_array_or_scalar( + partial(pymbolic.primitives.Expression.__call__, self), + expr) + + mapper_method = "map_c_function" + +# }}} + + +# {{{ technical helpers + +class OperatorBinding(LeafBase): + def __init__(self, op, field): + self.op = op + self.field = field + + mapper_method = intern("map_operator_binding") + + def __getinitargs__(self): + return self.op, self.field + + def is_equal(self, other): + from hedge.tools import field_equal + return (other.__class__ == self.__class__ + and other.op == self.op + and field_equal(other.field, self.field)) + + def get_hash(self): + from hedge.tools import hashable_field + return hash((self.__class__, self.op, hashable_field(self.field))) + + +class PrioritizedSubexpression(pymbolic.primitives.CommonSubexpression): + """When the optemplate-to-code transformation is performed, + prioritized subexpressions work like common subexpression in + that they are assigned their own separate identifier/register + location. In addition to this behavior, prioritized subexpressions + are evaluated with a settable priority, allowing the user to + expedite or delay the evaluation of the subexpression. + """ + + def __init__(self, child, priority=0): + pymbolic.primitives.CommonSubexpression.__init__(self, child) + self.priority = priority + + def __getinitargs__(self): + return (self.child, self.priority) + + def get_extra_properties(self): + return {"priority": self.priority} + + +class BoundaryPair(LeafBase): + """Represents a pairing of a volume and a boundary field, used for the + application of boundary fluxes. + """ + + def __init__(self, field, bfield, tag=hedge.mesh.TAG_ALL): + self.field = field + self.bfield = bfield + self.tag = tag + + mapper_method = intern("map_boundary_pair") + + def __getinitargs__(self): + return (self.field, self.bfield, self.tag) + + def get_hash(self): + from hedge.tools import hashable_field + + return hash((self.__class__, + hashable_field(self.field), + hashable_field(self.bfield), + self.tag)) + + def is_equal(self, other): + from hedge.tools import field_equal + return (self.__class__ == other.__class__ + and field_equal(other.field, self.field) + and field_equal(other.bfield, self.bfield) + and other.tag == self.tag) + +# }}} + + +class Ones(LeafBase): + def __init__(self, quadrature_tag=None): + self.quadrature_tag = quadrature_tag + + def __getinitargs__(self): + return (self.quadrature_tag,) + + mapper_method = intern("map_ones") + + +# {{{ geometry data + +class NodeCoordinateComponent(LeafBase): + def __init__(self, axis, quadrature_tag=None): + self.axis = axis + self.quadrature_tag = quadrature_tag + + def __getinitargs__(self): + return (self.axis, self.quadrature_tag) + + mapper_method = intern("map_node_coordinate_component") + + +def nodes(dim, quadrature_tag=None): + return numpy.array([NodeCoordinateComponent(i, quadrature_tag) + for i in range(dim)], dtype=object) + + +class BoundaryNormalComponent(LeafBase): + def __init__(self, boundary_tag, axis, quadrature_tag=None): + self.boundary_tag = boundary_tag + self.axis = axis + self.quadrature_tag = quadrature_tag + + def __getinitargs__(self): + return (self.boundary_tag, self.axis, self.quadrature_tag) + + mapper_method = intern("map_normal_component") + + +def normal(tag, dimensions): + return numpy.array([BoundaryNormalComponent(tag, i) + for i in range(dimensions)], dtype=object) + +make_normal = MovedFunctionDeprecationWrapper(normal) + + +class GeometricFactorBase(LeafBase): + def __init__(self, quadrature_tag): + """ + :param quadrature_tag: quadrature tag for the grid on + which this geometric factor is needed, or None for + nodal representation. + """ + self.quadrature_tag = quadrature_tag + + def __getinitargs__(self): + return (self.quadrature_tag,) + + +class Jacobian(GeometricFactorBase): + mapper_method = intern("map_jacobian") + + +class ForwardMetricDerivative(GeometricFactorBase): + r""" + Pointwise metric derivatives representing + + .. math:: + + \frac{d x_{\mathtt{xyz\_axis}} }{d r_{\mathtt{rst\_axis}} } + """ + + def __init__(self, quadrature_tag, xyz_axis, rst_axis): + """ + :param quadrature_tag: quadrature tag for the grid on + which this geometric factor is needed, or None for + nodal representation. + """ + + GeometricFactorBase.__init__(self, quadrature_tag) + self.xyz_axis = xyz_axis + self.rst_axis = rst_axis + + def __getinitargs__(self): + return (self.quadrature_tag, self.xyz_axis, self.rst_axis) + + mapper_method = intern("map_forward_metric_derivative") + + +class InverseMetricDerivative(GeometricFactorBase): + r""" + Pointwise metric derivatives representing + + .. math:: + + \frac{d r_{\mathtt{rst\_axis}} }{d x_{\mathtt{xyz\_axis}} } + """ + + def __init__(self, quadrature_tag, rst_axis, xyz_axis): + """ + :param quadrature_tag: quadrature tag for the grid on + which this geometric factor is needed, or None for + nodal representation. + """ + + GeometricFactorBase.__init__(self, quadrature_tag) + self.rst_axis = rst_axis + self.xyz_axis = xyz_axis + + def __getinitargs__(self): + return (self.quadrature_tag, self.rst_axis, self.xyz_axis) + + mapper_method = intern("map_inverse_metric_derivative") + +# }}} + + +# vim: foldmethod=marker diff --git a/grudge/symbolic/tools.py b/grudge/symbolic/tools.py new file mode 100644 index 00000000..c13fe705 --- /dev/null +++ b/grudge/symbolic/tools.py @@ -0,0 +1,391 @@ +"""Operator templates: extra bits of functionality.""" + +from __future__ import division + +__copyright__ = "Copyright (C) 2008 Andreas Kloeckner" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + + +import numpy as np +import pymbolic.primitives # noqa +from pytools import MovedFunctionDeprecationWrapper +from decorator import decorator + + +# {{{ convenience functions for optemplate creation + +make_vector_field = \ + MovedFunctionDeprecationWrapper(pymbolic.primitives.make_sym_vector) + + +def get_flux_operator(flux): + """Return a flux operator that can be multiplied with + a volume field to obtain the interior fluxes + or with a :class:`BoundaryPair` to obtain the lifted boundary + flux. + """ + from hedge.tools import is_obj_array + from hedge.optemplate import VectorFluxOperator, FluxOperator + + if is_obj_array(flux): + return VectorFluxOperator(flux) + else: + return FluxOperator(flux) + + +def make_nabla(dim): + from hedge.tools import make_obj_array + from hedge.optemplate import DifferentiationOperator + return make_obj_array( + [DifferentiationOperator(i) for i in range(dim)]) + + +def make_minv_stiffness_t(dim): + from hedge.tools import make_obj_array + from hedge.optemplate import MInvSTOperator + return make_obj_array( + [MInvSTOperator(i) for i in range(dim)]) + + +def make_stiffness(dim): + from hedge.tools import make_obj_array + from hedge.optemplate import StiffnessOperator + return make_obj_array( + [StiffnessOperator(i) for i in range(dim)]) + + +def make_stiffness_t(dim): + from hedge.tools import make_obj_array + from hedge.optemplate import StiffnessTOperator + return make_obj_array( + [StiffnessTOperator(i) for i in range(dim)]) + + +def integral(arg): + import hedge.optemplate as sym + return sym.NodalSum()(sym.MassOperator()(sym.Ones())*arg) + + +def norm(p, arg): + """ + :arg arg: is assumed to be a vector, i.e. have shape ``(n,)``. + """ + import hedge.optemplate as sym + + if p == 2: + comp_norm_squared = sym.NodalSum()( + sym.CFunction("fabs")( + arg * sym.MassOperator()(arg))) + return sym.CFunction("sqrt")(sum(comp_norm_squared)) + + elif p == np.Inf: + comp_norm = sym.NodalMax()(sym.CFunction("fabs")(arg)) + from pymbolic.primitives import Max + return reduce(Max, comp_norm) + + else: + return sum(sym.NodalSum()(sym.CFunction("fabs")(arg)**p))**(1/p) + + +def flat_end_sin(x): + from hedge.optemplate.primitives import CFunction + from pymbolic.primitives import IfPositive + from math import pi + return IfPositive(-pi/2-x, + -1, IfPositive(x-pi/2, 1, CFunction("sin")(x))) + + +def smooth_ifpos(crit, right, left, width): + from math import pi + return 0.5*((left+right) + + (right-left)*flat_end_sin( + pi/2/width * crit)) +# }}} + + +# {{{ optemplate tools + +def is_scalar(expr): + from hedge.optemplate import ScalarParameter + return isinstance(expr, (int, float, complex, ScalarParameter)) + + +def get_flux_dependencies(flux, field, bdry="all"): + from hedge.flux import FluxDependencyMapper, FieldComponent + in_fields = list(FluxDependencyMapper( + include_calls=False)(flux)) + + # check that all in_fields are FieldComponent instances + assert not [in_field + for in_field in in_fields + if not isinstance(in_field, FieldComponent)] + + def maybe_index(fld, index): + from hedge.tools import is_obj_array + if is_obj_array(fld): + return fld[inf.index] + else: + return fld + + from hedge.tools import is_zero + from hedge.optemplate import BoundaryPair + if isinstance(field, BoundaryPair): + for inf in in_fields: + if inf.is_interior: + if bdry in ["all", "int"]: + value = maybe_index(field.field, inf.index) + + if not is_zero(value): + yield value + else: + if bdry in ["all", "ext"]: + value = maybe_index(field.bfield, inf.index) + + if not is_zero(value): + yield value + else: + for inf in in_fields: + value = maybe_index(field, inf.index) + if not is_zero(value): + yield value + + +def split_optemplate_for_multirate(state_vector, op_template, + index_groups): + class IndexGroupKillerSubstMap: + def __init__(self, kill_set): + self.kill_set = kill_set + + def __call__(self, expr): + if expr in kill_set: + return 0 + else: + return None + + # make IndexGroupKillerSubstMap that kill everything + # *except* what's in that index group + killers = [] + for i in range(len(index_groups)): + kill_set = set() + for j in range(len(index_groups)): + if i != j: + kill_set |= set(index_groups[j]) + + killers.append(IndexGroupKillerSubstMap(kill_set)) + + from hedge.optemplate import \ + SubstitutionMapper, \ + CommutativeConstantFoldingMapper + + return [ + CommutativeConstantFoldingMapper()( + SubstitutionMapper(killer)( + op_template[ig])) + for ig in index_groups + for killer in killers] + + +def ptwise_mul(a, b): + from pytools.obj_array import log_shape + a_log_shape = log_shape(a) + b_log_shape = log_shape(b) + + from pytools import indices_in_shape + + if a_log_shape == (): + result = np.empty(b_log_shape, dtype=object) + for i in indices_in_shape(b_log_shape): + result[i] = a*b[i] + elif b_log_shape == (): + result = np.empty(a_log_shape, dtype=object) + for i in indices_in_shape(a_log_shape): + result[i] = a[i]*b + else: + raise ValueError("ptwise_mul can't handle two non-scalars") + + return result + + +def ptwise_dot(logdims1, logdims2, a1, a2): + a1_log_shape = a1.shape[:logdims1] + a2_log_shape = a1.shape[:logdims2] + + assert a1_log_shape[-1] == a2_log_shape[0] + len_k = a2_log_shape[0] + + result = np.empty(a1_log_shape[:-1]+a2_log_shape[1:], dtype=object) + + from pytools import indices_in_shape + for a1_i in indices_in_shape(a1_log_shape[:-1]): + for a2_i in indices_in_shape(a2_log_shape[1:]): + result[a1_i+a2_i] = sum( + a1[a1_i+(k,)] * a2[(k,)+a2_i] + for k in xrange(len_k) + ) + + if result.shape == (): + return result[()] + else: + return result + +# }}} + + +# {{{ process_optemplate function + +def process_optemplate(optemplate, post_bind_mapper=None, + dumper=lambda name, optemplate: None, mesh=None, + type_hints={}): + + from hedge.optemplate.mappers import ( + OperatorBinder, CommutativeConstantFoldingMapper, + EmptyFluxKiller, InverseMassContractor, DerivativeJoiner, + ErrorChecker, OperatorSpecializer, GlobalToReferenceMapper) + from hedge.optemplate.mappers.bc_to_flux import BCToFluxRewriter + from hedge.optemplate.mappers.type_inference import TypeInferrer + + dumper("before-bind", optemplate) + optemplate = OperatorBinder()(optemplate) + + ErrorChecker(mesh)(optemplate) + + if post_bind_mapper is not None: + dumper("before-postbind", optemplate) + optemplate = post_bind_mapper(optemplate) + + if mesh is not None: + dumper("before-empty-flux-killer", optemplate) + optemplate = EmptyFluxKiller(mesh)(optemplate) + + dumper("before-cfold", optemplate) + optemplate = CommutativeConstantFoldingMapper()(optemplate) + + dumper("before-bc2flux", optemplate) + optemplate = BCToFluxRewriter()(optemplate) + + # Ordering restriction: + # + # - Must run constant fold before first type inference pass, because zeros, + # while allowed, violate typing constraints (because they can't be assigned + # a unique type), and need to be killed before the type inferrer sees them. + # + # - Must run BC-to-flux before first type inferrer run so that zeros in + # flux arguments can be removed. + + dumper("before-specializer", optemplate) + optemplate = OperatorSpecializer( + TypeInferrer()(optemplate, type_hints) + )(optemplate) + + # Ordering restriction: + # + # - Must run OperatorSpecializer before performing the GlobalToReferenceMapper, + # because otherwise it won't differentiate the type of grids (node or quadrature + # grids) that the operators will apply on. + + assert mesh is not None + dumper("before-global-to-reference", optemplate) + optemplate = GlobalToReferenceMapper(mesh.dimensions)(optemplate) + + # Ordering restriction: + # + # - Must specialize quadrature operators before performing inverse mass + # contraction, because there are no inverse-mass-contracted variants of the + # quadrature operators. + + dumper("before-imass", optemplate) + optemplate = InverseMassContractor()(optemplate) + + dumper("before-cfold-2", optemplate) + optemplate = CommutativeConstantFoldingMapper()(optemplate) + + dumper("before-derivative-join", optemplate) + optemplate = DerivativeJoiner()(optemplate) + + dumper("process-optemplate-finished", optemplate) + + return optemplate + +# }}} + + +# {{{ pretty printing + +def pretty(optemplate): + from hedge.optemplate.mappers import PrettyStringifyMapper + + stringify_mapper = PrettyStringifyMapper() + from pymbolic.mapper.stringifier import PREC_NONE + result = stringify_mapper(optemplate, PREC_NONE) + + splitter = "="*75 + "\n" + + bc_strs = stringify_mapper.get_bc_strings() + if bc_strs: + result = "\n".join(bc_strs)+"\n"+splitter+result + + cse_strs = stringify_mapper.get_cse_strings() + if cse_strs: + result = "\n".join(cse_strs)+"\n"+splitter+result + + flux_strs = stringify_mapper.get_flux_strings() + if flux_strs: + result = "\n".join(flux_strs)+"\n"+splitter+result + + flux_cses = stringify_mapper.flux_stringify_mapper.get_cse_strings() + if flux_cses: + result = "\n".join("flux "+fs for fs in flux_cses)+"\n\n"+result + + return result + +# }}} + + +@decorator +def memoize_method_with_obj_array_args(method, instance, *args): + """This decorator manages to memoize functions that + take object arrays (which are mutable, but are assumed + to never change) as arguments. + """ + dicname = "_memoize_dic_"+method.__name__ + + new_args = [] + for arg in args: + if isinstance(arg, np.ndarray) and arg.dtype == object: + new_args.append(tuple(arg)) + else: + new_args.append(arg) + new_args = tuple(new_args) + + try: + return getattr(instance, dicname)[new_args] + except AttributeError: + result = method(instance, *args) + setattr(instance, dicname, {new_args: result}) + return result + except KeyError: + result = method(instance, *args) + getattr(instance, dicname)[new_args] = result + return result + + +# vim: foldmethod=marker -- GitLab