diff --git a/.gitignore b/.gitignore index 05e0c40e71bd1f2e918495e6e811370baf1b687b..e6efa6fae37f239c8ed880ac0cf08db633158bca 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 0000000000000000000000000000000000000000..e912531c3179dd67e7d886391edf3bb91ce17c7d --- /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 Binary files /dev/null and b/examples/advection/space-dep.dat differ diff --git a/examples/advection/var-velocity.py b/examples/advection/var-velocity.py new file mode 100644 index 0000000000000000000000000000000000000000..81fb7e675525ed223320f0aea6eb9a5d6e85ba39 --- /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 Binary files /dev/null and b/examples/burgers/burgers.dat differ diff --git a/examples/burgers/burgers.py b/examples/burgers/burgers.py new file mode 100644 index 0000000000000000000000000000000000000000..20b4832a2b9bae479ff4d95c6da6fbc40b411352 --- /dev/null +++ b/examples/burgers/burgers.py @@ -0,0 +1,238 @@ +# 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 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 0000000000000000000000000000000000000000..ec99a4c7fd882c79b4c0e1b6b78b16ab578b8532 --- /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 0000000000000000000000000000000000000000..ac3be82f7d5c230a6150243354fc359938fe4f24 --- /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 0000000000000000000000000000000000000000..9f751a86eb4954c19a05c6b8a3fed31257db57fd --- /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 0000000000000000000000000000000000000000..352a855e8c9bf12df6729b8384e7976e2fb34fbc --- /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 0000000000000000000000000000000000000000..fb33c2ed0c1884e1123e50b573f9eb16d2e7c3cb --- /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 0000000000000000000000000000000000000000..dbe5f84d57bbcd7722cf925708c4904b30196403 --- /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 0000000000000000000000000000000000000000..ddbb43288692bd7807c5bd4a7b6779a817ba994b --- /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 0000000000000000000000000000000000000000..9e1bef7d7cd7b7936fbe3a6b6856c7a211e66743 --- /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 0000000000000000000000000000000000000000..4e5cf034f52ff294010fb17087a2d4a47b231d4b --- /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 0000000000000000000000000000000000000000..9439deeea01fb5b93d5137ddbd4555a3fce3cbe5 --- /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 0000000000000000000000000000000000000000..597c2b0b8d7862e11f723134a60d2f76e43eb58c --- /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 0000000000000000000000000000000000000000..f1e63e62445a2e735af4eece1859d83bdc2b7231 --- /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 0000000000000000000000000000000000000000..de8243249729281d783b8fb9b9b771d879543cc5 --- /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 0000000000000000000000000000000000000000..6cd193f72077f417e9f4ee250412d28d761a2478 --- /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 0000000000000000000000000000000000000000..641163859ce66ad7bebf63db3035f0d58abef797 --- /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 0000000000000000000000000000000000000000..64505c949f500177ce91203426cd75a479f27dad --- /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 0000000000000000000000000000000000000000..b1e2dfea38d85140176f484a684f492d8d91512f --- /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 0000000000000000000000000000000000000000..261434c1f020f06448dd0042c4adf1b280c944e2 --- /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 0000000000000000000000000000000000000000..9c3b11b64baafab78186d4ded0cdaa1589ae579e --- /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 0000000000000000000000000000000000000000..70f4a8e434f73aacba6d36ebb29b1c1170aac008 --- /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 0000000000000000000000000000000000000000..28a582f428be9e3193e4b012f4029d45464f0db9 --- /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 0000000000000000000000000000000000000000..33681ebcf6e3649f5a0a0a94ec76b0c18203623c --- /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 Binary files /dev/null and b/examples/maxwell/maxwell-2d/maxwell-3.dat differ diff --git a/examples/maxwell/maxwell-pml.py b/examples/maxwell/maxwell-pml.py new file mode 100644 index 0000000000000000000000000000000000000000..e365d80471dd12a45ab1e0ca64ac55f8210c2ced --- /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 0000000000000000000000000000000000000000..bd159b24881bbcbd6561a2e048c8cd43dff79f8f --- /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 0000000000000000000000000000000000000000..ac4ce519881c57bd1449947f76027aa7eb0328b2 --- /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 0000000000000000000000000000000000000000..d521c74502bbb10b55557b467db45ffd66a483ab --- /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 0000000000000000000000000000000000000000..48b7f910de80c258f3f8087832dca1ba81a0b116 --- /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 0000000000000000000000000000000000000000..2d0d91c6afbc5addef33b188b35685df9c740b83 --- /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 0000000000000000000000000000000000000000..70463ece7a66737814a451139abd9c12803f7440 --- /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 0000000000000000000000000000000000000000..22eaeb04f2bf30836b2cce9c664ddfaedd823509 --- /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 0000000000000000000000000000000000000000..d9142d67639b349b7f2edc25deba9e18623132e7 --- /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 0000000000000000000000000000000000000000..e567951896d0b205ddf058308b898dbcee03ead6 --- /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 0000000000000000000000000000000000000000..7c38eb5bf7a44746d0c7728ffa6f8af85e4b6aa8 --- /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 0000000000000000000000000000000000000000..a6930f720dce8ae46e7d7c29b40d23a290ffb4ad --- /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 0000000000000000000000000000000000000000..23598708a3942f40482b127c80286074f54ed734 --- /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 0000000000000000000000000000000000000000..8392a4ddc70e9bad3be744b8a6d8e50db6a13cc4 --- /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 0000000000000000000000000000000000000000..84446f8b2522c7641e4cfd9e6bc793f955a58ced --- /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 0000000000000000000000000000000000000000..5674fc9fdfdcbcaf35ac78da4de4e850e5a477a3 --- /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 0000000000000000000000000000000000000000..b8cb6216bf728bde9bfa301c5d10021a5cb6ef5b --- /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 0000000000000000000000000000000000000000..f60091d5edb7259c8203fb5d5625b1b02020c5d9 --- /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 0000000000000000000000000000000000000000..400a489d84851a0adb3cd2d1717eade172cfa393 --- /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 0000000000000000000000000000000000000000..a72aa713d2cf9c9184d870f1ddde57c778e08e16 --- /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 0000000000000000000000000000000000000000..58619e48d78138fe71d989b605ec68e1adec4783 --- /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 0000000000000000000000000000000000000000..4c7bafdf0b29514f3405636995992dfbcd15a531 --- /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 0000000000000000000000000000000000000000..af1f359388559494712bc9887ea7cc8a919b99f9 --- /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 0000000000000000000000000000000000000000..c13fe7051a3265f85cec7b5cf4282cfca448196b --- /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