Ejemplo n.º 1
0
def test_assign():
    c0 = Constant(1.)
    assert c0.values() == (1,)
    c0.assign(Constant(3))
    assert c0.values() == (3,)

    c1 = Constant([1, 2])
    assert (c1.values() == (1, 2)).all()
    c1.assign(Constant([3, 4]))
    assert (c1.values() == (3, 4)).all()
Ejemplo n.º 2
0
def test_values():
    import numpy as np

    c0 = Constant(1.)
    c0_vals = c0.values()
    assert np.all(c0_vals == np.array([1.], dtype=np.double))

    c1 = Constant((1., 2.))
    c1_vals = c1.values()
    assert np.all(c1_vals == np.array([1., 2.], dtype=np.double))

    c2 = Constant((1., 2., 3.))
    c2_vals = c2.values()
    assert np.all(c2_vals == np.array([1., 2., 3.], dtype=np.double))
Ejemplo n.º 3
0
def test_constant_float_conversion():
    c = Constant(3.45)
    assert float(c.values()[0]) == 3.45
Ejemplo n.º 4
0
class M3H3(object):

    def __init__(self, geometry, parameters, *args, **kwargs):
        self.parameters = parameters
        self.physics = [Physics(p) for p in parameters.keys()
                                                    if Physics.has_value(p)]
        self.interactions = kwargs.get('interactions', [])
        if len(self.interactions) > 0:
            self._check_physics_interactions()

        if 'time' in kwargs.keys():
            self.time = kwargs['time']
            kwargs.pop('time', None)
        else:
            self.time = Constant(self.parameters['start_time'])

        self._setup_geometries(geometry, self.physics)
        self._setup_problems(**kwargs)
        self._setup_solvers(**kwargs)


    def step(self):
        # Setup time stepping if running step function for the first time.
        if self.time.values()[0] == self.parameters['start_time']:
            self.num_steps, self.max_dt = self._get_num_steps()

        time = float(self.time)
        solution_fields = self.get_solution_fields()

        if Physics.ELECTRO in self.physics:
            for _ in range(self.num_steps[Physics.ELECTRO]):
                electro_fields = solution_fields[str(Physics.ELECTRO)]
                self.electro_solver.step(electro_fields[1])

        if Physics.SOLID in self.physics:
            for _ in range(self.num_steps[Physics.SOLID]):
                self.solid_solver.step()

        if Physics.FLUID in self.physics:
            for _ in range(self.num_steps[Physics.FLUID]):
                self.fluid_solver.step()

        if Physics.POROUS in self.physics:
            for _ in range(self.num_steps[Physics.POROUS]):
                self.porous_solver.step()
                
        self.time.assign(time + self.max_dt)
        return time, solution_fields


    def get_solution_fields(self):
        solution_fields = {}
        if Physics.ELECTRO in self.physics:
            solution_fields[str(Physics.ELECTRO)] =\
                                    self.electro_problem._get_solution_fields()
        if Physics.SOLID in self.physics:
            pass
        if Physics.FLUID in self.physics:
            pass
        if Physics.POROUS in self.physics:
            pass
        return solution_fields


    def add_stimulus(self, stimulus):
        assert hasattr(self, 'electro_problem'), \
            "Cannot add stimulus if electrophysiology has not been set up."
        self.electro_problem.add_stimulus(stimulus)


    def _get_num_steps(self):
        dt_physics = self._get_physics_dt()
        min_dt = min(dt_physics.values())
        max_dt = max(dt_physics.values())
        num_steps = {}
        if Physics.ELECTRO in self.physics:
            dt = dt_physics[Physics.ELECTRO]
            if self._check_dt_is_multiple(dt, min_dt):
                num_steps[Physics.ELECTRO] = int(max_dt/dt)
        if Physics.SOLID in self.physics:
            dt = dt_physics[Physics.SOLID]
            if self._check_dt_is_multiple(dt, min_dt):
                num_steps[Physics.SOLID] = int(max_dt/dt)
        if Physics.FLUID in self.physics:
            dt = dt_physics[Physics.FLUID]
            if self._check_dt_is_multiple(dt, min_dt):
                num_steps[Physics.FLUID] = int(max_dt/dt)
        if Physics.POROUS in self.physics:
            dt = dt_physics[Physics.POROUS]
            if self._check_dt_is_multiple(dt, min_dt):
                num_steps[Physics.POROUS] = int(max_dt/dt)
        return num_steps, max_dt


    def _check_dt_is_multiple(self, dt, min_dt):
        if not (dt/min_dt).is_integer():
            msg = "Time step sizes have to be multiples of each other."\
                    "{} is not a multiple of {}".format(dt, min_dt)
            raise ValueError(msg)
        else:
            return True


    def _get_physics_dt(self):
        dt = {}
        if Physics.ELECTRO in self.physics:
            dt[Physics.ELECTRO] = self.parameters[str(Physics.ELECTRO)]['dt']
        if Physics.SOLID in self.physics:
            dt[Physics.SOLID] = self.parameters[str(Physics.SOLID)]['dt']
        if Physics.FLUID in self.physics:
            dt[Physics.FLUID] = self.parameters[str(Physics.FLUID)]['dt']
        if Physics.POROUS in self.physics:
            dt[Physics.POROUS] = self.parameters[str(Physics.POROUS)]['dt']
        return dt


    def _setup_problems(self, **kwargs):
        if Physics.ELECTRO in self.physics:
            self.electro_problem = ElectroProblem(
                                        self.geometries[Physics.ELECTRO],
                                        self.time,
                                        self.parameters[str(Physics.ELECTRO)],
                                        **kwargs)
            
        if Physics.SOLID in self.physics:
            self.solid_problem = SolidProblem(
                                        self.geometries[Physics.SOLID], 
                                        self.time,
                                        self.parameters[str(Physics.SOLID)],
                                        **kwargs)

        if Physics.FLUID in self.physics:
            self.fluid_problem = FluidProblem(
                                        self.geometries[Physics.FLUID],
                                        self.time,
                                        self.parameters[str(Physics.FLUID)],
                                        **kwargs)

        if Physics.POROUS in self.physics:
            self.porous_problem = PorousProblem(
                                        self.geometries[Physics.POROUS],
                                        self.time,
                                        self.parameters[str(Physics.POROUS)],
                                        **kwargs)


    def _setup_solvers(self, **kwargs):
        interval = (self.parameters['start_time'], self.parameters['end_time'])
        solution_fields = self.get_solution_fields()

        if Physics.ELECTRO in self.physics:
            elabel = str(Physics.ELECTRO)
            electro_fields = solution_fields[elabel]
            parameters = self.parameters[elabel]['linear_variational_solver']
            self.electro_solver = BasicBidomainSolver(self.time,
                                    self.electro_problem._form, electro_fields,
                                    parameters, **kwargs)

        if Physics.SOLID in self.physics:
            parameters = self.parameters[str(Physics.SOLID)]
            self.solid_solver = SolidSolver(
                                    self.solid_problem._form, self.time,
                                    interval, parameters['dt'], parameters,
                                    **kwargs)

        if Physics.FLUID in self.physics:
            parameters = self.parameters[str(Physics.FLUID)]
            self.fluid_solver = FluidSolver(
                                    self.fluid_problem._form, self.time,
                                    interval, parameters['dt'], parameters,
                                    **kwargs)

        if Physics.POROUS in self.physics:
            parameters = self.parameters[str(Physics.POROUS)]
            self.porous_solver = PorousSolver(
                                    self.porous_problem._form, self.time,
                                    interval, parameters['dt'], parameters,
                                    **kwargs)


    def _setup_geometries(self, geometry, physics):
        self.geometries = {}

        if isinstance(geometry, MultiGeometry):
            for phys in physics:
                try:
                    self.geometries[phys] = geometry.geometries[phys.value]
                except KeyError:
                    msg = "Could not find a geometry for {} physics in "\
                            "MultiGeometry. Ensure that geometry labels "\
                            "correspond to values in Physics "\
                            "enum.".format(phys.value)
                    raise KeyError(msg)

                assert isinstance(self.geometries[phys], HeartGeometry)
        elif len(physics) == 1:
            self.geometries[physics[0]] = geometry
        else:
            for phys in physics:
                self.geometries[phys] = geometry.copy(deepcopy=True)


    def _check_physics_interactions(self):
        # If multiple physics are defined, check that all are involved in an
        # interaction and that physics involved in an interaction are set up
        if len(self.physics) == 0 and len(self.interactions) > 0:
            msg = "At least one interaction has been set up, but no physics\n"\
                    "Interactions: {}".format(self.interactions)
            raise KeyError(msg)
        if len(self.physics) > 1:
            int_physics = set(np.array([ia.to_list()
                                            for ia in self.interactions]).flat)
            for p in int_physics:
                if p not in self.physics:
                    msg = "Physics {} appears in interaction, but is not set "\
                            "up.".format(p)
                    raise KeyError(msg)
            for p in self.physics:
                if p not in int_physics:
                    msg = "Physcis {} is set up, but does not appear in any "\
                            "interaction.".format(p)
                    raise KeyError(msg)


    def _setup_electro_problem(self, parameters):
        self.electro_problem = ElectroProblem(self.geometries[Physics.ELECTRO],
                                                self.time, parameters)


    def _setup_solid_problem(self, parameters):
        self.solid_problem = SolidProblem(self.geometries[Physics.SOLID],
                                                self.time, parameters)


    def _setup_fluid_problem(self, parameters):
        self.fluid_problem = FluidProblem(self.geometries[Physics.FLUID],
                                                self.time, parameters)


    def _setup_porous_problem(self, parameters):
        self.porous_problem = PorousProblem(self.geometries[Physics.POROUS],
                                                self.time, parameters)


    def update_parameters(self, physics, parameters):
        if physics == Physics.ELECTRO:
            self.parameters.set_electro_parameters(parameters)
        elif physics == Physics.SOLID:
            self.parameters.set_solid_parameters(parameters)
        elif physics == Physics.FLUID:
            self.parameters.set_fluid_parameters(parameters)
        elif physics == Physics.POROUS:
            self.parameters.set_porous_parameters(parameters)
Ejemplo n.º 5
0
class CavityProblemSetup():
    def __init__(self, parameters, mesh_name, facet_name):
        """
        Create the required function spaces, functions and boundary conditions
        for a channel flow problem
        """
        self.mesh = Mesh()
        with XDMFFile(mesh_name) as infile:
            infile.read(self.mesh)

        mvc = MeshValueCollection("size_t", self.mesh,
                                  self.mesh.topology().dim() - 1)
        with XDMFFile(facet_name) as infile:
            infile.read(mvc, "name_to_read")
        mf = self.mf = cpp.mesh.MeshFunctionSizet(self.mesh, mvc)
        self.bc_dict = {"bottom": 1, "right": 2, "top": 3, "left": 4}

        T_init = Constant(parameters["initial temperature [°C]"])
        self.t_amb = Constant(parameters["ambient temperature [°C]"])
        self.t_feeder = Constant(parameters["temperature feeder [°C]"])
        self.k_top = Constant(parameters["thermal conductivity top [W/(m K)]"])
        self.k_lft = Constant(
            parameters["thermal conductivity left [W/(m K)]"])
        self.k_btm = Constant(
            parameters["thermal conductivity bottom [W/(m K)]"])
        self.k_rgt = Constant(
            parameters["thermal conductivity right [W/(m K)]"])
        self.U_m = Constant(parameters["mean velocity lid [m/s]"])
        g = parameters["gravity [m/s²]"]
        self.g = Constant((0.0, -g))
        self.dt = Constant(parameters["dt [s]"])
        self.D = Constant(parameters["Diffusivity [-]"])

        self.V = V = VectorFunctionSpace(self.mesh, 'P', 2)
        self.Q = Q = FunctionSpace(self.mesh, 'P', 1)
        self.T = T = FunctionSpace(self.mesh, 'P', 1)

        self.ds_ = Measure("ds", domain=self.mesh, subdomain_data=mf)
        self.vu, self.vp, self.vt = (TestFunction(V), TestFunction(Q),
                                     TestFunction(T))
        self.u_, self.p_, self.t_ = Function(V), Function(Q), Function(T)
        self.mu, self.rho = Function(T), Function(T)
        self.u_1, self.p_1, self.t_1, self.rho_1 = (Function(V), Function(Q),
                                                    Function(T), Function(T))
        self.u, self.p, self.t = (TrialFunction(V), TrialFunction(Q),
                                  TrialFunction(T))

        # boundary conditions
        self.no_slip = Constant((0., 0))
        self.topflow = Expression(("-x[0] * (x[0] - 1.0) * 6.0 * m", "0.0"),
                                  m=self.U_m,
                                  degree=2)
        bc0 = DirichletBC(V, self.topflow, mf, self.bc_dict["top"])
        bc1 = DirichletBC(V, self.no_slip, mf, self.bc_dict["left"])
        bc2 = DirichletBC(V, self.no_slip, mf, self.bc_dict["bottom"])
        bc3 = DirichletBC(V, self.no_slip, mf, self.bc_dict["right"])
        # bc4 = df.DirichletBC(Q, df.Constant(0), top)
        # bc3 = df.DirichletBC(T, df.Constant(800), top)
        self.bcu = [bc0, bc1, bc2, bc3]
        self.bcp = [DirichletBC(Q, Constant(0), mf, self.bc_dict["top"])]
        self.bcp = []
        self.bct = []
        # self.bct = [DirichletBC(T, Constant(self.t_feeder), mf,
        #                         self.bc_dict["top"])]

        self.robin_boundary_terms = (
            self.k_btm * (self.t - self.t_amb) * self.vt * self.ds_(1) +
            self.k_rgt * (self.t - self.t_amb) * self.vt * self.ds_(2)
            # + self.k_top*(self.t - self.t_feeder)*self.vt*self.ds_(3)
            + self.k_lft * (self.t - self.t_amb) * self.vt * self.ds_(4))
        print("k, T", self.k_btm.values(), self.t_feeder.values())
        print("k, T", self.k_rgt.values(), self.t_amb.values())
        # print("k, T", self.k_top.values(), self.t_amb.values())
        print("k, T", self.k_lft.values(), self.t_amb.values())

        # set initial values
        # TODO: find a better solution
        x, y = T.tabulate_dof_coordinates().T
        self.u_1.vector().vec().array[:] = 1e-6
        # self.u_k.vector().vec().array[:] = 1e-6
        self.p_.vector().vec(
        ).array[:] = -self.rho.vector().vec().array * g * y
        self.p_1.vector().vec(
        ).array[:] = -self.rho.vector().vec().array * g * y
        self.t_1.vector().vec().array = T_init
        self.t_.assign(self.t_1)
        return

    def stokes(self):
        P2 = VectorElement("CG", self.mesh.ufl_cell(), 2)
        P1 = FiniteElement("CG", self.mesh.ufl_cell(), 1)
        TH = P2 * P1
        VQ = FunctionSpace(self.mesh, TH)
        mf = self.mf
        self.no_slip = Constant((0., 0))
        self.topflow = Expression(("-x[0] * (x[0] - 1.0) * 6.0 * m", "0.0"),
                                  m=self.U_m,
                                  degree=2)
        bc0 = DirichletBC(VQ.sub(0), self.topflow, mf, self.bc_dict["top"])
        bc1 = DirichletBC(VQ.sub(0), self.no_slip, mf, self.bc_dict["left"])
        bc2 = DirichletBC(VQ.sub(0), self.no_slip, mf, self.bc_dict["bottom"])
        bc3 = DirichletBC(VQ.sub(0), self.no_slip, mf, self.bc_dict["right"])
        # bc4 = DirichletBC(VQ.sub(1), Constant(0), mf, self.bc_dict["top"])
        bcs = [bc0, bc1, bc2, bc3]

        vup = TestFunction(VQ)
        up = TrialFunction(VQ)
        # the solution will be in here:
        up_ = Function(VQ)

        u, p = split(up)  # Trial
        vu, vp = split(vup)  # Test
        u_, p_ = split(up_)  # Function holding the solution
        F = self.mu*inner(grad(vu), grad(u))*dx - inner(div(vu), p)*dx \
            - inner(vp, div(u))*dx + dot(self.g*self.rho, vu)*dx
        solve(lhs(F) == rhs(F), up_, bcs=bcs)
        self.u_.assign(project(u_, self.V))
        self.p_.assign(project(p_, self.Q))
        return

    def initial_condition_from_file(self, path_u, path_p):
        f_in = XDMFFile(path_u)
        f_in.read_checkpoint(self.u_, "f", 0)
        f_in = XDMFFile(path_p)
        f_in.read_checkpoint(self.p_, "f", 0)
        return

    def get_rho(self):
        return self.rho.vector().vec().array

    def set_rho(self, rho):
        self.rho.vector().vec().array[:] = rho

    def get_mu(self):
        return self.mu.vector().vec().array

    def set_mu(self, mu):
        self.mu.vector().vec().array[:] = mu

    def get_t(self):
        return self.t_.vector().vec().array

    def set_t(self, t):
        self.t_.vector().vec().array[:] = t

    def get_dt(self):
        return self.dt.values()

    def set_dt(self, dt):
        self.dt.assign(dt)

    def get_D(self):
        return self.D.values()

    def set_D(self, D):
        self.D.assign(D)

    def get_t_amb(self):
        return self.t_amb.values()

    def set_t_amb(self, t_amb):
        self.t_amb.assign(t_amb)

    def plot(self):
        cmap = mpl.cm.inferno
        cmap_r = mpl.cm.inferno_r
        u, p, t, m, r = self.u_, self.p_, self.t_, self.mu, self.rho
        mesh = self.mesh
        w0 = u.compute_vertex_values(mesh)
        w0.shape = (2, -1)
        magnitude = np.linalg.norm(w0, axis=0)
        x, y = np.split(mesh.coordinates(), 2, 1)
        u, v = np.split(w0, 2, 0)
        x, y, u, v = x.ravel(), y.ravel(), u.ravel(), v.ravel()
        tri = mesh.cells()
        pressure = p.compute_vertex_values(mesh)
        temperature = t.compute_vertex_values(mesh)
        viscosity = m.compute_vertex_values(mesh)
        density = r.compute_vertex_values(mesh)

        fig = plt.figure(figsize=(12, 8))
        ax1 = plt.subplot(121)
        ax2 = plt.subplot(243, sharex=ax1, sharey=ax1)
        ax3 = plt.subplot(244, sharex=ax1, sharey=ax1)
        ax4 = plt.subplot(247, sharex=ax1, sharey=ax1)
        ax5 = plt.subplot(248, sharex=ax1, sharey=ax1)
        ax1.plot(x, y, "k.", ms=.5)
        not0 = magnitude > 1e-6
        if np.sum(not0) > 0:
            ax1.quiver(x[not0], y[not0], u[not0], v[not0], magnitude[not0])
        c2 = ax2.tricontourf(x, y, tri, pressure, levels=40, cmap=cmap)
        c3 = ax3.tricontourf(x, y, tri, temperature, levels=40, cmap=cmap)
        c4 = ax4.tricontourf(
            x,
            y,
            tri,
            viscosity,
            levels=40,
            # vmin=self.mu(800, .1), vmax=self.mu(600, .1),
            cmap=cmap_r)
        c5 = ax5.tricontourf(
            x,
            y,
            tri,
            density,
            levels=40,
            # vmin=self.rho(800.), vmax=self.rho(600.),
            cmap=cmap_r)
        plt.colorbar(c2, ax=ax2)
        plt.colorbar(c3, ax=ax3, ticks=[temperature.min(), temperature.max()])
        plt.colorbar(c4, ax=ax4, ticks=[viscosity.min(), viscosity.max()])
        plt.colorbar(c5, ax=ax5, ticks=[density.min(), density.max()])
        ax1.set_aspect("equal")
        ax2.set_aspect("equal")
        ax3.set_aspect("equal")
        ax4.set_aspect("equal")
        ax5.set_aspect("equal")
        ax1.set_title("velocity\n{:.4f} ... {:.5f} m/s".format(
            magnitude.min(), magnitude.max()))
        ax2.set_title("pressure")
        ax3.set_title("temperature")
        ax4.set_title("viscosity")
        ax5.set_title("density")
        ax1.set_xlim([-.1, 1.1])
        ax1.set_ylim([-.1, 1.1])
        # plt.tight_layout()
        return fig, (ax1, ax2)
Ejemplo n.º 6
0
class VOFMixin(object):
    """
    This is a mixin class to avoid having duplicates of the methods calculating
    rho, nu and mu. Any subclass using this mixin must define the method
    "get_colour_function(k)" and can also redefine the boolean property that
    controls the way mu is calculated, "calculate_mu_directly_from_colour_function".
    """

    calculate_mu_directly_from_colour_function = True
    default_polynomial_degree_colour = 0
    _level_set_view = None

    @classmethod
    def create_function_space(cls, simulation):
        mesh = simulation.data['mesh']
        cd = simulation.data['constrained_domain']
        Vc_name = simulation.input.get_value(
            'multiphase_solver/function_space_colour',
            'Discontinuous Lagrange', 'string')
        Pc = simulation.input.get_value(
            'multiphase_solver/polynomial_degree_colour',
            cls.default_polynomial_degree_colour,
            'int',
        )
        Vc = FunctionSpace(mesh, Vc_name, Pc, constrained_domain=cd)
        simulation.data['Vc'] = Vc
        simulation.ndofs += Vc.dim()

    def set_physical_properties(self,
                                rho0=None,
                                rho1=None,
                                nu0=None,
                                nu1=None,
                                read_input=False):
        """
        Set rho and nu (density and kinematic viscosity) in both domain 0
        and 1. Either specify all of rho0, rho1, nu0 and nu1 or set
        read_input to True which will read from the physical_properties
        section of the simulation input object.
        """
        sim = self.simulation
        if read_input:
            rho0 = sim.input.get_value('physical_properties/rho0',
                                       required_type='float')
            rho1 = sim.input.get_value('physical_properties/rho1',
                                       required_type='float')
            nu0 = sim.input.get_value('physical_properties/nu0',
                                      required_type='float')
            nu1 = sim.input.get_value('physical_properties/nu1',
                                      required_type='float')
        self.df_rho0 = Constant(rho0)
        self.df_rho1 = Constant(rho1)
        self.df_nu0 = Constant(nu0)
        self.df_nu1 = Constant(nu1)
        self.df_smallest_rho = self.df_rho0 if rho0 <= rho1 else self.df_rho1

    def set_rho_min(self, rho_min):
        """
        This is used to bring rho_min closer to rho_max for the initial
        linear solver iterations (to speed up convergence)
        """
        self.df_smallest_rho.assign(Constant(rho_min))

    def get_colour_function(self, k):
        """
        Return the colour function on timestep t^{n+k}
        """
        raise NotImplementedError(
            'The get_colour_function method must be implemented by subclass!')

    def get_density(self, k=None, c=None):
        """
        Calculate the blended density function as a weighted sum of
        rho0 and rho1. The colour function is unity when rho=rho0
        and zero when rho=rho1

        Return the function as defined on timestep t^{n+k}
        """
        if c is None:
            assert k is not None
            c = self.get_colour_function(k)
        else:
            assert k is None
        return self.df_rho0 * c + self.df_rho1 * (1 - c)

    def get_laminar_kinematic_viscosity(self, k=None, c=None):
        """
        Calculate the blended kinematic viscosity function as a weighted
        sum of nu0 and nu1. The colour function is unity when nu=nu0 and
        zero when nu=nu1

        Return the function as defined on timestep t^{n+k}
        """
        if c is None:
            assert k is not None
            c = self.get_colour_function(k)
        else:
            assert k is None
        return self.df_nu0 * c + self.df_nu1 * (1 - c)

    def get_laminar_dynamic_viscosity(self, k=None, c=None):
        """
        Calculate the blended dynamic viscosity function as a weighted
        sum of mu0 and mu1. The colour function is unity when mu=mu0 and
        zero when mu=mu1

        Return the function as defined on timestep t^{n+k}
        """
        if self.calculate_mu_directly_from_colour_function:
            if c is None:
                assert k is not None
                c = self.get_colour_function(k)
            else:
                assert k is None
            mu0 = self.df_nu0 * self.df_rho0
            mu1 = self.df_nu1 * self.df_rho1
            return mu0 * c + mu1 * (1 - c)

        else:
            nu = self.get_laminar_kinematic_viscosity(k, c)
            rho = self.get_density(k, c)
            return nu * rho

    def get_density_range(self):
        """
        Return the maximum and minimum densities, rho
        """
        rho0 = self.df_rho0.values()[0]
        rho1 = self.df_rho1.values()[0]
        return min(rho0, rho1), max(rho0, rho1)

    def get_laminar_kinematic_viscosity_range(self):
        """
        Return the maximum and minimum kinematic viscosities, nu
        """
        nu0 = self.df_nu0.values()[0]
        nu1 = self.df_nu1.values()[0]
        return min(nu0, nu1), max(nu0, nu1)

    def get_laminar_dynamic_viscosity_range(self):
        """
        The minimum and maximum laminar dynamic viscosities, mu.

        Mu is either calculated directly from the colour function, in this
        case mu is a linear function, or as a product of nu and rho, where
        it is a quadratic function and can have (in i.e the case of water
        and air) have maximum values() in the middle of the range c ∈ (0, 1)
        """
        rho0 = self.df_rho0.values()[0]
        rho1 = self.df_rho1.values()[0]
        nu0 = self.df_nu0.values()[0]
        nu1 = self.df_nu1.values()[0]

        if self.calculate_mu_directly_from_colour_function:
            mu0 = nu0 * rho0
            mu1 = nu1 * rho1
            return min(mu0, mu1), max(mu0, mu1)
        else:
            c = numpy.linspace(0, 1, 1000)
            nu = nu0 * c + nu1 * (1 - c)
            rho = rho0 * c + rho1 * (1 - c)
            mu = nu * rho
            return mu.min(), mu.max()

    def get_level_set_view(self):
        """
        Get a view of this VOF field as a level set function
        """
        if self._level_set_view is None:
            self._level_set_view = LevelSetView(self.simulation)
            c = self.get_colour_function(0)
            self._level_set_view.set_density_field(c)
        return self._level_set_view