def update_boundary_conditions(self): """ Update the boundary conditions for new wall location and new wall temperature. """ # Update the thermal boundary condition at the wall based on the current concentration Tf_wall_last = self.Tf_wall self.Tf = Tf_depression(self.C,linear=True)/abs(self.T_inf) self.C_wall = dolfin.project(self.C,self.sol_V).vector()[self.sol_idx_wall] Tf_wall_hold = Tf_depression(self.C_wall,linear=True)/abs(self.T_inf) self.Tf_wall = Tf_wall_last + (Tf_wall_hold-Tf_wall_last)*self.Tf_reg # Reset ice boundary condition self.bc_iWall = dolfin.DirichletBC(self.ice_V, self.Tf_wall, self.iWall) # Reset solution boundary condition self.bc_sWall = dolfin.DirichletBC(self.sol_V, self.Tf_wall, self.sWall)
def solve_molecular(self,rho_solute=const.rhom): """ Solve the molecular diffusion problem if 'solve_sol_mol' is not in the flags list this will be an instantaneous mixing problem. """ # Solve solution concentration # Diffusivity ramp = dolfin.Expression('x[0] > minR ? 100. : 1.',minR=np.log(self.Rstar)-0.5,degree=1) self.Dstar = dolfin.project(dolfin.Expression('ramp*diff_ratio*exp(-2.*x[0])',degree=1,ramp=ramp,diff_ratio=self.astar_i/self.Lewis),self.sol_V) # calculate solute flux (this is like the Stefan condition for the molecular diffusion problem Dwall = self.Dstar(self.sol_coords[self.sol_idx_wall]) self.solFlux = -(self.C_wall/Dwall)*(self.dR/self.dt) # Set the concentration for points that moved out of the grid to match the solute flux u0_c_hold = self.u0_c.vector()[:].copy() u0_c_hold[~self.sol_idx_extrapolate] = self.C_wall+self.solFlux*(np.exp(self.sol_coords[~self.sol_idx_extrapolate,0])-self.Rstar) u0_c_hold[u0_c_hold<0.]=0. self.u0_c.vector()[:] = u0_c_hold[:] # Variational Problem F_c = (self.u_s-self.u0_c)*self.v_s*dolfin.dx + \ self.dt*dolfin.inner(dolfin.grad(self.u_s), dolfin.grad(self.Dstar*self.v_s))*dolfin.dx - \ self.dt*self.solFlux*self.v_s*self.sds(1) F_c = dolfin.action(F_c,self.C) # First derivative J = dolfin.derivative(F_c, self.C, self.u_s) # handle the bounds lower = dolfin.project(dolfin.Constant(0.0),self.sol_V) upper = dolfin.project(dolfin.Constant(rho_solute),self.sol_V) # set bounds and solve snes_solver_parameters = {"nonlinear_solver": "snes", "snes_solver": {"linear_solver": "lu", "maximum_iterations": 20, "report": True, "error_on_nonconvergence": False}} problem = dolfin.NonlinearVariationalProblem(F_c, self.C, J=J) problem.set_bounds(lower, upper) solver = dolfin.NonlinearVariationalSolver(problem) solver.parameters.update(snes_solver_parameters) dolfin.info(solver.parameters, True) (iter, converged) = solver.solve() self.u0_c.assign(self.C) # Recalculate the freezing temperature self.Tf_last = self.Tf self.Tf = Tf_depression(self.C.vector()[self.sol_idx_wall,0],linear=True) # Get the updated solution properties self.rhos = dolfin.project(dolfin.Expression('C + rhow*(1.-C/rho_solute)', degree=1,C=self.C,rhow=const.rhow,rho_solute=rho_solute),self.sol_V) self.cs = dolfin.project(dolfin.Expression('ce*(C/rho_solute) + cw*(1.-C/rho_solute)', degree=1,C=self.C,cw=const.cw,ce=const.ce,rho_solute=rho_solute),self.sol_V) self.ks = dolfin.project(dolfin.Expression('ke*(C/rho_solute) + kw*(1.-C/rho_solute)', degree=1,C=self.C,kw=const.kw,ke=const.ke,rho_solute=rho_solute),self.sol_V) self.rhos_wall = self.rhos.vector()[self.sol_idx_wall] self.cs_wall = self.cs.vector()[self.sol_idx_wall] self.ks_wall = self.ks.vector()[self.sol_idx_wall]
def run(self,verbose=False,initialize_array=True): """ Iterate the model through the given time array. """ if initialize_array: self.r_ice_result = [np.exp(self.ice_coords[:,0])*self.R_melt] self.T_ice_result = [np.array(self.u0_i.vector()[:]*abs(self.T_inf))] self.r_sol_result = [np.exp(self.sol_coords[:,0])*self.R_melt] self.T_sol_result = [np.array(self.u0_s.vector()[:]*abs(self.T_inf))] self.Tf_result = [Tf_depression(np.array(self.u0_c.vector()[:]),linear=True)] for i,t in enumerate(self.ts[1:]): if verbose: print(round(t*self.t0/60.),end=' min, ') # --- Ethanol Injection --- # self.injection_energy_balance(self.source[i+1]) # --- Thermal Diffusion --- # self.update_boundary_conditions() self.solve_thermal() # --- Move the Mesh --- # self.move_wall() # break the loop if the hole is completely frozen if 'Frozen' in self.flags: print('Frozen Hole!') break # --- Molecular Diffusion --- # self.solve_molecular() # Save the new wall location self.Rstar = np.exp(self.ice_coords[self.ice_idx_wall,0]) # --- Export --- # if t in self.save_times: self.r_ice_result = np.append(self.r_ice_result,[np.exp(self.ice_coords[:,0])*self.R_melt],axis=0) self.T_ice_result = np.append(self.T_ice_result,[self.u0_i.vector()[:]*abs(self.T_inf)],axis=0) self.r_sol_result = np.append(self.r_sol_result,[np.exp(self.sol_coords[:,0])*self.R_melt],axis=0) self.T_sol_result = np.append(self.T_sol_result,[self.u0_s.vector()[:]*abs(self.T_inf)],axis=0) self.Tf_result = np.append(self.Tf_result,[Tf_depression(self.u0_c.vector()[:],linear=True)],axis=0)
def update_boundary_conditions(self, data_dir=None): """ Update the thermal boundary conditions for new wall location based on the current concentration. """ # Update the melting temperature self.Tf = Tf_depression(self.C, data_dir=data_dir) / abs(self.T_inf) self.Tf_wall = self.Tf # Reset ice boundary condition self.bc_iWall = dolfin.DirichletBC(self.ice_V, self.Tf_wall, self.iWall)
def get_initial_conditions(self, rho_solute=const.rhom, data_dir=None): """ Set the initial condition at the end of melting (melting can be solved analytically) """ # --- Initial states --- # # ice temperature self.u0_i = dolfin.Function(self.ice_V) T, lam, self.R_melt, self.t_melt = analyticalMelt( np.exp(self.ice_coords[:, 0]) * self.R_melt, self.T_inf, self.Q_initialize, R_target=self.R_melt) self.u0_i.vector()[:] = T / abs(self.T_inf) # --- Time Array --- # # Now that we have the melt-out time, we can define the time array self.ts = np.arange(self.t_melt, self.t_final + self.dt, self.dt) / self.t0 self.dt /= self.t0 # Define the antifreeze source self.source_timing = self.ts[np.argmin( abs(self.ts - self.source_timing / self.t0))] if 'gaussian_source' in self.flags: self.source_duration /= self.t0 self.source = self.source_mass_final / ( self.source_duration * np.sqrt(2. * np.pi)) * np.exp(-.5 * ( (self.ts - self.source_timing) / self.source_duration)**2.) else: self.source = np.zeros_like(self.ts) self.source[np.argmin( abs(self.ts - self.source_timing))] = self.source_mass_final / self.dt # --- Define the test and trial functions --- # self.u_i = dolfin.TrialFunction(self.ice_V) self.v_i = dolfin.TestFunction(self.ice_V) self.T_i = dolfin.Function(self.ice_V) self.C = self.C_init self.Tf_wall = Tf_depression(self.C, data_dir=data_dir) # Get the updated solution properties self.rhos = self.C + const.rhow * (1. - self.C / rho_solute) self.cs = (self.C / rho_solute) * const.ce + (1. - (self.C / rho_solute)) * const.cw self.ks = (self.C / rho_solute) * const.ke + (1. - (self.C / rho_solute)) * const.kw self.rhos_wall, self.cs_wall, self.ks_wall = self.rhos, self.cs, self.ks self.flags.append('get_ic')
def get_initial_conditions(self,rho_solute=const.rhom): """ Set the initial condition at the end of melting (melting can be solved analytically """ # --- Initial states --- # # ice temperature self.u0_i = dolfin.Function(self.ice_V) T,lam,self.R_melt,self.t_melt = analyticalMelt(np.exp(self.ice_coords[:,0])*self.R_melt,self.T_inf,self.Q_initialize,R_target=self.R_melt) self.u0_i.vector()[:] = T/abs(self.T_inf) # solution temperature self.u0_s = dolfin.Function(self.sol_V) T,lam,self.R_melt,self.t_melt = analyticalMelt(np.exp(self.sol_coords[:,0])*self.R_melt,self.T_inf,self.Q_initialize,R_target=self.R_melt) self.u0_s.vector()[:] = T/abs(self.T_inf) # solution concentration self.u0_c = dolfin.interpolate(dolfin.Constant(self.C_init),self.sol_V) # --- Time Array --- # # Now that we have the melt-out time, we can define the time array self.ts = np.arange(self.t_melt,self.t_final+self.dt,self.dt)/self.t0 self.dt /= self.t0 # Define the ethanol source self.source_timing = self.ts[np.argmin(abs(self.ts-self.source_timing/self.t0))] if 'gaussian_source' in self.flags: self.source_duration /= self.t0 self.source = self.source_mass_final/(self.source_duration*np.sqrt(np.pi))*np.exp(-((self.ts-self.source_timing)/self.source_duration)**2.) else: self.source = np.zeros_like(self.ts) self.source[np.argmin(abs(self.ts-self.source_timing))] = self.source_mass_final/self.dt # --- Define the test and trial functions --- # self.u_i = dolfin.TrialFunction(self.ice_V) self.v_i = dolfin.TestFunction(self.ice_V) self.T_i = dolfin.Function(self.ice_V) self.u_s = dolfin.TrialFunction(self.sol_V) self.v_s = dolfin.TestFunction(self.sol_V) self.T_s = dolfin.Function(self.sol_V) self.C = dolfin.Function(self.sol_V) self.Tf = Tf_depression(self.C,linear=True)/abs(self.T_inf) self.Tf_wall = dolfin.project(self.Tf,self.sol_V).vector()[self.sol_idx_wall] # Get the updated solution properties self.rhos = dolfin.project(dolfin.Expression('C + rhow*(1.-C/rho)',degree=1,C=self.C,rhow=const.rhow,rho=rho_solute),self.sol_V) self.cs = dolfin.project(dolfin.Expression('ce*(C/rho) + cw*(1.-C/rho)',degree=1,C=self.C,cw=const.cw,ce=const.ce,rho=rho_solute),self.sol_V) self.ks = dolfin.project(dolfin.Expression('ke*(C/rho) + kw*(1.-C/rho)',degree=1,C=self.C,kw=const.kw,ke=const.ke,rho=rho_solute),self.sol_V) self.rhos_wall = self.rhos.vector()[self.sol_idx_wall] self.cs_wall = self.cs.vector()[self.sol_idx_wall] self.ks_wall = self.ks.vector()[self.sol_idx_wall] self.flags.append('get_ic')
def injection_energy_balance(self, source, solute='methanol', data_dir=None): """ Update the concentration and temperature at the time of injection. """ # Calculate the injection source actually adds to total concentration C_inject = source * self.dt / (np.pi * (self.Rstar * self.R_melt)**2.) # Hard set on concentration (assume that it mixes quickly) self.C += C_inject # enthalpy of mixing, always exothermic so gives off energy (J m-3) H, phi = Hmix(C_inject, solute=solute) # put this added energy toward uniformly warming the solution phi_dT = -phi / (self.rhos * self.cs) / abs(self.T_inf) # Hard set on solution temperature (assume that the mixing energy spreads evenly) self.Tf = Tf_depression(self.C, data_dir=data_dir) / abs(self.T_inf) # Bump the last freezing temperature up # this way the mixing enthalpy is accounted for in wall movement self.Tf_last = (Tf_depression(self.C - C_inject, data_dir=data_dir) + phi_dT) / abs(self.T_inf)
def solve_molecular(self, rho_solute=const.rhom, data_dir=None): """ Update the solution concentration depending on how far the hole wall moved. """ # Recalculate the solution concentration after wall moves self.C *= (self.Rstar / np.exp(self.ice_coords[self.ice_idx_wall, 0]))**2. # Recalculate the freezing temperature self.Tf_last = self.Tf self.Tf = Tf_depression(self.C, data_dir=data_dir) # Get the updated solution properties self.rhos = self.C + const.rhow * (1. - self.C / rho_solute) self.cs = (self.C / rho_solute) * const.ce + (1. - (self.C / rho_solute)) * const.cw self.ks = (self.C / rho_solute) * const.ke + (1. - (self.C / rho_solute)) * const.kw self.rhos_wall, self.cs_wall, self.ks_wall = self.rhos, self.cs, self.ks
def initiate_solution_diffusion(mod,rho_solute=const.rhom): """ Before the equations can be solved for the liquid solution: initial conditions and solution properties need to be setup update domain time array and ethanol source timing initial conditions variational problem solution properties boundary conditions To be run once after get_initial_conditions() and get_boundary_conditions() """ # --- Get new domain --- # mod.sol_mesh = dolfin.IntervalMesh(mod.n,mod.Rstar_center,mod.Rstar) mod.sol_V = dolfin.FunctionSpace(mod.sol_mesh,'CG',1) mod.sol_mesh.coordinates()[:] = np.log(mod.sol_mesh.coordinates()) mod.sol_coords = mod.sol_V.tabulate_dof_coordinates().copy() mod.sol_idx_wall = np.argmax(mod.sol_coords) # --- Time array --- # # Now that we have the melt-out time, we can define the time array mod.ts = np.arange(mod.t_init,mod.t_final+mod.dt,mod.dt)/mod.t0 mod.dt /= mod.t0 # Define the ethanol source mod.source_timing = mod.source_timing/mod.t0 mod.source_duration /= mod.t0 mod.source = mod.source_mass_final/(mod.source_duration*np.sqrt(np.pi))*np.exp(-((mod.ts-mod.source_timing)/mod.source_duration)**2.) # --- Set initial conditions --- # # solution temperature mod.u0_s = dolfin.Function(mod.sol_V) mod.u0_s.vector()[:] = mod.Tf_wall # solution concentration mod.u0_c = dolfin.project(dolfin.Constant(mod.C),mod.sol_V) # --- Set up the variational Problem --- # mod.u_s = dolfin.TrialFunction(mod.sol_V) mod.v_s = dolfin.TestFunction(mod.sol_V) mod.T_s = dolfin.Function(mod.sol_V) mod.C = dolfin.project(dolfin.Constant(mod.C),mod.sol_V) mod.Tf = Tf_depression(mod.C,linear=True)/abs(mod.T_inf) mod.Tf_wall = dolfin.project(mod.Tf,mod.sol_V).vector()[mod.sol_idx_wall] # --- Get the solution properties --- # mod.rhos = dolfin.project(dolfin.Expression('C + rhow*(1.-C/rho_solute)',degree=1,C=mod.C,rhow=const.rhow,rho_solute=rho_solute),mod.sol_V) mod.cs = dolfin.project(dolfin.Expression('ce*(C/rho_solute) + cw*(1.-C/rho_solute)',degree=1,C=mod.C,cw=const.cw,ce=const.ce,rho_solute=rho_solute),mod.sol_V) mod.ks = dolfin.project(dolfin.Expression('ke*(C/rho_solute) + kw*(1.-C/rho_solute)',degree=1,C=mod.C,kw=const.kw,ke=const.ke,rho_solute=rho_solute),mod.sol_V) mod.rhos_wall = mod.rhos.vector()[mod.sol_idx_wall] mod.cs_wall = mod.cs.vector()[mod.sol_idx_wall] mod.ks_wall = mod.ks.vector()[mod.sol_idx_wall] # --- Boundary Conditions --- # # Liquid boundary condition at hole wall (same temperature as ice) class sWall(dolfin.SubDomain): def inside(self, x, on_boundary): return on_boundary and x[0] > mod.sol_coords[mod.sol_idx_wall] - const.tol # center flux class center(dolfin.SubDomain): def inside(self, x, on_boundary): return on_boundary and x[0] < mod.w_center + const.tol # Initialize boundary classes mod.sWall = sWall() mod.center = center() # This will be used in the boundary condition for mass diffusion mod.boundaries = dolfin.MeshFunction("size_t", mod.sol_mesh, 0) # this index 0 is an alternative to the command boundaries.set_all(0) mod.sWall.mark(mod.boundaries, 1) mod.center.mark(mod.boundaries, 2) mod.sds = dolfin.Measure("ds")(subdomain_data=mod.boundaries)