def _setup_equation(self, biomass_height): height = biomass_height + self._layer_height size = height * self._layer mesh = self.space.construct_mesh(height) variables, terms = [], [] phi = CellVariable(name=self.solute.name, mesh=mesh, hasOld=True) for r in self.reactions: variables.append( CellVariable(name=f"{r.bacteria.name}_rate", mesh=mesh, value=0.0)) terms.append( ImplicitSourceTerm(coeff=(variables[-1] / (phi + self._sr)))) equation = DiffusionTerm(coeff=self.diffusivity) - sum(terms) phi.constrain(1, where=mesh.facesTop) for var, coef in zip( variables, [r.rate_coefficient()[:size] for r in self.reactions]): try: var.setValue(coef / self.space.dV) except ValueError as err: print("Boundary layer height greater than system size") raise err phi.setValue(self.solute.value.reshape(-1)[:size]) return equation, phi, size
def __solve_diffusion(self, model): oxygen_grid = model.environments[self.oxygen_env_name] nx = oxygen_grid.xsize ny = oxygen_grid.ysize nz = oxygen_grid.zsize dx = dy = dz = 1.0 D = self.oxygen_diffusion_coeff mesh = Grid3D(dx=dx, dy=dy, nx=nx, ny=ny, dz=dz, nz=nz) phi = CellVariable(name="solutionvariable", mesh=mesh) phi.setValue(0.) start = time.time() for _ in range(self.diffusion_solve_iterations): source_grid, sink_grid = self.__get_source_sink_grids(phi, model) eq = TransientTerm() == DiffusionTerm( coeff=D) + source_grid - sink_grid eq.solve(var=phi, dt=1) eq = TransientTerm() == DiffusionTerm(coeff=D) eq.solve(var=phi, dt=self.dt) end = time.time() print("Solving oxygen diffusion took %s seconds" % str(end - start)) return phi, nx, ny, nz
def define_convection_variable(self, current_time): x_convection, y_convection = self.get_convection_x_and_y(current_time) if self.baseline_convection is None: convection_variable = CellVariable(mesh=self.mesh, rank=1) # Only define the variable from scratch once else: convection_variable = self.baseline_convection convection_variable.setValue((x_convection, y_convection)) return convection_variable
# dfdphi = a**2 * phi * (1 - phi) * (1 - 2 * phi) # dfdphi_ = a**2 * (1 - phi) * (1 - 2 * phi) # d2fdphi2 = a**2 * (1 - 6 * phi * (1 - phi)) dfdphi = ((1.0 / N_A) - (1.0 / N_B)) + (1.0 / N_A) * numerix.log(phi) - ( 1.0 / N_B) * numerix.log(1.0 - phi) + chi_AB * (1.0 - 2 * phi) d2fdphi2 = (1.0 / (N_A * phi)) + (1.0 / (N_B * (1.0 - phi))) - 2 * chi_AB eq1 = (TransientTerm(var=phi) == DiffusionTerm(coeff=D, var=psi)) eq2 = (ImplicitSourceTerm( coeff=1., var=psi) == ImplicitSourceTerm(coeff=d2fdphi2, var=phi) - d2fdphi2 * phi + dfdphi - DiffusionTerm(coeff=kappa, var=phi)) eq = eq1 & eq2 elapsed = 0. dt = 1.0 if __name__ == "__main__": duration = 1000. solver = LinearLUSolver(tolerance=1e-9, iterations=500) while elapsed < duration: elapsed += dt eq.solve(dt=dt, solver=solver) phi.setValue(0.0, where=phi < 0.0) phi.setValue(1.0, where=phi > 1.0) print(elapsed) if (elapsed % 50 == 0): vw = VTKCellViewer(vars=(phi, psi)) vw.plot(filename="%s_vashista_output.vtk" % (elapsed))
def runLeveler(kLeveler=0.018, bulkLevelerConcentration=0.02, cellSize=0.1e-7, rateConstant=0.00026, initialAcceleratorCoverage=0.0, levelerDiffusionCoefficient=5e-10, numberOfSteps=400, displayRate=10, displayViewers=True): kLevelerConsumption = 0.0005 aspectRatio = 1.5 faradaysConstant = 9.6485e4 gasConstant = 8.314 acceleratorDiffusionCoefficient = 4e-10 siteDensity = 6.35e-6 atomicVolume = 7.1e-6 charge = 2 metalDiffusionCoefficient = 4e-10 temperature = 298. overpotential = -0.25 bulkMetalConcentration = 250. bulkAcceleratorConcentration = 50.0e-3 initialLevelerCoverage = 0. cflNumber = 0.2 cellsBelowTrench = 10 trenchDepth = 0.4e-6 trenchSpacing = 0.6e-6 boundaryLayerDepth = 98.7e-6 i0Suppressor = 0.3 i0Accelerator = 22.5 alphaSuppressor = 0.5 alphaAccelerator = 0.4 alphaAdsorption = 0.62 m = 4 b = 2.65 A = 0.3 Ba = -40 Bb = 60 Vd = 0.098 Bd = 0.0008 etaPrime = faradaysConstant * overpotential / gasConstant / temperature mesh = TrenchMesh(cellSize=cellSize, trenchSpacing=trenchSpacing, trenchDepth=trenchDepth, boundaryLayerDepth=boundaryLayerDepth, aspectRatio=aspectRatio, angle=numerix.pi * 4. / 180.) distanceVar = GapFillDistanceVariable(name='distance variable', mesh=mesh, value=-1.) distanceVar.setValue(1., where=mesh.electrolyteMask) distanceVar.calcDistanceFunction() levelerVar = SurfactantVariable(name="leveler variable", value=initialLevelerCoverage, distanceVar=distanceVar) acceleratorVar = SurfactantVariable(name="accelerator variable", value=initialAcceleratorCoverage, distanceVar=distanceVar) bulkAcceleratorVar = CellVariable(name='bulk accelerator variable', mesh=mesh, value=bulkAcceleratorConcentration) bulkLevelerVar = CellVariable(name='bulk leveler variable', mesh=mesh, value=bulkLevelerConcentration) metalVar = CellVariable(name='metal variable', mesh=mesh, value=bulkMetalConcentration) def depositionCoeff(alpha, i0): expo = numerix.exp(-alpha * etaPrime) return 2 * i0 * (expo - expo * numerix.exp(etaPrime)) coeffSuppressor = depositionCoeff(alphaSuppressor, i0Suppressor) coeffAccelerator = depositionCoeff(alphaAccelerator, i0Accelerator) exchangeCurrentDensity = acceleratorVar.interfaceVar * ( coeffAccelerator - coeffSuppressor) + coeffSuppressor currentDensity = metalVar / bulkMetalConcentration * exchangeCurrentDensity depositionRateVariable = currentDensity * atomicVolume / charge / faradaysConstant extensionVelocityVariable = CellVariable(name='extension velocity', mesh=mesh, value=depositionRateVariable) kAccelerator = rateConstant * numerix.exp(-alphaAdsorption * etaPrime) kAcceleratorConsumption = Bd + A / (numerix.exp(Ba * (overpotential + Vd)) + numerix.exp(Bb * (overpotential + Vd))) q = m * overpotential + b levelerSurfactantEquation = AdsorbingSurfactantEquation( levelerVar, distanceVar=distanceVar, bulkVar=bulkLevelerVar, rateConstant=kLeveler, consumptionCoeff=kLevelerConsumption * depositionRateVariable) accVar1 = acceleratorVar.interfaceVar accVar2 = (accVar1 > 0) * accVar1 accConsumptionCoeff = kAcceleratorConsumption * (accVar2**(q - 1)) acceleratorSurfactantEquation = AdsorbingSurfactantEquation( acceleratorVar, distanceVar=distanceVar, bulkVar=bulkAcceleratorVar, rateConstant=kAccelerator, otherVar=levelerVar, otherBulkVar=bulkLevelerVar, otherRateConstant=kLeveler, consumptionCoeff=accConsumptionCoeff) advectionEquation = TransientTerm() + FirstOrderAdvectionTerm( extensionVelocityVariable) metalEquation = buildMetalIonDiffusionEquation( ionVar=metalVar, distanceVar=distanceVar, depositionRate=depositionRateVariable, diffusionCoeff=metalDiffusionCoefficient, metalIonMolarVolume=atomicVolume) metalVar.constrain(bulkMetalConcentration, mesh.facesTop) bulkAcceleratorEquation = buildSurfactantBulkDiffusionEquation( bulkVar=bulkAcceleratorVar, distanceVar=distanceVar, surfactantVar=acceleratorVar, otherSurfactantVar=levelerVar, diffusionCoeff=acceleratorDiffusionCoefficient, rateConstant=kAccelerator * siteDensity) bulkAcceleratorVar.constrain(bulkAcceleratorConcentration, mesh.facesTop) bulkLevelerEquation = buildSurfactantBulkDiffusionEquation( bulkVar=bulkLevelerVar, distanceVar=distanceVar, surfactantVar=levelerVar, diffusionCoeff=levelerDiffusionCoefficient, rateConstant=kLeveler * siteDensity) bulkLevelerVar.constrain(bulkLevelerConcentration, mesh.facesTop) eqnTuple = ((advectionEquation, distanceVar, (), None), (levelerSurfactantEquation, levelerVar, (), None), (acceleratorSurfactantEquation, acceleratorVar, (), None), (metalEquation, metalVar, (), None), (bulkAcceleratorEquation, bulkAcceleratorVar, (), GeneralSolver()), (bulkLevelerEquation, bulkLevelerVar, (), GeneralSolver())) narrowBandWidth = 20 * cellSize levelSetUpdateFrequency = int(0.7 * narrowBandWidth / cellSize / cflNumber / 2) totalTime = 0.0 if displayViewers: try: raise Exception from mayaviSurfactantViewer import MayaviSurfactantViewer viewers = (MayaviSurfactantViewer(distanceVar, acceleratorVar.interfaceVar, zoomFactor=1e6, datamax=0.5, datamin=0.0, smooth=1, title='accelerator coverage'), MayaviSurfactantViewer(distanceVar, levelerVar.interfaceVar, zoomFactor=1e6, datamax=0.5, datamin=0.0, smooth=1, title='leveler coverage')) except: class PlotVariable(CellVariable): def __init__(self, var=None, name=''): CellVariable.__init__(self, mesh=mesh.fineMesh, name=name) self.var = self._requires(var) def _calcValue(self): return numerix.array(self.var(self.mesh.cellCenters)) viewers = (Viewer(PlotVariable(var=acceleratorVar.interfaceVar)), Viewer(PlotVariable(var=levelerVar.interfaceVar))) for step in range(numberOfSteps): if displayViewers: if step % displayRate == 0: for viewer in viewers: viewer.plot() if step % levelSetUpdateFrequency == 0: distanceVar.calcDistanceFunction() extensionVelocityVariable.setValue(depositionRateVariable) extOnInt = numerix.where( distanceVar.globalValue > 0, numerix.where(distanceVar.globalValue < 2 * cellSize, extensionVelocityVariable.globalValue, 0), 0) dt = cflNumber * cellSize / extOnInt.max() distanceVar.extendVariable(extensionVelocityVariable) for eqn, var, BCs, solver in eqnTuple: eqn.solve(var, boundaryConditions=BCs, dt=dt, solver=solver) totalTime += dt point = ((1.25e-08, ), (3.125e-07, )) value = 2.02815779e-08 return abs(float(distanceVar(point, order=1)) - value) < cellSize / 10.0
class Yarn1DModel(object): """ Yarn1DModel is a special diffusion model for a single yarn which is composed by a certain amount of fibers. A cross-section of a fiber is generated. The domain is a line from the center of the yarn to the surface. On this line there are some fibers distributed as in the module yarn1Dgrid. Only diffusion processes in a single fiber and yarn are considered. ODE of scipy solve the diffusion process in the layers of DEET and permithrine which are on the fiber An overlap region outside of the yarn can be added for multiscale simulations Fipy solve the transient diffusion problem in the whole domain """ def __init__(self, config): """ a config class must be passed in that contains the required settings """ self.cfg = config self.verbose = self.cfg.get('general.verbose') self.time_period = self.cfg.get('time.time_period') self.delta_t = self.cfg.get('time.dt') self.steps = int((self.time_period*(1.+self.delta_t*1e-6)) // self.delta_t) #set correct delta_t self.delta_t = self.time_period / self.steps if self.verbose: print "Timestep used in yarn1d model:", self.delta_t self.diff_coef = self.cfg.get('diffusion.diffusion_coeff') self.init_conc_func = eval(self.cfg.get('initial.init_conc1d')) self.number_fiber = self.cfg.get('fiber.number_fiber') self.blend = self.cfg.get('fiber.blend') self.blend = [x/100. for x in self.blend] self.nr_models = self.cfg.get('fiber.number_type') assert self.nr_models == len(self.blend) == len(self.cfg.get('fiber.fiber_config')) #Initialize the tortuosity self.tortuosity = self.cfg.get('yarn.tortuosity') #construct the config for the fibers self.cfg_fiber = [] for filename in self.cfg.get('fiber.fiber_config'): if not os.path.isabs(filename): filename = os.path.normpath(os.path.join( os.path.dirname(self.cfg.filename), filename)) self.cfg_fiber.append(FiberConfigManager.get_instance(filename)) #set values from the yarn on this inifile self.cfg_fiber[-1].set("time.time_period", self.time_period) if self.cfg_fiber[-1].get("time.dt") > self.cfg.get("time.dt"): self.cfg_fiber[-1].set("time.dt", self.cfg.get("time.dt")) #we need stepwize solution, we select cvode self.cfg_fiber[-1].set("general.method", 'FVM') self.cfg_fiber[-1].set("general.submethod", 'cvode_step') #we check that boundary is transfer or evaporation bty = self.cfg_fiber[-1].get("boundary.type_right") if bty not in ['evaporation', 'transfer']: raise ValueError, 'Boundary type for a fiber should be evaporation or transfer' if self.verbose: print 'NOTE: Fiber has boundary out of type %s' % bty #set data in case fiber with extension area is used self.cfg_fiber[-1].set("fiber.extenddiff", self.diff_coef/self.tortuosity) #some memory self.step_old_time = None self.step_old_sol = None #use the area function for calculating porosity self.prob_area = eval(self.cfg.get('fiber.prob_area')) # boundary data self.bound_type = conf.BOUND_TYPE[self.cfg.get('boundary.type_right')] self.boundary_conc_out = self.cfg.get('boundary.conc_out') self.boundary_D_out = self.cfg.get('boundary.D_out') self.boundary_dist = self.cfg.get('boundary.dist_conc_out') self.boundary_transf_right = self.cfg.get('boundary.transfer_coef') self.nr_fibers = self.cfg.get('fiber.number_fiber') self.plotevery = self.cfg.get("plot.plotevery") self.writeevery = self.cfg.get("plot.writeevery") #allow a multiscale model to work with a source in overlap zone self.source_overlap = 0. self.initialized = False self.fiberconc_center = 0 self.fiberconc_middle = 0 self.fiberconc_surface = 0 def times(self, timestep, end=None): """ Compute the time at one of our steps If end is given, all times between step timestep and step end are returned as a list, with end included """ if end is None: return timestep * self.delta_t else: begin = timestep * self.delta_t end = end * self.delta_t return np.linspace(begin, end, end-begin + 1) def create_mesh(self): """ Create a mesh for use in the model. We use an equidistant mesh! grid: the space position of each central point for every cell element (r-coordinate); """ self.end_point = self.cfg.get('domain.yarnradius') self.nr_edge = self.cfg.get('domain.n_edge') self.nr_cell = self.nr_edge - 1 self.use_extend = self.cfg.get("domain.useextension") self.fiberlayout_method = self.cfg.get('domain.fiberlayout_method') self.areaextend = 0. if self.use_extend: self.end_extend = self.end_point + \ self.cfg.get('domain.extensionfraction') * self.end_point self.nr_edge_extend = max(2, int(self.nr_edge*self.cfg.get('domain.extensionfraction'))) self.areaextend = np.pi * (self.end_extend**2 - self.end_point**2) else: self.end_extend = self.end_point self.nr_edge_extend = 1 #we now construct the full edge grid self.nr_edge_tot = self.nr_edge + self.nr_edge_extend - 1 self.nr_cell_tot = self.nr_edge_tot - 1 self.grid_edge = np.empty(self.nr_edge_tot, float) self.grid_edge[:self.nr_edge] = np.linspace(0., self.end_point, self.nr_edge) self.grid_edge[self.nr_edge:] = np.linspace(self.end_point, self.end_extend, self.nr_edge_extend)[1:] #construct cell centers from this self.grid = (self.grid_edge[:-1] + self.grid_edge[1:])/2. #obtain cell sizes self.delta_r = self.grid_edge[1:] - self.grid_edge[:-1] grid_square = np.power(self.grid_edge, 2) self.delta_rsquare = grid_square[1:] - grid_square[:-1] #create fiber models as needed: one per fibertype and per cell in the yarn model self.fiber_models = [0] * (self.nr_edge - 1) self.fiber_mass = np.empty((self.nr_edge - 1, self.nr_models), float) self.source_mass = np.empty((self.nr_edge - 1, self.nr_models), float) self.source_conc = np.empty((self.nr_edge - 1, self.nr_models), float) self.source = np.zeros(self.nr_edge_tot - 1, float) for ind in range(self.nr_edge-1): self.fiber_models[ind] = [] for cfg in self.cfg_fiber: cfg.set("fiber.extendinit_conc", self.init_conc_func(self.grid[ind])) self.fiber_models[ind].append(FiberModel(cfg)) #calculate the porosity as n=(pi Ry^2-nr_fibers pi Rf^2) / pi Ry^2 #porosity in the yarn self.porosity = np.ones(self.nr_cell_tot, float) self.volfracfib = [] # volume fraction of the fiber types if self.fiberlayout_method == 'virtlocoverlap': value_from_areafunction = np.zeros(self.nr_cell, float) for i_porosity in range(len(self.prob_area)): function_area = self.prob_area[i_porosity] value_from_areafunction += function_area(self.grid[:]) #plot the value from the function and the porosity value to check #the calculation for porosity plt.figure() plt.plot(self.grid[:], 1 - value_from_areafunction, '-', color = 'red') plt.plot(self.grid[:], 1 - function_area(self.grid[:]), '*') plt.xlabel('Yarn domain') plt.ylabel('value') plt.ylim(0., 1.0) plt.show() self.porosity[:self.nr_cell] = 1. - value_from_areafunction[:self.nr_cell] else: for blend, model in zip(self.blend, self.fiber_models[0]): print 'fiberradius', model.radius(), 'yarnradius', self.end_point self.volfracfib.append( blend * self.nr_fibers * np.power(model.radius(), 2) / np.power(self.end_point, 2) ) if np.sum(self.volfracfib) > 1: raise ValueError, 'porosity negative, unrealistic number of fibers in yarn cross section, %f fibers per yarn * Rf^2/Ry^2 = %f' % (self.nr_fibers,np.sum(self.volfracfib)) raw_input() self.porosity[:self.nr_cell] = 1 - np.sum(self.volfracfib) #print 'porosity in yarn', self.porosity[:self.nr_cell], #nrf is number of fibers in the shell at that grid position # per radial if not self.fiberlayout_method == 'virtlocoverlap': self.nrf_shell = (self.delta_rsquare[:self.nr_cell] / (self.end_point**2) * self.nr_fibers) else: raise NotImplementedError, 'nrfibers per shell still to determine' #we now have porosity and fiber models, we can calculate area extend available for ind in range(self.nr_edge-1): for fibmod in self.fiber_models[ind]: area_extend = np.pi * self.delta_rsquare[ind] \ * self.porosity[ind] / self.nrf_shell[ind] fibmod.set_areaextend(area_extend) # set fiber.extendarea in the fiber model! #create cylindrical 1D grid over domain for using fipy to view. if self.plotevery: self.mesh_yarn = CylindricalGrid1D(dr=tuple(self.delta_r[:self.nr_cell])) self.mesh_yarn.periodicBC = False self.mesh_yarn = self.mesh_yarn #print 'mesh yarn', self.grid_edge, ', delta_r yarn', self.delta_r def initial_yarn1d(self): """ initial concentration over the domain""" self.init_conc = np.empty(self.nr_cell_tot, float) #zero to the outside for ind, r in enumerate(self.grid[:self.nr_cell]): self.init_conc[ind] = self.init_conc_func(r) self.init_conc[self.nr_cell:] = self.boundary_conc_out def get_data(self, cellnr): index = cellnr return index def out_conc(self, data, t): """ return the concentration of compound in the void zone of cell cellnr at time t """ timenowyarn = self.step_old_time if t >= timenowyarn: #return data return self.step_old_sol[data] raise ValueError, 'out concentration should only be requested at a later time' def solve_fiber_init(self): """ Solve the diffusion process for a repellent on the fiber at radial position r in the yarn. &C/&t = 1/r * &(Dr&C/&r) / &r The diffusion coefficient is constant. The finite volume method is used to discretize the right side of equation. The mesh in this 1-D condition is uniform. """ for ind, models in enumerate(self.fiber_models): for type, model in enumerate(models): model.run_init() model.solve_init() #rebind the out_conc method to a call to yarn1d if model.use_extend: model.set_outconc(self.out_conc(ind, 0)) else: model.set_userdata(self.get_data(ind)) model.out_conc = lambda t, data: self.out_conc(data, t) self.fiber_mass[ind, type] = model.calc_mass(model.initial_c1) self.fiberconc_center = model.initial_c1[0] n = int((model.tot_edges_no_extend-2)/2) self.fiberconc_middle = model.initial_c1[n] self.fiberconc_surface = model.initial_c1[model.tot_edges_no_extend-2] def do_fiber_step(self, stoptime): """ Solve the diffusion process on the fiber up to stoptime, starting from where we where last. The flux is the BC: S*h(C_equi - C_yarn(t))*H(C-C_b,C_equi-C_yarn(t)) """ for ind, models in enumerate(self.fiber_models): for type, model in enumerate(models): if model.use_extend: model.set_outconc(self.out_conc(ind, self.step_old_time)) time, result = model.do_step(stoptime, needreinit=True) self.fiberconc_center = result[0] n = int((model.tot_edges_no_extend-2)/2) self.fiberconc_middle = result[n] self.fiberconc_surface = result[model.tot_edges_no_extend-2] #filedata= open(utils.OUTPUTDIR + os.sep + "fiberconc_%05d" %stoptime + ".txt",'w') #filedata.write("conc on %.10f is %s" % (stoptime,result)) #filedata.close() tmp = model.calc_mass(result) self.source_mass[ind, type] = self.fiber_mass[ind, type] - tmp self.fiber_mass[ind, type] = tmp def get_fiber_conc(self): """ method for reading fiberconcentrations from roommodel for last computed fiber in the yarn """ return self.fiberconc_center, self.fiberconc_middle, self.fiberconc_surface def _set_bound_flux(self, flux_edge, conc_r): """ Method that takes BC into account to set flux on edge flux here is the flux per radial Data is written to flux_edge, conc_r contains solution in the cell centers """ flux_edge[0] = 0. #avergage porosity on edge: if self.use_extend: porright = (self.porosity[self.nr_cell-1] + self.porosity[self.nr_cell]) / 2 else: porright = (self.porosity[self.nr_cell-1] + 1.) / 2 diffright = (self.diff_coef/self.tortuosity + self.boundary_D_out)/ 2 if self.bound_type == conf.TRANSFER: # tranfer flux, in x: flux_x = tf * C, so radially per radial a flux # flux_radial = 2 Pi * radius * flux_x / 2 * Pi flux_edge[self.nr_edge-1] = self.boundary_transf_right * porright \ * conc_r[self.nr_cell-1] * self.grid_edge[self.nr_edge-1] elif self.bound_type == conf.DIFF_FLUX: # diffusive flux with the outside # flux radial = - D_out * (conc_out - yarn_edge_conc)/dist_conc_out * radius if self.use_extend: conright = conc_r[self.nr_cell] bcdist = (self.delta_r[self.nr_cell-1] + self.delta_r[self.nr_cell]) / 2. else: conright = self.boundary_conc_out bcdist = self.boundary_dist flux_edge[self.nr_edge-1] = -(diffright * porright * (conright - conc_r[self.nr_cell-1]) / bcdist * self.grid_edge[self.nr_edge-1]) if self.use_extend: #zero flux at right boundary flux_edge[-1] = 0. def calc_mass(self, conc): """ calculate current amount of mass of volatile based on data currently stored """ #first we calculate the mass in the void space: mass = np.sum(conc[:self.nr_cell] * (np.power(self.grid_edge[1:self.nr_edge], 2) - np.power(self.grid_edge[:self.nr_edge-1], 2)) * self.porosity[:self.nr_cell] ) * np.pi ## print "mass in void space yarn", mass #now we add the mass in the fibers for ind, pos in enumerate(self.grid[:self.nr_cell]): for type, blend in enumerate(self.blend): #nrf_shell is number of fibers of blend in the shell at that grid position massfib = (self.fiber_mass[ind, type] * self.nrf_shell[ind] * blend) #print 'mass fiber', self.fiber_mass[ind,type], 'nr fibers per shell', self.nrf_shell[ind] mass += massfib #print 'yarn conc', conc #print 'yarn totalmass', mass, 'microgram' return mass def calc_mass_overlap(self, conc): """ calculate current amount of mass of volatile in the overlap region based on data currently stored. From mol/microm^2 to mol """ #we calculate the mass in the void space: mass = np.sum(conc[self.nr_cell:] * (np.power(self.grid_edge[self.nr_edge:], 2) - np.power(self.grid_edge[self.nr_edge-1:-1], 2)) * self.porosity[self.nr_cell:] ) * np.pi #print "porosity",self.porosity ##print 'yarn mass overlap', conc[self.nr_cell:], mass return mass def set_source(self, timestep): ## we calculated the source_mass from a fiber, using upscaling via volume averaging technique: ## source conc = nrf_shell * conc_r * \int_{r_{i+1}}^r_i rdr / V = nrf_shell * conc_r / (2*\pi) ## source mass = source conc * V = nrf_shell * conc_r * (r_{i+1}^2-r_i^2)*pi / 2*pi """ Method to calculate the radial source term Per radial we have the global equation \partial_t (n r C) = \partial_r (D/tau) r \partial_r (n C) + r Source where Source is mass per time per volume released/absorbed by the fibers So Source = nrf_shell * mass_source_fiber / (V \Delta t) This equation is integrated over a shell and devided by n (the porosity), and we determine n d_t w, with w = rC, where the sourceterm is \int_{r_i}^{r_{i+1}} r Source dr and is the term here calculated and stored in self.source As we assume Source constant over a shell by averaging out the mass over the area of void space of a shell (nV), we have Source = nrf_shell * mass_source_fiber/(nV \Delta t) * \Delta r_i^2 / 2 self.source_mass contains per shell how much mass was released in previous step by one fiber. Suppose this mass is M. We determine how many fibers there are radially, multiply this with M and divide by volume V * porosity n \delta t to obtain Source-concentration, since concentration is mass/volume time. Afterwards we multiply this Source with \Delta r_i^2 / (2 n \Delta r) coming from the integration (int n d_t w gives the term n \Delta r). """ for ind, pos in enumerate(self.grid_edge[:self.nr_cell]): self.source[ind] = 0. #V is the area of the shell V = np.pi*((pos+self.delta_r[ind])**2-pos**2) for type, blend in enumerate(self.blend): #nrf_shell is number of fibers of blend in the shell at that grid position # per radial # self.source_mass is the mass coming out of one fiber, we need a concentration # so,nrf_shell * self.source_conc = (nrf_shell * self.source_mass) / (porosity*V_shell) self.source_conc[ind,type] = (self.source_mass[ind, type] / (self.porosity[ind]*V) ) self.source[ind] += (self.source_conc[ind, type] * self.nrf_shell[ind] * blend) self.source[ind] /= timestep ## Note: source must be per second, so divided by the timestep def f_conc1_ode(self, t, conc_r, diff_u_t): """ Solving the radial yarn 1D diffusion equation: n \partial_t (rC) = \partial_r (D/tau r n \partial_r C) + Source * r with Source the conc amount per time unit added at r. Solution is obtained by integration over a cell, so n \delta r d_t (r C) = flux_right - flux_left + Source (\delta r^2 /2) so n d_t C = 1 / (r \delta r) * (flux_right - flux_left + Source (\delta r^2 /2) ) """ grid = self.grid #Initialize the flux rate on the edges flux_edge = self.__tmp_flux_edge #set flux on edge 0, self.nr_edge-1 and -1 self._set_bound_flux(flux_edge, conc_r) #calculate flux rate in each edge of the domain flux_edge[1:self.nr_edge-1] = -(2 * (self.diff_coef/self.tortuosity) * self.grid_edge[1:self.nr_edge-1] * (conc_r[1:self.nr_cell]-conc_r[:self.nr_cell-1]) /(self.delta_r[:self.nr_cell-1]+self.delta_r[1:self.nr_cell]) * (self.porosity[:self.nr_cell-1] + self.porosity[1:self.nr_cell])/2 ) if self.use_extend: # diffusion in the outside region flux_edge[self.nr_edge:-1] = -(2 * self.boundary_D_out * self.grid_edge[self.nr_edge:-1] * (conc_r[self.nr_cell+1:]-conc_r[self.nr_cell:-1]) /(self.delta_r[self.nr_cell:-1]+self.delta_r[self.nr_cell+1:]) * (self.porosity[self.nr_cell:-1] + self.porosity[self.nr_cell+1:])/2 ) diff_u_t[:] = ((flux_edge[:-1]-flux_edge[1:]) / self.delta_r[:]/ self.porosity[:] + self.source[:] * self.delta_rsquare / 2 / self.delta_r ) if self.use_extend and self.source_overlap: #porosity assumed 1 in extend! diff_u_t[self.nr_cell:] += (self.source_overlap * self.delta_rsquare[self.nr_cell:] / 2 / self.delta_r[self.nr_cell:]) diff_u_t[:] = diff_u_t[:] / self.grid[:] # still division by r to move from w to C def solve_ode_init(self): """ Initialize the ode solver """ self.initial_t = 0. self.step_old_time = self.initial_t n_cells = len(self.init_conc) self.ret_y = np.empty(n_cells, float) self.__tmp_flux_edge = np.empty(n_cells+1, float) self.tstep = 0 self.step_old_sol = np.empty(n_cells, float) self.step_old_sol[:] = self.init_conc[:] self.solver = sc_ode('cvode', self.f_conc1_ode, min_step_size=1e-9, first_step_size=1e-16, rtol=1e-6, atol=1e-10, max_steps=50000, lband=1, uband=1) self.solver.init_step(self.step_old_time, self.init_conc) self.initialized = True def do_ode_step(self, stoptime): """Solve the yarnmodel up to stoptime, continuing from the present state, return the time, concentration after step """ #fix where next to stop self.solver.set_options(tstop=stoptime) if not self.initialized: raise Exception, 'Solver ode not initialized' #solve the problem flag, realtime = self.solver.step(stoptime, self.ret_y) if flag < 0: raise Exception, 'could not find solution, flag %d' % flag assert np.allclose(realtime, stoptime), "%f %f" % (realtime, stoptime) return stoptime, self.ret_y def do_yarn_init(self): """ generic initialization needed before yarn can be solved """ self.create_mesh() self.initial_yarn1d() if not self.initialized: self.solve_ode_init() self.solve_fiber_init() def do_yarn_step(self, stoptime): """ Solve yarn up to time t. This does: 1. solve the fiber up to t 2. set correct source term for the yarn 3. solve the yarn up to t """ compute = True #even is step is large, we don't compute for a longer time than delta_t t = self.step_old_time while compute: t += self.delta_t if t >= stoptime - self.delta_t/100.: t = stoptime compute = False self.do_fiber_step(t) self.set_source(t-self.step_old_time) #we need to reinit as rhs changed if REINIT_ALWAYS: self.solver.init_step(self.step_old_time, self.step_old_sol) realtime, self.step_old_sol = self.do_ode_step(t) self.tstep += 1 self.step_old_time = t # filedata= open(utils.OUTPUTDIR + os.sep + "fiberconccenter" + ".txt",'w') # for i in range(0,len(self.fiberconc_center)): # filedata.write("%.5f %.5f\n" % (self.fiberconc_center[i,0],self.fiberconc_center[i,1])) # filedata.close() # filedata= open(utils.OUTPUTDIR + os.sep + "fiberconcmiddle" + ".txt",'w') # for i in range(0,len(self.fiberconc_middle)): # filedata.write("%.5f %.5f\n" % (self.fiberconc_middle[i,0],self.fiberconc_middle[i,1])) # filedata.close() # filedata= open(utils.OUTPUTDIR + os.sep + "fiberconcsurface" + ".txt",'w') # for i in range(0,len(self.fiberconc_surface)): # filedata.write("%.5f %.5f\n" % (self.fiberconc_surface[i,0],self.fiberconc_surface[i,1])) # filedata.close() return realtime, self.step_old_sol def view_sol(self, times, conc): """ Show the solution in conc with times. conc[i][:] contains solution at time times[i] """ if self.plotevery: self.solution_view = CellVariable(name="Yarn radial concentration", mesh=self.mesh_yarn, value=conc[0][:self.nr_cell]) if isinstance(conc, np.ndarray): maxv = conc.max() * 1.2 minv = conc.min() * 0.9 else: maxv = np.max(conc) * 1.2 minv = np.min(conc) * 0.9 self.viewer = Matplotlib1DViewer(vars=self.solution_view, datamin=minv, datamax=maxv) self.viewerplotcount = 0 self.viewerwritecount = 0 for time, con in zip(times, conc): self.solution_view.setValue(con[:self.nr_cell]) if self.viewerplotcount == 0 or (self.writeevery and self.viewerwritecount == 0): self.viewer.axes.set_title('time %s' %str(time)) if self.writeevery and self.viewerwritecount == 0: #plot and savefig self.viewer.plot(filename=utils.OUTPUTDIR + os.sep \ + 'yarnconc%08.4f.png' % time) else: #only plot self.viewer.plot() self.viewerplotcount += 1 self.viewerplotcount = self.viewerplotcount % self.plotevery if self.writeevery: self.viewerwritecount += 1 self.viewerwritecount = self.viewerwritecount % self.writeevery def run(self, wait=False): self.do_yarn_init() #data storage, will lead to memerror if many times ! n_cells = len(self.init_conc) self.conc1 = np.empty((self.steps+1, n_cells), float) self.conc1[0][:] = self.init_conc[:] print 'Start mass of DEET per grid cell per fiber type' for ind, masses in enumerate(self.fiber_mass): print 'cell', ind, for mass in masses: print mass, ' - ', print ' ' mc1 = self.calc_mass(self.step_old_sol) mc2 = self.calc_mass_overlap(self.step_old_sol) print 'Total mass in yarn', mc1, ', mass in overlap zone:', mc2, \ 'Sum', mc1 + mc2 tstep = 0 t = self.times(tstep+1) while t <= self.time_period+self.delta_t/10: tstep += 1 #print 'solving t', t rt, rety = self.do_yarn_step(t) self.conc1[self.tstep][:] = self.ret_y[:] t = self.times(tstep+1) print 'Final mass of DEET per grid cell per fiber type' for ind, masses in enumerate(self.fiber_mass): print 'cell', ind, for mass in masses: print mass, ' - ', print ' ' mc1 = self.calc_mass(self.step_old_sol) mc2 = self.calc_mass_overlap(self.step_old_sol) print 'Total mass in yarn', mc1, ', mass in overlap zone:', mc2, \ 'Sum', mc1 + mc2 self.view_sol(self.times(0,self.steps), self.conc1) if wait: raw_input("Finished yarn1d run")
Line Loop(2) = {7, 8, 9, 10}; Line Loop(3) = {11, 12, 13, 14}; Plane Surface(1) = {1, 2, 3}; Plane Surface(2) = {2}; Plane Surface(3) = {3}; Physical Line("Ground") = {4, 5, 6}; Physical Surface("Field") = {1}; Physical Surface("Anode") = {2}; Physical Surface("Cathode") = {3}; """ for refinement in range(10): mesh = Gmsh2D(geo, background=monitor) charge = CellVariable(mesh=mesh, name=r"$\rho$", value=0.) charge.setValue(+1, where=mesh.physicalCells["Anode"]) charge.setValue(-1, where=mesh.physicalCells["Cathode"]) potential = CellVariable(mesh=mesh, name=r"$\psi$") potential.constrain(0., where=mesh.physicalFaces["Ground"]) eq = DiffusionTerm(coeff=1.) == -charge res0 = eq.sweep(var=potential) res = eq.justResidualVector(var=potential) res1 = numerix.L2norm(res) res1a = CellVariable(mesh=mesh, value=abs(res)) res = CellVariable(mesh=mesh, name="residual", value=abs(res) / mesh.cellVolumes**(1./mesh.dim) / 1e-3)
I1 = Variable(value=((0, -1), (1, 0))) DIF_COEF = ALPHA**2 * (1. + C_ani * BETA) * (D_DIAG * I0 + D_OFF * I1) TAU = 0.0003 KAPPA_1 = 0.9 KAPPA_2 = 20. phase_EQ = (TransientTerm(TAU) == DiffusionTerm(DIF_COEF) + ImplicitSourceTerm( (phase - 0.5 - KAPPA_1 / np.pi * np.arctan(KAPPA_2 * D_temp)) * (1 - phase))) #%% Circular Solidified Region in the Center radius = DX * 5.0 C_circ = (NX * DX / 2, NY * DY / 2) X, Y = mesh.cellCenters phase.setValue(1., where=((X - C_circ[0])**2 + (Y - C_circ[1])**2) < radius**2) D_temp.setValue(-0.5) #%% Plotting if __name__ == "__main__": try: import pylab class DendriteViewer(Matplotlib2DGridViewer): def __init__(self, phase, D_temp, title=None, limits={}, **kwlimits):
def runSimpleTrenchSystem(faradaysConstant=9.6e4, gasConstant=8.314, transferCoefficient=0.5, rateConstant0=1.76, rateConstant3=-245e-6, catalystDiffusion=1e-9, siteDensity=9.8e-6, molarVolume=7.1e-6, charge=2, metalDiffusion=5.6e-10, temperature=298., overpotential=-0.3, metalConcentration=250., catalystConcentration=5e-3, catalystCoverage=0., currentDensity0=0.26, currentDensity1=45., cellSize=0.1e-7, trenchDepth=0.5e-6, aspectRatio=2., trenchSpacing=0.6e-6, boundaryLayerDepth=0.3e-6, numberOfSteps=5, displayViewers=True): cflNumber = 0.2 numberOfCellsInNarrowBand = 10 cellsBelowTrench = 10 yCells = cellsBelowTrench \ + int((trenchDepth + boundaryLayerDepth) / cellSize) xCells = int(trenchSpacing / 2 / cellSize) from fipy.tools import serialComm mesh = Grid2D(dx=cellSize, dy=cellSize, nx=xCells, ny=yCells, communicator=serialComm) narrowBandWidth = numberOfCellsInNarrowBand * cellSize distanceVar = DistanceVariable(name='distance variable', mesh=mesh, value=-1., hasOld=1) bottomHeight = cellsBelowTrench * cellSize trenchHeight = bottomHeight + trenchDepth trenchWidth = trenchDepth / aspectRatio sideWidth = (trenchSpacing - trenchWidth) / 2 x, y = mesh.cellCenters distanceVar.setValue(1., where=(y > trenchHeight) | ((y > bottomHeight) & (x < xCells * cellSize - sideWidth))) distanceVar.calcDistanceFunction(order=2) catalystVar = SurfactantVariable(name="catalyst variable", value=catalystCoverage, distanceVar=distanceVar) bulkCatalystVar = CellVariable(name='bulk catalyst variable', mesh=mesh, value=catalystConcentration) metalVar = CellVariable(name='metal variable', mesh=mesh, value=metalConcentration) expoConstant = -transferCoefficient * faradaysConstant \ / (gasConstant * temperature) tmp = currentDensity1 * catalystVar.interfaceVar exchangeCurrentDensity = currentDensity0 + tmp expo = numerix.exp(expoConstant * overpotential) currentDensity = expo * exchangeCurrentDensity * metalVar \ / metalConcentration depositionRateVariable = currentDensity * molarVolume \ / (charge * faradaysConstant) extensionVelocityVariable = CellVariable(name='extension velocity', mesh=mesh, value=depositionRateVariable) surfactantEquation = AdsorbingSurfactantEquation( surfactantVar=catalystVar, distanceVar=distanceVar, bulkVar=bulkCatalystVar, rateConstant=rateConstant0 + rateConstant3 * overpotential**3) advectionEquation = TransientTerm() + AdvectionTerm( extensionVelocityVariable) metalEquation = buildMetalIonDiffusionEquation( ionVar=metalVar, distanceVar=distanceVar, depositionRate=depositionRateVariable, diffusionCoeff=metalDiffusion, metalIonMolarVolume=molarVolume, ) metalVar.constrain(metalConcentration, mesh.facesTop) from surfactantBulkDiffusionEquation import buildSurfactantBulkDiffusionEquation bulkCatalystEquation = buildSurfactantBulkDiffusionEquation( bulkVar=bulkCatalystVar, distanceVar=distanceVar, surfactantVar=catalystVar, diffusionCoeff=catalystDiffusion, rateConstant=rateConstant0 * siteDensity) bulkCatalystVar.constrain(catalystConcentration, mesh.facesTop) if displayViewers: try: from mayaviSurfactantViewer import MayaviSurfactantViewer viewer = MayaviSurfactantViewer(distanceVar, catalystVar.interfaceVar, zoomFactor=1e6, datamax=0.5, datamin=0.0, smooth=1, title='catalyst coverage') except: viewer = MultiViewer( viewers=(Viewer(distanceVar, datamin=-1e-9, datamax=1e-9), Viewer(catalystVar.interfaceVar))) else: viewer = None levelSetUpdateFrequency = int(0.8 * narrowBandWidth \ / (cellSize * cflNumber * 2)) for step in range(numberOfSteps): if step > 5 and step % 5 == 0 and viewer is not None: viewer.plot() if step % levelSetUpdateFrequency == 0: distanceVar.calcDistanceFunction(order=2) extensionVelocityVariable.setValue(depositionRateVariable()) distanceVar.updateOld() distanceVar.extendVariable(extensionVelocityVariable, order=2) dt = cflNumber * cellSize / extensionVelocityVariable.max() advectionEquation.solve(distanceVar, dt=dt) surfactantEquation.solve(catalystVar, dt=dt) metalEquation.solve(metalVar, dt=dt) bulkCatalystEquation.solve(bulkCatalystVar, dt=dt, solver=GeneralSolver(tolerance=1e-15, iterations=2000)) try: import os filepath = os.path.splitext(__file__)[0] + '.gz' print catalystVar.allclose(numerix.loadtxt(filepath), rtol=1e-4) except: return 0
from fipy import Grid1D, CellVariable, Viewer from fipy.tools import numerix, dump import numpy from scipy.special import wofz # ----------------- Mesh Generation ----------------------- L = 4.0 nx = 100 mesh = Grid1D(nx=nx, Lx=L) x = mesh.cellCenters[0] # Cell position plasma_disp_real = CellVariable(name=r"Re$(X(z))$", mesh=mesh) plasma_disp_imag = CellVariable(name=r"Im$(X(z))$", mesh=mesh) full_plasma_disp = numpy.array(1j*numerix.sqrt(numerix.pi)\ * numerix.exp(-x**2)*wofz(x)) print full_plasma_disp plasma_disp_real.setValue(full_plasma_disp.real) plasma_disp_imag.setValue(full_plasma_disp.imag) initial_viewer = Viewer((plasma_disp_real, plasma_disp_imag),\ xmin=0.0, legend='best') raw_input("Pause for Initial Conditions")
t1[] = Rotate {{0,0,1},{0,0,0},Pi/2} {Duplicata{Surface{1};}}; t2[] = Rotate {{0,0,1},{0,0,0},Pi} {Duplicata{Surface{1};}}; t3[] = Rotate {{0,0,1},{0,0,0},Pi*3/2} {Duplicata{Surface{1};}}; t4[] = Rotate {{0,1,0},{0,0,0},-Pi/2} {Duplicata{Surface{1};}}; t5[] = Rotate {{0,0,1},{0,0,0},Pi/2} {Duplicata{Surface{t4[0]};}}; t6[] = Rotate {{0,0,1},{0,0,0},Pi} {Duplicata{Surface{t4[0]};}}; t7[] = Rotate {{0,0,1},{0,0,0},Pi*3/2} {Duplicata{Surface{t4[0]};}}; //create entire inner and outer shell Surface Loop(100)={1,t1[0],t2[0],t3[0],t7[0],t4[0],t5[0],t6[0]}; ''', order=2).extrude( extrudeFunc=lambda r: 1.1 * r) # doctest: +GMSH Phi = CellVariable(name=r"$\Phi$", mesh=mesh) # doctest: +GMSH Phi.setValue(GaussianNoiseVariable(mesh=mesh, mean=0.5, variance=0.01)) # doctest: +GMSH if __name__ == "__main__": try: print("\n done") #Intermediate Print Statement #viewer = MayaviClient(vars=phi,datamin=0., datamax=1.,daemon_file="/home/elekrv/Documents/Scripts/CahnSphere/sphereDaemon.py") #Commented as not required anymore viewer = VTKViewer(vars=Phi, datamin=0., datamax=1., xmin=-2.5, zmax=2.5) #Changed Statement print("\n Daemon file") #Intermediate Print Statement except (NameError, ImportError, SystemError, TypeError): viewer = VTKViewer(vars=Phi, datamin=0., datamax=1.,
import re import os # Setting up a mesh nx = 50 dx = 0.02 L = nx * dx mesh = Grid1D(nx=nx, dx=dx) # Defining the cell variable c = CellVariable(mesh=mesh, name=r"$c$") # Initialise c with a funky profile x = mesh.cellCenters[0] c.value = 0.0 c.setValue((0.3 * numerix.sin(2.0 * x * numerix.pi)) + 0.5) # Setting up the no-flux boundary conditions c.faceGrad.constrain(0.0, where=mesh.facesLeft) c.faceGrad.constrain(0.0, where=mesh.facesRight) # Provide value for diffusion and reaction coefficient D = 1.0 k = -2.0 # Specifying our alpha value. We will only use backwards Euler going forward alpha = 1 # Defining the equation with the reaction term. eq = TransientTerm() == DiffusionTerm(coeff=D) + ImplicitSourceTerm(coeff=k)
from fipy.tools import numerix from math import log nx = 100 dx = 0.3 mesh = Grid1D(dx=dx, nx=nx) phi = CellVariable(name=r"$\phi$", mesh=mesh) psi = CellVariable(name=r"$\psi$", mesh=mesh) # noise = GaussianNoiseVariable(mesh=mesh, mean=0.8, variance=0.002).value # phi[:] = noise x = mesh.cellCenters[0] phi.value = 0.1 phi.setValue(0.2, where=x < 0.3*(nx*dx)) phi.setValue(0.6, where=x > 0.3*(nx*dx)) phi.setValue(0.2, where=x > 0.32*(nx*dx)) phi.setValue(0.6, where=x > 0.7*(nx*dx)) phi.setValue(0.2, where=x > 0.72*(nx*dx)) D = a = epsilon = 1. N_A = 500 N_B = 500 chi_AB = 0.015 kappa = 2*chi_AB / 3.0 dfdphi = ((1.0/N_A) - (1.0/N_B)) + (1.0/N_A)*numerix.log(phi) - (1.0/N_B)*numerix.log(1.0 - phi) + chi_AB*(1.0 - 2*phi) d2fdphi2 = (1.0/(N_A*phi)) + (1.0/(N_B*(1.0 - phi))) - 2*chi_AB
def runGold(faradaysConstant=9.6e4, consumptionRateConstant=2.6e+6, molarVolume=10.21e-6, charge=1.0, metalDiffusion=1.7e-9, metalConcentration=20.0, catalystCoverage=0.15, currentDensity0=3e-2 * 16, currentDensity1=6.5e-1 * 16, cellSize=0.1e-7, trenchDepth=0.2e-6, aspectRatio=1.47, trenchSpacing=0.5e-6, boundaryLayerDepth=90.0e-6, numberOfSteps=10, taperAngle=6.0, displayViewers=True): cflNumber = 0.2 numberOfCellsInNarrowBand = 20 mesh = TrenchMesh(cellSize=cellSize, trenchSpacing=trenchSpacing, trenchDepth=trenchDepth, boundaryLayerDepth=boundaryLayerDepth, aspectRatio=aspectRatio, angle=numerix.pi * taperAngle / 180.) narrowBandWidth = numberOfCellsInNarrowBand * cellSize distanceVar = GapFillDistanceVariable(name='distance variable', mesh=mesh, value=-1.) distanceVar.setValue(1., where=mesh.electrolyteMask) distanceVar.calcDistanceFunction() catalystVar = SurfactantVariable(name="catalyst variable", value=catalystCoverage, distanceVar=distanceVar) metalVar = CellVariable(name='metal variable', mesh=mesh, value=metalConcentration) exchangeCurrentDensity = currentDensity0 + currentDensity1 * catalystVar.interfaceVar currentDensity = metalVar / metalConcentration * exchangeCurrentDensity depositionRateVariable = currentDensity * molarVolume / charge / faradaysConstant extensionVelocityVariable = CellVariable(name='extension velocity', mesh=mesh, value=depositionRateVariable) catalystSurfactantEquation = AdsorbingSurfactantEquation( catalystVar, distanceVar=distanceVar, bulkVar=0, rateConstant=0, consumptionCoeff=consumptionRateConstant * extensionVelocityVariable) advectionEquation = TransientTerm() + FirstOrderAdvectionTerm( extensionVelocityVariable) metalEquation = buildMetalIonDiffusionEquation( ionVar=metalVar, distanceVar=distanceVar, depositionRate=depositionRateVariable, diffusionCoeff=metalDiffusion, metalIonMolarVolume=molarVolume) metalVar.constrain(metalConcentration, mesh.facesTop) if displayViewers: try: from .mayaviSurfactantViewer import MayaviSurfactantViewer viewer = MayaviSurfactantViewer(distanceVar, catalystVar.interfaceVar, zoomFactor=1e6, datamax=1.0, datamin=0.0, smooth=1, title='catalyst coverage', animate=True) except: class PlotVariable(CellVariable): def __init__(self, var=None, name=''): CellVariable.__init__(self, mesh=mesh.fineMesh, name=name) self.var = self._requires(var) def _calcValue(self): return numerix.array(self.var(self.mesh.cellCenters)) viewer = MultiViewer( viewers=(Viewer( PlotVariable( var=distanceVar), datamax=1e-9, datamin=-1e-9), Viewer(PlotVariable(var=catalystVar.interfaceVar)))) else: viewer = None levelSetUpdateFrequency = int(0.7 * narrowBandWidth / cellSize / cflNumber / 2) step = 0 while step < numberOfSteps: if step % 10 == 0 and viewer is not None: viewer.plot() if step % levelSetUpdateFrequency == 0: distanceVar.calcDistanceFunction() extensionVelocityVariable.setValue( numerix.array(depositionRateVariable)) dt = cflNumber * cellSize / max(extensionVelocityVariable.globalValue) distanceVar.extendVariable(extensionVelocityVariable) advectionEquation.solve(distanceVar, dt=dt) catalystSurfactantEquation.solve(catalystVar, dt=dt) metalEquation.solve(metalVar, dt=dt) step += 1 point = ((5e-09, ), (1.15e-07, )) value = 1.45346701e-09 return abs(float(distanceVar(point, order=1)) - value) < cellSize / 10.0
distanceViewer = Viewer(vars=distanceVariable, datamin=-initialRadius, datamax=initialRadius) surfactantViewer = Viewer(vars=surfactantVariable, datamin=0., datamax=100.) velocityViewer = Viewer(vars=velocity, datamin=0., datamax=200.) distanceViewer.plot() surfactantViewer.plot() velocityViewer.plot() totalTime = 0 for step in range(steps): print('step', step) velocity.setValue(surfactantVariable.interfaceVar * k) distanceVariable.extendVariable(velocity) timeStepDuration = cfl * dx / velocity.max() distanceVariable.updateOld() advectionEquation.solve(distanceVariable, dt=timeStepDuration) surfactantEquation.solve(surfactantVariable, dt=1) totalTime += timeStepDuration velocityViewer.plot() distanceViewer.plot() surfactantViewer.plot() finalRadius = numerix.sqrt(2 * k * initialRadius * initialSurfactantValue * totalTime + initialRadius**2)
def runSimpleTrenchSystem(faradaysConstant=9.6e4, gasConstant=8.314, transferCoefficient=0.5, rateConstant0=1.76, rateConstant3=-245e-6, catalystDiffusion=1e-9, siteDensity=9.8e-6, molarVolume=7.1e-6, charge=2, metalDiffusion=5.6e-10, temperature=298., overpotential=-0.3, metalConcentration=250., catalystConcentration=5e-3, catalystCoverage=0., currentDensity0=0.26, currentDensity1=45., cellSize=0.1e-7, trenchDepth=0.5e-6, aspectRatio=2., trenchSpacing=0.6e-6, boundaryLayerDepth=0.3e-6, numberOfSteps=5, displayViewers=True): cflNumber = 0.2 numberOfCellsInNarrowBand = 10 cellsBelowTrench = 10 yCells = cellsBelowTrench \ + int((trenchDepth + boundaryLayerDepth) / cellSize) xCells = int(trenchSpacing / 2 / cellSize) from fipy.tools import serialComm mesh = Grid2D(dx = cellSize, dy = cellSize, nx = xCells, ny = yCells, communicator=serialComm) narrowBandWidth = numberOfCellsInNarrowBand * cellSize distanceVar = DistanceVariable( name = 'distance variable', mesh = mesh, value = -1., hasOld = 1) bottomHeight = cellsBelowTrench * cellSize trenchHeight = bottomHeight + trenchDepth trenchWidth = trenchDepth / aspectRatio sideWidth = (trenchSpacing - trenchWidth) / 2 x, y = mesh.cellCenters distanceVar.setValue(1., where=(y > trenchHeight) | ((y > bottomHeight) & (x < xCells * cellSize - sideWidth))) distanceVar.calcDistanceFunction(order=2) catalystVar = SurfactantVariable( name = "catalyst variable", value = catalystCoverage, distanceVar = distanceVar) bulkCatalystVar = CellVariable( name = 'bulk catalyst variable', mesh = mesh, value = catalystConcentration) metalVar = CellVariable( name = 'metal variable', mesh = mesh, value = metalConcentration) expoConstant = -transferCoefficient * faradaysConstant / (gasConstant * temperature) tmp = currentDensity1 * catalystVar.interfaceVar exchangeCurrentDensity = currentDensity0 + tmp expo = numerix.exp(expoConstant * overpotential) currentDensity = expo * exchangeCurrentDensity * metalVar / metalConcentration depositionRateVariable = currentDensity * molarVolume / (charge * faradaysConstant) extensionVelocityVariable = CellVariable( name = 'extension velocity', mesh = mesh, value = depositionRateVariable) surfactantEquation = AdsorbingSurfactantEquation( surfactantVar = catalystVar, distanceVar = distanceVar, bulkVar = bulkCatalystVar, rateConstant = rateConstant0 + rateConstant3 * overpotential**3) advectionEquation = TransientTerm() + AdvectionTerm(extensionVelocityVariable) metalEquation = buildMetalIonDiffusionEquation( ionVar = metalVar, distanceVar = distanceVar, depositionRate = depositionRateVariable, diffusionCoeff = metalDiffusion, metalIonMolarVolume = molarVolume, ) metalVar.constrain(metalConcentration, mesh.facesTop) from .surfactantBulkDiffusionEquation import buildSurfactantBulkDiffusionEquation bulkCatalystEquation = buildSurfactantBulkDiffusionEquation( bulkVar = bulkCatalystVar, distanceVar = distanceVar, surfactantVar = catalystVar, diffusionCoeff = catalystDiffusion, rateConstant = rateConstant0 * siteDensity ) bulkCatalystVar.constrain(catalystConcentration, mesh.facesTop) if displayViewers: try: from .mayaviSurfactantViewer import MayaviSurfactantViewer viewer = MayaviSurfactantViewer(distanceVar, catalystVar.interfaceVar, zoomFactor = 1e6, datamax=0.5, datamin=0.0, smooth = 1, title = 'catalyst coverage') except: viewer = MultiViewer(viewers=( Viewer(distanceVar, datamin=-1e-9, datamax=1e-9), Viewer(catalystVar.interfaceVar))) else: viewer = None levelSetUpdateFrequency = int(0.8 * narrowBandWidth \ / (cellSize * cflNumber * 2)) for step in range(numberOfSteps): if step>5 and step % 5 == 0 and viewer is not None: viewer.plot() if step % levelSetUpdateFrequency == 0: distanceVar.calcDistanceFunction(order=2) extensionVelocityVariable.setValue(depositionRateVariable()) distanceVar.updateOld() distanceVar.extendVariable(extensionVelocityVariable, order=2) dt = cflNumber * cellSize / extensionVelocityVariable.max() advectionEquation.solve(distanceVar, dt = dt) surfactantEquation.solve(catalystVar, dt = dt) metalEquation.solve(metalVar, dt = dt) bulkCatalystEquation.solve(bulkCatalystVar, dt = dt, solver=GeneralSolver(tolerance=1e-15, iterations=2000)) try: import os filepath = os.path.splitext(__file__)[0] + '.gz' print(catalystVar.allclose(numerix.loadtxt(filepath), rtol = 1e-4)) except: return 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)
L = 5.0 mesh = Grid1D(nx=nx, Lx=L) x = mesh.cellCenters[0] # Cell position # ----------------- Variable Declarations ----------------- density = CellVariable(name=r"$n$", mesh=mesh, hasOld=True) temperature = CellVariable(name=r"$T$", mesh=mesh, hasOld=True) Z = CellVariable(name=r"$Z$", mesh=mesh, hasOld=True) Diffusivity = CellVariable(name=r"$D$", mesh=mesh, hasOld=True) # ----------- Initial Conditions of Z --------------------- Z0L = 1.0 # L--mode Z0H = Z_S * (1.0 - numerix.tanh((L * x - L) / 2.0)) # H--mode Z.setValue(Z0H) # ----------------- Diffusivities ------------------------- # Itohs'/Zohm's model D_Zohm = (D_max + D_min) / 2.0 + ((D_max - D_min) * numerix.tanh(Z)) / 2.0 # Stap's Model alpha_sup = 0.5 D_Staps = D_min + (D_max - D_min) / (1.0 + alpha_sup * numerix.dot(Z.grad, Z.grad)) # Flow-Shear Model a1, a3 = 1.0, 0.5 # ASSUMES a2 = 0 D_Shear = D_min + (D_max - D_min) / (1.0 + a1 * (Z)**2 + a3 * numerix.dot(Z.grad, Z.grad)) # CHOOSE DIFFUSIVITY HERE! D_choice = D_Staps
x = mesh.cellCenters[0] # Cell position X = mesh.faceCenters[0] # Face position, if needed # -------------- State Variable Declarations -------------- density = CellVariable(name=r"$n$", mesh=mesh, hasOld=True) temperature = CellVariable(name=r"$T$", mesh=mesh, hasOld=True) U = CellVariable(name=r"$U$", mesh=mesh, hasOld=True) Z = CellVariable(name=r"$Z$", mesh=mesh, hasOld=True) Diffusivity = CellVariable(name=r"$D$", mesh=mesh, hasOld=True) U.setValue(density * temperature / (gamma - 1.0)) # -------------- Other Variable Declarations -------------- # Thermal velocities (most probable) v_Ti = CellVariable(name=r"$v_{th,i}$", mesh=mesh) v_Te = CellVariable(name=r"$v_{th,e}$", mesh=mesh) # Neutrals density in use for CX friction n_0 = CellVariable(name=r"$n_0$", mesh=mesh) # Poloidal gyro-(Larmor) radii rho_pi = CellVariable(name=r"$\rho_{\theta i}$", mesh=mesh) rho_pe = CellVariable(name=r"$\rho_{\theta e}$", mesh=mesh) # Banana orbit bounce frequencies omega_bi = CellVariable(name=r"$\omega_{bi}$", mesh=mesh)
def runGold(faradaysConstant=9.6e4, consumptionRateConstant=2.6e+6, molarVolume=10.21e-6, charge=1.0, metalDiffusion=1.7e-9, metalConcentration=20.0, catalystCoverage=0.15, currentDensity0=3e-2 * 16, currentDensity1=6.5e-1 * 16, cellSize=0.1e-7, trenchDepth=0.2e-6, aspectRatio=1.47, trenchSpacing=0.5e-6, boundaryLayerDepth=90.0e-6, numberOfSteps=10, taperAngle=6.0, displayViewers=True): cflNumber = 0.2 numberOfCellsInNarrowBand = 20 mesh = TrenchMesh(cellSize = cellSize, trenchSpacing = trenchSpacing, trenchDepth = trenchDepth, boundaryLayerDepth = boundaryLayerDepth, aspectRatio = aspectRatio, angle = numerix.pi * taperAngle / 180.) narrowBandWidth = numberOfCellsInNarrowBand * cellSize distanceVar = GapFillDistanceVariable( name = 'distance variable', mesh = mesh, value = -1.) distanceVar.setValue(1., where=mesh.electrolyteMask) distanceVar.calcDistanceFunction() catalystVar = SurfactantVariable( name = "catalyst variable", value = catalystCoverage, distanceVar = distanceVar) metalVar = CellVariable( name = 'metal variable', mesh = mesh, value = metalConcentration) exchangeCurrentDensity = currentDensity0 + currentDensity1 * catalystVar.interfaceVar currentDensity = metalVar / metalConcentration * exchangeCurrentDensity depositionRateVariable = currentDensity * molarVolume / charge / faradaysConstant extensionVelocityVariable = CellVariable( name = 'extension velocity', mesh = mesh, value = depositionRateVariable) catalystSurfactantEquation = AdsorbingSurfactantEquation( catalystVar, distanceVar = distanceVar, bulkVar = 0, rateConstant = 0, consumptionCoeff = consumptionRateConstant * extensionVelocityVariable) advectionEquation = TransientTerm() + FirstOrderAdvectionTerm(extensionVelocityVariable) metalEquation = buildMetalIonDiffusionEquation( ionVar = metalVar, distanceVar = distanceVar, depositionRate = depositionRateVariable, diffusionCoeff = metalDiffusion, metalIonMolarVolume = molarVolume) metalVar.constrain(metalConcentration, mesh.facesTop) if displayViewers: try: from .mayaviSurfactantViewer import MayaviSurfactantViewer viewer = MayaviSurfactantViewer(distanceVar, catalystVar.interfaceVar, zoomFactor = 1e6, datamax=1.0, datamin=0.0, smooth = 1, title = 'catalyst coverage', animate=True) except: class PlotVariable(CellVariable): def __init__(self, var = None, name = ''): CellVariable.__init__(self, mesh = mesh.fineMesh, name = name) self.var = self._requires(var) def _calcValue(self): return numerix.array(self.var(self.mesh.cellCenters)) viewer = MultiViewer(viewers=( Viewer(PlotVariable(var = distanceVar), datamax=1e-9, datamin=-1e-9), Viewer(PlotVariable(var = catalystVar.interfaceVar)))) else: viewer = None levelSetUpdateFrequency = int(0.7 * narrowBandWidth / cellSize / cflNumber / 2) step = 0 while step < numberOfSteps: if step % 10 == 0 and viewer is not None: viewer.plot() if step % levelSetUpdateFrequency == 0: distanceVar.calcDistanceFunction() extensionVelocityVariable.setValue(numerix.array(depositionRateVariable)) dt = cflNumber * cellSize / max(extensionVelocityVariable.globalValue) distanceVar.extendVariable(extensionVelocityVariable) advectionEquation.solve(distanceVar, dt = dt) catalystSurfactantEquation.solve(catalystVar, dt = dt) metalEquation.solve(metalVar, dt = dt) step += 1 point = ((5e-09,), (1.15e-07,)) value = 1.45346701e-09 return abs(float(distanceVar(point, order=1)) - value) < cellSize / 10.0
# ----------------- Variable Declarations ----------------- density = CellVariable(name=r"$n$", mesh=mesh, hasOld=True) temperature = CellVariable(name=r"$T$", mesh=mesh, hasOld=True) Z = CellVariable(name=r"$Z$", mesh=mesh, hasOld=True) ## Diffusivity D_Zohm = CellVariable(name="Zohm", mesh=mesh, hasOld=True) D_Staps = CellVariable(name="Staps", mesh=mesh, hasOld=True) D_Shear = CellVariable(name="Flow-Shear", mesh=mesh, hasOld=True) # ------------- Initial Conditions and Diffusivity--------- Z0 = Z_S*(1 - numerix.tanh((L*x - L) / 2)) Z.setValue(Z0) # Zohm's model D_Zohm.setValue((D_max + D_min) / 2.0 + ((D_max - D_min)*numerix.tanh(Z)) / 2.0) # Stap's Model alpha_sup = 0.5 D_Staps.setValue(D_min + (D_max - D_min) / (1.0 + alpha_sup*(Z.grad.mag)**2)) # Flow-Shear Model a1, a2, a3 = 0.7, 1.25, 0.5 D_Shear.setValue(D_min + (D_max - D_min) / (1.0 + a1*Z**2 + a2*Z*(Z.grad) + a3*(Z.grad)**2)) # ----------------- Boundary Conditions ------------------- # Z Boundary Conditions: # d/dx(Z(0,t)) == Z / lambda_Z # mu*D/epsilon * d/dx(Z(L,t)) == 0
duration = TIME_MAX time_stride = TIME_STRIDE timestep = 0 # Defining the solver to improve numerical stabilty solver = LinearLUSolver(tolerance=1e-9, iterations=50, precon="lu") # solver = PETSc.KSP().create() start = time.time() while elapsed < duration: if (timestep == 0): vw = VTKCellViewer(vars=(x_a, mu_AB)) vw.plot(filename="0_output.%d.vtk" % (parallelComm.procID)) elapsed += dt timestep += 1 x_a.updateOld() mu_AB.updateOld() res = 1e+10 while res > 1e-10: res = eq.sweep(dt=dt, solver=solver) x_a.setValue(0.0, where=x_a < 0.0) x_a.setValue(1.0, where=x_a > 1.0) print("sweep!") print(elapsed) end = time.time() print(end - start) if (timestep % time_stride == 0): vw = VTKCellViewer(vars=(x_a, mu_AB)) vw.plot(filename="%s_output.%d.vtk" % (elapsed, parallelComm.procID))
def runLeveler(kLeveler=0.018, bulkLevelerConcentration=0.02, cellSize=0.1e-7, rateConstant=0.00026, initialAcceleratorCoverage=0.0, levelerDiffusionCoefficient=5e-10, numberOfSteps=400, displayRate=10, displayViewers=True): kLevelerConsumption = 0.0005 aspectRatio = 1.5 faradaysConstant = 9.6485e4 gasConstant = 8.314 acceleratorDiffusionCoefficient = 4e-10 siteDensity = 6.35e-6 atomicVolume = 7.1e-6 charge = 2 metalDiffusionCoefficient = 4e-10 temperature = 298. overpotential = -0.25 bulkMetalConcentration = 250. bulkAcceleratorConcentration = 50.0e-3 initialLevelerCoverage = 0. cflNumber = 0.2 cellsBelowTrench = 10 trenchDepth = 0.4e-6 trenchSpacing = 0.6e-6 boundaryLayerDepth = 98.7e-6 i0Suppressor = 0.3 i0Accelerator = 22.5 alphaSuppressor = 0.5 alphaAccelerator = 0.4 alphaAdsorption = 0.62 m = 4 b = 2.65 A = 0.3 Ba = -40 Bb = 60 Vd = 0.098 Bd = 0.0008 etaPrime = faradaysConstant * overpotential / gasConstant / temperature mesh = TrenchMesh(cellSize = cellSize, trenchSpacing = trenchSpacing, trenchDepth = trenchDepth, boundaryLayerDepth = boundaryLayerDepth, aspectRatio = aspectRatio, angle = numerix.pi * 4. / 180.) distanceVar = GapFillDistanceVariable( name = 'distance variable', mesh = mesh, value = -1.) distanceVar.setValue(1., where=mesh.electrolyteMask) distanceVar.calcDistanceFunction() levelerVar = SurfactantVariable( name = "leveler variable", value = initialLevelerCoverage, distanceVar = distanceVar) acceleratorVar = SurfactantVariable( name = "accelerator variable", value = initialAcceleratorCoverage, distanceVar = distanceVar) bulkAcceleratorVar = CellVariable(name = 'bulk accelerator variable', mesh = mesh, value = bulkAcceleratorConcentration) bulkLevelerVar = CellVariable( name = 'bulk leveler variable', mesh = mesh, value = bulkLevelerConcentration) metalVar = CellVariable( name = 'metal variable', mesh = mesh, value = bulkMetalConcentration) def depositionCoeff(alpha, i0): expo = numerix.exp(-alpha * etaPrime) return 2 * i0 * (expo - expo * numerix.exp(etaPrime)) coeffSuppressor = depositionCoeff(alphaSuppressor, i0Suppressor) coeffAccelerator = depositionCoeff(alphaAccelerator, i0Accelerator) exchangeCurrentDensity = acceleratorVar.interfaceVar * (coeffAccelerator - coeffSuppressor) + coeffSuppressor currentDensity = metalVar / bulkMetalConcentration * exchangeCurrentDensity depositionRateVariable = currentDensity * atomicVolume / charge / faradaysConstant extensionVelocityVariable = CellVariable( name = 'extension velocity', mesh = mesh, value = depositionRateVariable) kAccelerator = rateConstant * numerix.exp(-alphaAdsorption * etaPrime) kAcceleratorConsumption = Bd + A / (numerix.exp(Ba * (overpotential + Vd)) + numerix.exp(Bb * (overpotential + Vd))) q = m * overpotential + b levelerSurfactantEquation = AdsorbingSurfactantEquation( levelerVar, distanceVar = distanceVar, bulkVar = bulkLevelerVar, rateConstant = kLeveler, consumptionCoeff = kLevelerConsumption * depositionRateVariable) accVar1 = acceleratorVar.interfaceVar accVar2 = (accVar1 > 0) * accVar1 accConsumptionCoeff = kAcceleratorConsumption * (accVar2**(q - 1)) acceleratorSurfactantEquation = AdsorbingSurfactantEquation( acceleratorVar, distanceVar = distanceVar, bulkVar = bulkAcceleratorVar, rateConstant = kAccelerator, otherVar = levelerVar, otherBulkVar = bulkLevelerVar, otherRateConstant = kLeveler, consumptionCoeff = accConsumptionCoeff) advectionEquation = TransientTerm() + FirstOrderAdvectionTerm(extensionVelocityVariable) metalEquation = buildMetalIonDiffusionEquation( ionVar = metalVar, distanceVar = distanceVar, depositionRate = depositionRateVariable, diffusionCoeff = metalDiffusionCoefficient, metalIonMolarVolume = atomicVolume) metalVar.constrain(bulkMetalConcentration, mesh.facesTop) bulkAcceleratorEquation = buildSurfactantBulkDiffusionEquation( bulkVar = bulkAcceleratorVar, distanceVar = distanceVar, surfactantVar = acceleratorVar, otherSurfactantVar = levelerVar, diffusionCoeff = acceleratorDiffusionCoefficient, rateConstant = kAccelerator * siteDensity) bulkAcceleratorVar.constrain(bulkAcceleratorConcentration, mesh.facesTop) bulkLevelerEquation = buildSurfactantBulkDiffusionEquation( bulkVar = bulkLevelerVar, distanceVar = distanceVar, surfactantVar = levelerVar, diffusionCoeff = levelerDiffusionCoefficient, rateConstant = kLeveler * siteDensity) bulkLevelerVar.constrain(bulkLevelerConcentration, mesh.facesTop) eqnTuple = ( (advectionEquation, distanceVar, (), None), (levelerSurfactantEquation, levelerVar, (), None), (acceleratorSurfactantEquation, acceleratorVar, (), None), (metalEquation, metalVar, (), None), (bulkAcceleratorEquation, bulkAcceleratorVar, (), GeneralSolver()), (bulkLevelerEquation, bulkLevelerVar, (), GeneralSolver())) narrowBandWidth = 20 * cellSize levelSetUpdateFrequency = int(0.7 * narrowBandWidth / cellSize / cflNumber / 2) totalTime = 0.0 if displayViewers: try: raise Exception from .mayaviSurfactantViewer import MayaviSurfactantViewer viewers = ( MayaviSurfactantViewer(distanceVar, acceleratorVar.interfaceVar, zoomFactor = 1e6, datamax=0.5, datamin=0.0, smooth = 1, title = 'accelerator coverage'), MayaviSurfactantViewer(distanceVar, levelerVar.interfaceVar, zoomFactor = 1e6, datamax=0.5, datamin=0.0, smooth = 1, title = 'leveler coverage')) except: class PlotVariable(CellVariable): def __init__(self, var = None, name = ''): CellVariable.__init__(self, mesh = mesh.fineMesh, name = name) self.var = self._requires(var) def _calcValue(self): return numerix.array(self.var(self.mesh.cellCenters)) viewers = (Viewer(PlotVariable(var=acceleratorVar.interfaceVar)), Viewer(PlotVariable(var=levelerVar.interfaceVar))) for step in range(numberOfSteps): if displayViewers: if step % displayRate == 0: for viewer in viewers: viewer.plot() if step % levelSetUpdateFrequency == 0: distanceVar.calcDistanceFunction() extensionVelocityVariable.setValue(depositionRateVariable) extOnInt = numerix.where(distanceVar.globalValue > 0, numerix.where(distanceVar.globalValue < 2 * cellSize, extensionVelocityVariable.globalValue, 0), 0) dt = cflNumber * cellSize / extOnInt.max() distanceVar.extendVariable(extensionVelocityVariable) for eqn, var, BCs, solver in eqnTuple: eqn.solve(var, boundaryConditions = BCs, dt = dt, solver=solver) totalTime += dt point = ((1.25e-08,), (3.125e-07,)) value = 2.02815779e-08 return abs(float(distanceVar(point, order=1)) - value) < cellSize / 10.0