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

Barre

En bref

Une barre ne transmet que l’effort axial : un treillis, un câble tendu, un tirant. Elle se pose sur des segments, en 1-D, 2-D ou 3-D.

opérateurpyrucast.model.truss(fespace, strain_measure="linear")
tagmechanical
typedomaine avec comportement (label() : "Truss")
élémentsSEG2

Une barre n’a aucune raideur transversale : chaque nœud doit être tenu dans les directions que la barre ne porte pas (par d’autres barres, un treillis triangulé, ou des appuis), sinon la matrice est singulière. Tout autre élément qu’un SEG2 est refusé, comme une géométrie de révolution.

Théorie

Le long de l’axe \(s\) de la barre, de section \(A\) :

\[ N = E\,A\,\varepsilon, \qquad \varepsilon = \frac{du_s}{ds}, \qquad \frac{dN}{ds} + f = 0 . \]

La direction de la barre est le vecteur unitaire \(c = (x_B - x_A)/L\), tiré des coordonnées des nœuds. Avec l’interpolation linéaire, la déformation est constante dans l’élément, et la raideur s’écrit directement dans les axes globaux, sans matrice de repère :

\[ K_e = \frac{E\,A}{L} \begin{bmatrix} c\,c^{\mathsf T} & -c\,c^{\mathsf T} \\ -c\,c^{\mathsf T} & c\,c^{\mathsf T} \end{bmatrix}. \]

En 1-D, \(c = 1\) et l’on retrouve \((EA/L)\,[[1, -1], [-1, 1]]\). La masse est \((\rho A L/6)\,[[2, 1], [1, 2]]\) sur chaque direction, et la raideur géométrique sous l’effort \(N\) vaut \((N/L)\,[[I, -I], [-I, I]]\) : elle raidit toutes les directions, l’axiale comprise.

Avec strain_measure="green_lagrange", la barre suit les grands déplacements : voir Grandes transformations.

Variables

Selon la dimension des Coords :

1-D2-D3-D
primalesu_xu_x, u_yu_x, u_y, u_z
dualesf_xf_x, f_yf_x, f_y, f_z
entrée du comportementeps_xxeps_xx, eps_yy, eps_xyeps_xx, eps_yy, eps_zz, eps_yz, eps_xz, eps_xy
sortie du comportementnnn

n est l’effort axial \(E\,A\,c^{\mathsf T}\varepsilon\,c\), lu le long de la barre dans la déformation du point.

matériau requisE, A
matériau facultatifrho
étataucun

Ce qu’elle produit

raideur\(K_e\)
masseexige rho
géométriquesous l’effort n
tangente= raideur
forces internes\(n\,c\) aux deux nœuds, de signes opposés : le même \(B\) que la raideur

Exemple

Une barre de longueur 2, de module 200 000 et de section 0,01, tirée par une force de 1000 : \(u = F\,L/(E\,A) = 1\).

import pyrucast

# Une barre de longueur 2, fixée à gauche, guidée en y à droite, tirée par F = 1000.
c = pyrucast.Coords(2)
a = c.add_node([0.0, 0.0])
b = c.add_node([2.0, 0.0])
ligne = pyrucast.mesh.line(a, b, 1)
barre = pyrucast.model.truss(pyrucast.FiniteElementSpace(ligne))
appuis = (
    pyrucast.model.dirichlet(pyrucast.mesh.poi1_from_nodes([a]), barre, "u_x")
    | pyrucast.model.dirichlet(pyrucast.mesh.poi1_from_nodes([a]), barre, "u_y")
    | pyrucast.model.dirichlet(pyrucast.mesh.poi1_from_nodes([b]), barre, "u_y")
)
modele = barre | appuis
materiaux = pyrucast.element_field.material_field(modele, [("E", 200e3), ("A", 0.01)])

force = pyrucast.NodeField(ligne, ["f_x"])
force.unit().set_value(b, "f_x", 1000.0)
u = pyrucast.matrix.stiffness(modele, materiaux).solve(force)  # valeurs imposées nulles
print(u.value(b, "u_x"))  # 1.0 = F·L/(E·A)

deformation = pyrucast.element_field.strain(modele, u, materiaux)
repos = pyrucast.element_field.initial_state(
    modele, materiaux
)  # l'état de départ de la loi
effort = pyrucast.element_field.integrate_behavior(
    modele, deformation, materiaux, repos
)
print(effort.components(), effort[0].value(0, 0, "n"))  # ['n'] 1000.0

Côté Rust

Truss (src/models/truss.rs) calcule ses matrices par des fonctions pures, element_stiffness, element_mass et element_geometric, et ses forces internes par internal_force_element, qui applique le même \(B\) que la raideur : pour une loi linéaire, \(\int B^{\mathsf T}\sigma\) vaut exactement \(K\,u\).

use pyrucast::aggregate::Aggregate;
use pyrucast::atoms::{ElementType, Node};
use pyrucast::containers::finite_element_space::FiniteElementSpace;
use pyrucast::containers::mesh::{Mesh, SubMesh};
use pyrucast::containers::model::Model;
use pyrucast::containers::node_field::{NodeField, SubNodeField};
use pyrucast::coords::Coords;
use pyrucast::handle::Handle;
use pyrucast::ops::element_field::StrainMeasure;
use pyrucast::ops::model;
use pyrucast::ops::solver::solve;
use pyrucast::Result;

#[test]
fn truss_bar_recovers_axial_elongation() -> Result<()> {
    const E: f64 = 210.0e9; // Young's modulus (Pa)
    const A: f64 = 1.0e-4; // section area (m²)
    const L: f64 = 2.0; // length (m)
    const F: f64 = 1000.0; // axial force at the right end (N)

    // ── Mesh: one horizontal SEG2 bar ──────────────────────────────────────
    let coords = Handle::new(Coords::new(2)?);
    let n0 = Node::create_in(coords.clone(), &[0.0, 0.0])?;
    let n1 = Node::create_in(coords.clone(), &[L, 0.0])?;
    let mut mesh = Mesh::from_submesh(SubMesh::new(coords.clone(), ElementType::SEG2));
    mesh.add_cell(&[n0.id(), n1.id()])?;
    let fes = FiniteElementSpace::lagrange1(&mesh)?;

    // ── Modèle : barre + appuis (Dirichlet homogènes) ──────────────────────
    // Homogeneous (u = 0) BCs: the imposed value defaults to 0, so we only need
    // to introduce the constraint. A bar has no transverse stiffness, hence
    // `u_y` is clamped at both nodes to make the system well-posed.
    let clamp = |target: &Model, node: &Node, var: &str| -> Result<Model> {
        let imposed = Mesh::from_submesh(SubMesh::poi1_from_nodes(std::slice::from_ref(node))?);
        model::dirichlet(&imposed, target, var, Default::default())
    };
    let mut model = model::truss(&fes, StrainMeasure::Linear)?;
    model = model.union(&clamp(&model, &n0, "u_x")?)?;
    model = model.union(&clamp(&model, &n0, "u_y")?)?;
    model = model.union(&clamp(&model, &n1, "u_y")?)?;

    // ── Matériau E, A (Dirichlet ignoré automatiquement) ───────────────────
    let materials = pyrucast::ops::element_field::material_field(&model, &[("E", E), ("A", A)])?;

    // ── Loading: axial force F at the right node ───────────────────────────
    let mut load_sm = SubMesh::new(coords.clone(), ElementType::POI1);
    load_sm.add_cell(&[n1.id()])?;
    let load_sm = Handle::new(load_sm);
    let mut rhs = SubNodeField::from_poi1(&load_sm, vec!["f_x".into()])?;
    rhs.set_value(n1.id(), "f_x", F)?;
    let rhs = NodeField::from_sub(rhs);

    // ── Assemblage + résolution ────────────────────────────────────────────
    let stiffness = pyrucast::ops::matrix::stiffness(&model, &materials)?;
    let solution = solve(&stiffness, &rhs)?;

    // ── Comparaison à l'analytique : u_x = F·L / (E·A) ─────────────────────
    let expected = F * L / (E * A);
    let ux = solution.value(n1.id(), "u_x")?;
    assert!(
        (ux - expected).abs() < 1e-10 * expected,
        "u_x = {ux}, attendu {expected}"
    );
    // The left node is clamped, the right end does not move transversally.
    assert!(solution.value(n0.id(), "u_x")?.abs() < 1e-18);
    assert!(solution.value(n1.id(), "u_y")?.abs() < 1e-18);

    Ok(())
}