# interaction drag Describes the entry `drag` at version `0.4.0`, registered by `mime.interactions.hydrodynamic.Drag`. Kept registered beside it: `drag` at version `0.3.0`, by `mime.interactions.hydrodynamic.PointForceDrag`, which a document naming that version loads; its differences are in the hand-written part's versions. ## At a glance - interaction `drag`, version `0.4.0`, namespace `mime` - Family: [`hydrodynamic`](../../families/hydrodynamic.md), with 2 built rungs among its members; compared on its page - Rungs built: [`surrogate`](#rung-surrogate), [`mrs`](#rung-mrs) - Nodes: [`SurrogateDragNode`](../../nodes/SurrogateDragNode.md), [`MRSDragNode`](../../nodes/MRSDragNode.md) - The equations are on the node page: [equations](../../nodes/MRSDragNode.md#governing-equations) - Undocumented, so with no equations to link: [`SurrogateDragNode`](../../nodes/SurrogateDragNode.md) - Derivative, by rung: [`surrogate`](#rung-surrogate) `piecewise_jump`, [`mrs`](#rung-mrs) `exact` - Validity: 9 declared lines ## What it models, and when not to use it ### `drag` The medium's resistance to a body's motion: the 6 x 6 operator a rigid body's dynamics solves with implicitly, about its centre of mass, in the world frame, with the load a flowing medium puts on the body held still beside it (zero in a medium at rest, the only medium built). The rung is written in the script, and there is no default: `Drag.Surrogate(...)` or `Drag.MRS(...)`. ## Rung `surrogate` The learned resistance of a de Jongh screw body in a straight tube: a Cholesky network trained on the MRS resistances of that family, read at the body's offset from the tube's axis, with the near-wall lubrication correction blended in when `lubrication` is written. It covers the bodies and tubes it was trained on and refuses others when the experiment is planned. **When not to use it.** For any body but a de Jongh screw, or a tube outside its training data; for a body far from a wall, where the mrs rung needs no training. ## Rung `mrs` The regularised-Stokeslet resistance of any rigid body whose hull kind gives surface samples (a sphere, a capsule, the de Jongh helix, a cylinder, a box, an ellipsoid) [@Cortez2005], on a separated quadrature [@Smith2018; @GallagherSmith2021]: the kind samples its surface on the patches written (its whole closed surface when none are), neighbouring force points about `spacing` apart, each with one traction, constant over its cell; the integral is the kind's own quadrature `quadrature_refinement` times finer, each of its points in the cell of the force point nearest it, so that the blob, `blob` times the force points' mean spacing, can be smaller than the force points' spacing and still be resolved. The force points and the quadrature are placed by the body's pose at every step, so the resistance turns with the body, and they are a smooth function of the kind's numbers, so a study's derivative in a radius or a wavenumber passes through them: its derivative is exact, checked against differences. **What it costs.** The part of the matrix the body makes with itself is the same at every pose, so it is assembled once per build, in the body's frame, over the quadrature in chunks (never every force point against every quadrature point at once), and held: in free space the whole resistance is held and each step only turns it, so a run costs its build; with a wall, each step adds the wall's correction at the force points and solves, which costs as the cube of the force points' count. **A wall's correction is evaluated at the force points**: the image system varies over the distance to the wall, which the force points resolve while the body is more than about a force spacing from it. **Its defaults are by family, measured** (`docs/choosing_spacing.md` holds the numbers): for a compact body (a sphere, and any kind not measured) the count of force points 0.3.0 took, the blob a tenth of their spacing and the quadrature twenty times finer; for a screw the coarsest count that keeps its shape, the blob a quarter and the quadrature eight times finer. The quadrature's refinement, when not written, is two over the blob. The build refuses a free resistance whose symmetric part is not positive definite, or which is symmetric only to more than a hundredth of itself (a check for gross defects only: the point forces are symmetric to rounding however far off they are), and, with a wall, a resistance at the body's starting pose that is not positive definite (`drag.mrs-resistance`); a refinement written below its rule's where the rule's quadrature moves the free resistance's significant entries by more than a twentieth (`drag.mrs-quadrature`: that move estimates the written quadrature's error, and it predicted the free-space misses measured, the point forces' at a small blob among them); and a force point with no quadrature point in its cell (`drag.empty-cell`). **Near a wall** (MIME-ANO-130 holds the numbers). The wall's image is taken at the force points unless `wall_refinement` is written. Taken there, it left the confined resistance indefinite within half a force spacing of the wall, on the point forces as on the separated quadrature, its sign changing with micrometres of the pose. Written, the step integrates the image over each force point's cell on a quadrature that many times finer than the force spacing, each cell's weights scaled to its area and its points capped at twice the mean, as the free part is integrated (the image is a kernel between any targets and any source cells, the wall kind's own, and the same kernel will take one body's force points against another's cells). Integrated, the resistance is definite further in, at a step several times the point image's. So: - a confined body that a contact holds off the wall takes, by default, a force spacing no coarser than its closest approach over `CONFINED_GAP` force spacings, outside the band the resistance was measured indefinite in (over `INTEGRATED_GAP` with `wall_refinement` written), and the build report states what that costs: the force points, the unknowns a step solves, the image's reads and the run's memory. On a millimetre screw at a contact's usual margin that is thousands of force points, most of a laptop's memory and most of a minute a step on its CPU in float64 (MIME-ANO-130 has the figures), a price worth reading before the run: write a coarser spacing for a look, and expect the stop below if the body reaches the band; - on a machine with a laptop's memory, write `wall_refinement`. The default was chosen by a rule fixed before its measurement that weighed a step's time alone, not memory, and memory is where the two differ: at a millimetre screw's usual margin, the integrated image's coarser spacing took a step about as long as the default's and under a third of its memory, and was definite on every floor roll measured, as the default was. The build report names the refinement to write and the memory below which it is the option, beside the run's memory, and MIME-ANO-130 holds the figures; - each step with a wall judges its resistance before the body takes it: one that is not positive definite (its least eigenvalue, each row and column scaled by its diagonal, not above zero) is replaced by NaN and the run stops at that step, `drag.mrs-indefinite`, naming the step, the pose, the body's least distance from the wall and a force spacing that clears it; a step with the body within one force spacing of the wall raises the flag `near_wall`, and one whose resistance is symmetric only to more than a twentieth of itself raises `asymmetric` (its error is at least half that share). The check is on the resistance the node delivers, so it holds for any solve that delivers one. **Several bodies.** Each body's drag is computed alone, from its own pose and the medium's wall: a second body in the same medium changes neither's, on either rung, since no hydrodynamic interaction between bodies is built in MIME 0.3.0. The build report says which bodies share a medium. `wall_kind` names the wall whose correction the solve adds: `free_space`, an unbounded medium, for a medium that fills no region, and [`tube`](../wall_kind/tube.md), a straight circular tube's no-slip wall, for one that fills a tube's lumen. **When not to use it.** - In a tube or near any wall: the free-space wall leaves it out, and a medium that fills a region is refused with it. - Where the cost matters near a wall: each step with a wall solves a dense system of three unknowns per force point, so its cost grows as the cube of their count; the coarsest spacing that resolves the body's shape is the one to write. - Within a fraction of a force spacing of a wall at a coarse spacing: the resistance can be indefinite there, with the image at the force points or integrated over each cell, and a run that reaches one stops (MIME-ANO-130). The point forces (a quadrature refinement of one) are no way out: they were measured indefinite near the wall at every spacing mapped. Write a finer force spacing, as the refusal says, or hold the body further from the wall. A definite resistance close to the wall is not shown accurate. - On a laptop with less memory than script A's frozen point needs on this rung (MIME-ANO-130 states it). - For a body whose hull is several parts, or whose kind gives no samples: refused when planned. - Where the error must be bounded rather than estimated: the regularised method's error is measured, never bounded, against the sphere's exact regularised drag and Stokes's law, the prolate spheroid's closed form, Haberman and Sayre's confined sphere and the shipped free-space reference of a de Jongh screw by its oracle tests and, at the defaults, by `scripts/default_spacing_table.py`, whose misses the default states in numbers on the stamp and in `mime pending`: write `spacing=` for a number to use. Leaving out a patch, as the de Jongh helix's caps were left out before, is a model difference of its own. - As the converged value of a de Jongh helix at the counts the v0.2 line used: its samples and that line's triangle mesh differ there, by how much its validity lines say, and neither has converged; the converged diagonal lies between the two (MIME-ANO-116). A surrogate trained on either inherits that error. ## Versions Version 0.4.0 separates the mrs rung's quadrature from its force points and chooses the rung's defaults again by measurement; its surrogate rung computes what 0.3.0's did. 0.3.0 stays registered so that a folder saved with it loads and reproduces (the frozen sim2real register's experiments build it): its mrs rung puts a point force at each sample, the samples its quadrature, with the blob half their mean spacing, which a quadrature refinement of one on 0.4.0 also gives; a script that writes `Drag.MRS(...)` gets 0.4.0. Version 0.3.0 reads the body's shape from its hull's kind, not from declared values; 0.2.0 moved the resistance from the end of the body's axis to its centre; 0.1.0 delivered it about the end (MIME-ANO-112). The mrs rung was added to 0.3.0, changing nothing the surrogate computes. ## Settings | Setting | Type | Default | Dimension | Role | Default's kind | |---|---|---|---|---|---| | `blob` | `float \| None` | `null` | none | numerical | numerical | | `lubrication` | `Lubrication \| None` | `null` | a group, `lubrication` | design | convention | | `patches` | `tuple[str, ...] \| None` | `null` | none | numerical | convention | | `quadrature_refinement` | `int \| None` | `null` | none | numerical | numerical | | `rung` | `str \| None` | `null` | none | design | none | | `spacing` | `Annotated[Quantity \| None, Length]` | `null` | length (m) | numerical | numerical | | `wall_kind` | `str \| None` | `null` | none | physical | determined | | `wall_refinement` | `int \| None` | `null` | none | numerical | convention | | `weights` | `Asset \| None` | `null` | none | design | none | Each role and default's kind is the entry's `setting_roles` (`mime.registry.SettingRole`); a setting that declares none is required. Each default is a claim (`mime.registry.DefaultKind`): a determined default is resolved in every profile, a convention taken in every profile, a numerical default taken in exploratory only and stamped pending. Their rules and bases: - `blob` (numerical): on the mrs rung, by the hull kind's family (SEPARATED_BLOB): a tenth of the force points' mean spacing for a compact body, a quarter for a screw; chosen by measurement with the spacing's default: the regularisation's error grows with the blob, so a smooth body's drag wants it small; a screw's swim speed barely depends on it and its quadrature costs the square of the blob's inverse (scripts/default_spacing_table.py) - `lubrication` (convention): the setting's written default; no lubrication correction is blended in unless one is declared - `patches` (convention): the setting's written default; the hull's closed surface: every patch its kind has - `quadrature_refinement` (numerical): on the mrs rung, two over the blob, rounded up: the quadrature's spacing at most half the blob; chosen by measurement: finer, neither family's number moved by what the stamp states (the sphere's drag and rotational drag, a screw's speed, over a ladder of blobs); coarser, the quadrature no longer resolves the blob (scripts/default_spacing_table.py) - `spacing` (numerical): on the mrs rung, the largest spacing, to three significant figures, at which the hull's kind gives its family's count of force points on its grid (SEPARATED_SAMPLES: a compact body's, a screw's); for a body a law holds off the wall of its medium's region (a contact), no coarser than that closest approach over CONFINED_GAP force spacings, or INTEGRATED_GAP where wall_refinement is written; a first look at a laptop's cost, chosen by measurement, never a converged value: for each family the count whose miss against its reference (Stokes's law, the shipped helix reference) is within the stated error at a run's cost a laptop's CPU takes (scripts/default_spacing_table.py measures the misses and the run times on a ladder of spacings, and the stamp states them) - `wall_kind` (determined): on the mrs rung, the one wall kind that matches the region the medium fills (free_space where it fills none); it is the one value the other inputs allow - `wall_refinement` (convention): the setting's written default; none, the image at the force points: integrating it over each cell is written where it is wanted (MIME-ANO-130) Settings inside a group: | Setting | Dimension | May be none | |---|---|---| | `lubrication.epsilon` | length (m) | yes | ## Roles, reads and deliveries Roles, bound by keyword when the entry is attached: `body` (body), `medium` (medium). Reads: | Label | Reads | |---|---| | `pose` | body [`body.pose`](../../vocabulary.md#namespace-mime) | Delivers: | Label | Role | Into the target | |---|---|---| | `background_load` | body | [`body.background_load`](../../mechanisms/load.md) | | `resistance` | body | [`body.resistance`](../../mechanisms/operator.md) | Reads values of the things it is bound to: | Label | Role | Value | |---|---|---| | `viscosity` | medium | `viscosity` | Reads, values and deliveries that its `plan` adds for a particular construction are not in these tables: they list what the class declares, and `plan` is code the build runs when the entry is attached. ## Rungs A rung is a section of this page, and the entry's `rung` setting names it; there is no default. ### Rung surrogate - [`SurrogateDragNode`](../../nodes/SurrogateDragNode.md) Declared in `builds`; the node walk compares it with what a round-trip case builds. Supported constructions, by what each bound thing's dynamics or field provider must offer: | Requires | Note | |---|---| | body: flag rigid, accepts [`body.background_load`](../../mechanisms/load.md), accepts [`body.resistance`](../../mechanisms/operator.md), publishes [`body.pose`](../../vocabulary.md#namespace-mime) | a rigid body whose dynamics takes a resistance, in a medium at rest filling a straight tube | The derivative through this rung is `piecewise_jump`: not the exact derivative of what it computes everywhere. Switches the law evaluates, each declared once by its owner: | Switch | Indicator | Orders, per output group | Route to exact | |---|---|---|---| | `drag.axis` | the body's distance from the tube's axis, m; on the axis within AXIS_ULPS units in the last place of the tube's radius (a band), where the frame takes the body's own axes | `resistance` -1 (a jump) | a surrogate whose canonical resistance at zero offset is invariant under rotation about the tube's axis, which makes the resistance continuous across the axis, and which changes smoothly in the offset across it, for example through the offset's square, which makes it differentiable there (invariance alone leaves a cone, in the offset's length); the trained surrogate has neither, and no frame taken on the axis can give it them | | `drag.across` | the squared length of the body's x axis across the tube, less a quarter (on the axis, it picks the body's x or y axis for the frame) | `resistance` -1 (a jump) | as drag.axis's: a surrogate symmetric about the tube's axis needs no frame on it | | `drag.end` | the cosine between the body's x axis and the tube's, plus END_TIE: the side the resistance is moved from | `resistance` -1 (a jump) | none: the switch lies where the body's axis lies across the tube, outside the training data, where a body would be refused | | `drag.room` | 0.95 of the room between the body's outer radius and the wall, in the body's radius | `resistance` 0 (a kink) | none needed: a body that does not fit its tube is refused when planned, so it is never live | | `drag.clamp` | the offset less the largest the surrogate is read at, in the body's radius | `resistance` 0 (a kink) | a smooth saturation of the offset, or refusing a pose beyond the clamp | | `drag.clamp_divisor` | the offset the clamp divides by, less its floor of 1e-10, in the body's radius | `resistance` 0 (a kink) | none needed: a clamped body is never that close to the axis, so it is never live | | `drag.feature_gap_floor` | the gap from the body's outer radius to the wall, less the surrogate's floor, in the body's radius | `resistance` 0 (a kink) | a smooth floor, a softplus | | `drag.gap_floor` | the lubrication's gap less its floor of 1 nm, m | `resistance` 0 (a kink) | a smooth floor, a softplus | | `drag.log_floor` | the lubrication's logarithm of the outer radius over the gap | `resistance` 0 (a kink) | a smooth floor, a softplus | ### Rung mrs - [`MRSDragNode`](../../nodes/MRSDragNode.md) Declared in `builds`; the node walk compares it with what a round-trip case builds. Supported constructions, by what each bound thing's dynamics or field provider must offer: | Requires | Note | |---|---| | body: flag rigid, accepts [`body.background_load`](../../mechanisms/load.md), accepts [`body.resistance`](../../mechanisms/operator.md), publishes [`body.pose`](../../vocabulary.md#namespace-mime) | a rigid body whose dynamics takes a resistance and whose hull kind gives surface samples, in a medium at rest that a wall kind matches: in MIME 0.3, an unbounded one | The derivative through this rung is `exact`, the derivative of what it computes. ## Gradient status The derivative depends on the rung attached, and a saved experiment's lock records that rung's. Each rung's section above gives its own status and the switches its nodes evaluate: - [`surrogate`](#rung-surrogate): `piecewise_jump` - [`mrs`](#rung-mrs): `exact` The entry's own status, the least of its rungs', is `piecewise_jump`. ## Flags Raised by its nodes and reported on every reading, series, export and printout of a run's result over the window it covers; a flag never blocks a read. | Flag | Rung | Node | Raised when | |---|---|---|---| | `clamp_fired` | `surrogate` | its node | the body's offset from the tube's axis lay beyond the clamp near the wall, so the surrogate read the resistance at the clamp, not at the offset | | `tilt_fired` | `surrogate` | its node | the body's axis lay further from the tube's than the tilt the surrogate is declared valid to (see the rung's validity line): it was trained for a helix along the tube's axis and cannot see a tilt, so its resistance is that of the body along the axis | | `bore_fired` | `surrogate` | its node | the body's axis lay so far from the tube's that the body, as the cylinder of its length and outer radius, no longer fits the tube's bore: the geometric limit, beyond the declared tilt's, past which a body longer than the bore has gone through the wall | | `indefinite` | `mrs` | its node | the step's resistance, the wall's correction in it, was not positive definite (its least eigenvalue, each row and column scaled by its diagonal, not above zero): the node replaced it by NaN before the body took it, so the run stopped there (drag.mrs-indefinite, MIME-ANO-130) | | `near_wall` | `mrs` | its node | the body lay within one force spacing of the wall (NEAR_WALL), where the resistance has been measured indefinite closer in (MIME-ANO-130) and a definite one is not shown accurate | | `asymmetric` | `mrs` | its node | the step's resistance, before its symmetric part was taken, was symmetric only to more than a twentieth of itself (CONFINED_ASYMMETRY_LIMIT), so its error is at least half that share (the share bounds it from below; a small share bounds nothing) | ## Validity Declared validity lines: free text, not parsed, and not yet checked against the operating point or the region each one describes. - each body's drag is computed alone, from its own pose and the medium's wall: a second body in the same medium does not change it, on either rung (no hydrodynamic interaction between bodies in MIME 0.3.0; the build reports the bodies that share a medium) - surrogate: quasi-steady Stokes flow (the limit of vanishing Reynolds number), as its training resistances were computed; the Reynolds number, by the body's translation and by its turning, is computed after each run (the post-run watch) and flagged past the bound the entry declares - surrogate: de Jongh screw bodies (cross-section modulation 0.33, two starts) in a straight tube; nu, kappa and L/R checked against the training data at plan time; the offset declared up to 0.3 of the tube's radius and clamped beyond 0.95 of the room to the wall; its accuracy against the MRS it was checked against is MIME-ANO-114's, up to 16% in swim speed between 0.2 and 0.3 of the radius - surrogate: the resistance is moved from the end of the body's axis, about which it was trained, to the centre of mass, half the body's length along its x axis taken as the helix's, which the surrogate takes to lie along the tube's; it cannot see a tilt (away from the axis its resistance is a function of the body's centre and of which way the body's axis points along the tube, and of nothing else about the orientation) - surrogate: valid for a body whose axis lies within 5 degrees of the tube's, a limit declared (mime.nodes.hydro.TILT_VALIDITY), not fitted, from one MRS measurement of FL-9 on the axis (docs/data/tilt_validity.json, scripts/tilt_validity.py): at that tilt the force-free swim speed differs from the aligned body's by less than the 16% this entry states for the surrogate against the boundary element, and at the next tilt measured, in one of two directions, it does not; one spacing, one offset, one body, and the MRS itself not converged at that spacing; tilt_fired is raised at the step the tilt passes the limit, and bore_fired at the step the body, as the cylinder of its length and outer radius with its centre on the axis, no longer fits the bore - mrs: quasi-steady Stokes flow, the limit of vanishing Reynolds number; the Reynolds number, by the body's translation and by its turning, is computed after each run (the post-run watch) and flagged past the bound the entry declares - mrs: the body's hull alone is sampled, on the patches written; a patch left out (the de Jongh helix's caps, which the line before this one left out) is a model difference, not a rounding - mrs: a medium at rest that its wall kind matches; on the separated quadrature its error is the regularisation's, of the order of the blob over the body's size, and the force points' constant tractions', of the order of the force spacing, the quadrature's falling as quadrature_refinement rises; measured against the sphere's exact regularised drag and Stokes's law, the prolate spheroid's closed form, Haberman and Sayre's confined sphere and a de Jongh screw's shipped free-space reference by its oracle tests, not bounded here - mrs: a wall's correction is taken at the force points unless wall_refinement is written, and they do not resolve the image near the wall: within half a force spacing of it the resistance was measured indefinite, and within a quarter of one with the image integrated over each cell (a step whose resistance is indefinite stops the run, drag.mrs-indefinite), within one a step raises near_wall, and there a definite resistance is not shown accurate; in the tube the confinement's effect converges more slowly than the free part (MIME-ANO-130) ## Reynolds number The Reynolds number, by the hull's diameter and the body's speed, up to which the entry's model is declared. The post-run watch reads it back after every run: a flag on the result when a run lies past it, never a refusal, and no part of the graph. A declaration, not a fit. | Rung | Declared up to Re | Basis | |---|---|---| | `surrogate` | `0.1` | Stokes flow is the limit of vanishing Reynolds number, and its error grows with it. The bound is a convention taken from a sphere sedimenting in a still fluid, whose drag the first correction of Oseen's expansion raises by three sixteenths of the Reynolds number, about a fiftieth at the bound. That correction is a sedimenting sphere's, and a force-free swimmer, which has no net force, does not have it; no correction of the neglected inertia has been measured for any body of MIME's. The bound is declared for every body the rung serves from the sphere alone, and says how far a run is from Stokes flow, not how much its answer is off | | `mrs` | `0.1` | Stokes flow is the limit of vanishing Reynolds number, and its error grows with it. The bound is a convention taken from a sphere sedimenting in a still fluid, whose drag the first correction of Oseen's expansion raises by three sixteenths of the Reynolds number, about a fiftieth at the bound. That correction is a sedimenting sphere's, and a force-free swimmer, which has no net force, does not have it; no correction of the neglected inertia has been measured for any body of MIME's. The bound is declared for every body the rung serves from the sphere alone, and says how far a run is from Stokes flow, not how much its answer is off | ## Family Declared: [`hydrodynamic`](../../families/hydrodynamic.md). The family's conformance suite is run by a test of the lab or of MIME; which test, and in which lane, is resolved when the evidence resolver lands. ## References [@deJongh2025NonlinDyn], [@CoxBrenner1967], [@GoldmanCoxBrenner1967], [@BradyBossis1988], [@Cortez2005], [@Smith2018], [@GallagherSmith2021] ## Other declarations | Class variable | Type | Value | |---|---|---| | `compact_samples` | `ClassVar[int]` | `338` | | `holds_off` | `ClassVar[str \| None]` | `null` | | `id_roles` | `ClassVar[tuple[str, ...] \| None]` | `null` | | `moving` | `ClassVar[bool]` | `false` | | `mrs_settings` | `ClassVar[tuple[str, ...]]` | `["spacing", "blob", "patches", "wall_kind", "quadrature_refinement", "wall_refinement"]` | | `samples_by_family` | `ClassVar[Mapping[str, int]]` | `{"screw": 191, "sphere": 338}` | | `surrogates` | `ClassVar[Mapping[str, ScrewSurrogate]]` | `` too long to show; see `mime registry --json` `` | | `unset_spacing_planned` | `ClassVar[bool]` | `true` |