Esempio n. 1
0
class Battery():
    def __init__(
            self, cell_temperature_celcius, capacity_Ah, bruggeman_coefficient,
            charge_transfer_coefficient, current_collector_cross_section_area,
            electrolyte_diffusivity,
            transference_number):  # Initialises physical properties of cell

        ########################################## Compute & Assign Constants ##################################################
        self.T = 273.0 + cell_temperature_celcius  # Could move into electrode-level later
        self.Q = capacity_Ah * 3600.0
        self.brug = bruggeman_coefficient  # Could move into electrode-level later when electrodes may have unique brug. values
        self.alpha = charge_transfer_coefficient  # Could move into electrode-level later when electrodes may have unique alpha values
        self.A = current_collector_cross_section_area
        self.De = electrolyte_diffusivity
        self.t_plus = transference_number

    def Battery_scale_quantities(self, overall_thickness, kappa_string):
        # self.L = overall_thickness
        self.kappa = lambdify(
            Ce, sympify(kappa_string), "numpy"
        )  # Function to return electrolyte electrical conductivity, given a species concentration
        # NOTE: Consider changing this function's name since it's only the kappa definition now

######################################## Battery-level Mesh Generation #################################################

    def define_global_mesh(self, node_spacing, normalised_domain_thickness
                           ):  # Only Ce & phi_e are solved on this mesh
        self.dx = node_spacing  # node_spacing vector passed can be either uniformly or non-uniformly spaced points
        self.L_normalised = normalised_domain_thickness
        self.axial_mesh = Grid1D(Lx=self.L_normalised, dx=self.dx)

    def Battery_cellvariables(self, neg_epsilon_e, sep_epsilon_e,
                              pos_epsilon_e, neg_a_s, pos_a_s, nx_neg, nx_sep,
                              nx_pos, j_battery_value, ce_initial,
                              phi_e_initial, neg_L, sep_L, pos_L, neg_De_eff,
                              sep_De_eff, pos_De_eff):
        self.Ce = CellVariable(mesh=self.axial_mesh,
                               value=ce_initial,
                               hasOld=True)
        self.phi_e = CellVariable(mesh=self.axial_mesh, value=phi_e_initial)

        self.epsilon_e_value = numerix.zeros(nx_neg + nx_sep + nx_pos)
        self.epsilon_e_value[0:nx_neg], self.epsilon_e_value[
            nx_neg:nx_neg + nx_sep] = neg_epsilon_e, sep_epsilon_e
        self.epsilon_e_value[nx_neg + nx_sep:] = pos_epsilon_e
        self.epsilon_e = CellVariable(mesh=self.axial_mesh,
                                      value=self.epsilon_e_value)

        self.epsilon_e_eff_value = numerix.zeros(nx_neg + nx_sep + nx_pos)
        self.epsilon_e_eff_value[0:nx_neg] = neg_epsilon_e**self.brug
        self.epsilon_e_eff_value[nx_neg:nx_neg +
                                 nx_sep] = sep_epsilon_e**self.brug
        self.epsilon_e_eff_value[nx_neg + nx_sep:] = pos_epsilon_e**self.brug
        self.epsilon_e_eff = CellVariable(mesh=self.axial_mesh,
                                          value=self.epsilon_e_eff_value)

        self.a_s_value = numerix.zeros(nx_neg + nx_pos + nx_sep)
        self.a_s_value[0:nx_neg] = neg_a_s
        self.a_s_value[nx_neg:nx_neg + nx_sep] = 0.0
        self.a_s_value[nx_neg + nx_sep:] = pos_a_s
        self.a_s = CellVariable(mesh=self.axial_mesh, value=self.a_s_value)

        self.L_value = numerix.zeros(nx_neg + nx_pos + nx_sep)
        self.L_value[0:nx_neg], self.L_value[nx_neg:nx_neg +
                                             nx_sep] = neg_L, sep_L
        self.L_value[nx_neg + nx_sep:] = pos_L
        self.L = CellVariable(mesh=self.axial_mesh, value=self.L_value)

        self.De_eff_value = numerix.zeros(nx_neg + nx_pos + nx_sep)
        self.De_eff_value[0:nx_neg], self.De_eff_value[
            nx_neg:nx_neg + nx_sep], self.De_eff_value[
                nx_neg + nx_sep:] = neg_De_eff, sep_De_eff, pos_De_eff
        self.De_eff = CellVariable(mesh=self.axial_mesh,
                                   value=self.De_eff_value)

        self.j_battery = CellVariable(mesh=self.axial_mesh,
                                      value=j_battery_value)

############################# Define Eff. Diff. Coefficients & Volume Fractions ########################################

    def Battery_facevariables(self, neg_De_eff, sep_De_eff, pos_De_eff,
                              neg_epsilon_e, sep_epsilon_e, pos_epsilon_e,
                              normalised_neg_length, normalised_sep_length):
        self.De_eff = FaceVariable(
            mesh=self.axial_mesh
        )  # Define effective diffusion coefficient as a FiPy facevariable
        self.De_eff.setValue(
            neg_De_eff,
            where=(self.axial_mesh.faceCenters[0] <= normalised_neg_length)
        )  # Set its value to that for the neg. diff. coefficient in the negative electrode domain
        self.De_eff.setValue(
            sep_De_eff,
            where=((
                normalised_neg_length  # Set its value to the correct value in the separator
                < self.axial_mesh.faceCenters[0]) &
                   (self.axial_mesh.faceCenters[0] <
                    (normalised_neg_length + normalised_sep_length))))
        self.De_eff.setValue(
            pos_De_eff,
            where=(
                self.axial_mesh.faceCenters[
                    0]  # Set its value to the correct value in the positive electrode domain
                >= (normalised_neg_length + normalised_sep_length)))

        self.epsilon_e_eff_facevariable = FaceVariable(
            mesh=self.axial_mesh
        )  # Define electrolyte phase volume fraction as a FiPy facevariable
        self.epsilon_e_eff_facevariable.setValue(
            neg_epsilon_e**self.
            brug,  # Compute eff. value for neg. domain & set it in neg. electrode domain
            where=(self.axial_mesh.faceCenters[0] <= normalised_neg_length))
        self.epsilon_e_eff_facevariable.setValue(
            sep_epsilon_e**self.
            brug,  # Compute eff. value for sep. domain & set it in sep. electrode domain
            where=((normalised_neg_length < self.axial_mesh.faceCenters[0]) &
                   (self.axial_mesh.faceCenters[0] <
                    (normalised_neg_length + normalised_sep_length))))
        self.epsilon_e_eff_facevariable.setValue(
            pos_epsilon_e**self.
            brug,  # Compute eff. value for pos. domain & set it in pos. electrode domain
            where=(self.axial_mesh.faceCenters[0] >=
                   (normalised_neg_length + normalised_sep_length)))
    (X_faces**2 + Y_faces**2)**.5 > R_outer - cellSize / 10.)

#convectionCoeff=200.  #W/m^2/K
Bi_desired = 10.  #SETTING
convectionCoeff = Bi_desired * k / R_inner
logging.info('convection coefficient is %.2E' % convectionCoeff)

Bi = convectionCoeff * R_inner / k  #Biot number
logging.info('Biot number is %.2E' % Bi)

#ref: https://github.com/usnistgov/fipy/blob/develop/documentation/USAGE.rst#applying-robin-boundary-conditions
#warning: avoid confusion between convectionCoeff in that fipy documentation, which refers to terms involving "a", and convectionCoeff here, which refers to a heat transfer convection coefficient at a boundary
Gamma0 = D_thermal
Gamma = FaceVariable(mesh=mesh, value=Gamma0)
mask = surfaceFaces
Gamma.setValue(0., where=mask)
dPf = FaceVariable(mesh=mesh,
                   value=mesh._faceToCellDistanceRatio *
                   mesh.cellDistanceVectors)
Af = FaceVariable(mesh=mesh, value=mesh._faceAreas)
#RobinCoeff = (mask * Gamma0 * Af / (dPf.dot(a) + b)).divergence  #a is zero in our case
b = 1.
RobinCoeff = (mask * Gamma0 * Af / b).divergence  #a is zero in our case
#eq = (TransientTerm() == DiffusionTerm(coeff=Gamma) + RobinCoeff * g - ImplicitSourceTerm(coeff=RobinCoeff * mesh.faceNormals.dot(a)))   #a is zero in our case
# g in this formulation is -convectionCoeff/k*var, where var=T-T_infinity
eq = (TransientTerm() == DiffusionTerm(coeff=Gamma) +
      ImplicitSourceTerm(RobinCoeff * -convectionCoeff / k))

# embed()
# sys.exit()
Esempio n. 3
0
nx = 10
ny = 1

valueLeft = 0.
fluxRight = 1.
timeStepDuration = 1.

L = 10.

dx = L / nx
dy = 1.

mesh = Tri2D(dx, dy, nx, ny)

var = CellVariable(name="solution variable", mesh=mesh, value=valueLeft)

diffCoeff = FaceVariable(mesh=mesh, value=1.0)

x = mesh.faceCenters[0]
diffCoeff.setValue(0.1, where=(L / 4. <= x) & (x < 3. * L / 4.))

var.faceGrad.constrain([[1.], [0.]], mesh.facesRight)

var.constrain(valueLeft, mesh.facesLeft)

if __name__ == '__main__':
    import fipy.tests.doctestPlus
    exec(fipy.tests.doctestPlus._getScript())
    input('finished')
Esempio n. 4
0
class Circumbinary(object):
    def __init__(self, rmax=1.0e4, ncell=300, dt=1.0e-6, delta=1.0e-100,
                 fudge=1.0e-3, q=1.0, gamma=100, mdisk=0.1, odir='output',
                 bellLin=True, emptydt=0.001, **kargs):
        self.rmax = rmax
        self.ncell = ncell
        self.dt = dt
        self.delta = delta
        self.mDisk = mdisk
        Omega0 = (G*M/(gamma*a)**3)**0.5
        nu0 = alpha*cs**2/Omega0
        self.chi = 2*fudge*q**2*np.sqrt(G*M)/nu0/a*(gamma*a)**1.5
        self.T0 = mu*Omega0/alpha/k*nu0
        self.gamma = gamma
        self.fudge = fudge
        self.q = q
        self.nu0 = nu0
        self.t = 0.0
        self.odir = odir
        self.bellLin = bellLin
        self.emptydt = emptydt
        self._genGrid()
        self.r = self.mesh.cellCenters.value[0]
        self.rF = self.mesh.faceCenters.value[0]
        if self.q > 0.0:
            self.gap = np.where(self.rF < 1.7/gamma)
        else:
            self.gap = np.where(self.rF < 1.0/gamma)
        self._genSigma()
        self._genTorque()
        self._genT(bellLin=self.bellLin, **kargs)
        self._genVr()
        self._buildEq()

    def _genGrid(self, inB=1.0):
        """Generate a logarithmically spaced grid"""
        logFaces = np.linspace(-np.log(self.gamma/inB), np.log(self.rmax), num=self.ncell+1)
        logFacesLeft = logFaces[:-1]
        logFacesRight = logFaces[1:]
        dr = tuple(np.exp(logFacesRight) - np.exp(logFacesLeft))
        self.mesh = CylindricalGrid1D(dr=dr, origin=(inB/self.gamma,))

    def _genSigma(self, width=0.1):
        """Create dependent variable Sigma"""
        # Gaussian initial condition
        value = self.mDisk*M/np.sqrt(2*np.pi)/(self.gamma*a*width)*\
                np.exp(-0.5*np.square(self.r-1.0)/width**2)/(2*np.pi*self.gamma*self.r*a)
        # Make it dimensionless
        value /= self.mDisk*M/(self.gamma*a)**2
        idxs = np.where(self.r < 0.1)
        value[idxs] = 0.0
        value = tuple(value)

        # Create the dependent variable and set the boundary conditions
        # to zero
        self.Sigma = CellVariable(name='Surface density',
                                 mesh=self.mesh, hasOld=True, value=value)
        #self.Sigma.constrain(0, self.mesh.facesLeft)
        #self.Sigma.constrain(0, self.mesh.facesRight)

    def _genTorque(self):
        """Generate Torque"""
        self.Lambda = FaceVariable(name='Torque at cell faces', mesh=self.mesh, rank=1)
        self.LambdaCell = CellVariable(name='Torque at cell centers', mesh=self.mesh)
        LambdaArr = np.zeros(self.rF.shape)
        LambdaArr[1:] = self.chi*np.power(1.0/(self.rF[1:]*self.gamma-1.0), 4)
        #LambdaArr[self.gap] = 0.0; LambdaArr[self.gap] = LambdaArr.max()
        self.Lambda.setValue(LambdaArr)
        self.LambdaCell.setValue(self.chi*np.power(1.0/(self.r*self.gamma-1.0), 4))
        self.LambdaCell[np.where(self.LambdaCell > LambdaArr.max())] = LambdaArr.max()

    def _interpT(self):
        """
        Get an initial guess for T using an interpolation of the solutions for T
        in the various thermodynamic limits.
        """
        Lambda = self.Lambda/self.chi*self.fudge*self.q**2*G*M/a
        LambdaCell = self.LambdaCell/self.chi*self.fudge*self.q**2*G*M/a
        Sigma = self.Sigma*M/(self.gamma*a)**2
        r = self.r*a*self.gamma #In physical units (cgs)
        self.Omega = np.sqrt(G*M/r**3)
        self.TvThin = np.power(9.0/4*alpha*k/sigma/mu/kappa0*self.Omega, 1.0/(3.0+beta))
        self.TtiThin = np.power(1/sigma/kappa0*(OmegaIn-self.Omega)*LambdaCell, 1.0/(4.0+beta))
        self.Ti = np.power(np.square(eta/7*L/4/np.pi/sigma)*k/mu/G/M*r**(-3), 1.0/7)
        self.TvThick = np.power(27.0/64*kappa0*alpha*k/sigma/mu*self.Omega*Sigma**2, 1.0/(3.0-beta))
        self.TtiThick = np.power(3*kappa0/16/sigma*Sigma**2*(OmegaIn-self.Omega)*LambdaCell, 1.0/(4.0-beta))
        #return np.power(self.TvThin**4 + self.TvThick**4 + self.TtiThin**4 + self.TtiThick**4 + self.Ti**4, 1.0/4)/self.T0
        return np.power(self.TvThin**4 + self.TvThick**4 + self.Ti**4, 1.0/4)/self.T0

    def _genT(self, bellLin=True, **kargs):
        """Create a cell variable for temperature"""
        if bellLin:
            @pickle_results(os.path.join(self.odir, "interpolator.pkl"))
            def buildInterpolator(r, gamma, q, fudge, mDisk, **kargs):
                # Keep in mind that buildTemopTable() returns the log10's of the values
                rGrid, SigmaGrid, temp = thermopy.buildTempTable(r*a*gamma, q=q, f=fudge, **kargs)
                # Go back to dimensionless units
                rGrid -= np.log10(a*gamma)
                SigmaGrid -= np.log10(mDisk*M/gamma**2/a**2)
                # Get the range of values for Sigma in the table
                rangeSigma = (np.power(10.0, SigmaGrid.min()), np.power(10.0, SigmaGrid.max()))
                # Interpolate in the log of dimensionless units
                return rangeSigma, RectBivariateSpline(rGrid, SigmaGrid, temp)
            # Pass the radial grid in phsyical units
            # Get back interpolator in logarithmic space
            rangeSigma, log10Interp = buildInterpolator(self.r, self.gamma, self.q, self.fudge, self.mDisk, **kargs)
            rGrid = np.log10(self.r)
            SigmaMin = np.ones(rGrid.shape)*rangeSigma[0]
            SigmaMax = np.ones(rGrid.shape)*rangeSigma[1]
            r = self.r*a*self.gamma #In physical units (cgs)
            self.Omega = np.sqrt(G*M/r**3)
            Ti = np.power(np.square(eta/7*L/4/np.pi/sigma)*k/mu/G/M*r**(-3), 1.0/7)
            T = np.zeros(Ti.shape)
            # Define wrapper function that uses the interpolator and stores the results
            # in an array given as a second argument. It can handle zero or negative
            # Sigma values.
            def func(Sigma):
                good = np.logical_and(Sigma > rangeSigma[0], Sigma < rangeSigma[1])
                badMin = np.logical_and(True, Sigma < rangeSigma[0])
                badMax = np.logical_and(True, Sigma > rangeSigma[1])
                if np.sum(good) > 0:
                    T[good] = np.power(10.0, log10Interp.ev(rGrid[good], np.log10(Sigma[good])))
                if np.sum(badMin) > 0:
                    T[badMin] = np.power(10.0, log10Interp.ev(rGrid[badMin], np.log10(SigmaMin[badMin])))
                if np.sum(badMax) > 0:
                    raise ValueError("Extrapolation to large values of Sigma is not allowed, build a table with a larger Sigmax")
                    T[badMax] = np.power(10.0, log10Interp.ev(rGrid[badMax], np.log10(SigmaMax[badMax])))
                return T
            # Store interpolator as an instance method
            self._bellLinT = func
            # Save the temperature as an operator variable
            self.T = self.Sigma._UnaryOperatorVariable(lambda x: self._bellLinT(x))

        # Initialize T with the interpolation of the various thermodynamic limits
        else:
            self.T = self._interpT()

    def _genVr(self):
        """Generate the face variable that stores the velocity values"""
        r = self.r #In dimensionless units (cgs)
        # viscosity at cell centers in cgs
        self.nu = alpha*k/mu/self.Omega/self.nu0*self.T
        self.visc = r**0.5*self.nu*self.Sigma
        #self.visc.grad.constrain([self.visc/2/self.r[0]], self.mesh.facesLeft)
        #self.Sigma.constrain(self.visc.grad/self.nu*2*self.r**0.5, where=self.mesh.facesLeft)
        # I add the delta to avoid divisions by zero
        self.vrVisc = -3/self.rF**(0.5)/(self.Sigma.faceValue + self.delta)*self.visc.faceGrad
        if self.q > 0.0:
            self.vrTid = self.Lambda*np.sqrt(self.rF)

    def _buildEq(self):
        """
        Build the equation to solve, we can change this method to impelement other
        schemes, e.g. Crank-Nicholson.
        """
        # The current scheme is an implicit-upwind
        if self.q > 0.0:
            self.vr = self.vrVisc + self.vrTid
            self.eq = TransientTerm(var=self.Sigma) == - ExplicitUpwindConvectionTerm(coeff=self.vr, var=self.Sigma)
        else:
            self.vr = self.vrVisc
            mask_coeff = (self.mesh.facesLeft * self.mesh.faceNormals).getDivergence()
            self.eq = TransientTerm(var=self.Sigma) == - ExplicitUpwindConvectionTerm(coeff=self.vr, var=self.Sigma)\
                                                       - mask_coeff*3.0/2*self.nu/self.mesh.x*self.Sigma.old

    def dimensionalSigma(self):
        """
        Return Sigma in dimensional form (cgs)
        """
        return self.Sigma.value*self.mDisk*M/(self.gamma*a)**2

    def dimensionalFJ(self):
        """
        Return the viscous torque in dimensional units (cgs)
        """
        return 3*np.pi*self.nu.value*self.nu0*self.dimensionalSigma()*np.sqrt(G*M*self.r*a*self.gamma)

    def dimensionalTime(self, t=None, mode='yr'):
        """
        Return current time in dimensional units (years or seconds)
        """
        if t == None:
            t = self.t
        if mode == 'yr' or mode == 'years' or mode == 'year':
            return t*(a*self.gamma)**2/self.nu0/(365*24*60*60)
        else:
            return t*(a*self.gamma)**2/self.nu0

    def dimensionlessTime(self, t, mode='yr'):
        """
        Returns the dimensionless value of the time given as an argument
        """
        if mode == 'yr' or mode == 'years' or mode == 'year':
            return t/(a*self.gamma)**2*self.nu0*(365*24*60*60)
        else:
            return t/(a*self.gamma)**2*self.nu0

    def singleTimestep(self, dtMax=0.001, dt=None, update=True, emptyDt=False):
        """
        Evolve the system for a single timestep of size `dt`
        """
        if dt:
            self.dt = dt
        if emptyDt:
            vr = self.vr.value[0]
            if self.q == 0.0:
                vr[0] = -3.0/2*self.nu.faceValue.value[0]/self.rF[0]
            #vr[np.where(self.Sigma.value)] = self.delta
            self.flux = self.rF[1:]*vr[1:]-self.rF[:-1]*vr[:-1]
            self.flux = np.maximum(self.flux, self.delta)
            self.dts = self.mesh.cellVolumes/(self.flux)
            self.dts[np.where(self.Sigma.value == 0.0)] = np.inf
            self.dts[self.gap] = np.inf
            self.dt = self.emptydt*np.amin(self.dts)
        self.dt = min(dtMax, self.dt)
        try:
            self.eq.sweep(dt=self.dt)
            if np.any(self.Sigma.value < 0.0):
                self.singleTimestep(dt=self.dt/2)
            if update:
                self.Sigma.updateOld()
            self.t += self.dt
        except FloatingPointError:
            import ipdb; ipdb.set_trace()

    def evolve(self, deltaTime, **kargs):
        """
        Evolve the system using the singleTimestep method
        """
        tEv = self.t + deltaTime
        while self.t < tEv:
            dtMax = tEv - self.t
            self.singleTimestep(dtMax=dtMax, **kargs)

    def revert(self):
        """
        Revert evolve method if update=False was used, otherwise
        it has no effect.
        """
        self.Sigma.setValue(self.Sigma.old.value)

    def writeToFile(self):
        fName = self.odir + '/t{0}.pkl'.format(self.t)
        with open(fName, 'wb') as f:
            pickle.dump((self.t, self.Sigma.getValue()), f)

    def readFromFile(self, fName):
        with open(fName, 'rb') as f:
            t, Sigma = pickle.load(f)
        self.t = t
        self.Sigma.setValue(Sigma)

    def loadTimesList(self):
        path = self.odir
        files = os.listdir(path)
        if '.DS_Store' in files:
            files.remove('.DS_Store')
        if 'interpolator.pkl' in files:
            files.remove('interpolator.pkl')
        if 'init.pkl' in files:
            files.remove('init.pkl')
        self.times = np.zeros((len(files),))
        for i, f in enumerate(files):
            match = re.match(r"^t((\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?)\.pkl", f)
            if match == None:
                print "WARNING: File {0} has an unexepected name".format(f)
                files.remove(f)
                continue
            self.times[i] = float(match.group(1))
        self.times.sort()
        self.files = files

    def loadTime(self, t):
        """
        Load the file with the time closest to `t`
        """
        idx = (np.abs(self.times-t)).argmin()
        fName = self.odir + '/t'+str(self.times[idx]) + '.pkl'
        self.readFromFile(fName)
Esempio n. 5
0
"""
from fipy import Variable, FaceVariable, CellVariable, Grid1D, TransientTerm, DiffusionTerm, Viewer
from fipy.tools import numerix

# Setting up a mesh
nx = 50
dx = 0.02
L = nx * dx
mesh = Grid1D(nx=nx, dx=dx)
c = CellVariable(mesh=mesh, name=r"$c$", value=0.0)

# Setting up the spatially varying diffusion coefficient
# Due to the mechanics of the solver, the diffusion coefficient variable must be defined on the mesh face
D = FaceVariable(mesh=mesh, value=2.0)
x = mesh.faceCenters[0]
D.setValue(1.0, where=(x > L / 2.0))

# Boundary conditions
valueLeft = 1.0
valueRight = 0.0
c.constrain(valueLeft, mesh.facesLeft)
c.constrain(valueRight, mesh.facesRight)

#Equation
eq = (TransientTerm() == DiffusionTerm(coeff=D))

dt = 0.001
t = Variable(0.0)
time_stride = 20
timestep = 0
run_time = 1
Esempio n. 6
0
# 1D convection diffusion equation (mobile domain)
# version with \frac{\partial c_{im}}{\partial t}
eqM =  (TransientTerm(1.0,var=cm) + TransientTerm(betaT,var=cim) == 
       DiffusionTerm(DR,var=cm) - ExponentialConvectionTerm(u/(Rm*phim),var=cm))

# immobile domain (lumped approach)
eqIM = TransientTerm(Rim*phiim,var=cim) == beta/Rim*(cm - ImplicitSourceTerm(1.0,var=cim))

# couple equations
eqn = eqM & eqIM

viewer = Viewer(vars=(cm,cim), datamin=0.0, datamax=1.0)
viewer.plot()
time = 0.0

for step in range(steps):
    time += timeStep

    if time < 0.5:
        u.setValue((1.0,))
    elif time < 1.0:
        u.setValue((0.0,))
    else:
        u.setValue((-1.0,))

    eqn.solve(dt=timeStep)
    viewer.plot()


Esempio n. 7
0
valueLeft = 0.
fluxRight = 1.
timeStepDuration = 1.

L = 10.

dx = L / nx
dy = 1.

mesh = Tri2D(dx, dy, nx, ny)

var = CellVariable(
    name = "solution variable",
    mesh = mesh,
    value = valueLeft)

diffCoeff = FaceVariable(mesh = mesh, value = 1.0)

x = mesh.faceCenters[0]
diffCoeff.setValue(0.1, where=(L/4. <= x) & (x < 3. * L / 4.))

var.faceGrad.constrain([[1.], [0.]], mesh.facesRight)

var.constrain(valueLeft, mesh.facesLeft)

if __name__ == '__main__':
    import fipy.tests.doctestPlus
    exec(fipy.tests.doctestPlus._getScript())
    input('finished')