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
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)
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
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())
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], )
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
[[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)
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"]
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)
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$"
# \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
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)
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)
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')
# 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)
def initialise_food_equation(self): self.food_PDE = ( fipy.TransientTerm() == fipy.DiffusionTerm(coeff=self.D_food) - fipy.ImplicitSourceTerm(coeff=self.gamma * self.rho))
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)
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