@@ -15,24 +15,113 @@ net = uw.meshing.FaultNetwork(
1515 [(" Main" , main_pts), (" Splay" , splay_pts), (" Cross" , cross_pts)],
1616 hierarchy = [" Main" , " Splay" , " Cross" ]) # seniority order
1717
18- mesh = net.prepare(h = 0.006 ).build() # junctions -> mesh -> split
18+ mesh = net.prepare(h = 0.006 ).build(width = 0.01 ) # junctions -> mesh -> split
1919
2020v = uw.discretisation.MeshVariable(" V" , mesh, 2 , degree = 2 )
2121p = uw.discretisation.MeshVariable(" P" , mesh, 1 , degree = 0 ,
2222 continuous = False )
2323stokes = uw.systems.Stokes(mesh, velocityField = v, pressureField = p)
24- stokes.constitutive_model = uw.constitutive_models.ViscoPlasticFlowModel
25- stokes.constitutive_model.yield_mode = " min"
26- stokes.constitutive_model.Parameters.shear_viscosity_0 = 1.0
27- stokes.constitutive_model.Parameters.yield_stress = \
28- net.damage_yield(v, dial = 0.05 ) # the junction glue
29- stokes.consistent_jacobian = True
30- net.apply_contact(stokes) # no-opening pairs, all pieces
24+ stokes.constitutive_model = uw.constitutive_models.ViscousFlowModel
25+ stokes.constitutive_model.Parameters.shear_viscosity_0 = \
26+ net.junction_patch(eta_0 = 1.0 ) # the junction glue (linear)
27+ net.apply(stokes) # no-opening pairs, all pieces
3128# ... wall boundary conditions ...
3229info = net.solve(stokes)
3330print (net.slips(stokes)) # peak slip per piece
3431```
3532
33+ ## One specification, two realisations
34+
35+ A fault is specified once — a trace, its rank in the hierarchy, and the
36+ properties it carries — and then * realised* . Which realisation you get
37+ is a keyword on ` build ` , not a different set of calls:
38+
39+ ``` python
40+ net.prepare(h = 0.006 )
41+ mesh = net.build(width = 0.01 ) # cut, node-pair contact
42+ mesh = net.build(width = 0.002 , realisation = " ti" ) # volumetric weak plane
43+ net.apply(stokes, eta_1 = 0.01 ) # eta_1: TI only
44+ ```
45+
46+ Both realisations place the same ribbon band along the same prepared
47+ pieces, so the cells are identical and results from the two may be
48+ compared directly. The band is meshed around the trace's own points and
49+ segments, which become mesh vertices and edges, so ** the mesh can be cut
50+ whatever the width is** — measured complete, with exact vertex
51+ coincidence, down to a band a tenth of the background element size. The
52+ realisation is a free choice, not something the mesh grants or refuses.
53+
54+ What differs is what ` width ` * means* . For the split it is a resolution
55+ parameter: the band exists to give the cut its own vertices, and its
56+ thickness is not physics. For the weak plane it is constitutive — the
57+ layer thickness that sets the slip rate through ` V = 2 e_nt w ` — so it
58+ wants two or three elements across it. That is the whole asymmetry, and
59+ it is about the rheology rather than the mesh.
60+
61+ ` slips() ` reports each realisation in its own quantity: the tangential
62+ jump between the two nodes of a cut pair, or the jump in tangential
63+ velocity across the layer, sampled one half-width plus a cell either
64+ side of the spine. Both are the fault's own throughput; a probe placed
65+ further out reads the surrounding flow as well and over-reads short
66+ strands. The gauge is rank-local: each rank reports only the probe
67+ pairs it owns and omits a piece it holds no pair of, so a
68+ max-reduction across ranks recovers the network's answer. This
69+ matters because ` evaluate ` answers for any point it is handed,
70+ extrapolating from the nearest local cell when the point is not in
71+ the local mesh — a band-less rank would otherwise report a far-field
72+ extrapolation as the band's slip.
73+
74+ ` build(width=None) ` keeps the older no-band path — graded refinement
75+ cut directly. It is split-only, and its mesh is not the one a weak
76+ plane would use, so do not compare across that choice.
77+
78+ ** The band is material, not scaffolding.** It is easy to read the band
79+ as something the weak plane needs and the split merely tolerates. It is
80+ not: the band is a meshed region of material * around* the fault, and a
81+ segmented fault does its interesting work exactly there — at the strand
82+ tips, and in the ligaments where one cut stops short of the next. Damage
83+ in those places needs cells to live in, and the band is where they are.
84+ ` net.band ` is the mask, ` net.footprints ` the per-strand ones, in either
85+ realisation.
86+
87+ ` net.band_yield(tau_y) ` gives a rheology for the whole band: von
88+ Mises yield confined to it, everything outside set far too strong to
89+ yield. Read the next paragraph before using it as glue.
90+
91+ ``` python
92+ stokes.constitutive_model = uw.constitutive_models.ViscoPlasticFlowModel
93+ stokes.constitutive_model.Parameters.yield_stress = net.band_yield(4.0 )
94+ stokes.consistent_jacobian = True
95+ ```
96+
97+ A released fault flank sits far below ` tau_y ` and is untouched, so
98+ the breakdown appears where the mechanics puts it — but that is tips
99+ and bends as much as joints. Measured on the S-fault rig: at the
100+ strength that repairs a stepover, more than half the yielded band cells
101+ were on strand flanks and free tips, and one step weaker the whole
102+ main strand had become a weak fault. A uniform threshold cannot pick
103+ out the welds alone, because the stress concentration at a weld is not
104+ far enough above the tip and bend concentrations. ` band_yield ` is a
105+ damage model for the band; the junction glue is ` junction_patch ` .
106+
107+ The two realisations' interface parameters correspond, which is worth
108+ keeping in view when comparing them: the zero-thickness limit of a band
109+ of viscosity ` eta_band ` and width ` w ` is an interface viscosity
110+ ` eta_f = eta_band / w ` , which is the ` conds ` argument of
111+ ` add_fault_bc ` . The weak plane's ` V = 2 e_nt w ` is precisely what the
112+ contact replaces with a genuine slip rate.
113+
114+ ** Properties belong to the fault.** ` net.surface(name) ` returns the
115+ retained {class}` ~underworld3.meshing.surfaces.Surface ` for a piece.
116+ Friction, accumulated slip, a damage state live there, on the fault,
117+ and outlive any one realisation of it:
118+
119+ ``` python
120+ main = net.surface(" Main" )
121+ friction = main.add_variable(" mu" , size = 1 )
122+ friction.data[:] = 0.6
123+ ```
124+
36125## The recipe, and why each piece is the way it is
37126
38127** Hierarchy.** At an X crossing the senior fault runs through and the
@@ -48,17 +137,70 @@ same answer when the junction patch is refined 2x. Make the join as
48137small as the mesh allows; buy fidelity with elements, not physical
49138size.
50139
51- ** The glue.** ` damage_yield ` places a compact viscoplastic plug at
52- each junction: yield ` dial * (1 + 2 * edot_II) ` inside, effectively
53- rigid outside, sharp ` Piecewise ` boundaries. The strength and the
54- rate-regularisation move together on ONE dial (separating them makes
55- the solve harsh without making the zone weaker). Zone stress is
56- proportional to the dial down to a ~ 100x viscosity contrast with
57- Newton-from-cold still converging — the compact plug conditions like a
58- hole, not like a thin weak layer, so the classic thin-inclusion
59- Schur breakdown never appears. ` dial=0.05 ` is near-invisible in the
60- stress field at unchanged cost; ` dial=0.01 ` reaches the transmission
61- ceiling of an inviscid plug at roughly double cost.
140+ ** The glue, and where it goes.** The split only goes wrong at the
141+ joints: away from them the cut * is* the target every volumetric
142+ representation converges to, and adding weakness along a whole strand
143+ makes the fault over-weak in a way that depends on the band width. So
144+ the glue is placed, not found. ` junction_cells() ` reads the places off
145+ the mesh itself: the ribbon (the band with its extrapolated margins)
146+ is everything the weak-plane realisation would treat as fault, the cut
147+ chains are what the split sliced, and a band cell whose nearest spine
148+ point lies in a piece's margin * and* which sits inside a second
149+ piece's ribbon is where two pieces meet without being joined — a
150+ kissing branch, an abutting pair, the intact bridge of a stepover.
151+ Free tips are excluded on purpose: a margin that runs into intact
152+ material is a tip, and damage there lengthens the fault instead of
153+ joining it (measured: with the free tips included, nearly every
154+ yielded cell was at a tip and the main strand grew 1-6% longer in
155+ slip). The cells are dilated by one vertex ring, and that ring is
156+ not optional: the weld's stiffness lives in the intact material
157+ around the two tips, and the bare junction cells recover only a
158+ fifth to a quarter of the deficit even when fully plastic.
159+
160+ Two pieces that continue one another along a line — an abutting pair,
161+ a stepover's continuation — are placed on ** one spine** : two ribbons
162+ laid along the same line interleave their vertices into sliver cells
163+ (measured: 7800 cells below 1e-6 in area, and the velocity solve
164+ five times slower). ` build() ` groups such pieces (end tangents within
165+ 25 degrees, the far start within half a width of the line, within the
166+ margins' reach), bridges the gap with spine vertices at the local rung,
167+ and cuts each piece at its own ends; the gap is spine the split does
168+ not cut, which is exactly what the junction rule reads.
169+
170+ For the rule to see a joint, the ribbons have to meet across it.
171+ ` build() ` sees to that: at an end that sits on a prepared junction the
172+ tip margin is extended until the ribbon reaches the other piece's cut,
173+ so the whole ligament lies in both ribbons; free tips keep the default
174+ margin. An abutting pair that ` prepare() ` did not record as a junction
175+ (a gap wider than the ligament) is covered as far as the default
176+ margins overlap — a gap wider than that is two faults, and stays
177+ welded, which is what a gap of intact rock means.
178+
179+ ` junction_patch(eta_0, ratio=0.01) ` then makes those cells weak
180+ isotropic material, ` eta = ratio * eta_0 ` . A viscosity ratio rather
181+ than a yield stress, because the joint only has to be broken and a
182+ ratio needs no stress scale — nothing about the block or the loading
183+ has to be known to set it. Measured against the two end members on
184+ the S-fault rig (the fault * longer* , one continuous cut, and the fault
185+ * cut* , abutting cuts, at two resolutions): the patch recovers
186+ 0.8-0.97 of the continuous fault's transmission across the joint; the
187+ slip crosses on the cut itself (the segment's pair jump reaches the
188+ continuous fault's); the rest of the network keeps the split's answer
189+ (main strand within 2.5%); the weak patch reproduces a fully plastic
190+ patch on the same cells to 1-2% and is insensitive to the ratio from
191+ 0.01 to 0.001; the solve is linear and costs the split's velocity
192+ iterations, with only the pressure block noticing the contrast
193+ (hence 0.01, not smaller). Gluing a joint does change the partition
194+ between the strands that meet there — a reconnected main line takes
195+ back slip a through-going branch was carrying past the weld — which
196+ is the junction working, not the patch leaking.
197+
198+ ` damage_yield ` is the older glue: a viscoplastic plug of radius
199+ ` max(2.5 h, 1.2 pull) ` at each * prepared* junction point, yield
200+ ` dial * (1 + 2 * edot_II) ` inside, strength and rate-regularisation on
201+ one dial, sharp ` Piecewise ` boundaries. It stays available for
202+ studies of the glue itself, and it does not see stepover bridges,
203+ which are not prepared junctions.
62204
63205** No prescribed reconnection.** Nothing tells the network how to link
64206up: the stress lobes of the abutting tips decide. A collinear gap
@@ -120,15 +262,45 @@ directly. Place is currently OPT-IN for networks: on graded
120262solve is pathological — an open operator-health work item; on uniform
121263bases it is healthy.
122264
265+ ` build(width=...) ` gives the 3-D network the same finite-width
266+ contract as 2-D: the margin-expanded patches are thickened by
267+ ` ±width/2 ` into ONE fused band (junctions free), and each un-expanded
268+ patch is embedded in the band as a conforming mid-surface — so the
269+ same mesh is cut and split (` realisation="split" ` ) or left whole for
270+ the volumetric weak plane (` realisation="ti" ` ), exactly as in 2-D.
271+ The honoured footprints are exact for planar patches (band cells
272+ within the patch's own in-plane outline and half a width of its
273+ plane), the weak-plane director is the patch normal, and ` slips() `
274+ reports the plane form of each gauge: the tangential pair jump for
275+ the split, the in-plane velocity jump across the layer for the weak
276+ plane. Junction glue in 3-D remains ` damage_yield ` 's tubes about the
277+ junction segments — ` junction_cells ` is the 2-D ribbon rule and
278+ refuses in 3-D. Interior networks only: a band that reaches the
279+ domain boundary is refused loudly (its embedded mid-surface cannot
280+ yet be clipped against the boundary).
281+
123282v1 scope, refused loudly outside it: planar patches (the
124283` rim_polygon ` contract), convex rims, genuine X crossings (a
125284near-miss — close but not crossing — is refused rather than guessed
126- at); parallel MULTI-fault splitting (the pairing does not yet migrate
127- through redistribution — single faults are parallel-validated).
285+ at). Multi-fault networks split and solve in parallel: ` split_faults `
286+ redistributes ONCE, keyed on the union of the network's facets, and
287+ every split then runs with serial topology. Either realisation runs
288+ its velocity block on the geometric multigrid tail the band's base
289+ mesh owns (` build ` adopts it on the final mesh; ` net.solve ` says so
290+ when a solve falls back to algebraic multigrid). Placement is
291+ gather-first, so the cells gmsh fills into the carved cavity — the
292+ band and its graded surround — live on one rank; only the base's far
293+ field is balanced. On the crossing-patches fixture that is 8012 of
294+ 8405 cells on one rank at np=4, and the solve is not faster than
295+ serial: parallel is a correctness mode for this path, not a speed-up,
296+ until the placed region is rebalanced.
128297
129298## Limitations
130299
131- - 3-D: planar convex patches, X crossings only, serial (above).
300+ - 3-D: planar convex patches, X crossings only (above). Finite-width
301+ bands are interior-only (an outcropping band is refused), and
302+ ` junction_cells ` is the 2-D ribbon rule — 3-D junction glue is
303+ ` damage_yield ` 's tubes.
132304- One damage dial per network in ` damage_yield ` (per-junction values:
133305 build the expression with ` uw.meshing.damage_zone_yield ` directly).
134306- Time-dependent damage (wear-in/healing) is study-level for now: see
0 commit comments