Introduction
pyrucast est une librairie d’éléments finis dont le cœur est écrit en Rust et qui expose une API Python. Elle s’inspire des principes de cast3m : un noyau d’objets typés, accompagné de fonctions opérant sur ces objets.
📖 Référence API Rust (rustdoc). Cette documentation couvre les principes, l’architecture et les exemples ; la référence par item (signatures, types, modules) est générée par rustdoc et publiée à côté de ce book : https://pyrucast.github.io/pyrucast/rust/.
Philosophie
- Simplicité avant tout. Le code doit rester maintenable et éditable par un humain non expert ; on évite la sophistication gratuite.
- Dépendances minimales. Tout ajout de dépendance externe (Rust ou Python) requiert un accord explicite.
- Vérification continue. Chaque objet est livré avec des tests unitaires Rust, des doctests, des tests Python et un chapitre de cette documentation.
Modèle d’objets (arbre de dépendances)
Chaque structure ne dépend que des structures qui la précèdent. La plupart
viennent par paire zone / agrégat (SubMesh/Mesh…) — c’est le motif
Agrégat, avec sa composition par union |.
Coords ── Node (NodeId u32 stable ; Node = accesseur RAII)
│
├── NodeField (agrège des SubNodeField) valeurs par nœud × composante
│
└── Mesh (agrège des SubMesh, + ElementType) géométrie
└── FiniteElementSpace (agrège des SubFiniteElementSpace)
│ (+ Interpolation, QuadratureRule)
├── ElementField (agrège des SubElementField) valeurs aux Gauss
└── Model (agrège des SubModel : physiques + contraintes)
└── ops::matrix ──► Matrix (matrice creuse, DOFs nommés)
Model + ElementField (matériau) ──► stiffness ──► Matrix
Matrix + NodeField (second membre) ──► solve ──► NodeField (solution)
Deux traits transverses factorisent le comportement commun :
Aggregate (accès len/[i]/union |) et
Field/SubField (composantes nommées, min/max, arithmétique),
partagés entre NodeField et ElementField.
Résumé des rôles :
| Structure | Rôle |
|---|---|
Coords | Référentiel de coordonnées de nœuds (plusieurs configurations) |
Node | Accesseur utilisateur d’un nœud, avec protection GC automatique |
SubMesh / Mesh | Cellules d’un même ElementType / union de sous-maillages |
SubNodeField / NodeField | Valeurs par nœud × composante (zone / agrégat) |
SubFiniteElementSpace / FiniteElementSpace | Formulation EF (interpolation + quadrature) / union |
SubElementField / ElementField | Valeurs par cellule × point de Gauss × composante |
SubModel / Model | Physique ou contrainte locale / problème complet |
SubMatrix / Matrix | Matrice creuse dont les lignes/colonnes sont des DOFs (NodeId, champ) |
SubEvolution / Evolution | Valeur (scalaire ou champ) tabulée vs une variable, interpolée linéairement (zone / agrégat) |
Premiers pas
Exemple minimal en Rust :
#[test]
fn un_maillage_minimal() -> Result<()> {
let coords = Handle::new(Coords::new(2)?);
let a = Node::create_in(coords.clone(), &[0.0, 0.0])?;
let b = Node::create_in(coords.clone(), &[1.0, 0.0])?;
let mut sm = SubMesh::new(coords.clone(), ElementType::SEG2);
sm.add_cell(&[a.id(), b.id()])?;
// The aggregate does not carry the `Coords`: the submeshes are what hold it.
// `Mesh::from_submesh(sm)` is the shortcut for the single-submesh case.
let mut mesh = Mesh::empty();
mesh.add_sub(Handle::new(sm))?;
println!("{}", mesh); // Mesh: 1 submesh(es), 1 cell(s) total
Ok(())
}
Exemple équivalent en Python :
import pyrucast
c = pyrucast.Coords(dim=2)
a = c.add_node([0.0, 0.0])
b = c.add_node([1.0, 0.0])
mesh = pyrucast.Mesh(c, "SEG2")
mesh.unit().add_cell([a, b])
print(mesh) # Mesh: 1 submesh(es), 1 cell(s) total