def __init__(self, eqn, domain, domain_params, bc_params, solver_params, settings): ''' Reactive transport class for carbonation A Lattice Boltzmann method based reactive transport model \ where reactions are computed using geochemical solver PHREEQC ''' if (settings['diffusivity']['type'] == 'fixed'): print("newMlvl") eqn = 'MultilevelAdvectionDiffusion2' #solver_params['tauref'] = 1 #domain_params['Deref'] = settings['diffusivity']['D_border'] #solver_params['tauref'] = 0.5*np.max(settings['Dref'])/domain_params['Deref']+0.5#5.5 for 1e-10 self.auto_time_step = solver_params.get('auto_time_step', True) self.phrqc = Phrqc(domain, domain_params, bc_params, solver_params) components = self.phrqc.components bc = self.phrqc.boundary_conditions init_c = self.phrqc.initial_conditions for name in components: if name not in domain_params: domain_params[name] = {} domain_params[name]['c'] = init_c[name] for name in bc: for comp in components: if comp not in bc_params: bc_params[comp] = {} bc_params[comp][name] = ['c', bc[name][comp]] self.fluid = Multicomponent(eqn, components, domain, domain_params, bc_params, solver_params) self.solid = Solid(domain, domain_params, solver_params) self.ptype = 'CSH' if hasattr(self.fluid, 'Si') else 'CH' self.app_tort_degree = settings['app_tort']['degree'] self.update_solid_params() self.nodetype = deepcopy(domain.nodetype) self.apply_settings(settings) self.update_nodetype() self.update_border_and_interface(self.nodetype) #self.fluid.call("_set_relaxation_params") self.solid.prev_border = deepcopy(self.solid.border) self.Dref = settings['Dref']
def __init__(self, eqn, domain, domain_params, bc_params, solver_params, settings): ''' Reactive transport class for carbonation A Lattice Boltzmann method based reactive transport model \ where reactions are computed using geochemical solver PHREEQC ''' self.auto_time_step = solver_params.get('auto_time_step', True) self.phrqc = phrqc.CarbonationPhrqc(domain, domain_params, bc_params, solver_params) components = self.phrqc.components bc = self.phrqc.boundary_conditions init_c = self.phrqc.initial_conditions for name in components: if name not in domain_params: domain_params[name] = {} domain_params[name]['c'] = init_c[name] for name in bc: for comp in components: if comp not in bc_params: bc_params[comp] = {} bc_params[comp][name] = ['c', bc[name][comp]] self.fluid = Multicomponent(eqn, components, domain, domain_params, bc_params, solver_params) self.solid = Solid(domain, domain_params, solver_params) self.ptype = 'CSH' if hasattr(self.fluid, 'Si') else 'CH' self.nodetype = deepcopy(domain.nodetype) self.apply_settings(settings) self.update_volume() self.update_porosity() self.update_app_tort() self.update_init_phrqc() self.update_bc(settings) self.update_phases() self.update_nodetype() self.update_target_SI()
class CarbonationRT(PhrqcReactiveTransport): def __init__(self, eqn, domain, domain_params, bc_params, solver_params, settings): ''' Reactive transport class for carbonation A Lattice Boltzmann method based reactive transport model \ where reactions are computed using geochemical solver PHREEQC ''' self.auto_time_step = solver_params.get('auto_time_step', True) self.phrqc = phrqc.CarbonationPhrqc(domain, domain_params, bc_params, solver_params) components = self.phrqc.components bc = self.phrqc.boundary_conditions init_c = self.phrqc.initial_conditions for name in components: if name not in domain_params: domain_params[name] = {} domain_params[name]['c'] = init_c[name] for name in bc: for comp in components: if comp not in bc_params: bc_params[comp] = {} bc_params[comp][name] = ['c', bc[name][comp]] self.fluid = Multicomponent(eqn, components, domain, domain_params, bc_params, solver_params) self.solid = Solid(domain, domain_params, solver_params) self.ptype = 'CSH' if hasattr(self.fluid, 'Si') else 'CH' self.nodetype = deepcopy(domain.nodetype) self.apply_settings(settings) self.update_volume() self.update_porosity() self.update_app_tort() self.update_init_phrqc() self.update_bc(settings) self.update_phases() self.update_nodetype() self.update_target_SI() def advance(self): self.update_diffusivity() print(self.iters, self.dt) print('self.fluid.Ca.D0[1,:]', self.fluid.Ca.D0[1, :]) print('solid mass calcite', self.solid.phaseqty[0][1, :]) print('solid mass CH', self.solid.phaseqty[1][1, :]) print(self.fluid.Ca.cphi) print('self.solid.poros[1,:]', self.solid.poros[1, :]) print('self.solid.tort[1,:]', self.solid.app_tort[1, :]) print('tau[1,:]', self.fluid.Ca.tau[1, :]) print("self.iters=:", self.iters) print("befor LB [Ca]:", self.fluid.Ca.c[1, :]) print("before LB [C]:", self.fluid.C.c[1, :]) print("ss[Ca]:", self.fluid.Ca.ss[1, :]) print("----------------------------------") self.fluid.call('advance') self.update_target_SI() self.update_source() self.update_phases() self.update_velocity() self.update_nodetype() #%% UPDATES def update_volume(self): vol = np.zeros(self.solid.shape) phase_list = deepcopy(self.solid.diffusive_phase_list) for num, phase in enumerate(phase_list, start=1): val = getattr(self.solid, phase) vol += val.c * self.solid.mvol[num - 1] self.solid.vol = vol def update_porosity(self): self.solid.poros = 1. - self.solid.vol / self.solid.voxel_vol if np.any(self.solid.poros <= 1e-10): sys.exit('Negative or zero porosity') def update_app_tort(self, ): self.solid.app_tort = 1. * self.solid.poros**self.solid.app_tort_degree def update_boundary_cells(self): nodes = np.zeros(np.shape(self.nodetype)) nodes[0, :] = ct.Type.SOLID nodes[-1, :] = ct.Type.SOLID nodes[:, 0] = ct.Type.SOLID nodes[:, -1] = ct.Type.SOLID return nodes def update_bc(self, settings): ptype = self.ptype for key in self.phrqc.boundary_solution_labels: self.fluid.Ca.bc[key] = ['flux', 0.0] self.fluid.Ca._bc[key + 'bc'] = 'flux' if (settings['bc']['type'] == 'pco2'): self.fluid.C.bc[key] = ['flux', 0.0] self.fluid.C._bc[key + 'bc'] = 'flux' self.fluid.H.bc[key] = ['flux', 0.0] self.fluid.H._bc[key + 'bc'] = 'flux' self.fluid.O.bc[key] = ['flux', 0.0] self.fluid.O._bc[key + 'bc'] = 'flux' if (ptype == 'CSH'): self.fluid.Si.bc[key] = ['flux', 0.0] self.fluid.Si._bc[key + 'bc'] = 'flux' def update_init_phrqc(self): self.phrqc.boundcells = self.update_boundary_cells() if 'portlandite' in self.solid.diffusive_phase_list: self.phrqc.init_port = self.solid.portlandite.c > 0 if self.ptype == 'CSH': self.phrqc.init_csh = np.logical_or( np.logical_or(self.solid.CSHQ_TobD.c > 0, self.solid.CSHQ_TobH.c > 0), np.logical_or(self.solid.CSHQ_JenD.c > 0, self.solid.CSHQ_JenH.c > 0)) if 'calcite' in self.solid.diffusive_phase_list: self.phrqc.is_calc = self.solid.calcite.c > 0 self.phrqc._target_SI = np.zeros(self.solid.shape) self.phrqc.nodetype = deepcopy(self.nodetype) if self.solid.nphases > 0: phaseqty = self.solid.phaseqty_for_phrqc() self.phrqc.modify_solid_phases(phaseqty) def update_phrqc(self): self.phrqc.poros = deepcopy(self.solid.poros) self.phrqc.is_calc = self.solid.calcite.c > 0 #TODO check if necessary - move to update phrqc function if (self.phrqc.precipitation == 'interface'): if (self.phrqc.active != 'interface'): self.phrqc.nodetype = deepcopy(self.nodetype) def apply_settings(self, settings): self.settings = settings self.Dref = settings['Dref'] self.dx = settings['dx'] self.solid.app_tort_degree = settings['app_tort']['degree'] self.phrqc.pinput = settings['bc'] self.phrqc.active = settings['active_nodes'] self.phrqc.precipitation = settings['precipitation'] self.phrqc.pcs = settings['pcs_mode']['pcs'] self.phrqc.phrqc_flags['only_mineral'] = False self.phrqc.phrqc_flags['only_interface'] = False self.phrqc.phrqc_flags['only_fluid'] = False self.phrqc.phrqc_flags['smart_run'] = False if (settings['active_nodes'] == 'all'): self.phrqc.nodetype = deepcopy(self.nodetype) elif (settings['active_nodes'] == 'interface'): self.phrqc.phrqc_flags['only_interface'] = True elif (settings['active_nodes'] == 'smart'): self.phrqc.phrqc_flags['smart_run'] = True elif (settings['active_nodes'] == 'mineral'): self.phrqc.phrqc_flags['only_mineral'] = True elif (settings['active_nodes'] == 'fluid'): self.phrqc.phrqc_flags['only_fluid'] = True else: sys.exit() if ('pco2' in settings) and (settings['bc']['type'] == 'pco2'): self.phrqc.ppt = settings['pco2'] else: self.phrqc.ppt = False def update_source(self): c = deepcopy(self.fluid.get_attr('c')) phaseqty = self.solid.phaseqty_for_phrqc() phase_list = deepcopy(self.solid.diffusive_phase_list) for num, phase in enumerate(phase_list, start=1): phaseqty[phase] = deepcopy(self.solid._diffusive_phaseqty[num - 1]) if (self.settings['dissolution'] == 'subgrid'): phaseqty['portlandite'][np.where(self.solid.border)] = 0 #phaseqty['portlandite'] = np.zeros(phaseqty['portlandite'].shape) print("self.phrqc.iters=", self.phrqc.iters) print("after LB [Ca]:", self.fluid.Ca.c[1, :]) print("after LB [C]:", self.fluid.C.c[1, :]) print("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^") self.phrqc.modify_solid_phases(phaseqty) ss = self.phrqc.modify_solution(c, self.dt, self.solid.nodetype) print(ss.keys()) print("after reaction self.phrqc.selected_out()['poros'][1,:]", self.phrqc.selected_output()['poros'][1, :]) print("after reaction self.phrqc.selected_out()['Ca'][1,:]", self.phrqc.selected_output()['Ca'][1, :]) print("after reaction self.phrqc.selected_out()['CH'][1,:]", self.phrqc.selected_output()['portlandite'][1, :]) print("after reaction self.phrqc.selected_out()['C'][1,:]", self.phrqc.selected_output()['C'][1, :]) print("after reaction self.phrqc.selected_out()['CC'][1,:]", self.phrqc.selected_output()['calcite'][1, :]) print("+++++++++++++++++++++++++++++++++++++++++") if (self.settings['dissolution'] == 'multilevel'): pqty = self.solid.update(self.phrqc.dphases) else: if self.iters > 1: pqty = self.solid.update(self.phrqc.dphases) self.update_solid_params() ss = self.update_border_solution(c, ss) self.update_solid_params() self.update_target_SI() self.update_phrqc() ss = self.update_no_flux(ss) self.fluid.set_attr('ss', ss) print("at last self.phrqc.selected_out()['Ca'][1,:]", self.phrqc.selected_output()['Ca'][1, :]) print("at last self.phrqc.selected_out()['CH'][1,:]", self.phrqc.selected_output()['portlandite'][1, :]) print("at last self.phrqc.selected_out()['C'][1,:]", self.phrqc.selected_output()['C'][1, :]) print("at last self.phrqc.selected_out()['CC'][1,:]", self.phrqc.selected_output()['calcite'][1, :]) def update_border_solution(self, c, ss): #poros = self.solid.poros# phrqc_poros = self.solid.poros #self.phrqc.selected_output()['poros'] # fraction = self.settings['subgrid']['fraction'] result = {} by = np.where(self.solid.border)[0] bx = np.where(self.solid.border)[1] bf = np.where(self.solid.border.flatten())[0] for i in np.arange(0, np.sum(self.solid.border)): if fraction > 0: cell_m = bf[i] + 1 result = self.update_equilibrium( result, cell_m, self.solid.portlandite.c[by[i], bx[i]], phrqc_poros[by[i], bx[i]], fraction) print('border changing results:', result) ssnew = {} for name in self.phrqc.components: ssnew[name] = (result[str(cell_m)][name] - c[name][by[i], bx[i]]) / self.dt ssnew[name] *= phrqc_poros[by[i], bx[i]] ss[name][by[i], bx[i]] = ssnew[name] for i in np.arange(0, np.sum(self.solid.border)): self.solid.portlandite.c[by[i], bx[i]] = result[str(bf[i] + 1)]['portlandite_m'] #self.solid.calcite.c[by[i], bx[i]] = result[str(bf[i]+1)]['calcite_m'] return ss def update_equilibrium(self, result, n_ch, m_ch, porosity, fraction=1): print("now we are in border mixing!!!!!!!!!!!!!!!") print(fraction) ncell = 123456 fract = fraction / porosity if fract > 1: fract = 1 #print(fract) modify_str = [] modify_str.append("EQUILIBRIUM_PHASES %i" % ncell) modify_str.append("Portlandite 0 %.20e dissolve only" % (m_ch)) modify_str.append("END") modify_str.append('MIX %i' % ncell) modify_str.append('%i %.20e' % (n_ch, fract)) #modify_str.append('%i 1' %n_int) modify_str.append('SAVE solution %i' % ncell) modify_str.append("END") modify_str.append('USE solution %i' % ncell) modify_str.append('USE equilibrium_phase %i' % ncell) modify_str.append('SAVE solution %i' % ncell) modify_str.append("END") modify_str.append("END") modify_str = '\n'.join(modify_str) self.phrqc.IPhreeqc.RunString(modify_str) output = self.phrqc.IPhreeqc.GetSelectedOutputArray() #print(modify_str) #print(output) print('1st mixing', output) port = output[2][10] modify_str = [] modify_str.append("EQUILIBRIUM_PHASES %i" % n_ch) modify_str.append("Portlandite 0 %.20e" % (0)) # dissolve only modify_str.append("END") modify_str.append('USE equilibrium_phase %i' % n_ch) modify_str.append('MIX %i' % ncell) modify_str.append('%i 1' % ncell) modify_str.append( '%i %.20e' % (n_ch, 1. - fract)) #modify_str.append('%i 0' %n_int) modify_str.append('SAVE solution %i' % (n_ch)) modify_str.append('SAVE equilibrium_phase %i' % (n_ch)) modify_str.append("END") modify_str = '\n'.join(modify_str) self.phrqc.IPhreeqc.RunString(modify_str) output = self.phrqc.IPhreeqc.GetSelectedOutputArray() print('2nd mixing', output) #print(modify_str) #print(output) comp = {} comp['portlandite_m'] = port comp['C'] = output[1][6] comp['Ca'] = output[1][7] comp['H'] = (output[1][8] - self.phrqc.H_norm) comp['O'] = (output[1][9] - self.phrqc.O_norm) result[str(n_ch)] = comp return (result) def update_solid_params(self): ''' Calculates porosity, apparent tortuosity, volume and change in volume last time step ''' self.solid.prev_vol = deepcopy(self.solid.vol) self.update_volume() self.update_porosity() self.update_app_tort() if (self.settings['velocity'] == True): self.solid.dvol = self.solid.vol - self.solid.prev_vol self.fluid.call( 'update_transport_params', self.solid.poros, #move to solid params function self.solid.app_tort, self.auto_time_step) def update_phases(self, thres=1.0e-3): ch = self.solid.portlandite.c * self.solid.portlandite.mvol cc = self.solid.calcite.c * self.solid.calcite.mvol tot = cc + ch is_cl = (self.nodetype == 1) #clinker is_liq = (tot < thres) * ((self.nodetype == -2) + (self.nodetype == -1)) phases = is_cl * 2 + is_liq * (-1) if (self.ptype == 'CH'): is_cc = (cc == np.maximum.reduce([cc, ch])) * ~is_liq * ~is_cl is_ch = (ch == np.maximum.reduce([cc, ch])) * ~is_liq * ~is_cl phases += is_ch * (-10) + is_cc * (-15) if (self.ptype == 'CSH'): csh = self.solid.CSHQ_TobH.c * self.solid.CSHQ_TobH.mvol + \ self.solid.CSHQ_TobD.c * self.solid.CSHQ_TobH.mvol + \ self.solid.CSHQ_JenH.c * self.solid.CSHQ_TobH.mvol + \ self.solid.CSHQ_JenD.c * self.solid.CSHQ_TobH.mvol tot += csh is_cc = (cc == np.maximum.reduce([cc, ch, csh])) * ~is_liq * ~is_cl is_ch = (ch == np.maximum.reduce([cc, ch, csh])) * ~is_liq * ~is_cl is_csh = (csh == np.maximum.reduce([cc, ch, csh ])) * ~is_liq * ~is_cl phases += is_csh * (-5) + is_ch * (-10) + is_cc * (-15) self.solid.csh = csh self.solid.phases = phases #self.c = [] self.solid.prev_calc = deepcopy(self.solid.calcite.c > 1e-4) self.solid.prev_port = deepcopy(self.solid.portlandite.c > 0) def update_nodetype(self): ''' find neighbous of the multilevel cells ''' prev_nodetype = deepcopy(self.nodetype) is_port = (self.solid.portlandite.c > 0) is_calc = (self.solid.calcite.c > 0) #is_clinker = self.solid.nodetype == ct.Type.CLINKER is_solid = self.solid.nodetype == ct.Type.SOLID is_critical = np.zeros(np.shape(is_port)) is_interface = np.zeros(np.shape(is_port)) #calc_c = self.phrqc.solid_phase_conc['calcite'] if self.ptype == 'CH': is_liquid = (~is_port) if (self.iters > 1): is_critical = (self.solid.pore_size <= self.solid.threshold_pore_size) & is_calc & ( ~is_port) is_liquid = (~is_critical) & (~is_port) & (~is_solid) & ( ~is_calc) #&((prev_nodetype==-1)|(prev_nodetype==-2)) is_interface = (~is_critical) & (~is_port) & (~is_solid) & ( ~is_liquid) self.solid.nodetype = ct.Type.LIQUID * is_liquid + \ ct.Type.MULTILEVEL * is_critical + \ ct.Type.MULTILEVEL * is_port +\ ct.Type.INTERFACE * is_interface + \ ct.Type.SOLID * is_solid if self.ptype == 'CSH': is_csh= (self.solid.CSHQ_JenD.c>0) | (self.solid.CSHQ_JenH.c>0) | \ (self.solid.CSHQ_TobD.c>0) |(self.solid.CSHQ_TobH.c>0) &(~is_port) is_liquid = (~is_port) & (~is_csh) if (self.iters > 1): is_critical = (self.solid.pore_size <= self.solid.threshold_pore_size) & is_calc & ( ~is_port) is_liquid = (~is_critical) & (~is_port) & (~is_solid) & ( ~is_calc) & (~is_csh ) #&((prev_nodetype==-1)|(prev_nodetype==-2)) is_interface = (~is_critical) & (~is_port) & (~is_solid) & ( ~is_liquid) & (~is_csh) self.solid.nodetype = ct.Type.LIQUID * is_liquid + \ ct.Type.MULTILEVEL * is_critical + \ ct.Type.MULTILEVEL * is_port +\ ct.Type.MULTILEVEL * is_csh +\ ct.Type.INTERFACE * is_interface + \ ct.Type.SOLID * is_solid yantra._solvers.update2d.reassign_mlvl(self.solid.nodetype) self.solid.nodetype[prev_nodetype == ct.Type.SOLID] = ct.Type.SOLID yantra._solvers.update2d.reassign_mlvl_solid(self.solid.nodetype) self.solid.prev_calc_c = deepcopy( self.phrqc.solid_phase_conc['calcite']) self.nodetype = self.solid.nodetype self.update_border() self.fluid.set_attr('nodetype', self.solid.nodetype, component_dict=False) def update_diffusivity(self): Dref = self.Dref cc = self.settings['diffusivity']['CC'] ch = self.settings['diffusivity']['CH'] D_border = self.Dref if ('border' in self.settings['diffusivity']): D_border = self.settings['diffusivity']['border'] is_border = self.solid.border is_port = (self.solid.portlandite.c > 0) & (~is_border) is_calc = np.logical_and(self.solid.calcite.c > 0, ~is_port) #is_calc = np.logical_and(is_calc,~is_border) is_liquid = np.logical_and(~is_port, ~is_calc) is_liquid = np.logical_and(is_liquid, ~is_border) D_archie = Dref * self.solid.poros * self.solid.app_tort D_CC = D_archie D_CH = D_archie if (cc[0] == 'const'): D_CC = cc[1] * np.ones(D_CC.shape) elif (cc[0] == 'inverse'): mineral = self.solid.calcite.c * self.solid.calcite.mvol #D_CC =np.nan_to_num(1./((1-mineral)/Dref/self.solid.poros/self.solid.app_tort + mineral/cc[1]), Dref) D_CC = np.nan_to_num(1. / ((1 - mineral) / Dref + mineral / cc[1]), Dref) if (ch[0] == 'const'): D_CH = ch[1] * np.ones(D_CH.shape) elif (ch[0] == 'inverse'): mineral = self.solid.vol D_CH = np.nan_to_num( 1. / ((1 - mineral) / Dref / self.solid.poros / self.solid.app_tort + mineral / ch[1]), Dref) De = D_CH * is_port + D_CC * is_calc + D_CC * is_border + Dref * is_liquid self.fluid.set_attr('D0', De, component_dict=False) self.fluid.set_attr('Deref', np.max(De), component_dict=False) self.fluid.call("_set_relaxation_params") def update_velocity(self): if (self.settings['velocity']): is_solid = self.solid.nodetype >= 1 not_solid = ~is_solid neighbors = np.roll(not_solid, shift = 1, axis= 1).astype(int) + \ np.roll(not_solid, shift = -1, axis= 1).astype(int) +\ np.roll(not_solid, shift = 1, axis= 0).astype(int) +\ np.roll(not_solid, shift = -1, axis= 0).astype(int) self.fluid.u = self.solid.dvol / ( neighbors + 1 * (neighbors == 0)) * not_solid * self.solid.poros ux = (np.roll(self.fluid.u,1,1) - np.roll(self.fluid.u,-1,1))* \ not_solid uy = (np.roll(self.fluid.u,1,0) - np.roll(self.fluid.u,-1,0))* \ not_solid velocity = np.array((uy, ux)) velocity = {} for name in self.fluid.components: velocity[name] = np.array((ux, uy)) self.fluid.set_attr('u', velocity) else: pass def update_target_SI(self): self.solid.vol_ch = self.volume_CH() self.solid.vol_cc = self.volume_CC() if (self.settings['pcs_mode']['pores'] == 'cylinder'): #TODO check this case if (self.iters <= 1): self.solid.threshold_pore_size = self.settings['pcs_mode'][ 'crystal_size'] self.solid.pore_length = self.settings['pcs_mode'][ 'crystal_size'] self.solid.pore_amount = self.pore_amount() self.solid.free_vol_cc = (self.solid.voxel_vol - self.solid.vol_ch ) * self.dx**3 #self.free_volume() self.solid.pore_size = self.pore_size() self.solid.pore_volume_cc = self.pore_volume_CC() elif (self.settings['pcs_mode']['pores'] == 'block'): if (self.iters < 1): self.solid.threshold_crystal = self.settings['pcs_mode'][ 'crystal_size'] #default crystal size self.solid.threshold_pore_size = self.settings['pcs_mode'][ 'pore_size'] self.solid.threshold_distance = self.solid.threshold_pore_size * 2 self.solid.block_size = self.block_size() self.solid.free_vol_cc = ( self.solid.voxel_vol - self.solid.vol_ch ) * self.dx**3 #self.solid._poros * self.dx**3#self.get_pore_volume(self) self.solid.ncrystals = self.block_amount() self.solid.crystal_size = self.crystal_size() self.solid.distance = self.distance() self.solid.pore_size = self.pore_size() else: pass self.solid.target_SI = self.target_SI() self.phrqc._target_SI = self.solid.target_SI def update_no_flux(self, ss): ss['Ca'] = ss['Ca'] * (self.phrqc.boundcells == 0) + 0 * (self.phrqc.boundcells == 1) if (self.ptype == 'CSH'): ss['Si'] = ss['Si'] * (self.phrqc.boundcells == 0) + 0 * (self.phrqc.boundcells == 1) return (ss) def update_border(self): is_port = (self.solid.portlandite.c > 0) is_mineral = is_port | (self.solid.nodetype == ct.Type.SOLID) val = -16 temp = deepcopy(self.solid.nodetype) temp = temp * (~is_mineral) + val * is_mineral rolled = np.roll(temp, -1, axis = 1)/4+np.roll(temp, 1, axis = 1)/4 +\ np.roll(temp, -1, axis = 0)/4+np.roll(temp, 1, axis = 0)/4 border = (rolled != val) & is_port self.solid.border = border #%% VOLUMES def volume_CH(self): CH_vol = self.solid.portlandite.c * self.solid.portlandite.mvol return CH_vol def volume_CC(self): CC_vol = self.solid.calcite.c * self.solid.calcite.mvol return CC_vol def volume_CSH(self): CSH_vol = self.solid.CSHQ_JenD.c * self.solid.CSHQ_JenD.mvol +\ self.solid.CSHQ_JenH.c * self.solid.CSHQ_JenH.mvol +\ self.solid.CSHQ_TobD.c * self.solid.CSHQ_TobD.mvol +\ self.solid.CSHQ_TobH.c * self.solid.CSHQ_TobH.mvol return CSH_vol def pore_volume_CC(self): v = 0 if (self.settings['pcs_mode']['pores'] == 'cylinder'): v = self.solid.pore_amount * np.pi * self.solid.pore_size**2 * \ self.solid.pore_length #*2 else: v = self.free_volume() return v def free_volume(self): v = self.solid.voxel_vol - self.solid.vol_ch return v #%% PORE PROPERTIES def pore_amount(self, ptype='CH'): ''' Return matrix of pore amounts per each cell ''' vol = self.free_volume() n = self.settings['pcs_mode'][ 'pore_density'] * vol #* np.ones(np.shape(pore_vol)) n[n < 1] = 1 return n @staticmethod def get_cylinder_radius(pore_vol, pore_num, pore_length, dx): ''' Formula for cylinder pore radius Pore volume is total free volume or porosity ''' r = (pore_vol * dx**3 / (pore_num * np.pi * pore_length))**(1. / 2.) return r def pore_size(self): ''' Return matrix of pore radiuses per each cell ''' pore_size = np.ones(np.shape(self.solid.shape)) if (self.settings['pcs_mode']['pores'] == 'cylinder'): pore_size = self.get_cylinder_radius( self.solid.poros, self.solid.pore_amount, #self.settings['pcs']['pore_density'] self.solid.pore_length, self.dx) #pore_size[pore_size>self.solid.threshold_pore_size ] = self.solid.threshold_pore_size else: pore_size = self.solid.distance / 2. return pore_size def threshold_pore_size(self): si = self.settings['pcs_mode']['threshold_SI'] omega = 10.**si ps = (-1) * self.settings['si_params']['mvol'] *\ np.cos(np.pi*self.settings['si_params']['angle'])*2*\ self.settings['pcs_mode']['iene'] /\ self.settings['pcs_mode']['R'] /\ self.settings['pcs_mode']['T'] / np.log(omega) return ps def block_size(self): return self.solid.threshold_crystal + self.solid.threshold_distance def block_amount(self): v_cp = (self.solid.threshold_crystal + self.solid.threshold_distance)**3 N = self.solid.free_vol_cc / v_cp return N def crystal_size(self): return (self.solid.vol_cc / self.solid.ncrystals)**(1. / 3.) * self.dx def distance(self): return (self.solid.block_size - self.solid.crystal_size) def block_porosity(self): v_cp = ((self.solid.crystal_size + self.solid.distance) / self.dx)**3 v_c = (self.solid.crystal_size + self.dx)**3 p = 1 - v_c / v_cp return p #%% SATURATION INDEX def target_SI(self): pore_size = self.solid.pore_size si = self.saturation_index(pore_size) #is_port = (self.solid.vol_ch >0) not_critical = (pore_size >= self.solid.threshold_pore_size ) #rt.solid.radius_max #si[np.logical_or(is_port, not_critical)]= 0 si[not_critical] = 0 return si @staticmethod def saturation_ratio(r, int_energy=0.094, angle=np.pi, mvol=3.69e-5, gas_c=8.314, temp_kelv=298.3): ''' Saturation ratio for pore-size controlled solubility ''' omega = np.exp(-1 * mvol * np.cos(angle) * 2 * int_energy / (gas_c * temp_kelv * r)) return omega def saturation_index(self, size): ''' Saturation index ''' params = self.settings['pcs_mode'] omega = self.saturation_ratio(size, params.get('int_energy'), df.ANGLE, df.MVOL_CC, df.GAS_CONSTANT, df.TEMPERATURE) si = np.log10(omega) return si #%% PROPERTIES def csh_conc(self): csh = self.solid.CSHQ_TobH.c+ self.solid.CSHQ_TobD.c +\ self.solid.CSHQ_JenH.c + self.solid.CSHQ_JenD.c return csh
class LeachingRT(PhrqcReactiveTransport): def __init__(self, eqn, domain, domain_params, bc_params, solver_params, settings): ''' Reactive transport class for carbonation A Lattice Boltzmann method based reactive transport model \ where reactions are computed using geochemical solver PHREEQC ''' if (settings['diffusivity']['type'] == 'fixed'): print("newMlvl") eqn = 'MultilevelAdvectionDiffusion2' #solver_params['tauref'] = 1 #domain_params['Deref'] = settings['diffusivity']['D_border'] #solver_params['tauref'] = 0.5*np.max(settings['Dref'])/domain_params['Deref']+0.5#5.5 for 1e-10 self.auto_time_step = solver_params.get('auto_time_step', True) self.phrqc = Phrqc(domain, domain_params, bc_params, solver_params) components = self.phrqc.components bc = self.phrqc.boundary_conditions init_c = self.phrqc.initial_conditions for name in components: if name not in domain_params: domain_params[name] = {} domain_params[name]['c'] = init_c[name] for name in bc: for comp in components: if comp not in bc_params: bc_params[comp] = {} bc_params[comp][name] = ['c', bc[name][comp]] self.fluid = Multicomponent(eqn, components, domain, domain_params, bc_params, solver_params) self.solid = Solid(domain, domain_params, solver_params) self.ptype = 'CSH' if hasattr(self.fluid, 'Si') else 'CH' self.app_tort_degree = settings['app_tort']['degree'] self.update_solid_params() self.nodetype = deepcopy(domain.nodetype) self.apply_settings(settings) self.update_nodetype() self.update_border_and_interface(self.nodetype) #self.fluid.call("_set_relaxation_params") self.solid.prev_border = deepcopy(self.solid.border) self.Dref = settings['Dref'] def advance(self): #print(self.iters) self.update_border_and_interface(self.nodetype) if (~np.all(self.solid.border == self.solid.prev_border)): print("update") self.update_diffusivity() self.solid.prev_border = deepcopy(self.solid.border) self.fluid.call('advance') if ('Multilevel' in self.fluid.eqn) and (self.solid.n_diffusive_phases > 0): self.fluid.call('update_transport_params', self.solid.poros, self.solid.app_tort, self.auto_time_step) self.phrqc.poros = deepcopy(self.solid.poros) c = deepcopy(self.fluid.get_attr('c')) phaseqty = self.solid.phaseqty_for_phrqc() phase_list = deepcopy(self.solid.diffusive_phase_list) for num, phase in enumerate(phase_list, start=1): phaseqty[phase] = deepcopy(self.solid._diffusive_phaseqty[num - 1]) #if self.iters ==0: # self.phrqc.modify_solid_phases(phaseqty) #self.modify_eq(phaseqty) ss = self.phrqc.modify_solution(c, self.dt, self.solid.nodetype) if self.iters > 1: pqty = self.solid.update(self.phrqc.dphases) ss = self.update_portlandite_eq(c, ss) #if(self.settings['dissolution']=='subgrid'): #ss=self.update_border_solution(c,ss) self.set_volume() self.set_porosity() self.set_app_tort() self.update_nodetype() self.fluid.set_attr('nodetype', self.solid.nodetype, component_dict=False) self.fluid.set_attr('ss', ss) #self.fluid.call("_set_relaxation_params") def modify_eq(self, phaseqty): """ modifies the phaseqty in phreeqc Parameters ---------- phaseqty: dict dictionary containing an ndarray of new quantities of equilibrium phases """ phaseqty = self.phrqc.flatten_dict(phaseqty) modifystr = [] bord = np.where(self.solid.border.flatten())[0] + 1 for cell in range(self.phrqc.startcell, self.phrqc.stopcell + 1, 1): if (cell not in bord): modifystr.append("EQUILIBRIUM_PHASES_MODIFY %d" % cell) for key in phaseqty.keys(): modifystr.append("\t -component %s" % (key)) modifystr.append("\t\t%s\t%.20e" % ('-moles', phaseqty[key][cell - 1])) #modifystr.append("\t\t%s\t%.20e" %('-si', 2)) modifystr.append("end") modifystr = '\n'.join(modifystr) #print(modifystr) self.phrqc.IPhreeqc.RunString(modifystr) #%% SETTINGS def set_volume(self): self.solid.vol = np.zeros(self.solid.shape) phase_list = deepcopy(self.solid.diffusive_phase_list) for num, phase in enumerate(phase_list, start=1): val = getattr(self.solid, phase) self.solid.vol += val.c * self.solid.mvol[num - 1] def set_porosity(self): self.solid.poros = 1. - self.solid.vol / self.solid.voxel_vol if np.any(self.solid.poros <= 1e-10): sys.exit('Negative or zero porosity') def set_app_tort(self): d = self.app_tort_degree self.solid.app_tort = 1. * self.solid.poros**d def set_boundary_cells(self): nodes = np.zeros(np.shape(self.nodetype)) nodes[0, :] = ct.Type.SOLID nodes[-1, :] = ct.Type.SOLID nodes[:, 0] = ct.Type.SOLID nodes[:, -1] = ct.Type.SOLID return nodes def apply_settings(self, settings): self.settings = settings self.dx = settings['dx'] #%% UPDATES def update_diffusivity(self): Dref = self.Dref D_border = self.Dref if ('D_border' in self.settings['diffusivity']): D_border = self.settings['diffusivity']['D_border'] if ('D_CH' in self.settings['diffusivity']): D_CH = self.settings['diffusivity']['D_CH'] is_border = self.solid.border is_port = (self.solid.portlandite.c > 0) & (~is_border) is_liquid = np.logical_and(~is_port, ~is_border) #Dnew_lb = Dref*np.ones(np.shape(self.nodetype)) De = D_CH * is_port + D_border * is_border + Dref * is_liquid #print(De[1,:]) #Dnew_lb = De/self.solid.poros/self.solid.app_tort #(Dnew_lb[1,:]) self.fluid.set_attr('D0', De, component_dict=False) self.fluid.set_attr('Deref', np.max(De), component_dict=False) self.fluid.call("_set_relaxation_params") def update_solid_params(self): ''' Calculates porosity, apparent tortuosity, volume and change in volume last time step ''' self.set_volume() self.set_porosity() self.set_app_tort() def update_nodetype(self): prev_nodetype = self.nodetype is_port = (self.solid.portlandite.c > 0) is_solid = self.solid.nodetype == ct.Type.SOLID is_interface = np.zeros(np.shape(is_port)) #calc_c = self.phrqc.solid_phase_conc['calcite'] if self.ptype == 'CH': is_liquid = (~is_port) if (self.iters > 1): is_liquid = (~is_port) & (~is_solid) self.solid.nodetype = ct.Type.LIQUID * is_liquid + \ ct.Type.MULTILEVEL * is_port +\ ct.Type.INTERFACE * is_interface + \ ct.Type.SOLID * is_solid if self.ptype == 'CSH': pass yantra._solvers.update2d.reassign_mlvl(self.solid.nodetype) self.solid.nodetype[prev_nodetype == ct.Type.SOLID] = ct.Type.SOLID yantra._solvers.update2d.reassign_mlvl_solid(self.solid.nodetype) self.fluid.set_attr('nodetype', self.solid.nodetype, component_dict=False) self.nodetype = self.solid.nodetype def update_no_flux(self, ss): ss['Ca'] = ss['Ca'] * (self.phrqc.boundcells == 0) + 0 * (self.phrqc.boundcells == 1) return (ss) def update_border_and_interface(self, nodetype): is_port = (self.solid.portlandite.c > 0) is_mineral = is_port | (nodetype == ct.Type.SOLID) val = -16 temp = deepcopy(nodetype) temp = temp * (~is_mineral) + val * is_mineral border = self.get_border(temp, val, is_port) interface = self.get_interfaces(border, temp, val) self.solid.border = border self.solid.interface = interface def get_border(self, nt, val, is_port): rolled = np.roll(nt, -1, axis = 1)/4+np.roll(nt, 1, axis = 1)/4 +\ np.roll(nt, -1, axis = 0)/4+np.roll(nt, 1, axis = 0)/4 is_border = (rolled != val) & is_port return (is_border) def get_interfaces(self, is_border, nt, val): down = is_border & (np.roll(nt, 1, axis=0) != val) up = is_border & (np.roll(nt, -1, axis=0) != val) left = is_border & (np.roll(nt, 1, axis=1) != val) right = is_border & (np.roll(nt, -1, axis=1) != val) interfaces = { 'left': left, 'right': right, 'up': up, 'down': down, 'sum': 1 * down + 1 * up + 1 * left + 1 * right } return interfaces def update_border_solution(self, c, ss): phrqc_poros = self.phrqc.selected_output()['poros'] fr = np.zeros(phrqc_poros.shape) fraction = self.settings['subgrid']['fraction'] result = {} by = np.where(self.solid.border)[0] bx = np.where(self.solid.border)[1] bf = np.where(self.solid.border.flatten())[0] lx = self.nodetype.shape[1] for i in np.arange(0, np.sum(self.solid.border)): if fraction is None: fraction = 1 - phrqc_poros[by[i], bx[i]] else: if (self.settings['subgrid']['poros']): fraction = fraction - phrqc_poros[by[i], bx[i]] #print(fraction) if fraction > 0: fr[by[i], bx[i]] = fraction if (self.solid.interface['down'][by[i], bx[i]]): cell_i = bf[i] + 1 - lx cell_m = bf[i] + 1 result = self.update_neighbour_solution( result, cell_i, cell_m, self.solid.portlandite.c[by[i], bx[i]], fraction) ssnew = {} for name in self.phrqc.components: ssnew[name] = ( result[str(cell_m) + ' ' + str(cell_i)][name] - c[name][by[i] - 1, bx[i]]) / self.dt ssnew[name] *= phrqc_poros[by[i] - 1, bx[i]] ss[name][by[i] - 1, bx[i]] = ssnew[name] if (self.solid.interface['up'][by[i], bx[i]]): cell_i = bf[i] + 1 + lx cell_m = bf[i] + 1 result = self.update_neighbour_solution( result, cell_i, cell_m, self.solid.portlandite.c[by[i], bx[i]], fraction) #self.solid.poros[1,1]) ssnew = {} for name in self.phrqc.components: ssnew[name] = ( result[str(cell_m) + ' ' + str(cell_i)][name] - c[name][by[i] + 1, bx[i]]) / self.dt ssnew[name] *= phrqc_poros[by[i] + 1, bx[i]] ss[name][by[i] + 1, bx[i]] = ssnew[name] if (self.solid.interface['left'][by[i], bx[i]]): cell_i = bf[i] cell_m = bf[i] + 1 result = self.update_neighbour_solution( result, cell_i, cell_m, self.solid.portlandite.c[by[i], bx[i]], fraction) ssnew = {} for name in self.phrqc.components: ssnew[name] = ( result[str(cell_m) + ' ' + str(cell_i)][name] - c[name][by[i], bx[i] - 1]) / self.dt ssnew[name] *= phrqc_poros[by[i], bx[i] - 1] ss[name][by[i], bx[i] - 1] = ssnew[name] if (self.solid.interface['right'][by[i], bx[i]]): cell_i = bf[i] + 2 cell_m = bf[i] + 1 result = self.update_neighbour_solution( result, cell_i, cell_m, self.solid.portlandite.c[by[i], bx[i]], fraction) ssnew = {} for name in self.phrqc.components: ssnew[name] = ( result[str(cell_m) + ' ' + str(cell_i)][name] - c[name][by[i], bx[i] + 1]) / self.dt ssnew[name] *= phrqc_poros[by[i], bx[i] + 1] ss[name][by[i], bx[i] + 1] = ssnew[name] for i in np.arange(0, np.sum(self.solid.border)): if (self.solid.interface['down'][by[i], bx[i]] and fr[by[i], bx[i]] > 0): self.solid.portlandite.c[by[i], bx[i]] = result[ str(bf[i] + 1) + ' ' + str(bf[i] - lx + 1)]['portlandite_m'] if (self.solid.interface['up'][by[i], bx[i]] and fr[by[i], bx[i]] > 0): self.solid.portlandite.c[by[i], bx[i]] = result[ str(bf[i] + 1) + ' ' + str(bf[i] + lx + 1)]['portlandite_m'] if (self.solid.interface['left'][by[i], bx[i]] and fr[by[i], bx[i]] > 0): self.solid.portlandite.c[by[i], bx[i]] = result[ str(bf[i] + 1) + ' ' + str(bf[i])]['portlandite_m'] if (self.solid.interface['right'][by[i], bx[i]] and fr[by[i], bx[i]] > 0): self.solid.portlandite.c[by[i], bx[i]] = result[ str(bf[i] + 1) + ' ' + str(bf[i] + 2)]['portlandite_m'] return ss def update_portlandite_eq(self, c, ss): phrqc_poros = self.phrqc.selected_output()['poros'] fr = np.zeros(phrqc_poros.shape) fraction = self.settings['subgrid']['fraction'] result = {} by = np.where(self.solid.border)[0] bx = np.where(self.solid.border)[1] bf = np.where(self.solid.border.flatten())[0] for i in np.arange(0, np.sum(self.solid.border)): if fraction > 0: fr[by[i], bx[i]] = fraction cell_m = bf[i] + 1 result = self.update_equilibrium( result, cell_m, self.solid.portlandite.c[by[i], bx[i]], phrqc_poros[by[i], bx[i]], fraction) ssnew = {} for name in self.phrqc.components: ssnew[name] = (result[str(cell_m)][name] - c[name][by[i], bx[i]]) / self.dt ssnew[name] *= phrqc_poros[by[i], bx[i]] ss[name][by[i], bx[i]] = ssnew[name] for i in np.arange(0, np.sum(self.solid.border)): self.solid.portlandite.c[by[i], bx[i]] = result[str(bf[i] + 1)]['portlandite_m'] return ss def update_equilibrium(self, result, n_ch, m_ch, porosity, fraction=1): ncell = 123456 fract = fraction / porosity if fract > 1: fract = 1 #print(fract) modify_str = [] modify_str.append("EQUILIBRIUM_PHASES %i" % ncell) modify_str.append("Portlandite 0 %.20e dissolve only" % (m_ch)) modify_str.append("END") modify_str.append('MIX %i' % ncell) modify_str.append('%i %.20e' % (n_ch, fract)) #modify_str.append('%i 1' %n_int) modify_str.append('SAVE solution %i' % ncell) modify_str.append("END") modify_str.append('USE solution %i' % ncell) modify_str.append('USE equilibrium_phase %i' % ncell) modify_str.append('SAVE solution %i' % ncell) modify_str.append("END") modify_str.append("END") modify_str = '\n'.join(modify_str) self.phrqc.IPhreeqc.RunString(modify_str) output = self.phrqc.IPhreeqc.GetSelectedOutputArray() #print(modify_str) #print(output) port = output[2][9] modify_str = [] modify_str.append("EQUILIBRIUM_PHASES %i" % n_ch) modify_str.append("Portlandite 0 %.20e" % (0)) # dissolve only modify_str.append("END") modify_str.append('USE equilibrium_phase %i' % n_ch) modify_str.append('MIX %i' % ncell) modify_str.append('%i 1' % ncell) modify_str.append( '%i %.20e' % (n_ch, 1. - fract)) #modify_str.append('%i 0' %n_int) modify_str.append('SAVE solution %i' % (n_ch)) #modify_str.append('SAVE equilibrium_phase %i' %(n_ch)) modify_str.append("END") modify_str = '\n'.join(modify_str) self.phrqc.IPhreeqc.RunString(modify_str) output = self.phrqc.IPhreeqc.GetSelectedOutputArray() #print(modify_str) #print(output) comp = {} comp['portlandite_m'] = port comp['Ca'] = output[1][6] comp['H'] = (output[1][7] - self.phrqc.H_norm) comp['O'] = (output[1][8] - self.phrqc.O_norm) result[str(n_ch)] = comp return (result) #%% PROPERIES def volume_CH(self): CH_vol = self.solid.portlandite.c * self.solid.portlandite.mvol return CH_vol def volume_CSH(self): CSH_vol = self.solid.CSHQ_JenD.c * self.solid.CSHQ_JenD.mvol +\ self.solid.CSHQ_JenH.c * self.solid.CSHQ_JenH.mvol +\ self.solid.CSHQ_TobD.c * self.solid.CSHQ_TobD.mvol +\ self.solid.CSHQ_TobH.c * self.solid.CSHQ_TobH.mvol return CSH_vol def free_volume(self): v = self.solid.voxel_vol - self.solid.vol_ch return v def csh_conc(self): csh = self.solid.CSHQ_TobH.c+ self.solid.CSHQ_TobD.c +\ self.solid.CSHQ_JenH.c + self.solid.CSHQ_JenD.c return csh