FiPy: Mesh1D (04/24/2010)

95 days ago by skregmi

# FiPy Example: Diffusion- mesh1D # This example takes the user through assembling a simple problem with :term:`FiPy`. # It describes different approaches to a 1D diffusion problem with constant # diffusivity and fixed value boundary conditions such that, # # .. math:: # :label: eq:diffusion:mesh1D:constantD # # \frac{\partial \phi}{\partial t} = D \nabla^2 \phi. # # The first step is to define a one dimensional domain with 50 solution # points. The :class:`~fipy.meshes.numMesh.grid1D.Grid1D` object represents a linear structured grid. The # parameter ``dx`` refers to the grid spacing (set to unity here). from fipy import * nx = 50 dx = 1. mesh = Grid1D(nx = nx, dx = dx) # :term:`FiPy` solves all equations at the centers of the cells of the mesh. We # thus need a :class:`~fipy.variables.cellVariable.CellVariable` object to hold the values of the # solution, with the initial condition :math:`\phi = 0` at :math:`t = 0`, phi = CellVariable(name="solution variable", mesh=mesh, value=0.) # We'll let D = 1. # for now. # The set of boundary conditions are given to the equation as a Python # :keyword:`tuple` or :keyword:`list` (the distinction is not generally important to :term:`FiPy`). # The boundary conditions # .. math:: # \phi = # \begin{cases} # 0& \text{at \(x = 1\),} \\ # 1& \text{at \(x = 0\).} # \end{cases} # are formed with a value valueLeft = 1 valueRight = 0 # and a set of faces over which they apply. # .. note:: # # Only faces around the exterior of the mesh can be used for boundary # conditions. # For example, here the exterior faces on the left of the domain are # extracted by ``mesh``:meth:`~fipy.meshes.common.mesh.Mesh.getFacesLeft``. A :class:`~fipy.boundaryConditions.fixedValue.FixedValue` boundary condition is # created with these faces and a value (``valueLeft``). BCs = (FixedValue(faces=mesh.getFacesRight(), value=valueRight), FixedValue(faces=mesh.getFacesLeft(), value=valueLeft)) # .. note:: # If no boundary conditions are specified on exterior faces, the default # boundary condition is ``FixedFlux(faces=someFaces, value=0.)``, equivalent to # :math:`\vec{n} \cdot \nabla \phi \rvert_\text{someFaces} = 0`. # If you have ever tried to numerically solve # Eq. :eq:`eq:diffusion:mesh1D:constantD`, # you most likely attempted "explicit finite differencing" with code # something like:: # for step in range(steps): # for j in range(cells): # phi_new[j] = phi_old[j] \ # + (D * dt / dx**2) * (phi_old[j+1] - 2 * phi_old[j] + phi_old[j-1]) # time += dt # plus additional code for the boundary conditions. In :term:`FiPy`, you would write # .. index:: ExplicitDiffusionTerm, TransientTerm eqX = TransientTerm() == ExplicitDiffusionTerm(coeff=D) # The largest stable timestep that can be taken for this explicit 1D # diffusion problem is :math:`\Delta t \le \Delta x^2 / (2 D)`. # We limit our steps to 90% of that value for good measure timeStepDuration = 0.9 * dx**2 / (2 * D) steps = 100 # If we're running interactively, we'll want to view the result, but not if # this example is being run automatically as a test. We accomplish this by # having Python check if this script is the "``__main__``" script, which will # only be true if we explicitly launched it and not if it has been imported # by another script such as the automatic tester. The factory function # :func:`Viewer` returns a suitable viewer depending on available # viewers and the dimension of the mesh. # .. index:: # module: fipy.viewers phiAnalytical = CellVariable(name="analytical value", mesh=mesh) viewer = Viewer(vars=(phi, phiAnalytical), datamin=0., datamax=1.) viewer.plot(filename="a.png") # In a semi-infinite domain, the analytical solution for this transient # diffusion problem is given by # :math:`\phi = 1 - \erf(x/2\sqrt{D t})`. If the :term:`SciPy` library is available, # the result is tested against the expected profile: x = mesh.getCellCenters()[0] t = timeStepDuration * steps try: from scipy.special import erf phiAnalytical.setValue(1 - erf(x / (2 * sqrt(D * t)))) except ImportError: print "The SciPy library is not available to test the solution to the transient diffusion equation" # We then solve the equation by repeatedly looping in time: for step in range(steps): eqX.solve(var=phi, boundaryConditions=BCs, dt=timeStepDuration) viewer.plot(filename="b%02d.png" % step) print phi.allclose(phiAnalytical, atol = 7e-4) # Expected: ## 1 # .. image:: mesh1Dexplicit.* # :width: 90% # :align: center # ----- # Although explicit finite differences are easy to program, we have just seen # that this 1D transient diffusion problem is limited to taking rather small # time steps. If, instead, we represent # Eq. :eq:`eq:diffusion:mesh1D:constantD` # as:: # # phi_new[j] = phi_old[j] \ # + (D * dt / dx**2) * (phi_new[j+1] - 2 * phi_new[j] + phi_new[j-1]) # # it is possible to take much larger time steps. Because ``phi_new`` appears on # both the left and right sides of the equation, this form is called # "implicit". In general, the "implicit" representation is much more # difficult to program than the "explicit" form that we just used, but in # :term:`FiPy`, all that is needed is to write eqI = TransientTerm() == DiffusionTerm(coeff=D) # reset the problem phi.setValue(valueRight) # and rerun with much larger time steps timeStepDuration *= 10 steps /= 10 for step in range(steps): eqI.solve(var=phi, boundaryConditions=BCs, dt=timeStepDuration) viewer.plot(filename="c%02d.png" % step) print phi.allclose(phiAnalytical, atol = 2e-2) # Expected: ## 1 # # .. image:: mesh1Dimplicit.* # :width: 90% # :align: center # # Note that although much larger *stable* timesteps can be taken with this # implicit version (there is, in fact, no limit to how large an implicit # timestep you can take for this particular problem), the solution is less # *accurate*. One way to achieve a compromise between *stability* and # *accuracy* is with the Crank-Nicholson scheme, represented by:: # # phi_new[j] = phi_old[j] + (D * dt / (2 * dx**2)) * \ # ((phi_new[j+1] - 2 * phi_new[j] + phi_new[j-1]) # + (phi_old[j+1] - 2 * phi_old[j] + phi_old[j-1])) # # which is essentially an average of the explicit and implicit schemes from # above. This can be rendered in :term:`FiPy` as easily as eqCN = eqX + eqI # We again reset the problem phi.setValue(valueRight) # and apply the Crank-Nicholson scheme until the end, when we apply one step # of the fully implicit scheme to drive down the error # (see, *e.g.*, section 19.2 of [NumericalRecipes]_). for step in range(steps - 1): eqCN.solve(var=phi, boundaryConditions=BCs, dt=timeStepDuration) viewer.plot(filename="d%02d.png" % step) eqI.solve(var=phi, boundaryConditions=BCs, dt=timeStepDuration) viewer.plot(filename="e.png") print phi.allclose(phiAnalytical, atol = 3e-3) # Expected: ## 1 # ----- # As mentioned above, there is no stable limit to how large a time step can # be taken for the implicit diffusion problem. In fact, if the time evolution # of the problem is not interesting, it is possible to eliminate the time # step altogether by omitting the :class:`~fipy.terms.transientTerm.TransientTerm`. The steady-state diffusion # equation # # .. math:: # # D \nabla^2 \phi = 0 # # is represented in :term:`FiPy` by # DiffusionTerm(coeff=D).solve(var=phi, boundaryConditions=BCs) viewer.plot(filename="f.png") # The analytical solution to the steady-state problem is no longer an error # function, but simply a straight line, which we can confirm to a tolerance # of :math:`10^{-10}`. L = nx * dx print phi.allclose(valueLeft + (valueRight - valueLeft) * x / L, rtol = 1e-10, atol = 1e-10) # Expected: ## 1 print "Implicit steady-state diffusion." # .. image:: mesh1DsteadyState.* # :width: 90% # :align: center # # ------ # # Often, boundary conditions may be functions of another variable in the # system or of time. # # For example, to have # # .. math:: # # \phi = \begin{cases} # (1 + \sin t) / 2 &\text{on \( x = 0 \)} \\ # 0 &\text{on \( x = L \)} \\ # \end{cases} # # we will need to declare time :math:`t` as a :class:`~fipy.variables.variable.Variable` time = Variable() # and then declare our boundary condition as a function of this :class:`~fipy.variables.variable.Variable` BCs = (FixedValue(faces=mesh.getFacesLeft(), value=0.5 * (1 + sin(time))), FixedValue(faces=mesh.getFacesRight(), value=0.)) # When we update ``time`` at each timestep, the left-hand boundary # condition will automatically update, dt = .1 while time() < 15: time.setValue(time() + dt) eqI.solve(var=phi, dt=dt, boundaryConditions=BCs) viewer.plot(filename="g.png") print "Time-dependent boundary condition." # .. image:: mesh1DtimedBC.* # :width: 90% # :align: center # ------ # Many interesting problems do not have simple, uniform diffusivities. We consider a # steady-state diffusion problem # .. math:: # # \nabla \cdot ( D \nabla \phi) = 0, # # with a spatially varying diffusion coefficient # # .. math:: # # D = \begin{cases} # 1& \text{for \( 0 < x < L / 4 \),} \\ # 0.1& \text{for \( L / 4 \le x < 3 L / 4 \),} \\ # 1& \text{for \( 3 L / 4 \le x < L \),} # \end{cases} # and with boundary conditions # :math:`\phi = 0` at :math:`x = 0` and :math:`D \frac{\partial \phi}{\partial x} # = 1` at :math:`x = L`, where :math:`L` is the length of the solution # domain. Exact numerical answers to this problem are found when the mesh # has cell centers that lie at :math:`L / 4` and :math:`3 L / 4`, or when the # number of cells in the mesh :math:`N_i` satisfies :math:`N_i = 4 i + 2`, # where :math:`i` is an integer. The mesh we've been using thus far is # satisfactory, with :math:`N_i = 50` and :math:`i = 12`. # # Because :term:`FiPy` considers diffusion to be a flux from one :class:`~fipy.meshes.numMesh.cell.Cell` to the next, # through the intervening :class:`~fipy.meshes.numMesh.face.Face`, we must define the non-uniform diffusion # coefficient on the mesh faces # # .. index:: FaceVariable D = FaceVariable(mesh=mesh, value=1.0) x = mesh.getFaceCenters()[0] D.setValue(0.1, where=(L / 4. <= x) & (x < 3. * L / 4.)) # The boundary conditions are a fixed value of valueLeft = 0. # to the left and a fixed flux of fluxRight = 1. # to the right: # .. index:: FixedFlux BCs = (FixedValue(faces=mesh.getFacesLeft(), value=valueLeft), FixedFlux(faces=mesh.getFacesRight(), value=fluxRight)) # We re-initialize the solution variable phi.setValue(0) # and obtain the steady-state solution with one implicit solution step DiffusionTerm(coeff = D).solve(var=phi, boundaryConditions = BCs) # The analytical solution is simply # # .. math:: # # \phi = \begin{cases} # x & \text{for \( 0 < x < L/4 \),} \\ # 10 x - 9L/4 & \text{for \( L/4 \le x < 3 L / 4 \),} \\ # x + 18 L / 4 & \text{for \( 3 L / 4 \le x < L \),} # \end{cases} # or x = mesh.getCellCenters()[0] phiAnalytical.setValue(x) phiAnalytical.setValue(10 * x - 9. * L / 4. , where=(L / 4. <= x) & (x < 3. * L / 4.)) phiAnalytical.setValue(x + 18. * L / 4. , where=3. * L / 4. <= x) print phi.allclose(phiAnalytical, atol = 1e-8, rtol = 1e-8) # Expected: ## 1 # And finally, we can plot the result Viewer(vars=(phi, phiAnalytical)).plot() print "Non-uniform steady-state diffusion." # .. image:: mesh1Dnon-uniform.* # :width: 90% # :align: center # ------ # Often, the diffusivity is not only non-uniform, but also depends on # the value of the variable, such that # .. math:: # :label: eq:diffusion:mesh1D:variableD # # \frac{\partial \phi}{\partial t} = \nabla \cdot [ D(\phi) \nabla \phi]. # With such a non-linearity, it is generally necessary to "sweep" the # solution to convergence. This means that each time step should be # calculated over and over, using the result of the previous sweep to update # the coefficients of the equation, without advancing in time. In :term:`FiPy`, this # is accomplished by creating a solution variable that explicitly retains its # "old" value by specifying ``hasOld`` when you create it. The variable does # not move forward in time until it is explicity told to ``updateOld()``. In # order to compare the effects of different numbers of sweeps, let us create # a list of variables: ``phi[0]`` will be the variable that is actually being # solved and ``phi[1]`` through ``phi[4]`` will display the result of taking the # corresponding number of sweeps (``phi[1]`` being equivalent to not sweeping # at all). valueLeft = 1. valueRight = 0. phi = [ CellVariable(name="solution variable", mesh=mesh, value=valueRight, hasOld=1), CellVariable(name="1 sweep", mesh=mesh), CellVariable(name="2 sweeps", mesh=mesh), CellVariable(name="3 sweeps", mesh=mesh), CellVariable(name="4 sweeps", mesh=mesh) ] # If, for example, # # .. math:: # # D = D_0 (1 - \phi) # we would simply write # Eq. :eq:`eq:diffusion:mesh1D:variableD` # as D0 = 1. eq = TransientTerm() == DiffusionTerm(coeff=D0 * (1 - phi[0])) # .. note:: # Because of the non-linearity, the Crank-Nicholson scheme does not work # for this problem. # We apply the same boundary conditions that we used for the uniform # diffusivity cases # BCs = (FixedValue(faces=mesh.getFacesRight(), value=valueRight), FixedValue(faces=mesh.getFacesLeft(), value=valueLeft)) # Although this problem does not have an exact transient solution, it # can be solved in steady-state, with # .. math:: # # \phi(x) = 1 - \sqrt{\frac{x}{L}} x = mesh.getCellCenters()[0] phiAnalytical.setValue(1. - sqrt(x/L)) # We create a viewer to compare the different numbers of sweeps with the # analytical solution from before. viewer = Viewer(vars=phi + [phiAnalytical], datamin=0., datamax=1.) viewer.plot(filename="f.png") # As described above, an inner "sweep" loop is generally required for # the solution of non-linear or multiple equation sets. Often a # conditional is required to exit this "sweep" loop given some # convergence criteria. Instead of using the :meth:`solve` method equation, # when sweeping, it is often useful to call :meth:`sweep` instead. The # :meth:`sweep` method behaves the same way as :meth:`solve`, but returns the # residual that can then be used as part of the exit condition. # # We now repeatedly run the problem with increasing numbers of # sweeps. # for sweeps in range(1,5): phi[0].setValue(valueRight) for step in range(steps): # only move forward in time once per time step phi[0].updateOld() # but "sweep" many times per time step for sweep in range(sweeps): res = eq.sweep(var=phi[0], boundaryConditions=BCs, dt=timeStepDuration) viewer.plot(filename="g%02d.png" % step) # copy the final result into the appropriate display variable phi[sweeps].setValue(phi[0]) viewer.plot(filename="z.png") #print Implicit variable diffusity. %d sweep(s). Residual = %f. (sweeps, (abs(res)))) # As can be seen, sweeping does not dramatically change the result, but the # "residual" of the equation (a measure of how accurately it has been solved) # drops about an order of magnitude with each additional sweep. # .. attention:: # # Choosing an optimal balance between the number of time steps, the number # of sweeps, the number of solver iterations, and the solver tolerance is # more art than science and will require some experimentation on your part # for each new problem. # # Finally, we can increase the number of steps to approach equilibrium, or we # can just solve for it directly eq = DiffusionTerm(coeff=D0 * (1 - phi[0])) phi[0].setValue(valueRight) res = 1e+10 while res > 1e-6: res = eq.sweep(var=phi[0], boundaryConditions=BCs, dt=timeStepDuration) print phi[0].allclose(phiAnalytical, atol = 1e-1) # Expected: ## 1 viewer.plot(filename="h.png") print "Implicit variable diffusity - steady-state." # .. image:: mesh1Dvariable.* # :width: 90% # :align: center # # ------ # # If this example had been written primarily as a script, instead of as # documentation, we would delete every line that does not begin with # either "``>>>``" or "``...``", and then delete those prefixes from the # remaining lines, leaving:: # # #!/usr/bin/env python # # ## This script was derived from # ## 'examples/diffusion/mesh1D.py' # # nx = 50 # dx = 1. # mesh = Grid1D(nx = nx, dx = dx) # phi = CellVariable(name="solution variable", # mesh=mesh, # value=0) # # :: # # eq = DiffusionTerm(coeff=D0 * (1 - phi[0])) # phi[0].setValue(valueRight) # res = 1e+10 # while res > 1e-6: # res = eq.sweep(var=phi[0], # boundaryConditions=BCs, # dt=timeStepDuration) # # print phi[0].allclose(phiAnalytical, atol = 1e-1) # # Expect: # # 1 # # # viewer.plot(filename="i.png") # print "Implicit variable diffusity - steady-state" 
       
/home/ondrej/ext/femhub-0.9.9.beta6/local/lib/python2.6/site-package\
s/FiPy-2.1-py2.6.egg/fipy/solvers/pysparse/linearPCGSolver.py:70:
DeprecationWarning: PyArray_FromDimsAndDataAndDescr: use
PyArray_NewFromDescr.
  info, iter, relres = itsolvers.pcg(A, b, x, self.tolerance,
self.iterations, Assor)
True
True
True
True
Implicit steady-state diffusion.
Time-dependent boundary condition.
True
/home/ondrej/ext/femhub-0.9.9.beta6/local/lib/python2.6/site-package\
s/FiPy-2.1-py2.6.egg/fipy/viewers/matplotlibViewer/__init__.py:40:
UserWarning: Matplotlib1DViewer efficiency is improved by setting
the 'datamax' and 'datamin' keys
  return Matplotlib1DViewer(vars=vars, title=title, **kwlimits)
Non-uniform steady-state diffusion.
True
Implicit variable diffusity - steady-state.