Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Contact (nœud-surface)

Une contrainte contact empêche les nœuds d’un maillage esclave de pénétrer une surface maître — le sibling unilatéral du baignage. Chaque nœud esclave s est apparié à sa facette maître la plus proche (poids de projection Nᵢ(ξ), normale n, jeu initial signé g₀), et la non-pénétration linéarisée s’écrit, par nœud esclave :

\[ g_0 + n \cdot u(s) - \sum_i N_i(\xi)\, n \cdot u(\text{maître}_i) \ \geq\ 0. \]

C’est une relation unilatérale (≥, cf. la section Relations unilatérales) à coefficients variant par nœud (comme le baignage) et couplant toutes les composantes du déplacement via la normale. Le modèle se résout avec solve_unilateral ; le multiplicateur λ ≤ 0 porte la réaction de contact (λ = 0 quand la paire est décollée).

Implémentation : src/models/contact.rs ; opérateur ops::model::contact(…).

Appariement à la construction (petits glissements)

L’appariement est calculé une seule fois, à la construction, par projection au point le plus proche de chaque nœud esclave sur la surface maître (ops::geom::project_points, cf. Opérateurs géométriques) : facette, ξ (clampé au domaine de référence — un nœud face à un bord se projette sur le bord), poids Nᵢ(ξ), normale et jeu signé. Appariement et normale sont ensuite figés : c’est le contact linéarisé (petits déplacements, petits glissements, sans frottement). Les deux maillages doivent partager un même Coords.

Orientation. La surface maître doit être orientée de façon cohérente, normale pointant vers le corps esclave : en 2D la normale d’un SEG2 est la tangente tournée de −90° (n = (t_y, −t_x)), en 3D celle d’un TRI3/QUA4 suit la règle de la main droite sur l’ordre des nœuds. Le jeu g₀ est alors positif quand c’est décollé, négatif quand ça pénètre.

Les nœuds-multiplicateurs sont mintés en interne (un par nœud esclave, colocalisé), accessibles après coup avec Model.multiplier_mesh().

Une relation par nœud esclave

Toutes les relations partagent la paire de variables du sous-modèle (surchargeables) :

rôlenomdéfaut
primale propre = réaction de contact λmultiplierlambda_contact
duale propre = ligne de contrainte + slot de −g₀imposed_valuecontact_gap

Signature complète :

model.contact(target, slave, master, variables)
# components : une paire (variable, target_dual) PAR dimension d'espace,
#              dans l'ordre ambiant, p.ex. [("u_x","f_x"), ("u_y","f_y")]

Contrairement au baignage (une relation par composante), le contact écrit une seule relation scalaire par nœud esclave : la normale couple les composantes entre elles (coefficients +n_c sur l’esclave, −Nᵢ·n_c sur chaque nœud maître). components doit donc en donner exactement une par dimension.

Second membre : le helper contact_gaps

Le second membre de chaque relation est −g₀ — une donnée géométrique que le sous-modèle connaît déjà. Le helper la transforme en champ de chargement, à fusionner avec | :

rhs = traction | model.contact_gaps()

L’omettre revient à traiter toutes les paires comme initialement en contact (g₀ = 0).

Exemple : patch test à deux blocs

Deux blocs élastiques empilés (jeu initial g₀), u_x bloqué partout (colonne uniaxiale), pression S sur le bloc du haut : le contact se ferme et transmet exactement σ_yy = −S ; les réactions −λᵢ sont les forces nodales cohérentes de la pression (Σ(−λᵢ) = S). En soulevant le bloc du haut, toutes les paires se relâchent (λ = 0 exactement).

# Master: upper edge of the lower block, walked in −x (normal +y, towards the slave).
master = pyrucast.Mesh(c, "SEG2")
for i in reversed(range(N)):
    master.unit().add_cell([bottom[idx(i + 1, N)], bottom[idx(i, N)]])
# Slave: nodes of the upper block's lower edge.
slave = pyrucast.mesh.poi1_from_nodes([top[idx(i, 0)] for i in range(N + 1)])

contact = pyrucast.model.contact(elasticite, slave, master, ["u_x", "u_y"])
# The upper edge's pressure is a term of the model, like the contact.
charge = pyrucast.model.flux(edge_fes, elasticite, "f_y")
model = elasticite | appuis | contact | charge
materials = pyrucast.element_field.material_field(
    model, [("E", 210.0), ("nu", 0.0), ("phi_f_y", -S)]
)

rhs = pyrucast.node_field.external_forces(model, materials) | model.contact_gaps()
solution = pyrucast.solver.solve_unilateral(
    pyrucast.matrix.stiffness(model, materials), model, rhs
)

Le déroulé complet (2D et 3D) est dans tests/contact.rs et tests/python/test_contact.py.

Périmètre v1 et suites

  • Petits glissements : appariement et normale figés à la construction ; un grand glissement demandera un ré-appariement en boucle (orchestré côté Python, comme le Newton de la plasticité).
  • Sans frottement : seul le jeu normal est contraint, le glissement tangentiel est libre.
  • Nœud-surface simple passe : pas de traitement maître/esclave symétrique, pas de mortar.