migflow.time_integration
- exception migflow.time_integration.CouplingUnstable
Bases:
RuntimeErrorA runtime guard aborted the run: continuing would only burn CPU.
- exception migflow.time_integration.CouplingWarning
Bases:
UserWarningA runtime guard fired: the coupled step looks unstable or unphysical.
- migflow.time_integration.iterate(fluid, particles, dt, min_nsub=1, contact_tol=1e-08, external_particles_forces=None, fixed_grains=False, after_sub_iter=None, max_nsub=None, check_residual_norm=-1, use_predictor_corrector=False, mu=None, rho_f=None, gravity=None)
Migflow solver: the solver type depends on the fluid and particles given as arguments.
- Parameters:
fluid – fluid structure
particles – particles structure
dt (
float) – time stepmin_nsub (
int) – minimal nsub for the particles iterationscontact_tol (
float) – tolerance for the contact solverexternal_particles_forces (
ndarray) – vector of external forces applied on the particlesfixed_grains (
bool) – boolean variable specifying if the grains are fixed in the fluidafter_sub_iter (
callable) – callback to execute once a sub iteration has been mademax_nsub (
int) – maximum number of times the time step can be further split if conergence is not reachedcheck_residual_norm (
float) – check if the fluid solver has converged to the specified normuse_predictor_corrector (
bool) – boolean variable specifying if the predictor-corrector scheme is usedmu (
float) – the coupling closure’s parameters; None reads them off the fluidrho_f (
float) – the coupling closure’s parameters; None reads them off the fluidgravity (
ndarray) – the coupling closure’s parameters; None reads them off the fluid
- Raises:
ValueError – fluid and particles cannot be both None
ValueError – external_particles_forces must have shape (number of particles,dimension)
- migflow.time_integration.iterate_iqn(fluid, particles, dt, min_nsub=1, contact_tol=1e-08, external_particles_forces=None, after_sub_iter=None, max_nsub=None, check_residual_norm=-1, max_iter=10, tol=0.001, omega=0.5, depth=12, reuse=4, fixed_grains=False, set_bodies=None, get_forces=None, coupler=None, set_geometry=None, set_closure=None, geometry=None, mu=None, rho_f=None)
IQN-ILS accelerated fluid-grain step: quasi-Newton on the grain velocities.
The contact-dominated counterpart to
iterate_patankar. It drives the fixed pointv = G(v)whereG(v)is “the velocity the DEM returns when the fluid force is computed with the grains moving at v” – contacts and all – and accelerates it with the IQN-ILS secant method inbody_coupling.IQN. BecauseGcontains the contact solve, an impulsive impact is reconciled with the fluid over the outer iterations rather than lagged; this is the scheme to reach for on deposits and impacts, where the explicit-contact Patankar scheme is fragile.RETURNS THE COUPLER for reuse across steps. IQN-ILS reuses secant columns from the last
reusesteps; clearing that history every step degrades it to barely-accelerated Picard (measured). Pass the returned coupler back in on the next step:cpl = None for step in …:
cpl = iterate_iqn(fluid, particles, dt, …, coupler=cpl)
On the first call
coupleris None and one is built here frommax_iter/tol/omega/depth/reuse; thereafter the supplied coupler’s own settings govern and those keyword arguments are ignored.BODY MASK. The coupler operates on ALL particles of nonzero radius:
mask = particles.r().ravel() > 0. Walls have zero radius (and zero mass, so a mass division would blow up) and are excluded; the velocity guess is written back only into the masked slots. This keeps the indexing generic – no testcase-specific wall/boundary count leaks into the library.Hooks for the unfitted path – EITHER
set_bodies(one hook per iteration) OR the split pairset_geometry/set_closure(tier A once per step, tier B per iteration);get_forcesis required with both:set_bodies(fluid, particles, v_full=None)– hand the solver the body CSR for the grains moving atv_full(the current IQN velocity guess, a full p-indexed array). Geometry stays caller-side (architectural rule).get_forces(fluid, particles)– the TOTAL non-contact force for the DEM, p-indexed, walls zero (fluid + gravity + external), as initerate_patankar.
THE SPLIT HOOKS (optional, opt-in):
set_geometry/set_closureset_bodiesis called once per OUTER ITERATION, so the whole body CSR – including the overlap build, which is the expensive part – is rebuilt every iteration. It does not have to be:restore_state()at the top of the loop puts the bodies back where they started, so WITHIN A STEP the positions and radii are identical across iterations and only the velocity guess moves. Only the closure (which reads the guess, hence the slip) is genuinely per-iteration.A caller who can split its geometry from its closure says so by passing BOTH
set_geometry(fluid, particles) -> geo– built ONCE PER STEP, before the loop, at the start-of-step positions.geois opaque to the library: whatever the caller’s closure hook needs (volume_couplingreturns its tier-A dict, and has already handed it to the kernel).set_closure(fluid, particles, geo, v_full) -> anything– called per iteration inset_bodies’ place, with the current velocity guess.
particlesis passed to both because the closure legitimately reads particle state the geometry does not carry (contact forces);geois passed through untouched.geometry=hands the library a geo the caller built ITSELF, outside the time loop, in place ofset_geometry: that is the held-bed case (darcy/2d_discs), where the bodies never move and tier A is invariant for the WHOLE RUN, not merely within a step. It is accepted only withfixed_grains=True– the iterating branch advances the grains, so a prebuilt geometry would be stale from the second step on, silently.These are opt-in and mutually exclusive with
set_bodies: given neither, theset_bodiespath runs exactly as before.Machinery per step:
save_state; capture the fluid solution;start_step; then untildone():restore_state(the fluid must see the START-of-step geometry, only the velocity guess varying), set the guess into the masked slots,set_bodies, reset the fluid solution,implicit_euler, form the force,_advance_particles,take_targetthe resulting grain velocities.
- migflow.time_integration.iterate_patankar(fluid, particles, dt, min_nsub=1, contact_tol=1e-08, external_particles_forces=None, fixed_grains=False, after_sub_iter=None, max_nsub=None, check_residual_norm=-1, set_bodies=None, get_forces=None, timers=None, prediction_contact_forces=True, patankar_datum=False, gravity=None, mu=None, rho_f=None)
Patankar (mixture-model) fluid-grain step: one fluid solve, one DEM advance.
This is the first-class library form of the Patankar scheme historically run through
iterate().iterate()itself is kept untouched for backward compatibility; this function duplicates its coupled statements verbatim (theset_bodies is Nonebranch below) rather than delegating, and adds the unfitted (caller-supplied body CSR) route.WHEN TO USE. patankar is the cheap coupling: a single fluid solve per step, no outer iteration. It is FRAGILE AT IMPULSIVE IMPACT – the contact force is carried explicitly into the DEM prediction and never reaches the fluid Jacobian (f00/f01/…), so contact and fluid cannot be reconciled inside the solve. That is inherent to the scheme, not a defect, and is exactly why
iterate_iqnexists for contact-dominated problems (deposits, impacts).THE CONTACT FORCE IS THE DEM SOLVER’S, BY DEFAULT (owner ruling 2026-08-16): the prediction carries the real contact forces, which is the physical coupling and converges to the contact-consistent hydrostatic state at temporal convergence. STABILITY IS THE TIME STEP’S JOB: the lagged force injects O(dt*Fc/m) of velocity per step, so dt must keep that below the flow scale – scale dt with grain density (the steel depot needs dt = 1e-3 where its rho_p = 1500 sibling takes 5e-3; fine-grain beds like vortices r = 2e-5 need dt sized accordingly or the IQN scheme).
prediction_contact_forces=Falseis the measured-legacy parity setting: migflow-main’s contact_forces getter always returned zeros (per-contact native vector + zero-filling shape guard), so the old model never saw a contact force – that is what the legacy-retrieval configuration reproduces, not the physical default.Two paths, selected by
set_bodies:set_bodies is None– LIBRARY PATH: tier A geometry, tier B closure withmodel="unfitted"(m = (x - c)/r, div_m = dim/r, the Babuska penalty blended on resolution into the Dallavalle law, trace band and both extra diffusivities live) and the tier C Patankar datum, which condenses the grain momentum into the single solve exactly as the old in-kernel prediction did.mu/rho_f/gravityare the closure’s parameters and are read off the fluid when not given.set_bodiesgiven – UNFITTED PATH. Geometry stays caller-side (the core never builds overlap geometry – an architectural rule):set_bodies(fluid, particles, v_full=None)hands the solver its body CSR, withv_full=Nonemeaning “use particles’ own velocity”.get_forces(fluid, particles)must return the TOTAL non-contact force to hand the DEM, p-indexed (walls included, zeros there): fluid + gravity + any external. This mirrors thecompute_node_forceconvention where the caller addsg*moutside – here the whole non-contact force composition is the caller’s responsibility, so the library never assumes where gravity or buoyancy enter. The flow isset_bodies -> implicit_euler -> F = get_forces + external -> advance.patankar_datum=Truemakes THE SCHEME apply its own semi-implicit treatment inside that single solve:set_bodiesmust return the tier dicts, and the library builds the affine datum from them (needsgravity).get_forcesis then just the fluid force plus gravity – no caller-side 1/(1 + gamma dt/m). Without it the branch is explicit and the case must do that correction itself, which is what made “patankar” name two different schemes.
Args mirror
iterate();set_bodies/get_forcesare the unfitted hooks.
- migflow.time_integration.predictor_corrector_iterate(fluid, particles, dt, min_nsub=1, contact_tol=1e-08, external_particles_forces=None, alpha=0.5, after_sub_iter=None, max_nsub=None, check_residual_norm=-1, mu=None, rho_f=None, gravity=None)
Predictor-corrector scheme to solve fluid and grains.
- Parameters:
fluid – fluid structure
particles – particles structure
dt (
float) – time stepmin_nsub (
int) – minimal nsub for the particles iterationscontact_tol (
float) – tolerance for the contact solverexternal_particles_forces (
ndarray) – external forces applied on the particlesalpha (
float) – parametre of the predictor-corrector scheme [alpha*f(n)+(1-alpha)*f(n+1)]after_sub_iter (
callable) – callback to execute once a sub iteration has been mademax_split – maximum number of times the time step can be further split if convergence is not reached
check_residual_norm (
float) – check if the fluid solver has converged to the specified normmu (
float) – the coupling closure’s parameters; None reads them off the fluidrho_f (
float) – the coupling closure’s parameters; None reads them off the fluidgravity (
ndarray) – the coupling closure’s parameters; None reads them off the fluid
- migflow.time_integration.reset_guards()
Reset the guard counters (a new run in the same process).