Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 17 additions & 8 deletions examples/Freefem/MMS/1D/bar.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,20 @@ def compute(nodes, topology):
return compute


def build_bar_scene(root, mms, E_eff, nx):
def build_bar_scene(root, mms, E_eff, nx, force_field, linear_solver):
"""Populate root with a static 1D bar scene on the non-dimensional domain [0,1].

BodyForce is assembled after init by BodyForceAssembler. BCs: Dirichlet at
x=0, Neumann `mms.traction_bc(E_eff)` at x=1.

force_field : name of the FEM force field to test
linear_solver : dict {"type": <name>, "parameters": {...}} of the linear solver
"""
root.addObject('RequiredPlugin', pluginName=[
"Elasticity",
"Sofa.Component.Constraint.Projective",
"Sofa.Component.LinearSolver.Direct",
"Sofa.Component.LinearSolver.Iterative",
"Sofa.Component.MechanicalLoad",
"Sofa.Component.ODESolver.Backward",
"Sofa.Component.StateContainer",
Expand All @@ -87,9 +91,9 @@ def build_bar_scene(root, mms, E_eff, nx):
maxNbIterationsNewton=10,
absoluteResidualStoppingThreshold=1e-10,
printLog=False)
Bar.addObject('SparseLDLSolver',
Bar.addObject(linear_solver["type"],
name="linearSolver",
template="CompressedRowSparseMatrixd")
**linear_solver["parameters"])
Bar.addObject('StaticSolver',
name="staticSolver",
newtonSolver="@newtonSolver",
Expand All @@ -103,7 +107,7 @@ def build_bar_scene(root, mms, E_eff, nx):
name="dofs",
template="Vec1d")

Bar.addObject('LinearSmallStrainFEMForceField',
Bar.addObject(force_field,
name="FEM",
template="Vec1d",
youngModulus=E_eff,
Expand All @@ -130,15 +134,18 @@ def case_scene(mms):
"""Return a `createScene(rootNode)` bound to this MMS case."""
def createScene(rootNode):
cfg = load_params()
build_bar_scene(rootNode, mms, cfg["E_eff"], cfg["reference"]["nx"])
build_bar_scene(rootNode, mms, cfg["E_eff"], cfg["reference"]["nx"],
force_field=cfg["forceField"],
linear_solver=cfg["linearSolver"])
return rootNode
return createScene


def solve_bar(mms, E_eff, nx):
def solve_bar(mms, E_eff, nx, force_field, linear_solver):
"""Build, run one static step, and return a BarSolution1D snapshot."""
root = Sofa.Core.Node("root")
build_bar_scene(root, mms, E_eff, nx)
build_bar_scene(root, mms, E_eff, nx, force_field=force_field,
linear_solver=linear_solver)
Sofa.Simulation.init(root)
Sofa.Simulation.animate(root, root.dt.value)
Bar = root.Bar
Expand Down Expand Up @@ -180,7 +187,9 @@ def plot_solution(case, x, u_h, u_ex, label_ex):
def run_reference_scene(mms):
"""Solve one MMS case at the reference mesh, write the solution table and plot."""
cfg = load_params()
sol = solve_bar(mms, cfg["E_eff"], cfg["reference"]["nx"])
sol = solve_bar(mms, cfg["E_eff"], cfg["reference"]["nx"],
force_field=cfg["forceField"],
linear_solver=cfg["linearSolver"])
l2 = l2_error_1d(sol.x0, sol.edges, sol.u_h, mms.u_ex, L2_QUADRATURE_1D)
h1 = h1_semi_error_1d(sol.x0, sol.edges, sol.u_h, mms.du_ex, H1_QUADRATURE_1D)
write_solution_table(f"solution_{mms.name}", sol.x0, sol.u_h, mms.u_ex,
Expand Down
7 changes: 7 additions & 0 deletions examples/Freefem/MMS/1D/params.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
{
"length": 2.0,
"youngModulus": 1e6,
"forceField": "LinearSmallStrainFEMForceField",
"linearSolver": {
"type": "SparseLDLSolver",
"parameters": {
"template": "CompressedRowSparseMatrixd"
}
},
"reference": {
"nx": 10
},
Expand Down
4 changes: 3 additions & 1 deletion examples/Freefem/MMS/1D/run_convergence.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
stem = f"{mms.name}_convergence"
hs, errors = run_convergence_series(
nx_values = cfg["convergence"]["nx_values"][mms.name],
run_fn = lambda nx, _m=mms: solve_bar(_m, cfg["E_eff"], nx),
run_fn = lambda nx, _m=mms: solve_bar(
_m, cfg["E_eff"], nx, force_field=cfg["forceField"],
linear_solver=cfg["linearSolver"]),
h_fn = lambda nx: 1.0 / (nx - 1),
error_fns = {
"L2": lambda sol, _m=mms: l2_error_1d(
Expand Down
26 changes: 18 additions & 8 deletions examples/Freefem/MMS/2D/beam.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,15 @@ def compute(nodes, topology):
return F_xy
return compute

def build_beam_scene(rootNode, mms, element, L=1.0, E=1e6, nu=0.3,
def build_beam_scene(rootNode, mms, element, force_field, linear_solver,
L=1.0, E=1e6, nu=0.3,
nx=10, ny=10, with_visual=True, dim="2d"):
"""Build a SOFA scene for `mms` on the 2D `element` strategy.

dim : "2d" → Vec2d template / plane stress
"3d" → Vec3d template / plane strain (z coordinate fixed at 0)
force_field : name of the FEM force field to test
linear_solver : dict {"type": <name>, "parameters": {...}} of the linear solver
Returns (dofs, topology). Nodes and connectivity become available
after `Sofa.Simulation.init(root)` runs, via
`dofs.rest_position.array()` and `element.read_connectivity(topology)`.
Expand All @@ -83,6 +86,7 @@ def build_beam_scene(rootNode, mms, element, L=1.0, E=1e6, nu=0.3,
"Sofa.Component.Constraint.Projective",
"Sofa.Component.Engine.Select",
"Sofa.Component.LinearSolver.Direct",
"Sofa.Component.LinearSolver.Iterative",
"Sofa.Component.MechanicalLoad",
"Sofa.Component.ODESolver.Backward",
"Sofa.Component.StateContainer",
Expand Down Expand Up @@ -116,16 +120,16 @@ def build_beam_scene(rootNode, mms, element, L=1.0, E=1e6, nu=0.3,
maxNbIterationsNewton=1,
absoluteResidualStoppingThreshold=1e-10,
printLog=False)
Beam.addObject("SparseLDLSolver", name="linearSolver",
template="CompressedRowSparseMatrixd")
Beam.addObject(linear_solver["type"], name="linearSolver",
**linear_solver["parameters"])

dofs = Beam.addObject("MechanicalObject", name="dofs", template=tmpl,
position=nodes_2d.tolist(),
showObject=with_visual, showObjectScale=0.005 * L)

topology = element.add_topology(Beam)

Beam.addObject("LinearSmallStrainFEMForceField", name="FEM", template=tmpl,
Beam.addObject(force_field, name="FEM", template=tmpl,
youngModulus=E, poissonRatio=nu, topology="@topology")

mms.apply_bcs(Beam, nodes_2d, L, dim)
Expand All @@ -150,11 +154,12 @@ def build_beam_scene(rootNode, mms, element, L=1.0, E=1e6, nu=0.3,
# Simulation runner
# ─────────────────────────────────────────────────────────────────────────────

def solve_beam(elem, mms, L, E, nu, nx, ny, dim="2d"):
def solve_beam(elem, mms, L, E, nu, nx, ny, force_field, linear_solver, dim="2d"):
"""Build, init, and run one static step. Returns a BeamSolution2D snapshot."""
root = Sofa.Core.Node("root")
dofs, topology = build_beam_scene(
root, mms, elem, L=L, E=E, nu=nu, nx=nx, ny=ny, with_visual=False, dim=dim
root, mms, elem, L=L, E=E, nu=nu, nx=nx, ny=ny, with_visual=False, dim=dim,
force_field=force_field, linear_solver=linear_solver
)
Sofa.Simulation.init(root)
# Read topology back from SOFA now that init has populated it.
Expand Down Expand Up @@ -246,9 +251,12 @@ def run_reference_scene(elem, mms):
L, E = cfg["length"], cfg["youngModulus"]
nu, dim = ref["nu"], ref["dim"]
nx = ny = ref["nx"]
ff = cfg["forceField"]
ls = cfg["linearSolver"]
hyp = "plane strain" if dim == "3d" else "plane stress"

sol = solve_beam(elem, mms, L, E, nu, nx, ny, dim=dim)
sol = solve_beam(elem, mms, L, E, nu, nx, ny,
force_field=ff, linear_solver=ls, dim=dim)
l2 = elem.compute_l2(sol, mms, L)
h1 = elem.compute_h1(sol, mms, L)

Expand Down Expand Up @@ -281,6 +289,8 @@ def createScene(rootNode):
build_beam_scene(rootNode, mms, element,
L=cfg["length"], E=cfg["youngModulus"],
nu=ref["nu"], nx=ref["nx"], ny=ref["nx"],
with_visual=True, dim=ref["dim"])
with_visual=True, dim=ref["dim"],
force_field=cfg["forceField"],
linear_solver=cfg["linearSolver"])
return rootNode
return createScene
9 changes: 8 additions & 1 deletion examples/Freefem/MMS/2D/params.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
{
"length": 1.0,
"youngModulus": 1e6,
"forceField": "LinearSmallStrainFEMForceField",
"linearSolver": {
"type": "SparseLDLSolver",
"parameters": {
"template": "CompressedRowSparseMatrixd"
}
},
"reference": {
"nx": 20,
"nx": 5,
"nu": 0.0,
"dim": "2d"
},
Expand Down
13 changes: 10 additions & 3 deletions examples/Freefem/MMS/2D/run_convergence.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,17 @@
from output import plot_convergence


def convergence_study(elem_specs, mms, L, E, nu, nx_values, dim="2d"):
def convergence_study(elem_specs, mms, L, E, nu, nx_values, force_field,
linear_solver, dim="2d"):
"""
Run a convergence series for each element type in elem_specs, write a
per-(element) text table, and one shared plot with L²/H¹ for every
element on the same axes.

elem_specs : list of dicts with keys 'elem', 'label', 'l2_style', 'h1_style'
dim : "2d" (plane stress) or "3d" (plane strain)
force_field : name of the FEM force field to test
linear_solver : dict {"type": <name>, "parameters": {...}} of the linear solver
"""
hyp = "plane strain" if dim == "3d" else "plane stress"
print(f"\n PoissonRatio = {nu} ({hyp})", flush=True)
Expand All @@ -40,7 +43,8 @@ def convergence_study(elem_specs, mms, L, E, nu, nx_values, dim="2d"):
hs, errors = run_convergence_series(
nx_values = nx_values,
run_fn = lambda nx, _e=elem: solve_beam(
_e, mms, L, E, nu, nx, nx, dim=dim),
_e, mms, L, E, nu, nx, nx,
force_field=force_field, linear_solver=linear_solver, dim=dim),
h_fn = lambda nx: L / (nx - 1),
error_fns = {
"L2": lambda sol, _e=elem: _e.compute_l2(sol, mms, L),
Expand All @@ -66,6 +70,8 @@ def convergence_study(elem_specs, mms, L, E, nu, nx_values, dim="2d"):
cfg = load_params()
L = cfg["length"]
E = cfg["youngModulus"]
ff = cfg["forceField"]
ls = cfg["linearSolver"]
conv = cfg["convergence"]

specs = [
Expand All @@ -81,4 +87,5 @@ def convergence_study(elem_specs, mms, L, E, nu, nx_values, dim="2d"):
print(f"\n== {mms.name} ==")
for DIM in conv["dim_values"]:
for nu in conv["nu_values"]:
convergence_study(specs, mms, L, E, nu, nx_vals, dim=DIM)
convergence_study(specs, mms, L, E, nu, nx_vals,
force_field=ff, linear_solver=ls, dim=DIM)
Loading
Loading