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

Nœud (Node)

Un Node désigne un nœud d’un Coords. Il ne stocke pas de coordonnées : il porte son Coords et l’identifiant du nœud, et lit tout dans le Coords — les coordonnées qu’il rend sont donc celles de la configuration active. C’est l’objet qu’on passe partout où une fonction attend un nœud : pyrucast.mesh.line(a, b, 10), pyrucast.mesh.arc(…), pyrucast.mesh.circle(…).

Créer et retrouver un nœud

Un Node naît toujours d’un Coords :

  • coords.add_node([x, y, …]) crée le nœud et rend son Node ;
  • coords.acquire(id) rend un Node pour un nœud qui existe déjà — un nœud posé par un mailleur, par exemple, dont on connaît l’identifiant.

Interface

  • id — l’identifiant entier, stable dans son Coords ;
  • position() — ses coordonnées dans la configuration active ;
  • set_position([x, y, …]) — les réécrit, dans la configuration active ;
  • coords() — le Coords auquel il appartient ;
  • node | node — un Mesh POI1 sur les deux nœuds (la même union | que les agrégats, cf. Agrégat), et mesh | node ajoute un point à un Mesh POI1 d’une seule zone ;
  • print(n), repr(n) et n.dump() — les trois niveaux de listing.
import pyrucast

c = pyrucast.Coords(dim=2)
a = c.add_node([0.0, 0.0])
b = c.add_node([1.0, 0.0])

print(a.id)  # 0
print(a.position())  # [0.0, 0.0]
a.set_position([0.5, 0.5])

# Union de nœuds → maillage POI1 (deux points), puis un point de plus.
poi = a | b
print(poi)  # Mesh: 1 submesh(es) [#…], 2 cell(s) total
poi = poi | c.add_node([2.0, 0.0])
print(poi.cell_count)  # 3

print(a)  # <Node #0>
a.dump()  # Node #0 @ (0.500, 0.500)

Un Node compte comme une référence

Chaque Node compte comme une référence à son nœud, celle que coords.refcount(id) additionne avec celles des maillages et des champs : add_node et acquire en ajoutent une, et le Node la rend quand Python le détruit. Une seconde variable sur le même objet (b = a) ne compte pas deux fois : c’est le même Node.

C’est ce qui le distingue d’un simple identifiant. Un entier ne dit pas à quel Coords il appartient et ne compte pour rien ; le Node porte son Coords, lit ses coordonnées dans la bonne configuration, et le nœud le compte parmi ses références tant qu’il existe. Porter son Coords lui permet aussi d’être refusé là où il n’a pas sa place : une maille, un champ, une matrice posés sur un autre Coords lèvent une erreur plutôt que de prendre le nœud de ce Coords qui porte le même numéro.

Côté Rust

En Rust, un Node est une paire (Handle<Coords>, NodeId) qui tient son compte par RAII : Clone ajoute une référence dans le Coords, Drop la rend. Il se crée par Node::create_in(coords, &[x, y, …]), et Node::acquire(coords, id) en rend un de plus pour un identifiant existant — le verbe que Python porte sur le Coords, Rust le porte sur le nœud. Il expose id(), position(), set_position(&[…]) et coords(), qui rend le Handle<Coords> porté.

#[test]
fn un_noeud_est_un_compteur_de_references() -> Result<()> {
    let coords = Handle::new(Coords::new(2)?);
    let n = Node::create_in(coords.clone(), &[1.0, 2.0])?;
    let id = n.id();
    let m = n.clone(); // une référence de plus
    assert_eq!(coords.read().refcount(id), 2);
    drop(n);
    drop(m); // chaque Drop rend la sienne
    assert_eq!(coords.read().refcount(id), 0);
    Ok(())
}

Le code interne peut manipuler des NodeId sans passer par Node, à condition de tenir lui-même le compte avec Coords::incref / Coords::decref : c’est ce que font les maillages et les champs. Le détail est dans Modèle mémoire.