mesh = mesh_file_reader(filepath, bc_indices)domain = FEDomain('domain', mesh)omega = domain.create_region('Omega', 'all')gamma0 = domain.create_region('Gamma 0', ' +v '.join('vertices of group {}'.format(n) for n in bc_indices[0]), 'facet')gamma1 = domain.create_region('Gamma 1', ' +v '.join('vertices of group {}'.format(n) for n in bc_indices[1]), 'facet')field = Field.from_args('fu', nm.float64, 'scalar', omega, approx_order=2)u = FieldVariable('u', 'unknown', field)v = FieldVariable('v', 'test', field, primary_var_name='u')integral = Integral('i', order=2)term = Term.new('dw_laplace(v, u)', integral, omega, v=v, u=u)eq = Equation('Laplace equation', term)eqs = Equations([eq])ebc0 = EssentialBC('Dirichlet 0', gamma0, {'u.all': 0.0})ebc1 = EssentialBC('Dirichlet 1', gamma1, {'u.all': 1.0})pb = Problem('Laplace problem', equations=eqs)pb.time_update(ebcs=Conditions([ebc0, ebc1]))vec = pb.solve()
from sfepy.discrete.probes import Probe
Probe.cache = pb.domain.get_evaluate_cache(cache=None, share_geometry=True)
> Anyway, my goal is to get the highest possible accuracy out of this. The
> only real limitations here are memory and speed. So basically my question
> is, given a fixed amount of memory and time, what would be the best choice
> of solver methods for this problem? Should I just go with the defaults?
> Also, is there some recommendation for settings such as the mesh order?
> Memory is my biggest concern; for time consumption something which takes an
> order of magnitude longer than the default direct solver would still be
> acceptable.
I recommend installing petsc, petsc4py, and trying something like:
'i10' : ('ls.petsc',
{'method' : 'cg', # ksp_type
'precond' : 'icc', # pc_type
'eps_a' : 1e-12, # abstol
'eps_r' : 1e-12, # rtol
'i_max' : 1000,} # maxits
),
This should be much more memory-friendly than the default direct solver
(umfpack if installed), and also much faster.
BTW. the snippet comes from tests/test_linear_solvers.py - have a look for
other possible solvers. Especially pyamg might be interesting, in case you have
troubles installing petsc with petsc4py.
> To clarify, I fully understand that there's no silver bullet that magically
> solves all my problems, I'm just looking for some pointers about what I
> should try, as my own experience in FEM solvers is quite limited. Thus far,
> I've successfully solved the problem with the defaults, and I've tried all
> the available SciPy iterative solvers to no avail. WIth the iterative
> solvers, convergence seems much too slow; this is likely because I did not
> supply a preconditioner, but I'm not entirely sure how that should be done
> in SfePy, or even what would be an appropriate preconditioner in the first
> place.
You are right, the preconditioning is crucial. The sfepy interface to scipy
solvers lacks that feature (this should be easily fixable - could you add an
enhancement issue?).
Luckily, the Laplacian is one of the few problems, where a
silver bullet is sort of known (IIRC) - the conjugate gradients with multigrid
preconditioning (-> try pyamg). Other kinds of preconditioning, such as cg +
icc from petsc proposed above should work very well too.
./run_tests.py tests/test_linear_solvers.py --filter-less
gives on my machine:
... 0.03 [s] (2.428e-12) : i10 ls.petsc cg icc
... 0.05 [s] (3.301e-12) : i20 ls.scipy_iterative cg
... 0.06 [s] (1.046e-12) : i21 ls.scipy_iterative bicgstab
... 0.11 [s] (3.575e-12) : i22 ls.scipy_iterative qmr
... 0.20 [s] (2.634e-12) : i01 ls.pyamg smoothed_aggregation_solver
... 0.29 [s] (3.108e-12) : i00 ls.pyamg ruge_stuben_solver
... 0.33 [s] (4.825e-15) : d00 ls.scipy_direct umfpack
... 1.04 [s] (1.592e-14) : d01 ls.scipy_direct superlu
The test solves the Laplace problem. The times of pyamg are somewhat
misleading, because the test problem is too small, and there is some setup in
amg...
import gcimport psutilimport osfrom sfepy.base.base import outputoutput.set_output(quiet=True)
def get_mem_usage(): process = psutil.Process(os.getpid()) mem = process.get_memory_info()[0] / float(2 ** 10) print("{} KB in use.".format(int(mem)))
def mem_tester(n, refinement_level=0): from sfepy.discrete.fem.utils import refine_mesh from sfepy.discrete.fem import FEDomain from sfepy.discrete.fem.mesh import Mesh for i in xrange(0, n): mesh = Mesh.from_file(refine_mesh('rectangle2D.mesh', refinement_level))
domain = FEDomain('domain', mesh)
del mesh, domain gc.collect() get_mem_usage()
def mem_tester_complete(n, refinement_level=0): from sfepy.discrete.fem.utils import refine_mesh from sfepy.discrete import (FieldVariable, Integral, Equation, Equations, Problem) from sfepy.discrete.fem import (FEDomain, Field) from sfepy.terms import Term from sfepy.discrete.conditions import Conditions, EssentialBC from sfepy.discrete.fem.mesh import Mesh for i in xrange(0, n): mesh = Mesh.from_file(refine_mesh('rectangle2D.mesh', refinement_level))
domain = FEDomain('domain', mesh) omega = domain.create_region('Omega', 'all')
gamma0 = domain.create_region('Gamma 0', 'vertices in (x < 0.00001)', 'facet') gamma1 = domain.create_region('Gamma 1', 'vertices in (x > 0.99999)', 'facet') field = Field.from_args('fu', np.float64, 'scalar', omega, approx_order=2)
u = FieldVariable('u', 'unknown', field) v = FieldVariable('v', 'test', field, primary_var_name='u') integral = Integral('i', order=2) term = Term.new('dw_laplace(v, u)', integral, omega, v=v, u=u) eq = Equation('Laplace equation', term) eqs = Equations([eq]) ebc0 = EssentialBC('Dirichlet 0', gamma0, {'u.all': 0.0}) ebc1 = EssentialBC('Dirichlet 1', gamma1, {'u.all': 1.0}) pb = Problem('Laplace problem', equations=eqs) pb.time_update(ebcs=Conditions([ebc0, ebc1])) vec = pb.solve()
gc.collect() get_mem_usage()
> PPS. On the subject of things getting left behind from previous problem
> solutions, I've noticed that the Probe cache isn't reset when a new problem
> is solved. This is not unexpected, I'd just like to check whether my
> workaround for this is correct. What I've done is include the following
> code after the one I posted above:
>
> from sfepy.discrete.probes import Probe
> Probe.cache = pb.domain.get_evaluate_cache(cache=None, share_geometry=True)
>
> Is this the correct way of going about this? The goal is to ensure that
> subsequent calls to PointsProbe() use the correct cache without needing to
> explicitly give them a cache object.
Seems OK. BTW. I have just fixed a bug - share_geometry value was ignored by
the probes, so they were always "sharing".
Or you can use:
Probe.cache = pb.domain.get_evaluate_cache(cache=Probe.cache, share_geometry=False)
etc. There is also a new method: Probe.get_evaluate_cache(), which returns the
correct cache, so you could selectively clear, for example, only cache.kdtree,
which depends on coordinates of the mesh, but not cache.iconn, which depends
only on the element connectivity.
Does it help?
r.
On 03/24/2015 07:37 PM, Jonatan Lehtonen wrote:
> Hello Robert,
>
> I have attached the mesh file and the script you asked for, which
> reproduces the first set of timings I gave in my previous post. The problem
> is essentially the same one I used in my memory test example. There is no
> material coefficient, I am just solving the equation Laplacian(u) = 0 with
> simple Dirichlet boundary conditions u = 0 and u = 1.
I have played with it a bit and found, that the gamg (Geometric algebraic
multigrid (AMG), see [1]) preconditioner from petsc works quite well.
[1] http://www.mcs.anl.gov/petsc/documentation/linearsolvertable.html
I have also modified a bit the script:
- the domain is refined in memory, without wasting time on saving and loading
the text files.
- added context managers for memory_profiler:
- to be run with: mprof run --python laplace.py
- view the memory usage: mprof plot
These are my observations, opinions (IMHO, AFAIK, IIRC etc.):
- in 2D, a good direct solver is hard to beat.
- P1 approximation, where the matrix is more sparse, are better for iterative
solvers than P2 etc.
- staying with pyamg seems a good strategy overall, but petsc cg+gamg behaves
similarly - there some memory might be wasted do to copying data from
numpy.scipy to petsc.
The attached plots show the memory usage and time spent, with names
r<refinement>_p<field_order>.png
> Also, I added an issue for the memory leak. :)
Closed already :)
> Regards,
> Jonatan
>
> PS. In case you're interested, the reason I'm solving this problem is to
> obtain a conformal mapping from an (essentially arbitrary) 2D region to a
> rectangle, using the method outlined in this article
> <http://math.aalto.fi/~vquach/work/publication/hqr-cam-final.pdf>. So the
> basic idea is to solve two Laplace problems with different boundary
> conditions and combine the solution to obtain a conformal mapping. Of
> course, in this example I've used, the solution is trivial since the mesh
> itself is a unit square. =)
Interesting! I will look at the article. What are the applications?
> Speaking of which, the above results in two uncoupled Laplace equations to
> be solved. Do you think there would be problems with solving these both in
> one go (that is, adding both equations to the same Problem-instance)? I've
> tried this, and Scipy direct warns about the system matrix being close to
> singular, but it seems to solve the problem just fine (and faster than
> solving two separate problems, with lower overall memory consumption). I'm
> just wondering, is this sort of approach ill-advised or even potentially
> unstable?
>
It should be ok. I just wonder, why the solver complains about a singular
matrix, when each of the problems has proper Dirichelt BCs... As a side note
see [2].
r.
[2]
http://sfepy.org/doc-devel/examples/thermo_elasticity/thermo_elasticity_ess.html
domain = FEDomain('domain', mesh)omega = domain.create_region('Omega', 'all')
gamma0 = domain.create_region('Gamma 0', ' +v '.join('vertices of group {}'.format(n) for n in bc_indices[0][0]), 'facet')gamma1 = domain.create_region('Gamma 1', ' +v '.join('vertices of group {}'.format(n) for n in bc_indices[0][1]), 'facet')gamma0_conj = domain.create_region('Gamma 0 conj', ' +v '.join('vertices of group {}'.format(n) for n in bc_indices[1][0]), 'facet')gamma1_conj = domain.create_region('Gamma 1 conj', ' +v '.join('vertices of group {}'.format(n) for n in bc_indices[1][1]), 'facet')
field = Field.from_args('fu', np.float64, 'scalar', omega, approx_order=2)u = FieldVariable('u', 'unknown', field)v = FieldVariable('v', 'test', field, primary_var_name='u')
u_conj = FieldVariable('u_conj', 'unknown', field)v_conj = FieldVariable('v_conj', 'test', field, primary_var_name='u_conj')
integral = Integral('i', order=2)term = Term.new('dw_laplace(v, u)', integral, omega, v=v, u=u)
term_conj = Term.new('dw_laplace(v_conj, u_conj)', integral, omega, v_conj=v_conj, u_conj=u_conj)
eq = Equation('Laplace equation', term)
eq_conj = Equation('Laplace equation conj', term_conj)eqs = Equations([eq, eq_conj])
ebc0 = EssentialBC('Dirichlet 0', gamma0, {'u.all': 0.0})ebc1 = EssentialBC('Dirichlet 1', gamma1, {'u.all': 1.0})
ebc0_conj = EssentialBC('Dirichlet 0 conj', gamma0_conj, {'u_conj.all': 0.0})ebc1_conj = EssentialBC('Dirichlet 1 conj', gamma1_conj, {'u_conj.all': 1.0})pb = Problem('Laplace problem', equations=eqs, nls=nls, ls=ls)pb.time_update(ebcs=Conditions([ebc0, ebc1, ebc0_conj, ebc1_conj]))vec = pb.solve()
> By the way, one thing I wasn't entirely sure of when writing this
> "combined" solver was which objects can be shared between the two
> equations. The code I've used (excluding mesh generation) is below, and
> conj (short for conjugate) stands for the second of the two problems. Am I
> right in assuming that domain, omega, field and integral can be shared by
> both equations, and everything else needs to be separated, like I've done
> below? I'm fairly sure the rest of this is correct, but not quite certain
> if the field can be shared.
You can share a field for as many variables as you need. The field only defines
the approximation (the FE basis), but the actual degrees of freedom to solve
for are determined by the variables.
So yes, everything can be shared (domain, regions, materials, solvers...).
r.
sfepy: reading mesh [line2, tri3, quad4, tetra4, hexa8] (rectangle2D.mesh)...sfepy: ...done in 0.04 ssfepy: updating variables...sfepy: ...donesfepy: setting up dof connectivities...sfepy: ...done in 0.00 ssfepy: matrix shape: (13339, 13339)sfepy: assembling matrix graph...sfepy: ...done in 0.01 ssfepy: matrix structural nonzeros: 151257 (8.50e-04% fill)sfepy: updating materials...sfepy: ...done in 0.00 ssfepy: nls: iter: 0, residual: 1.856814e+01 (rel: 1.000000e+00)sfepy: cg(gamg) convergence: 2 (CONVERGED_RTOL)sfepy: rezidual: 0.01 [s]sfepy: solve: 0.25 [s]sfepy: matrix: 0.02 [s]sfepy: nls: iter: 1, residual: 8.178809e-14 (rel: 4.404754e-15)sfepy: updating variables...sfepy: ...donesfepy: setting up dof connectivities...sfepy: ...done in 0.00 ssfepy: matrix shape: (13389, 13389)sfepy: assembling matrix graph...sfepy: ...done in 0.01 ssfepy: matrix structural nonzeros: 151987 (8.48e-04% fill)sfepy: updating materials...sfepy: ...done in 0.00 ssfepy: nls: iter: 0, residual: 1.323160e+01 (rel: 1.000000e+00)Traceback (most recent call last): File "petsc_problem.py", line 41, in <module> vec_conj = pb_conj.solve() File "/u/82/jonatan/unix/.local/lib/python2.7/site-packages/sfepy/discrete/problem.py", line 1000, in solve vec = nls(vec0, status=self.nls_status) File "/u/82/jonatan/unix/.local/lib/python2.7/site-packages/sfepy/solvers/nls.py", line 344, in __call__ eps_a=eps_a, eps_r=eps_r, mtx=mtx_a) File "/u/82/jonatan/unix/.local/lib/python2.7/site-packages/sfepy/solvers/ls.py", line 45, in _standard_call **kwargs) File "/u/82/jonatan/unix/.local/lib/python2.7/site-packages/sfepy/solvers/ls.py", line 333, in __call__ ksp.setOperators(pmtx) File "KSP.pyx", line 172, in petsc4py.PETSc.KSP.setOperators (src/petsc4py.PETSc.c:135262)petsc4py.PETSc.Error: error code 60[0] KSPSetOperators() line 538 in /m/home/home8/82/jonatan/unix/build/petsc/src/ksp/ksp/interface/itcreate.c[0] PCSetOperators() line 1088 in /m/home/home8/82/jonatan/unix/build/petsc/src/ksp/pc/interface/precon.c[0] Nonconforming object sizes[0] Cannot change local size of Amat after use old sizes 13339 13339 new sizes 13389 13389