Exemplo n.º 1
0
 def solveSectionThermal(self, TPrim, hConvPrim, TSec, hConvSec, TExt,
                         hConvExt):
     # Initialize convergence criteria
     res = 1e10
     n = 0
     # Run the non-linear iteration loop
     while (res > self.fvSolverSettings.tolerance
            and n < self.fvSolverSettings.maxNumIterations):
         # Interpolate temperature on faces
         TFaces = self.T.arithmeticFaceValue()
         # Compute thermal conductivity
         self.thermCond.setValue(self.thermCondModel(TFaces))
         # Set up the equation
         ## Diffusion term
         eqT = FP.DiffusionTerm(coeff=self.thermCond)
         ## Source terms (implicit + explicit) emulating boundary conditions
         eqT += -FP.ImplicitSourceTerm(
             self.cPrimCoeff *
             hConvPrim) + self.cPrimCoeff * hConvPrim * TPrim
         eqT += -FP.ImplicitSourceTerm(
             self.cSecCoeff * hConvSec) + self.cSecCoeff * hConvSec * TSec
         eqT += -FP.ImplicitSourceTerm(
             self.cExtCoeff * hConvExt) + self.cExtCoeff * hConvExt * TExt
         # Linear solve
         res = eqT.sweep(
             var=self.T,
             solver=self.linSolver,
             underRelaxation=self.fvSolverSettings.relaxationFactor)
         #print n, res
         n += 1
Exemplo n.º 2
0
def get_eqn(mesh, diff):
    """Generate a generic 1D diffusion equation with flux from the left

     Args:
      mesh: the mesh
      diff: the diffusion coefficient

    Returns:
      a tuple of the flux and the equation
    """
    flux = fipy.CellVariable(mesh, value=0.)
    eqn = fipy.TransientTerm() == fipy.DiffusionTerm(
        diff) + fipy.ImplicitSourceTerm(flux * GET_MASK(mesh) / mesh.dx)
    return (flux, eqn)
Exemplo n.º 3
0
    def calculate_potential(depletion_mask=None, y_dep_new=None):
        potential = fipy.CellVariable(mesh=mesh, name='potential', value=0.)
        electrons = fipy.CellVariable(mesh=mesh, name='e-')
        electrons.valence = -1
        charge = electrons * electrons.valence
        charge.name = "charge"

        # Uniform charge distribution by setting a uniform concentration of
        # electrons = 1
        electrons.setValue(rho_scale)

        # A depletion zone within the bulk requires an internal boundary
        # condition. Internal boundary conditions seem to challenge fipy, see:
        # http://www.ctcms.nist.gov/fipy/documentation/USAGE.html#applying-internal-boundary-conditions

        large_value = 1e+10  # Hack for optimizer

        if depletion_mask is not None:
            # FIXME: Generic depletion_mask not working
            # Is overwritten here with a simple 1D depletion mask
            depletion_mask = np.logical_and(potential.mesh.y > y_dep_new[0],
                                            potential.mesh.y > y_dep_new[0])
            potential.equation = (fipy.DiffusionTerm(coeff=epsilon_scaled) +
                                  charge == fipy.ImplicitSourceTerm(
                                      depletion_mask * large_value) -
                                  depletion_mask * large_value * V_bias)
        else:
            potential.equation = (fipy.DiffusionTerm(coeff=epsilon_scaled) +
                                  charge == 0.)

        # Calculate boundaries
        backplane = mesh.getFacesTop()
        readout_plane = mesh.getFacesBottom()

        electrodes = readout_plane
        bcs = [fipy.FixedValue(value=V_bias, faces=backplane)]
        X, _ = mesh.getFaceCenters()
        for pixel in range(n_pixel):
            pixel_position = width * (pixel + 1. / 2.) - width * n_pixel / 2.
            bcs.append(
                fipy.FixedValue(
                    value=V_readout if pixel_position == 0. else 0.,
                    faces=electrodes & (X > pixel_position - pitch / 2.) &
                    (X < pixel_position + pitch / 2.)))

        solver.solve(potential,
                     equation=potential.equation,
                     boundaryConditions=bcs)
        return potential
Exemplo n.º 4
0
    def solve(self):
        sideFaceFactor = fp.CellVariable(
            name="sideFaceFactor",
            mesh=self.mesh,
            value=self.areaExtFaces / (self.AcsMult * self.mesh.cellVolumes))
        # Create variables
        self.TCore = fp.CellVariable(name="coreTemperature",
                                     mesh=self.mesh,
                                     value=self.TAmb)
        self.TSurface = fp.CellVariable(name="surfaceTemperature",
                                        mesh=self.mesh,
                                        value=self.TAmb)
        # Apply boundary conditions:
        self.applyBC(self.mesh.facesLeft(), self.BC[0],
                     self.mesh.scaledFaceAreas[0] * self.AcsMult)
        self.applyBC(self.mesh.facesRight(), self.BC[1],
                     self.mesh.scaledFaceAreas[-1] * self.AcsMult)

        # Create linear solver
        linSolver = LinearLUSolver(tolerance=1e-10)

        # Create base equation (thermal conduction):
        eq = fp.DiffusionTerm(coeff=self.thermalConductivity, var=self.TCore)
        if (self.jouleHeating['active']):
            eq += self.jouleHeating['j']**2 * self.jouleHeating['eResistivity']

        if (self.radiation['active']):
            raise NotImplementedError('Radiation not implemented yet!')
        else:
            if (self.convection['active']):
                RAmb = 1. / self.convection['coefficient']
                if (self.insulation['active']):
                    RAmb += self.insulation['thickness'] / self.insulation[
                        'thermConductivity']
                eq += 1. / RAmb * (sideFaceFactor * self.TAmb -
                                   fp.ImplicitSourceTerm(coeff=sideFaceFactor,
                                                         var=self.TCore))
            eq.solve(var=self.TCore, solver=linSolver)
            if (self.convection['active'] and self.insulation['active']):
                # 0 - limited by conduction, 1 - limited by convection
                a1 = 1. / (RAmb * self.convection['coefficient'])
                self.TSurface.setValue(a1 * self.TCore() +
                                       (1 - a1) * self.TAmb)
            else:
                self.TSurface.setValue(self.TCore())
Exemplo n.º 5
0
def get_eq(params, eta, d2f):
    """Get the equation

    Args:
      params: the parameter dictionary
      eta: the phase field variable
      d2f: the free energy double derivative variable

    Returns:
      a dictionary of the equation and variables
    """
    return pipe(
        fp.CellVariable(mesh=eta.mesh, name="psi"),
        lambda x: (
            fp.TransientTerm(var=eta) == -fp.DiffusionTerm(
                coeff=params["mobility"], var=x) + fp.DiffusionTerm(
                    coeff=params["mobility"] * d2f, var=eta),
            fp.ImplicitSourceTerm(coeff=1.0, var=x) == fp.DiffusionTerm(
                coeff=params["kappa"], var=eta),
        ),
        lambda x: x[0] & x[1],
    )
Exemplo n.º 6
0
def calculate_potential(mesh, rho, epsilon, V_read, V_bias, x_dep):
    r''' Calculate the potential with a given space charge distribution.

    If the depletion width is too large the resulting potential will have
    a minimum < bias voltage. This is unphysical.
    '''

    # The field scales with rho / epsilon, thus scale to proper value to
    # counteract numerical instabilities
    epsilon_scaled = 1.
    rho_scale = rho / epsilon

    potential = fipy.CellVariable(mesh=mesh, name='potential', value=0.)

    electrons = fipy.CellVariable(mesh=mesh, name='e-')
    electrons.valence = -1

    electrons.setValue(rho_scale)

    charge = electrons * electrons.valence
    charge.name = "charge"

    # A depletion zone within the bulk requires an internal boundary condition
    # Internal boundary conditions seem to challenge fipy, see:
    # http://www.ctcms.nist.gov/fipy/documentation/USAGE.html#applying-internal-boundary-conditions

    large_value = 1e+15  # Hack for optimizer

    mask = mesh.x > x_dep
    potential.equation = (fipy.DiffusionTerm(coeff=epsilon_scaled) -
                          fipy.ImplicitSourceTerm(mask * large_value) +
                          mask * large_value * V_bias + charge == 0)

    potential.constrain(V_read, mesh.facesLeft)
    potential.constrain(V_bias, mesh.facesRight)

    solver.solve(potential, equation=potential.equation)

    return potential
Exemplo n.º 7
0
    [[1.], [0.]]) + density * gravity[0]
yVelocityEq = fp.DiffusionTerm(coeff=viscosity) - pressure.grad.dot(
    [[0.], [1.]]) + density * gravity[1]

ap = fp.CellVariable(mesh=mesh, value=1.)
coeff = 1. / ap.arithmeticFaceValue * mesh._faceAreas * mesh._cellDistances

x, y = mesh.cellCenters
top_right_cell = fp.CellVariable(mesh=mesh,
                                 value=(x > max(x) - params["cellSize"]) &
                                 (y > max(y) - params["cellSize"]))

large_value = 1e10
pressureCorrectionEq = (
    fp.DiffusionTerm(coeff=coeff) - velocity.divergence -
    fp.ImplicitSourceTerm(coeff=top_right_cell * large_value) +
    top_right_cell * large_value * 0.)

contrvolume = volumes.arithmeticFaceValue


def inlet_velocity(yy):
    return -0.001 * (yy - 3)**2 + 0.009


X, Y = mesh.faceCenters

xVelocity.constrain(inlet_velocity(Y), inlet)
xVelocity.constrain(0., walls)

yVelocity.constrain(0., walls | inlet)
Exemplo n.º 8
0
cbeta = 0.7
kappa = 2.
rho = 5.
M = 5.
k = 0.09
epsilon = 90.

ceq = fp.TransientTerm(var=c) == fp.DiffusionTerm(coeff=M, var=psi)

fchem = rho * (c - calpha)**2 * (cbeta - c)**2
felec = k * c * Phi / 2.
f = fchem + (kappa / 2.) * c.grad.mag**2 + felec
dfchemdc = 2 * rho * (c - calpha) * (cbeta - c) * (calpha + cbeta - 2 * c)
d2fchemd2c = 2 * rho * ((calpha + cbeta - 2 * c)**2 - 2 * (c - calpha) *
                        (cbeta - c))
psieq = (fp.ImplicitSourceTerm(
    coeff=1., var=psi) == fp.ImplicitSourceTerm(coeff=d2fchemd2c, var=c) -
         d2fchemd2c * c + dfchemdc - fp.DiffusionTerm(coeff=kappa, var=c) +
         fp.ImplicitSourceTerm(coeff=k, var=Phi))

stats = []

Phieq = fp.DiffusionTerm(var=Phi) == fp.ImplicitSourceTerm(coeff=-k / epsilon,
                                                           var=c)

eq = ceq & psieq & Phieq

x, y = mesh.cellCenters
X, Y = mesh.faceCenters

Phi.faceGrad.constrain(0.,
                       where=mesh.physicalFaces["top"]
Exemplo n.º 9
0
def hydrology(solve_mode,
              nx,
              ny,
              dx,
              dy,
              days,
              ele,
              phi_initial,
              catchment_mask,
              wt_canal_arr,
              boundary_arr,
              peat_type_mask,
              httd,
              tra_to_cut,
              sto_to_cut,
              diri_bc=0.0,
              neumann_bc=None,
              plotOpt=False,
              remove_ponding_water=True,
              P=0.0,
              ET=0.0,
              dt=1.0):
    """
    INPUT:
        - ele: (nx,ny) sized NumPy array. Elevation in m above c.r.p.
        - Hinitial: (nx,ny) sized NumPy array. Initial water table in m above c.r.p.
        - catchment mask: (nx,ny) sized NumPy array. Boolean array = True where the node is inside the computation area. False if outside.
        - wt_can_arr: (nx,ny) sized NumPy array. Zero everywhere except in nodes that contain canals, where = wl of the canal.
        - value_for_masked: DEPRECATED. IT IS NOW THE SAME AS diri_bc.
        - diri_bc: None or float. If None, Dirichlet BC will not be implemented. If float, this number will be the BC.
        - neumann_bc: None or float. If None, Neumann BC will not be implemented. If float, this is the value of grad phi.
        - P: Float. Constant precipitation. mm/day.
        - ET: Float. Constant evapotranspiration. mm/day.
    """
    #    dneg = []

    track_WT_drained_area = (239, 166)
    track_WT_notdrained_area = (522, 190)

    ele[~catchment_mask] = 0.
    ele = ele.flatten()
    phi_initial = (phi_initial + 0.0 * np.zeros((ny, nx))) * catchment_mask
    #    phi_initial = phi_initial * catchment_mask
    phi_initial = phi_initial.flatten()

    if len(ele) != nx * ny or len(phi_initial) != nx * ny:
        raise ValueError("ele or Hinitial are not of dim nx*ny")

    mesh = fp.Grid2D(dx=dx, dy=dy, nx=nx, ny=ny)
    phi = fp.CellVariable(
        name='computed H', mesh=mesh, value=phi_initial,
        hasOld=True)  #response variable H in meters above reference level

    if diri_bc != None and neumann_bc == None:
        phi.constrain(diri_bc, mesh.exteriorFaces)

    elif diri_bc == None and neumann_bc != None:
        phi.faceGrad.constrain(neumann_bc * mesh.faceNormals,
                               where=mesh.exteriorFaces)

    else:
        raise ValueError(
            "Cannot apply Dirichlet and Neumann boundary values at the same time. Contradictory values."
        )

    #*******omit areas outside the catchment. c is input
    cmask = fp.CellVariable(mesh=mesh, value=np.ravel(catchment_mask))
    cmask_not = fp.CellVariable(mesh=mesh,
                                value=np.array(~cmask.value, dtype=int))

    # *** drain mask or canal mask
    dr = np.array(wt_canal_arr, dtype=bool)
    dr[np.array(wt_canal_arr, dtype=bool) * np.array(
        boundary_arr, dtype=bool
    )] = False  # Pixels cannot be canals and boundaries at the same time. Everytime a conflict appears, boundaries win. This overwrites any canal water level info if the canal is in the boundary.
    drmask = fp.CellVariable(mesh=mesh, value=np.ravel(dr))
    drmask_not = fp.CellVariable(
        mesh=mesh, value=np.array(~drmask.value, dtype=int)
    )  # Complementary of the drains mask, but with ints {0,1}

    # mask away unnecesary stuff
    #    phi.setValue(np.ravel(H)*cmask.value)
    #    ele = ele * cmask.value

    source = fp.CellVariable(mesh=mesh,
                             value=0.)  # cell variable for source/sink

    #    CC=fp.CellVariable(mesh=mesh, value=C(phi.value-ele))                   # differential water capacity

    def D_value(phi, ele, tra_to_cut, cmask, drmask_not):
        # Some inputs are in fipy CellVariable type
        gwt = phi.value * cmask.value - ele

        d = hydro_utils.peat_map_h_to_tra(
            soil_type_mask=peat_type_mask, gwt=gwt,
            h_to_tra_and_C_dict=httd) - tra_to_cut

        # d <0 means tra_to_cut is greater than the other transmissivity, which in turn means that
        # phi is below the impermeable bottom. We allow phi to have those values, but
        # the transmissivity is in those points is equal to zero (as if phi was exactly at the impermeable bottom).
        d[d < 0] = 1e-3  # Small non-zero value not to wreck the computation

        dcell = fp.CellVariable(
            mesh=mesh, value=d
        )  # diffusion coefficient, transmissivity. As a cell variable.
        dface = fp.FaceVariable(mesh=mesh,
                                value=dcell.arithmeticFaceValue.value
                                )  # THe correct Face variable.

        return dface.value

    def C_value(phi, ele, sto_to_cut, cmask, drmask_not):
        # Some inputs are in fipy CellVariable type
        gwt = phi.value * cmask.value - ele

        c = hydro_utils.peat_map_h_to_sto(
            soil_type_mask=peat_type_mask, gwt=gwt,
            h_to_tra_and_C_dict=httd) - sto_to_cut
        c[c < 0] = 1e-3  # Same reasons as for D

        ccell = fp.CellVariable(
            mesh=mesh, value=c
        )  # diffusion coefficient, transmissivity. As a cell variable.
        return ccell.value

    D = fp.FaceVariable(
        mesh=mesh, value=D_value(phi, ele, tra_to_cut, cmask,
                                 drmask_not))  # THe correct Face variable.
    C = fp.CellVariable(
        mesh=mesh, value=C_value(phi, ele, sto_to_cut, cmask,
                                 drmask_not))  # differential water capacity

    largeValue = 1e20  # value needed in implicit source term to apply internal boundaries

    if plotOpt:
        big_4_raster_plot(
            title='Before the computation',
            raster1=(
                (hydro_utils.peat_map_h_to_tra(soil_type_mask=peat_type_mask,
                                               gwt=(phi.value - ele),
                                               h_to_tra_and_C_dict=httd) -
                 tra_to_cut) * cmask.value * drmask_not.value).reshape(ny, nx),
            raster2=(ele.reshape(ny, nx) - wt_canal_arr) * dr * catchment_mask,
            raster3=ele.reshape(ny, nx),
            raster4=(ele - phi.value).reshape(ny, nx))
        # for later cross-section plots
        y_value = 270

#        print "first cross-section plot"
#        ele_with_can = copy.copy(ele).reshape(ny,nx)
#        ele_with_can = ele_with_can * catchment_mask
#        ele_with_can[wt_canal_arr > 0] = wt_canal_arr[wt_canal_arr > 0]
#        plot_line_of_peat(ele_with_can, y_value=y_value, title="cross-section", color='green', nx=nx, ny=ny, label="ele")

# ********************************** PDE, STEADY STATE **********************************
    if solve_mode == 'steadystate':
        if diri_bc != None:
            #        diri_boundary = fp.CellVariable(mesh=mesh, value= np.ravel(diri_boundary_value(boundary_mask, ele2d, diri_bc)))

            eq = 0. == (
                fp.DiffusionTerm(coeff=D) + source * cmask * drmask_not -
                fp.ImplicitSourceTerm(cmask_not * largeValue) +
                cmask_not * largeValue * np.ravel(boundary_arr) -
                fp.ImplicitSourceTerm(drmask * largeValue) +
                drmask * largeValue * (np.ravel(wt_canal_arr))
                #                    - fp.ImplicitSourceTerm(bmask_not*largeValue) + bmask_not*largeValue*(boundary_arr)
            )

        elif neumann_bc != None:
            raise NotImplementedError("Neumann BC not implemented yet!")
            cmask_face = fp.FaceVariable(mesh=mesh,
                                         value=np.array(
                                             cmask.arithmeticFaceValue.value,
                                             dtype=bool))
            D[cmask_face.value] = 0.
            eq = 0. == (
                fp.DiffusionTerm(coeff=D) + source * cmask * drmask_not -
                fp.ImplicitSourceTerm(cmask_not * largeValue) +
                cmask_not * largeValue * (diri_bc) -
                fp.ImplicitSourceTerm(drmask * largeValue) +
                drmask * largeValue * (np.ravel(wt_canal_arr))
                #                + fp.DiffusionTerm(coeff=largeValue * bmask_face)
                #                - fp.ImplicitSourceTerm((bmask_face * largeValue *neumann_bc * mesh.faceNormals).divergence)
            )

    elif solve_mode == 'transient':
        if diri_bc != None:
            #        diri_boundary = fp.CellVariable(mesh=mesh, value= np.ravel(diri_boundary_value(boundary_mask, ele2d, diri_bc)))

            eq = fp.TransientTerm(coeff=C) == (
                fp.DiffusionTerm(coeff=D) + source * cmask * drmask_not -
                fp.ImplicitSourceTerm(cmask_not * largeValue) +
                cmask_not * largeValue * np.ravel(boundary_arr) -
                fp.ImplicitSourceTerm(drmask * largeValue) +
                drmask * largeValue * (np.ravel(wt_canal_arr))
                #                        - fp.ImplicitSourceTerm(bmask_not*largeValue) + bmask_not*largeValue*(boundary_arr)
            )
        elif neumann_bc != None:
            raise NotImplementedError("Neumann BC not implemented yet!")

    #********************************************************

    max_sweeps = 10  # inner loop.

    avg_wt = []
    wt_track_drained = []
    wt_track_notdrained = []

    cumulative_Vdp = 0.

    #********Finite volume computation******************
    for d in range(days):
        source.setValue(
            (P[d] - ET[d]) * .001 * np.ones(ny * nx)
        )  # source/sink. P and ET are in mm/day. The factor of 10^-3 converst to m/day. It does not matter that there are 100 x 100 m^2 in one pixel
        print("(d, P - ET) = ", (d, (P[d] - ET[d])))
        if plotOpt and d != 0:
            # print "one more cross-section plot"
            plot_line_of_peat(phi.value.reshape(ny, nx),
                              y_value=y_value,
                              title="cross-section",
                              color='cornflowerblue',
                              nx=nx,
                              ny=ny,
                              label=d)

        res = 0.0

        phi.updateOld()

        D.setValue(D_value(phi, ele, tra_to_cut, cmask, drmask_not))
        C.setValue(C_value(phi, ele, tra_to_cut, cmask, drmask_not))

        for r in range(max_sweeps):
            resOld = res

            res = eq.sweep(var=phi, dt=dt)  # solve linearization of PDE

            #print "sum of Ds: ", np.sum(D.value)/1e8
            #print "average wt: ", np.average(phi.value-ele)

            #print 'residue diference:    ', res - resOld

            if abs(res - resOld) < 1e-7:
                break  # it has reached to the solution of the linear system

        if solve_mode == 'transient':  #solving in steadystate will remove water only at the very end
            if remove_ponding_water:
                s = np.where(
                    phi.value > ele, ele, phi.value
                )  # remove the surface water. This also removes phi in those masked values (for the plot only)
                phi.setValue(s)  # set new values for water table

        if (D.value < 0.).any():
            print("Some value in D is negative!")

        # For some plots
        avg_wt.append(np.average(phi.value - ele))
        wt_track_drained.append(
            (phi.value - ele).reshape(ny, nx)[track_WT_drained_area])
        wt_track_notdrained.append(
            (phi.value - ele).reshape(ny, nx)[track_WT_notdrained_area])
        """ Volume of dry peat calc."""
        not_peat = np.ones(shape=peat_type_mask.shape)  # Not all soil is peat!
        not_peat[peat_type_mask == 4] = 0  # NotPeat
        not_peat[peat_type_mask == 5] = 0  # OpenWater
        peat_vol_weights = utilities.PeatV_weight_calc(
            np.array(~dr * catchment_mask * not_peat, dtype=int))
        dry_peat_volume = utilities.PeatVolume(peat_vol_weights,
                                               (ele - phi.value).reshape(
                                                   ny, nx))
        cumulative_Vdp = cumulative_Vdp + dry_peat_volume
        print("avg_wt  = ", np.average(phi.value - ele))
        #        print "wt drained = ", (phi.value - ele).reshape(ny,nx)[track_WT_drained_area]
        #        print "wt not drained = ", (phi.value - ele).reshape(ny,nx)[track_WT_notdrained_area]
        print("Cumulative vdp = ", cumulative_Vdp)

    if solve_mode == 'steadystate':  #solving in steadystate we remove water only at the very end
        if remove_ponding_water:
            s = np.where(
                phi.value > ele, ele, phi.value
            )  # remove the surface water. This also removes phi in those masked values (for the plot only)
            phi.setValue(s)  # set new values for water table

        # Areas with WT <-1.0; areas with WT >0
#        plot_raster_by_value((ele-phi.value).reshape(ny,nx), title="ele-phi in the end, colour keys", bottom_value=0.5, top_value=0.01)

    if plotOpt:
        big_4_raster_plot(
            title='After the computation',
            raster1=(
                (hydro_utils.peat_map_h_to_tra(soil_type_mask=peat_type_mask,
                                               gwt=(phi.value - ele),
                                               h_to_tra_and_C_dict=httd) -
                 tra_to_cut) * cmask.value * drmask_not.value).reshape(ny, nx),
            raster2=(ele.reshape(ny, nx) - wt_canal_arr) * dr * catchment_mask,
            raster3=ele.reshape(ny, nx),
            raster4=(ele - phi.value).reshape(ny, nx))

        fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(12, 9), dpi=80)
        x = np.arange(-21, 1, 0.1)
        axes[0, 0].plot(httd[1]['hToTra'](x), x)
        axes[0, 0].set(title='hToTra', ylabel='depth')
        axes[0, 1].plot(httd[1]['C'](x), x)
        axes[0, 1].set(title='C')
        axes[1, 0].plot()
        axes[1, 0].set(title="Nothing")
        axes[1, 1].plot(avg_wt)
        axes[1, 1].set(title="avg_wt_over_time")

        # plot surface in cross-section
        ele_with_can = copy.copy(ele).reshape(ny, nx)
        ele_with_can = ele_with_can * catchment_mask
        ele_with_can[wt_canal_arr > 0] = wt_canal_arr[wt_canal_arr > 0]
        plot_line_of_peat(ele_with_can,
                          y_value=y_value,
                          title="cross-section",
                          nx=nx,
                          ny=ny,
                          label="surface",
                          color='peru',
                          linewidth=2.0)

        plt.show()

#    change_in_canals = (ele-phi.value).reshape(ny,nx)*(drmask.value.reshape(ny,nx)) - ((ele-H)*drmask.value).reshape(ny,nx)
#    resulting_phi = phi.value.reshape(ny,nx)

    phi.updateOld()

    return (phi.value - ele).reshape(ny, nx)
Exemplo n.º 10
0
if parallelComm.procID == 0:
    dt = data.categories["dt_exact"]
else:
    dt = None

dt = parallelComm.bcast(dt)

elapsed = fp.Variable(name="$t$", value=startfrom * dt)

# linearize double-well
m_eta = 2 * (1 - 2 * eta)
dm_eta_deta = -4.
DW = m_eta * eta * (eta - 1)
dDW_deta = dm_eta_deta * eta * (eta - 1) + m_eta * (2 * eta - 1)
eq = (fp.TransientTerm() == (DW - dDW_deta * eta) +
      fp.ImplicitSourceTerm(coeff=dDW_deta) +
      fp.DiffusionTerm(coeff=kappa_fp) + eq_fp(xx, yy, elapsed))

solver = eq.getDefaultSolver()
print "solver:", repr(solver)

for step in range(1, numsteps + 1):
    eta.updateOld()
    for sweep in range(params['sweeps']):
        res = eq.sweep(var=eta, dt=dt, solver=solver)
    elapsed.value = elapsed() + dt

del solver

error = eta - eta_fp(xx, yy, elapsed - dt)
error.name = r"$\Delta\eta$"
Exemplo n.º 11
0
# \notag \\
# S_1 &\equiv \left.{\frac{\partial S}{\partial \phi}}\right|_\text{old}
# \notag \\
# &= \frac{\partial m_\phi}{\partial \phi} \phi (1 - \phi) + m_\phi (1 - 2\phi)
# \notag
# \end{align}

# In[9]:

mPhi = -2 * (1 - 2 * phi) + 30 * phi * (1 - phi) * Delta_f
dmPhidPhi = 4 + 30 * (1 - 2 * phi) * Delta_f
S1 = dmPhidPhi * phi * (1 - phi) + mPhi * (1 - 2 * phi)
S0 = mPhi * phi * (1 - phi) - S1 * phi

eq = (fp.TransientTerm() == fp.DiffusionTerm(coeff=1.) + S0 +
      fp.ImplicitSourceTerm(coeff=S1))

# ## Calculate total free energy
#
# > \begin{align}
# F[\phi] = \int\left[\frac{1}{2}(\nabla\phi)^2 + g(\phi) - \Delta f p(\phi)\right]\,dV \tag{6}
# \end{align}

# In[10]:

ftot = (0.5 * phi.grad.mag**2 + phi**2 * (1 - phi)**2 - Delta_f * phi**3 *
        (10 - 15 * phi + 6 * phi**2))
volumes = fp.CellVariable(mesh=mesh, value=mesh.cellVolumes)
F = ftot.cellVolumeAverage * volumes.sum()

# ## Define nucleation
Exemplo n.º 12
0
 def initialise_orientation_equation(self):
     self.orientation_PDE = (fipy.TransientTerm() ==
                             fipy.ImplicitSourceTerm(coeff=-self.D_rot) -
                             (self.v_0 / 2.0) * self.rho.grad / self.rho)
Exemplo n.º 13
0
def hydrology(mode,
              sensor_positions,
              nx,
              ny,
              dx,
              dy,
              days,
              ele,
              phi_initial,
              catchment_mask,
              wt_canal_arr,
              boundary_arr,
              peat_type_mask,
              peat_bottom_arr,
              transmissivity,
              t0,
              t1,
              t2,
              t_sapric_coef,
              storage,
              s0,
              s1,
              s2,
              s_sapric_coef,
              diri_bc=0.0,
              plotOpt=False,
              remove_ponding_water=True,
              P=0.0,
              ET=0.0,
              dt=1.0):
    """
    INPUT:
        - ele: (nx,ny) sized NumPy array. Elevation in m above c.r.p.
        - Hinitial: (nx,ny) sized NumPy array. Initial water table in m above c.r.p.
        - catchment mask: (nx,ny) sized NumPy array. Boolean array = True where the node is inside the computation area. False if outside.
        - wt_can_arr: (nx,ny) sized NumPy array. Zero everywhere except in nodes that contain canals, where = wl of the canal.
        - value_for_masked: DEPRECATED. IT IS NOW THE SAME AS diri_bc.
        - diri_bc: None or float. If None, Dirichlet BC will not be implemented. If float, this number will be the BC.
        - neumann_bc: None or float. If None, Neumann BC will not be implemented. If float, this is the value of grad phi.
        - P: Float. Constant precipitation. mm/day.
        - ET: Float. Constant evapotranspiration. mm/day.
    """
    #    dneg = []

    # track_WT_drained_area = (239,166)
    # track_WT_notdrained_area = (522,190)

    ele[~catchment_mask] = 0.
    ele = ele.flatten()
    phi_initial = (phi_initial + 0.0 * np.zeros((ny, nx))) * catchment_mask
    #    phi_initial = phi_initial * catchment_mask
    phi_initial = phi_initial.flatten()

    if len(ele) != nx * ny or len(phi_initial) != nx * ny:
        raise ValueError("ele or Hinitial are not of dim nx*ny")

    mesh = fp.Grid2D(dx=dx, dy=dy, nx=nx, ny=ny)
    phi = fp.CellVariable(
        name='computed H', mesh=mesh, value=phi_initial,
        hasOld=True)  #response variable H in meters above reference level

    if diri_bc != None:
        phi.constrain(diri_bc, mesh.exteriorFaces)

    else:
        raise ValueError("Dirichlet boundary conditions must have a value")

    #*******omit areas outside the catchment. c is input
    cmask = fp.CellVariable(mesh=mesh, value=np.ravel(catchment_mask))
    cmask_not = fp.CellVariable(mesh=mesh,
                                value=np.array(~cmask.value, dtype=int))

    # *** drain mask or canal mask
    dr = np.array(wt_canal_arr, dtype=bool)
    dr[np.array(wt_canal_arr, dtype=bool) * np.array(
        boundary_arr, dtype=bool
    )] = False  # Pixels cannot be canals and boundaries at the same time. Everytime a conflict appears, boundaries win. This overwrites any canal water level info if the canal is in the boundary.
    drmask = fp.CellVariable(mesh=mesh, value=np.ravel(dr))
    drmask_not = fp.CellVariable(
        mesh=mesh, value=np.array(~drmask.value, dtype=int)
    )  # Complementary of the drains mask, but with ints {0,1}

    # mask away unnecesary stuff
    #    phi.setValue(np.ravel(H)*cmask.value)
    #    ele = ele * cmask.value

    source = fp.CellVariable(mesh=mesh,
                             value=0.)  # cell variable for source/sink

    #    CC=fp.CellVariable(mesh=mesh, value=C(phi.value-ele))                   # differential water capacity

    def T_value(phi, ele, cmask, peat_bottom_arr):
        # Some inputs are in fipy CellVariable type
        gwt = (phi.value * cmask.value - ele).reshape(ny, nx)

        # T(h) - T(bottom)
        T = transmissivity(gwt, t0, t1, t2, t_sapric_coef) - transmissivity(
            peat_bottom_arr, t0, t1, t2, t_sapric_coef)

        # d <0 means tra_to_cut is greater than the other transmissivity, which in turn means that
        # phi is below the impermeable bottom. We allow phi to have those values, but
        # the transmissivity is in those points is equal to zero (as if phi was exactly at the impermeable bottom).
        T[T < 0] = 1e-3  # Small non-zero value not to wreck the computation

        Tcell = fp.CellVariable(mesh=mesh, value=T.flatten(
        ))  # diffusion coefficient, transmissivity. As a cell variable.
        Tface = fp.FaceVariable(mesh=mesh,
                                value=Tcell.arithmeticFaceValue.value
                                )  # THe correct Face variable.

        return Tface.value

    def S_value(phi, ele, cmask, peat_bottom_arr):
        # Some inputs are in fipy CellVariable type
        gwt = (phi.value * cmask.value - ele).reshape(ny, nx)

        S = storage(gwt, s0, s1, s2, s_sapric_coef) - storage(
            peat_bottom_arr, s0, s1, s2, s_sapric_coef)
        S[S < 0] = 1e-3  # Same reasons as for D

        Scell = fp.CellVariable(mesh=mesh, value=S.flatten(
        ))  # diffusion coefficient, transmissivity. As a cell variable.
        return Scell.value

    T = fp.FaceVariable(mesh=mesh,
                        value=T_value(
                            phi, ele, cmask,
                            peat_bottom_arr))  # THe correct Face variable.
    S = fp.CellVariable(mesh=mesh,
                        value=S_value(
                            phi, ele, cmask,
                            peat_bottom_arr))  # differential water capacity

    largeValue = 1e20  # value needed in implicit source term to apply internal boundaries

    if plotOpt:
        big_4_raster_plot(
            title='Before the computation',
            # raster1=((hydro_utils.peat_map_h_to_tra(soil_type_mask=peat_type_mask, gwt=(phi.value - ele), h_to_tra_and_C_dict=httd) - tra_to_cut)*cmask.value *drmask_not.value ).reshape(ny,nx),
            raster2=(ele.reshape(ny, nx) - wt_canal_arr) * dr * catchment_mask,
            raster3=ele.reshape(ny, nx),
            raster4=(ele - phi.value).reshape(ny, nx))
        # for later cross-section plots
        y_value = 270
    """
    ###### PDE ######
    """

    if mode == 'steadystate':
        temp = 0.
    elif mode == 'transient':
        temp = fp.TransientTerm(coeff=S)

    if diri_bc != None:
        #        diri_boundary = fp.CellVariable(mesh=mesh, value= np.ravel(diri_boundary_value(boundary_mask, ele2d, diri_bc))
        eq = temp == (
            fp.DiffusionTerm(coeff=T) + source * cmask * drmask_not -
            fp.ImplicitSourceTerm(cmask_not * largeValue) +
            cmask_not * largeValue * np.ravel(boundary_arr) -
            fp.ImplicitSourceTerm(drmask * largeValue) + drmask * largeValue *
            (np.ravel(wt_canal_arr))
            #                        - fp.ImplicitSourceTerm(bmask_not*largeValue) + bmask_not*largeValue*(boundary_arr)
        )

    #********************************************************

    max_sweeps = 10  # inner loop.

    avg_wt = []
    # wt_track_drained = []
    # wt_track_notdrained = []

    cumulative_Vdp = 0.

    #********Finite volume computation******************
    for d in range(days):

        if type(P) == type(ele):  # assume it is a numpy array
            source.setValue(
                (P[d] - ET[d]) * .001 * np.ones(ny * nx)
            )  # source/sink, in mm/day. The factor of 10^-3 takes into account that there are 100 x 100 m^2 in one pixel
            # print("(d,P) = ", (d, (P[d]-ET[d])* 10.))
        else:
            source.setValue((P - ET) * 10. * np.ones(ny * nx))
            # print("(d,P) = ", (d, (P-ET)* 10.))

        if plotOpt and d != 0:
            # print "one more cross-section plot"
            plot_line_of_peat(phi.value.reshape(ny, nx),
                              y_value=y_value,
                              title="cross-section",
                              color='cornflowerblue',
                              nx=nx,
                              ny=ny,
                              label=d)

        res = 0.0

        phi.updateOld()

        T.setValue(T_value(phi, ele, cmask, peat_bottom_arr))
        S.setValue(S_value(phi, ele, cmask, peat_bottom_arr))

        for r in range(max_sweeps):
            resOld = res

            res = eq.sweep(var=phi, dt=dt)  # solve linearization of PDE

            #print "sum of Ds: ", np.sum(D.value)/1e8
            #print "average wt: ", np.average(phi.value-ele)

            #print 'residue diference:    ', res - resOld

            if abs(res - resOld) < 1e-7:
                break  # it has reached to the solution of the linear system

        if remove_ponding_water:
            s = np.where(
                phi.value > ele, ele, phi.value
            )  # remove the surface water. This also removes phi in those masked values (for the plot only)
            phi.setValue(s)  # set new values for water table

        if (T.value < 0.).any():
            print("Some value in D is negative!")

        # For some plots
        avg_wt.append(np.average(phi.value - ele))
        # wt_track_drained.append((phi.value - ele).reshape(ny,nx)[track_WT_drained_area])
        # wt_track_notdrained.append((phi.value - ele).reshape(ny,nx)[track_WT_notdrained_area])
        """ Volume of dry peat calc."""
        not_peat = np.ones(shape=peat_type_mask.shape)  # Not all soil is peat!
        not_peat[peat_type_mask == 4] = 0  # NotPeat
        not_peat[peat_type_mask == 5] = 0  # OpenWater
        peat_vol_weights = utilities.PeatV_weight_calc(
            np.array(~dr * catchment_mask * not_peat, dtype=int))
        dry_peat_volume = utilities.PeatVolume(peat_vol_weights,
                                               (ele - phi.value).reshape(
                                                   ny, nx))
        cumulative_Vdp = cumulative_Vdp + dry_peat_volume

    if plotOpt:
        big_4_raster_plot(
            title='After the computation',
            # raster1=((hydro_utils.peat_map_h_to_tra(soil_type_mask=peat_type_mask, gwt=(phi.value - ele), h_to_tra_and_C_dict=httd) - tra_to_cut)*cmask.value *drmask_not.value ).reshape(ny,nx),
            raster2=(ele.reshape(ny, nx) - wt_canal_arr) * dr * catchment_mask,
            raster3=ele.reshape(ny, nx),
            raster4=(ele - phi.value).reshape(ny, nx))

        # fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(12,9), dpi=80)
        # x = np.arange(-21,1,0.1)
        # axes[0,0].plot(httd[1]['hToTra'](x),x)
        # axes[0,0].set(title='hToTra', ylabel='depth')
        # axes[0,1].plot(httd[1]['C'](x),x)
        # axes[0,1].set(title='C')
        # axes[1,0].plot()
        # axes[1,0].set(title="Nothing")
        # axes[1,1].plot(avg_wt)
        # axes[1,1].set(title="avg_wt_over_time")

        # # plot surface in cross-section
        # ele_with_can = copy.copy(ele).reshape(ny,nx)
        # ele_with_can = ele_with_can * catchment_mask
        # ele_with_can[wt_canal_arr > 0] = wt_canal_arr[wt_canal_arr > 0]
        # plot_line_of_peat(ele_with_can, y_value=y_value, title="cross-section", nx=nx, ny=ny, label="surface", color='peru', linewidth=2.0)

        # plt.show()

#    change_in_canals = (ele-phi.value).reshape(ny,nx)*(drmask.value.reshape(ny,nx)) - ((ele-H)*drmask.value).reshape(ny,nx)
#    resulting_phi = phi.value.reshape(ny,nx)

    wtd = (phi.value - ele).reshape(ny, nx)
    wtd_sensors = [wtd[i] for i in sensor_positions]

    return np.array(wtd_sensors)
Exemplo n.º 14
0
    def get_potential(max_iter=10):
        ''' Calculates the potential with boundary conditions.

        Can have a larger bias column radius to simulate a not fully depleted
        sensor
        '''

        r_bias = radius  # Start with full depletion assumption

        for i in range(max_iter):
            # Set boundary condition
            bcs = []
            allfaces = mesh.getExteriorFaces()
            X, Y = mesh.getFaceCenters()

            # Set boundary conditions
            # Set readout pillars potentials
            for pos_x, pos_y in desc.get_ro_col_offsets():
                ring = allfaces & ((X - pos_x)**2 + (Y - pos_y)**2 <
                                   (radius * 2)**2)
                bcs.append(fipy.FixedValue(value=V_readout, faces=ring))

            depletion_mask = None

            # Full bias pillars potentials = V_bias
            for pos_x, pos_y in desc.get_center_bias_col_offsets():
                ring = allfaces & ((X - pos_x)**2 + (Y - pos_y)**2 <
                                   (radius)**2)
                bcs.append(fipy.FixedValue(value=V_bias, faces=ring))
                if not np.any(depletion_mask):
                    depletion_mask = (potential.mesh.x - pos_x)**2 + \
                        (potential.mesh.y - pos_y)**2 < r_bias ** 2
                else:
                    depletion_mask |= (potential.mesh.x - pos_x)**2 + \
                        (potential.mesh.y - pos_y)**2 < (r_bias) ** 2

            # Side bias pillars potentials = V_bias
            for pos_x, pos_y in desc.get_side_bias_col_offsets():
                ring = allfaces & ((X - pos_x)**2 + (Y - pos_y)**2 <
                                   (radius)**2)
                bcs.append(fipy.FixedValue(value=V_bias, faces=ring))
                if not np.any(depletion_mask):
                    depletion_mask = (potential.mesh.x - pos_x)**2 + \
                        (potential.mesh.y - pos_y)**2 < r_bias ** 2
                else:
                    depletion_mask |= (potential.mesh.x - pos_x)**2 + \
                        (potential.mesh.y - pos_y)**2 < (r_bias) ** 2

            # Edge bias pillars potentials = V_bias
            for pos_x, pos_y in desc.get_edge_bias_col_offsets():
                ring = allfaces & ((X - pos_x)**2 + (Y - pos_y)**2 <
                                   (radius)**2)
                bcs.append(fipy.FixedValue(value=V_bias, faces=ring))
                if not np.any(depletion_mask):
                    depletion_mask = (potential.mesh.x - pos_x)**2 + \
                        (potential.mesh.y - pos_y)**2 < r_bias ** 2
                else:
                    depletion_mask |= (potential.mesh.x - pos_x)**2 + \
                        (potential.mesh.y - pos_y)**2 < (r_bias) ** 2

            # A depletion zone within the bulk requires an internal boundary
            # condition. Internal boundary conditions seem to challenge fipy
            # http://www.ctcms.nist.gov/fipy/documentation/USAGE.html#applying-internal-boundary-conditions

            large_value = 1e+10  # Hack for optimizer

            potential.equation = (fipy.DiffusionTerm(coeff=epsilon_scaled) +
                                  charge == fipy.ImplicitSourceTerm(
                                      depletion_mask * large_value) -
                                  depletion_mask * large_value * V_bias)

            solver.solve(potential,
                         equation=potential.equation,
                         boundaryConditions=bcs)

            # Check if fully depleted
            if not np.isclose(potential.arithmeticFaceValue().min(),
                              V_bias,
                              rtol=0.05,
                              atol=0.01):
                if i == 0:
                    logging.warning('Sensor is not fully depleted. '
                                    'Try to find depletion region. ')
            else:
                return potential

            # Get line between readout and bias column to check for full
            # depletion
            for x, y in desc.get_ro_col_offsets():
                if desc.position_in_center_pixel(x, y):
                    x_ro, y_ro = x, y
                    break
            for x, y in list(desc.get_center_bias_col_offsets()
                             ) + desc.get_edge_bias_col_offsets():
                if desc.position_in_center_pixel(x, y):
                    x_bias, y_bias = x, y
                    break

            pot_descr = Description(potential,
                                    min_x=min_x,
                                    max_x=max_x,
                                    min_y=min_y,
                                    max_y=max_y,
                                    nx=width_x * n_pixel_x,
                                    ny=width_y * n_pixel_y)

            N = 1000
            x = np.linspace(x_ro, x_bias, N)
            y = np.linspace(y_ro, y_bias, N)
            # Deselect position that is within the columns
            sel = ~desc.position_in_column(x, y)
            x, y = x[sel], y[sel]
            position = np.sqrt(x**2 + y**2)  # [um]
            phi = pot_descr.get_potential(x, y)
            x_r = position.max()
            x_min = position[np.atleast_1d(np.argmin(phi))[0]]
            #             import matplotlib.pyplot as plt
            #             plt.plot(position, phi, color='blue', linewidth=2,
            #                      label='Potential')
            #             plt.plot([x_r, x_r], plt.ylim())
            #             plt.plot([x_min, x_min], plt.ylim())
            #             plt.show()

            # Increase bias radius boundary to simulate not depleted
            # region sourrounding bias column
            r_bias += x_r - x_min

            logging.info('Depletion region error: %d um', x_r - x_min)

        raise RuntimeError('Unable to find the depletion region')
Exemplo n.º 15
0
# S_1 &\equiv \left.{\frac{\partial S}{\partial \phi}}\right|_\text{old}
# \notag \\
# &= \frac{\partial m_\phi}{\partial \phi} \phi (1 - \phi) + m_\phi (1 - 2\phi)
# \notag
# \end{align}

# In[8]:


mPhi = -2 * (1 - 2 * phi) + 30 * phi * (1 - phi) * Delta_f
dmPhidPhi = 4 + 30 * (1 - 2 * phi) * Delta_f
S1 = dmPhidPhi * phi * (1 - phi) + mPhi * (1 - 2 * phi)
S0 = mPhi * phi * (1 - phi) - S1 * phi

eq = (fp.TransientTerm() == 
      fp.DiffusionTerm(coeff=1.) + S0 + fp.ImplicitSourceTerm(coeff=S1))


# ## Calculate total free energy
# 
# > \begin{align}
# F[\phi] = \int\left[\frac{1}{2}(\nabla\phi)^2 + g(\phi) - \Delta f p(\phi)\right]\,dV \tag{6}
# \end{align}

# In[9]:


ftot = (0.5 * phi.grad.mag**2
        + phi**2 * (1 - phi)**2
        - Delta_f * phi**3 * (10 - 15 * phi + 6 * phi**2))
volumes = fp.CellVariable(mesh=mesh, value=mesh.cellVolumes)
Exemplo n.º 16
0
 def initialise_food_equation(self):
     self.food_PDE = (
         fipy.TransientTerm() == fipy.DiffusionTerm(coeff=self.D_food) -
         fipy.ImplicitSourceTerm(coeff=self.gamma * self.rho))
Exemplo n.º 17
0
def test_steady_state():
    """
    Test that we can create a steady-state model using
    FiPy that is similar to that given by our 'naive' solver
    """
    continental_crust.heat_generation = u(1, 'mW/m^3')
    _ = continental_crust.to_layer(u(3000, 'm'))
    section = Section([_])

    heatflow = u(15, 'mW/m^2')
    surface_temperature = u(0, 'degC')

    m = continental_crust
    q = heatflow
    k = m.conductivity
    a = m.heat_generation
    Cp = m.specific_heat
    rho = m.density

    def simple_heat_flow(x):
        # Density and heat capacity matter don't matter in steady-state
        T0 = surface_temperature
        return T0.to('K') + q * x / k + a / (2 * k) * x**2

    p = simple_heat_flow(section.cell_centers)
    dx = section.cell_sizes[0]
    test_profile = p.into('degC')

    grad = (-q / k).into('K/m')
    # Divergence
    div = (-a / k).into('K/m^2')

    a_ = -N.gradient(N.gradient(test_profile, dx), dx)
    assert all(a_ < 0)
    assert N.allclose(a_.min(), div)

    res = steady_state(section, heatflow, surface_temperature)
    profile = res.profile.into('degC')

    assert N.allclose(test_profile, profile)

    solver = FiniteSolver(_)
    solver.constrain(surface_temperature, None)
    solver.constrain(heatflow, None)

    res2 = solver.steady_state()
    # Make sure it's nonlinear and has constant 2nd derivative
    arr = solver.var.faceGrad.divergence.value
    assert N.allclose(sum(arr - arr[0]), 0)
    assert N.allclose(arr.mean(), div)

    # Test simple finite element model
    mesh = F.Grid1D(nx=section.n_cells, dx=section.cell_sizes[0].into('m'))
    T = F.CellVariable(name="Temperature", mesh=mesh)
    T.constrain(surface_temperature.into("K"), mesh.facesLeft)
    A = F.FaceVariable(mesh=mesh, value=m.diffusivity.into("m**2/s"))
    rad_heat = F.CellVariable(mesh=mesh, value=(a / Cp / rho).into("K/s"))

    D = F.DiffusionTerm(coeff=A) + F.ImplicitSourceTerm(coeff=rad_heat)
    sol = D.solve(var=T)
    val = T.faceGrad.divergence.value
    arr = T.value - 273.15
    #assert N.allclose(val.mean(), div)
    assert N.allclose(test_profile, arr)

    P = res2.profile.into('degC')
    assert N.allclose(test_profile, P)
Exemplo n.º 18
0
    ax[1,1].plot(trans, head, label='T'); ax[1,1].legend()

# BC
h_left = 1.; h_right = 0.
theta_left = numerix.exp(s0 + s1*h_left); theta_right = numerix.exp(s0 + s1*h_right)

h.constrain(h_left, where=mesh.facesLeft); h.constrain(h_right, where=mesh.facesRight)
h_implicit_source.constrain(h_left, where=mesh.facesLeft); h_implicit_source.constrain(h_right, where=mesh.facesRight)
h_convection.constrain(h_left, where=mesh.facesLeft); h_convection.constrain(h_right, where=mesh.facesRight)
theta.constrain(theta_left, where=mesh.facesLeft); theta.constrain(theta_right, where=mesh.facesRight)

# Boussinesq eq.

P = 0; ET = 0

eq_h_implicit_source = fp.TransientTerm(coeff=S) == fp.DiffusionTerm(coeff=T) +  P - ET + fp.ImplicitSourceTerm((S - S.old)/dt)
eq_h = fp.TransientTerm(coeff=S) == fp.DiffusionTerm(coeff=T) +  P - ET
# eq_h_convection = fp.TransientTerm() == fp.DiffusionTerm(coeff=T/S) +  P - ET + T/S**2 * S.faceGrad* fp.ExponentialConvectionTerm()
eq_theta = fp.TransientTerm() == fp.DiffusionTerm(coeff=D) + P - ET


#%%
"""
Solve several equations:
    - h version with implicit source term
    - h version without implicit source term
    - h version with S inside spatial derivative
    - theta version
"""
STEPS = 100
MAX_SWEEPS = 100