diff --git a/examples/wave/var-propagation-speed.py b/examples/wave/var-propagation-speed.py
index 3f4b9927c0436896d94e95e78f2d1325ec4a2045..24719059263217f760745f84afa2851668775450 100644
--- a/examples/wave/var-propagation-speed.py
+++ b/examples/wave/var-propagation-speed.py
@@ -1,11 +1,8 @@
-"""Variable-coefficient wave propagation."""
+"""Minimal example of a grudge driver."""
 
-from __future__ import division
-from __future__ import absolute_import
-from __future__ import print_function
-from six.moves import range
+from __future__ import division, print_function
 
-__copyright__ = "Copyright (C) 2009 Andreas Kloeckner"
+__copyright__ = "Copyright (C) 2015 Andreas Kloeckner"
 
 __license__ = """
 Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -29,171 +26,105 @@ THE SOFTWARE.
 
 
 import numpy as np
-from grudge.mesh import BTAG_ALL, BTAG_NONE
-
-
-def main(write_output=True,
-        dir_tag=BTAG_NONE,
-        neu_tag=BTAG_NONE,
-        rad_tag=BTAG_ALL,
-        flux_type_arg="upwind"):
-    from math import sin, cos, pi, exp, sqrt  # noqa
-
-    from grudge.backends import guess_run_context
-    rcon = guess_run_context()
-
-    dim = 2
-
-    if dim == 1:
-        if rcon.is_head_rank:
-            from grudge.mesh.generator import make_uniform_1d_mesh
-            mesh = make_uniform_1d_mesh(-10, 10, 500)
-    elif dim == 2:
-        from grudge.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 grudge.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 grudge.timestep.runge_kutta import LSRK4TimeStepper
-    stepper = LSRK4TimeStepper()
-
-    from grudge.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 grudge.symbolic as sym
-    sym_x = sym.nodes(2)
-    sym_source_center_dist = sym_x - source_center
+import pyopencl as cl
+from grudge.shortcuts import set_up_rk4
+from grudge import sym, bind, Discretization
 
-    from grudge.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 grudge.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 grudge.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 grudge.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 grudge.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()
+def main(write_output=True, order=4):
+    cl_ctx = cl.create_some_context()
+    queue = cl.CommandQueue(cl_ctx)
 
-        logmgr.close()
-        discr.close()
+    dims = 2
+    from meshmode.mesh.generation import generate_regular_rect_mesh
+    mesh = generate_regular_rect_mesh(
+            a=(-0.5,)*dims,
+            b=(0.5,)*dims,
+            n=(8,)*dims)
 
-    # }}}
-
-if __name__ == "__main__":
-    main(flux_type_arg="upwind")
+    discr = Discretization(cl_ctx, mesh, order=order)
 
+    source_center = np.array([0.1, 0.22, 0.33])[:mesh.dim]
+    source_width = 0.05
+    source_omega = 3
 
-# entry points for py.test ----------------------------------------------------
-def test_var_velocity_wave():
-    from pytools.test import mark_test
-    mark_long = mark_test.long
+    sym_x = sym.nodes(mesh.dim)
+    sym_source_center_dist = sym_x - source_center
+    sym_t = sym.ScalarVariable("t")
+    c =    sym.If(sym.Comparison(
+                np.dot(sym_x, sym_x), "<", 0.2),
+                -0.1, -0.2)
+    #c = np.dot(sym_x,sym_x)
+
+    from grudge.models.wave import VariableCoefficientWeakWaveOperator
+    from meshmode.mesh import BTAG_ALL, BTAG_NONE
+    op = VariableCoefficientWeakWaveOperator(c,
+            discr.dim,
+            source_f=(
+                sym.sin(source_omega*sym_t)
+                * sym.exp(
+                    -np.dot(sym_source_center_dist, sym_source_center_dist)
+                    / source_width**2)),
+            dirichlet_tag=BTAG_NONE,
+            neumann_tag=BTAG_NONE,
+            radiation_tag=BTAG_ALL,
+            flux_type="central")
+
+    queue = cl.CommandQueue(discr.cl_context)
+    from pytools.obj_array import join_fields
+    fields = join_fields(discr.zeros(queue),
+            [discr.zeros(queue) for i in range(discr.dim)])
+
+    # FIXME
+    #dt = op.estimate_rk4_timestep(discr, fields=fields)
+
+    op.check_bc_coverage(mesh)
+
+    # print(sym.pretty(op.sym_operator()))
+    bound_op = bind(discr, op.sym_operator())
+    print(bound_op)
+    # 1/0
+
+    def rhs(t, w):
+        return bound_op(queue, t=t, w=w)
+
+    if mesh.dim == 2:
+        dt = 0.04
+    elif mesh.dim == 3:
+        dt = 0.02
+
+    dt_stepper = set_up_rk4("w", dt, fields, rhs)
+
+    final_t = 10
+    nsteps = int(final_t/dt)
+    print("dt=%g nsteps=%d" % (dt, nsteps))
+
+    from grudge.shortcuts import make_visualizer
+    vis = make_visualizer(discr, vis_order=order)
+
+    step = 0
+
+    norm = bind(discr, sym.norm(2, sym.var("u")))
+
+    from time import time
+    t_last_step = time()
+
+    for event in dt_stepper.run(t_end=final_t):
+        if isinstance(event, dt_stepper.StateComputed):
+            assert event.component_id == "w"
+
+            step += 1
+
+            print(step, event.t, norm(queue, u=event.state_component[0]),
+                    time()-t_last_step)
+            if step % 10 == 0:
+                vis.write_vtk_file("fld-%04d.vtu" % step,
+                        [
+                            ("u", event.state_component[0]),
+                            ("v", event.state_component[1:]),
+                            ])
+            t_last_step = time()
 
-    for flux_type in ["upwind", "central"]:
-        yield ("dirichlet var-v wave equation with %s flux" % flux_type,
-                mark_long(main),
-                False, BTAG_ALL, BTAG_NONE, TAG_NONE, flux_type)
-    yield ("neumann var-v wave equation", mark_long(main),
-            False, BTAG_NONE, BTAG_ALL, TAG_NONE)
-    yield ("radiation-bc var-v wave equation", mark_long(main),
-            False, BTAG_NONE, TAG_NONE, BTAG_ALL)
 
-# vim: foldmethod=marker
+if __name__ == "__main__":
+    main()
diff --git a/grudge/execution.py b/grudge/execution.py
index 0241c82b532c9167c60362917a08c73123c385df..f78c345f3c87f4c495afa3685b15680c06fae0cd 100644
--- a/grudge/execution.py
+++ b/grudge/execution.py
@@ -119,12 +119,37 @@ class ExecutionMapper(mappers.Evaluator,
         then = self.rec(expr.then)
         else_ = self.rec(expr.else_)
 
-        result = cl.array.empty_like(then, queue=self.queue,
-                allocator=self.bound_op.allocator)
-        cl.array.if_positive(bool_crit, then, else_, out=result,
-                queue=self.queue)
+        import pymbolic.primitives as p
 
-        return result
+        var = p.Variable
+
+        i = var("i") #sym.Variable("i")
+        if isinstance(then,  pyopencl.array.Array):
+            sym_then = var("a")[i] #sym.Variable("a")[i]
+        else:
+            sym_then = var("a") # sym.Variable("a")
+            then = np.float64(then)
+
+        if isinstance(else_,  pyopencl.array.Array):
+            sym_else = var("b")[i] # sym.Variable("b")[i]
+        else:
+            sym_else = var("b") # sym.Variable("b")
+            else_ = np.float64(else_)
+
+        @memoize_in(self.bound_op, "map_if_knl")
+        def knl():
+            knl = lp.make_kernel(
+               "{[i]: 0<=i<n}",
+                [
+                    lp.Assignment(var("out")[i], p.If(var("crit")[i], sym_then, sym_else))
+
+                   #lp.Assignment(sym.Variable("out")[i], sym.If(sym.Variable("crit")[i], sym_then, sym_else))
+                ])
+            return lp.split_iname(knl, "i", 128, outer_tag="g.0", inner_tag="l.0")
+
+        evt, (out,) = knl()(self.queue, crit=bool_crit, a=then, b=else_)
+
+        return out
 
     def map_ref_diff_base(self, op, field_expr):
         raise NotImplementedError(
diff --git a/grudge/models/wave.py b/grudge/models/wave.py
index 98f0b4407324b82895da2040503de692849f9743..6ea1d923287761bba5efbb4998beda4a519e6318 100644
--- a/grudge/models/wave.py
+++ b/grudge/models/wave.py
@@ -180,187 +180,325 @@ class StrongWaveOperator(HyperbolicOperator):
     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`.
+class WeakWaveOperator(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 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.
 
-        \partial_t v - c \nabla u = 0
+    :math:`c` is assumed to be constant across all space.
     """
 
-    def __init__(
-            self, c, ambient_dim, source=0,
+    def __init__(self, c, ambient_dim, source_f=0,
             flux_type="upwind",
             dirichlet_tag=BTAG_ALL,
+            dirichlet_bc_f=0,
             neumann_tag=BTAG_NONE,
-            radiation_tag=BTAG_NONE,
-            time_sign=1,
-            diffusion_coeff=None,
-            diffusion_scheme=CentralSecondDerivative()
-            ):
-        """*c* and *source* are optemplate expressions.
-        """
+            radiation_tag=BTAG_NONE):
         assert isinstance(ambient_dim, int)
 
         self.c = c
-        self.time_sign = time_sign
         self.ambient_dim = ambient_dim
-        self.source = source
+        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.flux_type = flux_type
+        self.dirichlet_bc_f = dirichlet_bc_f
 
-        self.diffusion_coeff = diffusion_coeff
-        self.diffusion_scheme = diffusion_scheme
+        self.flux_type = flux_type
 
-    # {{{ flux ----------------------------------------------------------------
-    def flux(self, w, c_vol):
+    def flux(self, w):
         u = w[0]
         v = w[1:]
-        normal = sym.normal(w.tag, self.ambient_dim)
+        normal = sym.normal(w.dd, self.ambient_dim)
 
-        c = sym.RestrictToBoundary(w.tag)(c_vol)
+        flux_weak = join_fields(
+                np.dot(v.avg, normal),
+                u.avg * normal)
 
-        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
+            return -self.c*flux_weak
         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)
-                    )
+            return -self.c*(flux_weak + self.sign*join_fields(
+                    0.5*(u.int-u.ext),
+                    0.5*(normal * np.dot(normal, v.int-v.ext))))
+            # see doc/notes/grudge-notes.tm
+            # THis is terrible
+            #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 sym_operator(self):
+        d = self.ambient_dim
+
+        w = sym.make_sym_array("w", d+1)
+        u = w[0]
+        v = w[1:]
+
+        # boundary conditions -------------------------------------------------
+
+        # dirichlet BCs -------------------------------------------------------
+        dir_u = sym.cse(sym.interp("vol", self.dirichlet_tag)(u))
+        dir_v = sym.cse(sym.interp("vol", 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 = sym.Field("dir_bc_u")
+            dir_bc = join_fields(2*dir_g - dir_u, dir_v)
         else:
-            raise ValueError("invalid flux type '%s'" % self.flux_type)
+            dir_bc = join_fields(-dir_u, dir_v)
+
+        dir_bc = sym.cse(dir_bc, "dir_bc")
+
+        # neumann BCs ---------------------------------------------------------
+        neu_u = sym.cse(sym.interp("vol", self.neumann_tag)(u))
+        neu_v = sym.cse(sym.interp("vol", self.neumann_tag)(v))
+        neu_bc = sym.cse(join_fields(neu_u, -neu_v), "neu_bc")
+
+        # radiation BCs -------------------------------------------------------
+        rad_normal = sym.normal(self.radiation_tag, d)
+
+        rad_u = sym.cse(sym.interp("vol", self.radiation_tag)(u))
+        rad_v = sym.cse(sym.interp("vol", self.radiation_tag)(v))
 
-        return flux
+        rad_bc = sym.cse(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)
+                ), "rad_bc")
 
-    # }}}
+        # entire operator -----------------------------------------------------
+        nabla = sym.nabla(d)
 
-    def bind_characteristic_velocity(self, discr):
-        from grudge.symbolic.operators import ElementwiseMaxOperator
+        def flux(pair):
+            return sym.interp(pair.dd, "all_faces")(self.flux(pair))
+
+
+        #result = (
+                #- join_fields(
+                    #-self.c*np.dot(nabla, v),
+                    #-self.c*(nabla*u)
+                    #)
+                #+
+                #sym.InverseMassOperator()(
+                    #sym.FaceMassOperator()(
+                        #flux(sym.int_tpair(w))
+                        #+ flux(sym.bv_tpair(self.dirichlet_tag, w, dir_bc))
+                        #+ flux(sym.bv_tpair(self.neumann_tag, w, neu_bc))
+                        #+ flux(sym.bv_tpair(self.radiation_tag, w, rad_bc))
+                        #)))
+
+        result = sym.InverseMassOperator()( 
+                join_fields(
+                    -self.c*np.dot(sym.stiffness_t(self.ambient_dim), v),
+                    -self.c*(sym.stiffness_t(self.ambient_dim)*u)
+                    )
 
-        compiled = discr.compile(ElementwiseMaxOperator()(self.c))
 
-        def do(t, w, **extra_context):
-            return compiled(t=t, w=w, **extra_context)
+                - sym.FaceMassOperator()( flux(sym.int_tpair(w)) 
+                    + flux(sym.bv_tpair(self.dirichlet_tag, w, dir_bc))
+                    + flux(sym.bv_tpair(self.neumann_tag, w, neu_bc))
+                    + flux(sym.bv_tpair(self.radiation_tag, w, rad_bc))
 
-        return do
+                    ) )
 
-    def sym_operator(self, with_sensor=False):
-        d = self.ambient_dim
+        result[0] += self.source_f
 
-        w = sym.make_sym_array("w", d+1)
-        u = w[0]
-        v = w[1:]
+        return result
+
+    def check_bc_coverage(self, mesh):
+        from meshmode.mesh import check_bc_coverage
+        check_bc_coverage(mesh, [
+            self.dirichlet_tag,
+            self.neumann_tag,
+            self.radiation_tag])
+
+    def max_eigenvalue(self, t, fields=None, discr=None):
+        return abs(self.c)
 
-        flux_w = join_fields(self.c, w)
 
-        # {{{ boundary conditions
+# }}}
 
-        # Dirichlet
-        dir_c = sym.RestrictToBoundary(self.dirichlet_tag) * self.c
-        dir_u = sym.RestrictToBoundary(self.dirichlet_tag) * u
-        dir_v = sym.RestrictToBoundary(self.dirichlet_tag) * v
 
-        dir_bc = join_fields(dir_c, -dir_u, dir_v)
+# {{{ variable-velocity
 
-        # Neumann
-        neu_c = sym.RestrictToBoundary(self.neumann_tag) * self.c
-        neu_u = sym.RestrictToBoundary(self.neumann_tag) * u
-        neu_v = sym.RestrictToBoundary(self.neumann_tag) * v
+class VariableCoefficientWeakWaveOperator(HyperbolicOperator):
+    """This operator discretizes the wave equation
+    :math:`\\partial_t^2 u = c^2 \\Delta u`.
 
-        neu_bc = join_fields(neu_c, neu_u, -neu_v)
+    To be precise, we discretize the hyperbolic system
 
-        # Radiation
-        rad_normal = sym.make_normal(self.radiation_tag, d)
+    .. math::
 
-        rad_c = sym.RestrictToBoundary(self.radiation_tag) * self.c
-        rad_u = sym.RestrictToBoundary(self.radiation_tag) * u
-        rad_v = sym.RestrictToBoundary(self.radiation_tag) * v
+        \partial_t u - c \\nabla \\cdot v = 0
 
-        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)
-                )
+        \partial_t v - c \\nabla u = 0
 
-        # }}}
+    The sign of :math:`v` determines whether we discretize the forward or the
+    backward wave equation.
 
-        # {{{ diffusion -------------------------------------------------------
-        from pytools.obj_array import with_object_array_or_scalar
+    :math:`c` is assumed to be constant across all space.
+    """
 
-        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
+    def __init__(self, c, ambient_dim, source_f=0,
+            flux_type="upwind",
+            dirichlet_tag=BTAG_ALL,
+            dirichlet_bc_f=0,
+            neumann_tag=BTAG_NONE,
+            radiation_tag=BTAG_NONE):
+        assert isinstance(ambient_dim, int)
 
-                if with_sensor:
-                    diffusion_coeff += sym.Field("sensor")
+        self.c = c
+        self.ambient_dim = ambient_dim
+        self.source_f = source_f
 
-                from grudge.second_order import SecondDerivativeTarget
+        self.sign = sym.If(sym.Comparison(
+                            self.c, ">", 0),
+                                            1, -1)
 
-                # strong_form here allows the reuse the value of grad u.
-                grad_tgt = SecondDerivativeTarget(
-                        self.ambient_dim, strong_form=True,
-                        operand=arg)
+        self.dirichlet_tag = dirichlet_tag
+        self.neumann_tag = neumann_tag
+        self.radiation_tag = radiation_tag
 
-                self.diffusion_scheme.grad(grad_tgt, bc_getter=None,
-                        dirichlet_tags=[], neumann_tags=[])
+        self.dirichlet_bc_f = dirichlet_bc_f
+
+        self.flux_type = flux_type
+
+    def flux(self, w):
+        u = w[0]
+        v = w[1:]
+        normal = sym.normal(w.dd, self.ambient_dim)
+
+        surf_c = sym.interp("vol", u.dd)(self.c)
+
+        flux_weak = join_fields(
+                np.dot(v.avg, normal),
+                u.avg * normal)
+        return -surf_c*flux_weak
+
+
+        if self.flux_type == "central":
+            return -surf_c*flux_weak
+        #elif self.flux_type == "upwind":
+            # see doc/notes/grudge-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
 
-                div_tgt = SecondDerivativeTarget(
-                        self.ambient_dim, strong_form=False,
-                        operand=diffusion_coeff*grad_tgt.minv_all)
+        #return self.c*flux_strong
 
-                self.diffusion_scheme.div(div_tgt,
-                        bc_getter=None,
-                        dirichlet_tags=[], neumann_tags=[])
+    def sym_operator(self):
+        d = self.ambient_dim
 
-                return div_tgt.minv_all
-            else:
-                return 0
+        w = sym.make_sym_array("w", d+1)
+        u = w[0]
+        v = w[1:]
 
-        # }}}
+        # boundary conditions -------------------------------------------------
+
+        # dirichlet BCs -------------------------------------------------------
+        dir_u = sym.cse(sym.interp("vol", self.dirichlet_tag)(u))
+        dir_v = sym.cse(sym.interp("vol", 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 = sym.Field("dir_bc_u")
+            dir_bc = join_fields(2*dir_g - dir_u, dir_v)
+        else:
+            dir_bc = join_fields(-dir_u, dir_v)
+
+        dir_bc = sym.cse(dir_bc, "dir_bc")
+
+        # neumann BCs ---------------------------------------------------------
+        neu_u = sym.cse(sym.interp("vol", self.neumann_tag)(u))
+        neu_v = sym.cse(sym.interp("vol", self.neumann_tag)(v))
+        neu_bc = sym.cse(join_fields(neu_u, -neu_v), "neu_bc")
+
+        # radiation BCs -------------------------------------------------------
+        rad_normal = sym.normal(self.radiation_tag, d)
+
+        rad_u = sym.cse(sym.interp("vol", self.radiation_tag)(u))
+        rad_v = sym.cse(sym.interp("vol", self.radiation_tag)(v))
+
+        rad_bc = sym.cse(join_fields(
+                0.5*(rad_u - sym.interp("vol",self.radiation_tag)(self.sign)*np.dot(rad_normal, rad_v)),
+                0.5*rad_normal*(np.dot(rad_normal, rad_v) -sym.interp("vol",self.radiation_tag)(self.sign)*rad_u)
+                ), "rad_bc")
 
         # entire operator -----------------------------------------------------
         nabla = sym.nabla(d)
-        flux_op = sym.get_flux_operator(self.flux())
 
-        return (
-                - join_fields(
-                    - self.time_sign*self.c*np.dot(nabla, v) - make_diffusion(u)
-                    + self.source,
+        def flux(pair):
+            return sym.interp(pair.dd, "all_faces")(self.flux(pair))
+
+
+        #result = sym.InverseMassOperator()( 
+                #join_fields(
+                    #-self.c*np.dot(sym.stiffness_t(self.ambient_dim), v),
+                    #-self.c*(sym.stiffness_t(self.ambient_dim)*u)
+                    #)
+
+
+                #- sym.FaceMassOperator()( flux(sym.int_tpair(w)) 
+                    #+ flux(sym.bv_tpair(self.dirichlet_tag, w, dir_bc))
+                    #+ flux(sym.bv_tpair(self.neumann_tag, w, neu_bc))
+                    #+ flux(sym.bv_tpair(self.radiation_tag, w, rad_bc))
 
-                    -self.time_sign*self.c*(nabla*u) - with_object_array_or_scalar(
-                        make_diffusion, v)
+                    #)# )
+
+        result = sym.InverseMassOperator()( 
+                join_fields(
+                    -self.c*np.dot(sym.stiffness_t(self.ambient_dim), v),
+                    -self.c*(sym.stiffness_t(self.ambient_dim)*u)
                     )
-                +
-                sym.InverseMassOperator() * (
-                    flux_op(flux_w)
-                    + flux_op(sym.BoundaryPair(flux_w, dir_bc, self.dirichlet_tag))
-                    + flux_op(sym.BoundaryPair(flux_w, neu_bc, self.neumann_tag))
-                    + flux_op(sym.BoundaryPair(flux_w, rad_bc, self.radiation_tag))
-                    ))
+
+
+                - sym.FaceMassOperator()( flux(sym.int_tpair(w)) 
+                    + flux(sym.bv_tpair(self.dirichlet_tag, w, dir_bc))
+                    + flux(sym.bv_tpair(self.neumann_tag, w, neu_bc))
+                    + flux(sym.bv_tpair(self.radiation_tag, w, rad_bc))
+
+                    ) )
+
+        result[0] += self.source_f
+
+        return result
 
     def check_bc_coverage(self, mesh):
         from meshmode.mesh import check_bc_coverage
@@ -369,9 +507,9 @@ class VariableVelocityStrongWaveOperator(HyperbolicOperator):
             self.neumann_tag,
             self.radiation_tag])
 
-    def max_eigenvalue_expr(self):
-        import grudge.symbolic as sym
-        return sym.NodalMax()(sym.CFunction("fabs")(self.c))
+    def max_eigenvalue(self, t, fields=None, discr=None):
+        return abs(self.c)
+
 
 # }}}