def setup_class(self): np.random.seed(0) self.net = op.network.Cubic(shape=[4, 3, 1], spacing=1.) self.geo = op.geometry.GenericGeometry(network=self.net, pores=self.net.Ps, throats=self.net.Ts) self.geo['throat.conduit_lengths.pore1'] = 0.1 self.geo['throat.conduit_lengths.throat'] = 0.6 self.geo['throat.conduit_lengths.pore2'] = 0.1 self.phase = op.phases.GenericPhase(network=self.net) self.phys = op.physics.GenericPhysics(network=self.net, phase=self.phase, geometry=self.geo) self.phys['throat.diffusive_conductance'] = 1e-15 self.phys['throat.hydraulic_conductance'] = 1e-15 self.sf = StokesFlow(network=self.net, phase=self.phase) self.sf.set_value_BC(pores=self.net.pores('right'), values=1) self.sf.set_value_BC(pores=self.net.pores('left'), values=0) self.sf.run() self.phase.update(self.sf.results()) self.ad = AdvectionDiffusion(network=self.net, phase=self.phase) self.ad.settings._update({"cache_A": False, "cache_b": False}) self.ad.set_value_BC(pores=self.net.pores('right'), values=2) self.ad.set_value_BC(pores=self.net.pores('left'), values=0)
def run(self): r""" Execute the diffusion simulations in the principle directions. """ phase = GenericPhase(network=self.network) phase['pore.viscosity'] = 1.0 phase['throat.viscosity'] = 1.0 mod = models.physics.hydraulic_conductance.hagen_poiseuille for geom in self.project.geometries().values(): phys = GenericPhysics(network=self.network, phase=phase, geometry=geom) phys.add_model(propname='throat.hydraulic_conductance', model=mod) inlet = self.network.pores(self.settings['inlet']) outlet = self.network.pores(self.settings['outlet']) perm = StokesFlow(network=self.project.network, phase=phase) perm.set_value_BC(pores=inlet, values=1) perm.set_value_BC(pores=outlet, values=0) perm.run() phase.update(perm.results()) K = self._calc_eff_prop(inlets=inlet, outlets=outlet, domain_area=self.settings['area'], domain_length=self.settings['length'], rates=perm.rate(pores=inlet), prop_diff=1) return K
def _abs_perm_calc(self, phase, flow_pores): r""" Calculates absolute permeability of the medium using StokesFlow algorithm. The direction of flow is defined by flow_pores. This permeability is normalized by variables in darcy's law other than the rate. Parameters ---------- phase: phase object The phase for which the flow rate is calculated. flow_pores: numpy array Boundary pores that will have constant value boundary condition to in StokesFlow algorithm. First element is the inlet face (pores) for flow of invading phase through porous media. Second element is the outlet face (pores). Output: array [Kwp, Knwp] The value of absolute permeability of defending (if there is any) and invadin phase in the direction that is defined by flow_pores. Note: Absolute permeability is not dependent to the phase, but here we just need to calculate the rate instead of all variables that are contributing to the darcy's law. """ network = self.project.network St_p = StokesFlow(network=network, phase=phase) St_p.set_value_BC(pores=flow_pores[0], values=1) St_p.set_value_BC(pores=flow_pores[1], values=0) St_p.run() val = np.sum(abs(St_p.rate(pores=flow_pores[1]))) K_abs = val self.project.purge_object(obj=St_p) return K_abs
def _eff_perm_calc(self, flow_pores): r""" Calculates effective permeability of each phase using StokesFlow algorithm with updated multiphase physics models to account for the multiphase flow. The direction of the flow is defined by flow_pores. All variables except for the rate in darcy's law will be the same in relative permeability ratio. The effective rate represents the effective permeability in the nominator of relative permeability ratio. Parameters ---------- flow_pores: numpy array Boundary pores that will have constant value boundary condition in StokesFlow algorithm. First element is the inlet face (pores) for flow of invading phase through porous media. Second element is the outlet face (pores). Output: array [Kewp, Kenwp] The value of effective permeability of defending (if there is any) and invading phase in the direction that is defined by flow_pores. Note: To account for multiphase flow, multiphase physics model is added and updated in each saturation (saturation is related to the presence of another phase). Here, the conduit_hydraulic conductance is used as the conductance required by stokes flow algorithm. """ network = self.project.network self._regenerate_models() if self.settings['wp'] is not None: wp = self.project[self.settings['wp']] St_mp_wp = StokesFlow(network=network, phase=wp) St_mp_wp.setup(conductance='throat.conduit_hydraulic_conductance') St_mp_wp.set_value_BC(pores=flow_pores[0], values=1) St_mp_wp.set_value_BC(pores=flow_pores[1], values=0) St_mp_wp.run() Kewp = np.sum(abs(St_mp_wp.rate(pores=flow_pores[1]))) self.project.purge_object(obj=St_mp_wp) else: Kewp = None pass nwp = self.project[self.settings['nwp']] St_mp_nwp = StokesFlow(network=network, phase=nwp) St_mp_nwp.set_value_BC(pores=flow_pores[0], values=1) St_mp_nwp.set_value_BC(pores=flow_pores[1], values=0) St_mp_nwp.setup(conductance='throat.conduit_hydraulic_conductance') St_mp_nwp.run() Kenwp = np.sum(abs(St_mp_nwp.rate(pores=flow_pores[1]))) Kenwp = Kenwp self.project.purge_object(obj=St_mp_nwp) return [Kewp, Kenwp]
class AdvectionDiffusionTest: def setup_class(self): np.random.seed(0) self.net = op.network.Cubic(shape=[4, 3, 1], spacing=1.) self.geo = op.geometry.GenericGeometry(network=self.net, pores=self.net.Ps, throats=self.net.Ts) self.geo['throat.conduit_lengths.pore1'] = 0.1 self.geo['throat.conduit_lengths.throat'] = 0.6 self.geo['throat.conduit_lengths.pore2'] = 0.1 self.phase = op.phases.GenericPhase(network=self.net) self.phys = op.physics.GenericPhysics(network=self.net, phase=self.phase, geometry=self.geo) self.phys['throat.diffusive_conductance'] = 1e-15 self.phys['throat.hydraulic_conductance'] = 1e-15 self.sf = StokesFlow(network=self.net, phase=self.phase) self.sf.set_value_BC(pores=self.net.pores('right'), values=1) self.sf.set_value_BC(pores=self.net.pores('left'), values=0) self.sf.run() self.phase.update(self.sf.results()) self.ad = AdvectionDiffusion(network=self.net, phase=self.phase) self.ad.settings._update({"cache_A": False, "cache_b": False}) self.ad.set_value_BC(pores=self.net.pores('right'), values=2) self.ad.set_value_BC(pores=self.net.pores('left'), values=0) def test_settings(self): self.ad.settings._update({ 'quantity': "pore.blah", 'conductance': "throat.foo", 'diffusive_conductance': "throat.foo2", 'hydraulic_conductance': "throat.foo3", 'pressure': "pore.bar", }) assert self.ad.settings["quantity"] == "pore.blah" assert self.ad.settings["conductance"] == "throat.foo" assert self.ad.settings["diffusive_conductance"] == "throat.foo2" assert self.ad.settings["hydraulic_conductance"] == "throat.foo3" assert self.ad.settings["pressure"] == "pore.bar" # Reset back to previous values for other tests to run self.ad.settings._update({ 'quantity': "pore.concentration", 'conductance': "throat.ad_dif_conductance", "diffusive_conductance": "throat.diffusive_conductance", "hydraulic_conductance": "throat.hydraulic_conductance", "pressure": "pore.pressure" }) def test_conductance_gets_updated_when_pressure_changes(self): mod = op.models.physics.ad_dif_conductance.ad_dif self.phys.add_model(propname='throat.ad_dif_conductance', model=mod, s_scheme='powerlaw') g_old = self.phys["throat.ad_dif_conductance"] # Run StokesFlow with a different BC to change pressure field self.sf.set_value_BC(pores=self.net.pores('right'), values=1.5) # Running the next line should update "throat.ad_dif_conductance" self.sf.run() self.phase.update(self.sf.results()) self.ad.settings["conductance"] = "throat.ad_dif_conductance" self.ad.run() g_updated = self.phys["throat.ad_dif_conductance"] # Ensure conductance values are updated assert g_old.mean() != g_updated.mean() assert_allclose(g_updated.mean(), 1.01258990e-15) # Reset BCs for other tests to run properly self.sf.set_value_BC(pores=self.net.pores('right'), values=1) self.sf.run() def test_powerlaw_advection_diffusion(self): mod = op.models.physics.ad_dif_conductance.ad_dif self.phys.add_model(propname='throat.ad_dif_conductance_powerlaw', model=mod, s_scheme='powerlaw') self.phys.regenerate_models() self.ad.settings['conductance'] = 'throat.ad_dif_conductance_powerlaw' self.ad.run() x = [ 0., 0., 0., 0.89653, 0.89653, 0.89653, 1.53924, 1.53924, 1.53924, 2., 2., 2. ] y = self.ad['pore.concentration'] assert_allclose(actual=y, desired=x, rtol=1e-5) def test_upwind_advection_diffusion(self): mod = op.models.physics.ad_dif_conductance.ad_dif self.phys.add_model(propname='throat.ad_dif_conductance_upwind', model=mod, s_scheme='upwind') self.phys.regenerate_models() self.ad.settings['conductance'] = 'throat.ad_dif_conductance_upwind' self.ad.run() x = [ 0., 0., 0., 0.86486, 0.86486, 0.86486, 1.51351, 1.51351, 1.51351, 2., 2., 2. ] y = self.ad['pore.concentration'] assert_allclose(actual=y, desired=x, rtol=1e-5) def test_hybrid_advection_diffusion(self): mod = op.models.physics.ad_dif_conductance.ad_dif self.phys.add_model(propname='throat.ad_dif_conductance_hybrid', model=mod, s_scheme='hybrid') self.phys.regenerate_models() self.ad.settings['conductance'] = 'throat.ad_dif_conductance_hybrid' self.ad.run() x = [ 0., 0., 0., 0.89908, 0.89908, 0.89908, 1.54128, 1.54128, 1.54128, 2., 2., 2. ] y = self.ad['pore.concentration'] assert_allclose(actual=y, desired=x, rtol=1e-5) def test_exponential_advection_diffusion(self): mod = op.models.physics.ad_dif_conductance.ad_dif self.phys.add_model(propname='throat.ad_dif_conductance_exponential', model=mod, s_scheme='exponential') self.phys.regenerate_models() self.ad.settings[ 'conductance'] = 'throat.ad_dif_conductance_exponential' self.ad.run() x = [ 0., 0., 0., 0.89688173, 0.89688173, 0.89688173, 1.53952557, 1.53952557, 1.53952557, 2., 2., 2. ] y = self.ad['pore.concentration'] assert_allclose(actual=y, desired=x, rtol=1e-5) def test_outflow_BC(self): ad = AdvectionDiffusion(network=self.net, phase=self.phase) ad.settings["cache_A"] = False ad.set_value_BC(pores=self.net.pores('right'), values=2) for s_scheme in ['upwind', 'hybrid', 'powerlaw', 'exponential']: ad.settings._update({ 'quantity': 'pore.concentration', 'conductance': f'throat.ad_dif_conductance_{s_scheme}' }) ad.set_outflow_BC(pores=self.net.pores('left')) ad.run() y = ad[ad.settings['quantity']].mean() assert_allclose(actual=y, desired=2, rtol=1e-5) def test_add_outflow_overwrite_rate_and_value_BC(self): ad = AdvectionDiffusion(network=self.net, phase=self.phase) ad.set_rate_BC(pores=[0, 1], total_rate=1) ad.set_value_BC(pores=[2, 3], values=1) assert np.sum(np.isfinite(ad['pore.bc_rate'])) == 2 assert np.sum(np.isfinite(ad['pore.bc_value'])) == 2 ad.set_outflow_BC(pores=[0, 1, 2, 3]) assert np.sum(np.isfinite(ad['pore.bc_rate'])) == 0 assert np.sum(np.isfinite(ad['pore.bc_value'])) == 0 def test_value_BC_does_not_overwrite_outflow(self): ad = AdvectionDiffusion(network=self.net, phase=self.phase) ad.set_outflow_BC(pores=[0, 1]) with pytest.raises(Exception): ad.set_value_BC(pores=[0, 1], values=1) def test_add_rate_BC_fails_when_outflow_BC_present(self): ad = AdvectionDiffusion(network=self.net, phase=self.phase) ad.set_outflow_BC(pores=[0, 1]) with pytest.raises(Exception): ad.set_rate_BC(pores=[0, 1], total_rate=1) ad.set_rate_BC(pores=[2, 3], total_rate=1) assert np.all(ad['pore.bc_rate'][[2, 3]] == 0.5) def test_outflow_BC_rigorous(self): ad = AdvectionDiffusion(network=self.net, phase=self.phase) ad.settings["cache_A"] = False # Add source term so we get a non-uniform concentration profile self.phys["pore.A1"] = -5e-16 linear = op.models.physics.generic_source_term.linear self.phys.add_model(propname="pore.rxn", model=linear, A1="pore.A1", X="pore.concentration") internal_pores = self.net.pores(["left", "right"], mode="not") ad.set_source("pore.rxn", pores=internal_pores) ad.set_value_BC(pores=self.net.pores('right'), values=2) ad.set_outflow_BC(pores=self.net.pores('left')) for s_scheme in ['upwind', 'hybrid', 'powerlaw', 'exponential']: ad.settings._update({ 'quantity': 'pore.concentration', 'conductance': f'throat.ad_dif_conductance_{s_scheme}' }) ad.run() y = ad[ad.settings['quantity']].reshape(4, 3).mean(axis=1) # Calculate grad_c @ face on which outflow BC was imposed dydx_left_mean = (y[1] - y[0]) / (1 - 0) # Calculate grad_c across the entire network as reference dydx_total_mean = (y[1] - y[-1]) / (3 - 0) # Make sure that grad_c @ outflow BC face is "numerically" ~ 0 assert_allclose(actual=dydx_total_mean + dydx_left_mean, desired=dydx_total_mean) def test_rate(self): ad = AdvectionDiffusion(network=self.net, phase=self.phase) ad.settings["cache_A"] = False ad.set_value_BC(pores=self.net.pores('right'), values=2) ad.set_value_BC(pores=self.net.pores('left'), values=0) for s_scheme in ['upwind', 'hybrid', 'powerlaw', 'exponential']: ad.settings._update({ 'quantity': 'pore.concentration', 'conductance': f'throat.ad_dif_conductance_{s_scheme}', 's_scheme': s_scheme }) ad.run() mdot_inlet = ad.rate(pores=self.net.pores("right"))[0] mdot_outlet = ad.rate(pores=self.net.pores("left"))[0] temp = np.random.choice(self.net.pores(["right", "left"], mode="not"), size=3, replace=False) mdot_internal = ad.rate(pores=temp)[0] # Ensure no mass is generated within the network assert_allclose(mdot_inlet - mdot_internal, mdot_inlet) # Ensure mass is not conserved assert_allclose(mdot_inlet, -mdot_outlet) def teardown_class(self): ws = op.Workspace() ws.clear()