Transient analysis#
femorph_solver.solvers.transient.solve_transient() integrates the
structural equation of motion
over a uniform timestep grid using the Newmark-β scheme. With the
default parameters \(\beta = 1/4,\ \gamma = 1/2\) the integrator
reduces to the unconditionally-stable average-acceleration method; any
consistent pair (β, γ) can be used.
Algorithm#
Newmark-β updates:
Rearranging the equation of motion at \(t_{n+1}\) gives an effective linear system
with \(A = a_0 M + a_1 C + K\) and \(a_0 = 1/(\beta \Delta t^2)\), \(a_1 = \gamma / (\beta \Delta t)\). \(A\) is factored once outside the loop; every subsequent time step is a cheap back-solve.
API#
from femorph_solver.solvers.transient import solve_transient
K = m.stiffness_matrix()
M = m.mass_matrix()
# Constant step load (no damping):
result = solve_transient(
K, M, F=load_vector,
dt=1e-4,
n_steps=1000,
prescribed={d: 0.0 for d in fixed_dofs},
)
result.time # (n_steps + 1,) time grid
result.displacement # (n_steps + 1, N)
result.velocity # (n_steps + 1, N)
result.acceleration # (n_steps + 1, N)
Time-varying loads pass a callable instead of an array:
def load(t):
return force_template * np.sin(2 * np.pi * 50 * t)
result = solve_transient(K, M, F=load, dt=1e-4, n_steps=2000,
prescribed=prescribed)
Damping#
Pass a sparse damping matrix C if you have one:
C = 0.01 * M + 0.001 * K # Rayleigh damping
result = solve_transient(K, M, F=load, dt=1e-4, n_steps=1000, C=C,
prescribed=prescribed)
Without a C, the integrator runs the undamped form. femorph-solver
does not (yet) synthesise damping from material records; build the
matrix yourself with whatever model is appropriate.
Initial conditions#
u0 and v0 set the starting displacement / velocity. Omitting
them zero-initialises both. The initial acceleration is derived from
the equation of motion at \(t=0\) so M ü₀ = F(0) - K u₀ - C v₀
is satisfied exactly.
Explicit central-difference#
Setting beta=0, gamma=0.5 would give the explicit central-difference
method, but the current implementation factors \(A\) with
scipy.sparse.linalg.splu and thus requires \(\beta > 0\).
Explicit paths need a separate mass-factoring code path not yet
implemented.
Thread control#
thread_limit wraps both the one-off \(A\) factorisation and
every back-solve. See Thread control.
Stability notes#
Unconditional stability only applies to \(\gamma \geq 1/2\) and \(\beta \geq (1/4)(1/2 + \gamma)^2\). The default \((1/4, 1/2)\) sits exactly on that bound.
Numerical damping appears if \(\gamma > 1/2\). The trapezoid variant \((1/4, 1/2)\) is energy-conservative on linear problems.
See also#
Static analysis — the steady-state limit \((\dot u = \ddot u = 0)\).
Modal analysis — free-vibration eigenmodes of \((K, M)\).
Solvers — which sparse backend factors \(A\).