-
Notifications
You must be signed in to change notification settings - Fork 174
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Significant performance regression in transient advection diffusion from v2.8 to v3.0 #2631
Comments
@mkaguer Could you please take a look? |
Hi @JiayanJI Please post a minimal working example (so I can just copy and run it on my machine) that produces the error. |
import os
os.environ['KMP_DUPLICATE_LIB_OK']='True'
print('Start Tuto_stepbystep')
print('Read libraries')
import numpy as np
import openpnm as op
import porespy as ps
import numpy as np
import openpnm as op
import porespy as ps
np.set_printoptions(precision=5)
pn = op.network.Cubic(shape=[10, 5, 3], spacing=1e-3) # when we change the spacing to 1e-1, it will run.
print(pn)
geo =op.models.collections.geometry.spheres_and_cylinders
pn.add_model_collection(geo)
pn.regenerate_models()
print(pn)
print('Check health')
h = op.utils.check_network_health(network=pn)
print(h)
op.topotools.trim(pn, pores=h['disconnected_pores'])
h = op.utils.check_network_health(network=pn)
print(h)
print('Assign water phase and physics')
water = op.phase.Water(network=pn)
water['pore.diffusivity'] = 1e-9
water.add_model_collection(op.models.collections.phase.water)
water.add_model_collection(op.models.collections.physics.standard)
water.regenerate_models()
print('Performing Stokes flow')
sf = op.algorithms.StokesFlow(network=pn, phase=water)
sf.set_value_BC(pores=pn.pores('left'), values=50.0)
sf.set_value_BC(pores=pn.pores('right'), values=0)
sf.run()
water.update({'pore.pressure':sf['pore.pressure']})
fd = op.algorithms.TransientAdvectionDiffusion(network=pn, phase=water)
inlet = pn.pores('left')
outlet = pn.pores('right')
fd.set_value_BC(pores=inlet, values=1.0)
f = op.models.physics.source_terms.power_law
water.add_model(propname='pore.reaction',
model=f,
X='pore.concentration',
A1=1e-5,
A2=1,
A3=0.0,
regen_mode='deferred')
fd.set_source(propname='pore.reaction', pores=outlet)
print(fd)
tspan = (0, 20)
saveat = 5
fd.run(x0=0, tspan=tspan, saveat=saveat)
print(fd)
print(water)
print('the value of outlet')
c = fd['pore.concentration']
print(c) |
@JiayanJI I spent some time with your code and I think the problem is not the spacing, but your boundary conditions: For Stokes flow, you set a pressure gradient, which induces a flow from left to right. However, for your transport algorithm, you only set the concentration at the inlet (plus a source term at the outlet). This implies that you have no-flux at the outlet, which to me seems to be unphysical (in OpenPNM, the default boundary condition is no-flux, so if you don't specify anything, it means nothing exists your boundaries). In case it's not clear why this is probably unphysical, note that you have a net positive flow from left to right, but you don't allow any mass to exit the system (by not setting any boundary condition at the outlet for your transport algorithm). There are two options at the moment in OpenPNM which you can use: either use an "outflow" condition, which means no "gradient" (not necessarily no-flux), or just set a concentration boundary condition at the outlet. One final note: whichever you choose, you can no longer add a source term to your outlet since it'll be considered a "boundary" face. |
Thank you very much for your help. import os
os.environ['KMP_DUPLICATE_LIB_OK']='True'
print('Start Tuto_stepbystep')
print('Read libraries')
import os
import pickle
import imageio
import scipy as sp
import numpy as np
import openpnm as op
import porespy as ps
import scipy.ndimage as spim
import matplotlib.pyplot as plt
from porespy.filters import find_peaks, trim_saddle_points, trim_nearby_peaks
from porespy.tools import randomize_colors
from skimage.segmentation import watershed
ps.visualization.set_mpl_style()
np.set_printoptions(precision=4)
np.random.seed(10)
np.set_printoptions(precision=5)
pn = op.network.Cubic(shape=[10, 5, 3], spacing=1e-1) # when we change the spacing to 1e-1, it will run.
print(pn)
geo =op.models.collections.geometry.spheres_and_cylinders
pn.add_model_collection(geo)
pn.regenerate_models()
print(pn)
print('Check health')
h = op.utils.check_network_health(network=pn)
print(h)
op.topotools.trim(pn, pores=h['disconnected_pores'])
h = op.utils.check_network_health(network=pn)
print(h)
print('Assign water phase and physics')
water = op.phase.Water(network=pn)
water['pore.diffusivity'] = 1e-9
water.add_model_collection(op.models.collections.phase.water)
water.add_model_collection(op.models.collections.physics.standard)
water.regenerate_models()
print('Performing Stokes flow')
sf = op.algorithms.StokesFlow(network=pn, phase=water)
sf.set_value_BC(pores=pn.pores('left'), values=50.0)
sf.set_value_BC(pores=pn.pores('right'), values=0)
sf.run()
water.update({'pore.pressure':sf['pore.pressure']})
print('Performing Transient Advection Diffusion')
tad = op.algorithms.TransientAdvectionDiffusion(network=pn, phase=water)
inlet = pn.pores('left')
outlet = pn.pores('right')
tad.set_value_BC(pores=inlet, values=1.0)
tad.set_outflow_BC(pores=outlet)
# add source term
pn.source_pores = np.arange(90) # a pore not in the 'left' and 'right' "new boundary condition"
f = op.models.physics.source_terms.power_law
water.add_model(propname='pore.reaction',
model=f,
X='pore.concentration',
A1=1e-5,
A2=1,
A3=0.0,
regen_mode='deferred')
tad.set_source(propname='pore.reaction', pores=pn.source_pores)
print(tad)
tspan = (0, 20)
saveat = 5
tad.run(x0=0, tspan=tspan, saveat=saveat)
print(tad)
print(water)
print('the value of outlet')
c = tad['pore.concentration']
print(c) |
@JiayanJI I think I have figured the problem. Your source term is very huge, causing the system of equations to become very stiff. Typically, reaction rate is per unit volume (in case it's homogeneous reaction), or per unit area (in case in heterogeneous, like catalytic reactions), but in either cases, your rate constant needs to be multiplied by the pore volume or area, which shrinks it down quite a bit, making the system of equations relatively stable. The reason you don't have this problem when using a spacing of 1e-1, is that (1e-1)**2 and (1e-1)**3 (for area and volume, respectively) are not drastically differen than 1e-1 itself, whereas for relatively small spacings, i.e., 1e-3, the area and volume are multiple orders of magnitudes smaller, hence the rest of the story! Here's a minimal script that works: import matplotlib.pyplot as plt
import numpy as np
import openpnm as op
np.random.seed(0)
op.visualization.set_mpl_style()
shape = [10, 5, 3]
pn = op.network.Cubic(shape=[10, 5, 3], spacing=1e-3)
geo = op.models.collections.geometry.spheres_and_cylinders
pn.add_model_collection(geo)
pn.regenerate_models()
h = op.utils.check_network_health(network=pn)
op.topotools.trim(pn, pores=h['disconnected_pores'])
h = op.utils.check_network_health(network=pn)
water = op.phase.Water(network=pn)
water['pore.diffusivity'] = 1e-9
water.add_model_collection(op.models.collections.phase.water)
water.add_model_collection(op.models.collections.physics.standard)
water.regenerate_models()
sf = op.algorithms.StokesFlow(network=pn, phase=water)
sf.set_value_BC(pores=pn.pores('left'), values=50.0)
sf.set_value_BC(pores=pn.pores('right'), values=0)
sf.run()
water.update({'pore.pressure': sf['pore.pressure']})
fd = op.algorithms.TransientAdvectionDiffusion(network=pn, phase=water)
inlet = pn.pores('left')
outlet = pn.pores('right')
fd.set_value_BC(pores=inlet, values=1.0)
fd.set_outflow_BC(pores=outlet)
f = op.models.physics.source_terms.power_law
k0 = 2.0
water.add_model(propname='pore.reaction',
model=f,
X='pore.concentration',
A1=pn["pore.volume"]*k0,
A2=1,
A3=0.0,
regen_mode='deferred')
non_boundary = pn.pores(["left", "right"], mode="not")
Ps_rxn = np.random.choice(non_boundary, size=90, replace=False)
fd.set_source(propname='pore.reaction', pores=Ps_rxn)
tspan = (0, 1)
saveat = None
# integrator = op.integrators.ScipyLSODA()
integrator = op.integrators.ScipyRK45()
# integrator = op.integrators.ScipyRadau()
fd.run(x0=0, tspan=tspan, saveat=saveat)
c = fd.soln['pore.concentration']
fig, ax = plt.subplots()
for t in np.linspace(*tspan, 5):
c_x = c(t).reshape(shape).mean(axis=(1, 2))
ax.plot(c_x, label=f't={t:.2f}')
ax.legend() PS. Since you're using a "source" term, not a sink term, you see concentrations greater than 1. |
Thank you very much for your help. |
Thank you very much for your help.
This problems have been solved by your help.
Now, I meet a new problem.
For the code you provided, if I replace the network with a new network (35,000 pores, 63026 throats), the calculation speed is very slow, even if I do not calculate the chemical reaction source term, only the transient advection diffusion. This process (transient advection diffusion) allows for fast calculations on version 2.8.
Could you let me know how slow are we talking about? (ex. how long does it take to solve transient diffusion @ `tspan = (0,1)` with and without source term?)
|
Thanks for your attention. from pint import quantity
import os
os.environ['KMP_DUPLICATE_LIB_OK']='True'
print('Start Tuto_stepbystep')
print('Read libraries')
import os
import pickle
import imageio
import scipy as sp
import numpy as np
import openpnm as op
import porespy as ps
import scipy.ndimage as spim
import matplotlib.pyplot as plt
from porespy.filters import find_peaks, trim_saddle_points, trim_nearby_peaks
from porespy.tools import randomize_colors
from skimage.segmentation import watershed
ps.visualization.set_mpl_style()
np.set_printoptions(precision=4)
np.random.seed(10)
op.visualization.set_mpl_style()
print('Read image')
path = 'D:/Marie file/Modelisation_copy/Modelisation/Test_PNM/Images/N03/'
file_format = '.tif'
file_name = 'N03_bin_AV_TRAV'
file = file_name + file_format
fetch_file = os.path.join(path, file)
im = imageio.mimread(fetch_file, memtest='2000MiB')
im = ~np.array(im, dtype=bool)
print('Calculate porosity')
print(ps.metrics.porosity(im))
print('Import Net')
# import dict
with open("N03_bin_AV_TRAV.net.pkl", "rb") as tf:
net = pickle.load(tf)
print('Update network')
pn = op.network.Network()
pn.update(net)
prj = pn.project
print('Resolution_label')
resolution_label=0.01
op.topotools.label_faces(pn,resolution_label)
geo =op.models.collections.geometry.spheres_and_cylinders
pn.add_model_collection(geo)
pn.regenerate_models()
print(pn)
#####
#If I change the network to a cubic network, it is ok.
# shape = [10, 5, 3]
# pn = op.network.Cubic(shape=[10, 5, 3], spacing=1e-3)
# geo = op.models.collections.geometry.spheres_and_cylinders
# pn.add_model_collection(geo)
# pn.regenerate_models()
#
###
h = op.utils.check_network_health(network=pn)
op.topotools.trim(pn, pores=h['disconnected_pores'])
h = op.utils.check_network_health(network=pn)
water = op.phase.Water(network=pn)
water['pore.diffusivity'] = 1e-9
water.add_model_collection(op.models.collections.phase.water)
water.add_model_collection(op.models.collections.physics.standard)
water.regenerate_models()
sf = op.algorithms.StokesFlow(network=pn, phase=water)
sf.set_rate_BC(pores=pn.pores('left'), rates=7e-9) #rate boundary (m^3/s)
sf.set_value_BC(pores=pn.pores('right'), values=101325) #pressure boundary (pa)
####
# If I set this boundary condition, it also can get the results quickly.
#sf.set_value_BC(pores=pn.pores('left'), values=50.0)
#sf.set_value_BC(pores=pn.pores('right'), values=0)
#
#####
sf.run()
water.update({'pore.pressure': sf['pore.pressure']})
fd = op.algorithms.TransientAdvectionDiffusion(network=pn, phase=water)
inlet = pn.pores('left')
outlet = pn.pores('right')
fd.set_value_BC(pores=inlet, values=10.0)
fd.set_outflow_BC(pores=outlet)
tspan = (0, 1)
saveat = None
# integrator = op.integrators.ScipyLSODA()
integrator = op.integrators.ScipyRK45()
# integrator = op.integrators.ScipyRadau()
fd.run(x0=0, tspan=tspan, saveat=saveat)
c = fd.soln['pore.concentration']
print(c) |
files.zip |
Thanks for sharing the script. I'll look into it, but it has to wait as I'm busy with other stuff. I'll reply on this thread when I have something. In the meantime, just a general comment: the old approach was not very robust, simply because the time step was specified by the user. The error of a transient solver is tightly linked with the time steps you choose, so if you set a fixed time step, you absolutely have no control over the error. In the new approach, the time step is automatically set by the solver itself to maintain a specified atol and rtol. Long story short, just because the old solver "runs" doesn't mean that the results you're getting are accurate. So, I advise against using the old solver as you never know when your results are accurate and when they are not. |
OK, Thank you very much. |
Hi, professer, pp = phreeqpython()pp = phreeqpython.PhreeqPython(database='phreeqc.dat') np.random.seed(0) shape = [100, 50, 30] h = op.utils.check_network_health(network=pn) print('Assign water phase and physics') print('Performing Stokes flow') sf.set_rate_BC(pores=pn.pores('left'), rates=7e-9) # rate boundary(m^3/s):A day of calculations yielded no resultssf.set_value_BC(pores=pn.pores('right'), values=0) ad = op.algorithms.AdvectionDiffusion(network=pn, phase=water) # 稳态tad = op.algorithms.TransientAdvectionDiffusion(network=pn, phase=water) #瞬态 when the injection pressure is 10 pa, the solution time with source_terms is 1221.40s (without source_term =1385.74s)we can know that the problem is not in the source_term# add source termg=op.models.physics.source_terms.general_symbolic # fastwater['pore.a'] = 0.00273water['pore.b'] = -0.47923water['pore.c'] = -0.000738433water['pore.d'] = 0.15527water['pore.x'] = np.random.random(water.Np)y = 'ax**b + cx**d'arg_map = {'a':'pore.a', 'b':'pore.b', 'c':'pore.c','d':'pore.d'}water.add_model(propname='pore.general',model=g,eqn=y, x='pore.x', **arg_map)non_boundary = pn.pores(["left", "right"], mode="not")tad.set_source(propname='pore.reaction', pores=non_boundary)print(tad)tspan = (0,1) print(tad) op.io.project_to_xdmf(prj,'final_code') |
@JiayanJI Just wanted to update you. I was hoping to further look into this when I get the chance, but it seems that I'm only getting busier. Just wanted to let you know that I probably won't have the time. Sorry about that. |
For the same transient advection diffusion problem, the solving speed of the model in version 3.0 is significantly slower than that of version 2.8.
The code for 2.8:
The code for 3.0:
Can you give me some advice?
Thank you very much!!!
The text was updated successfully, but these errors were encountered: