easyfea 3.2.1__tar.gz → 3.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/Operators/NonLinear.py +30 -31
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Simulations/_hyperelastic.py +39 -19
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Simulations/_phasefield.py +11 -5
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Simulations/_simu.py +8 -3
- {easyfea-3.2.1/easyfea.egg-info → easyfea-3.2.2}/PKG-INFO +1 -1
- {easyfea-3.2.1 → easyfea-3.2.2/easyfea.egg-info}/PKG-INFO +1 -1
- {easyfea-3.2.1 → easyfea-3.2.2}/pyproject.toml +1 -1
- {easyfea-3.2.1 → easyfea-3.2.2}/AUTHORS.md +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/Elems/__init__.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/Elems/_beam.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/Elems/_hexa.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/Elems/_point.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/Elems/_prism.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/Elems/_quad.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/Elems/_seg.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/Elems/_tetra.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/Elems/_tri.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/Operators/Bilinear.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/Operators/Linear.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/Operators/__init__.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/__init__.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/_boundary_conditions.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/_field.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/_forms.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/_gauss.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/_group_elem.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/_linalg.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/_mesh.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/_mesher.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/_utils.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Geoms/__init__.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Geoms/_circle.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Geoms/_contour.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Geoms/_domain.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Geoms/_geom.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Geoms/_line.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Geoms/_points.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Geoms/_utils.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Models/Beam/__init__.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Models/Beam/_beam.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Models/Elastic/__init__.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Models/Elastic/_laws.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Models/HyperElastic/__init__.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Models/HyperElastic/_laws.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Models/HyperElastic/_state.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Models/__init__.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Models/_phasefield.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Models/_thermal.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Models/_utils.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Models/_weakforms.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Simulations/Solvers.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Simulations/__init__.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Simulations/_beam.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Simulations/_dic.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Simulations/_elastic.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Simulations/_thermal.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Simulations/_utils.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Simulations/_weakforms.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/Folder.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/GLTF.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/Matplotlib.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/MeshIO.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/Paraview.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/PyVista.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/Terminal.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/USD.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/Vizir.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/__init__.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/_cache.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/_mpi.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/_observers.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/_params.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/_requires.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/_tic.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/_types.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/__about__.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/__init__.py +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/LICENSE.txt +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/README.md +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/easyfea.egg-info/SOURCES.txt +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/easyfea.egg-info/dependency_links.txt +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/easyfea.egg-info/requires.txt +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/easyfea.egg-info/top_level.txt +0 -0
- {easyfea-3.2.1 → easyfea-3.2.2}/setup.cfg +0 -0
|
@@ -397,14 +397,13 @@ def __AdaptiveTimeQuadratureStressTensor(
|
|
|
397
397
|
material: "_HyperElastic",
|
|
398
398
|
state_n: "HyperElasticState",
|
|
399
399
|
state_np1: "HyperElasticState",
|
|
400
|
+
coefK: float,
|
|
400
401
|
tol: float,
|
|
401
402
|
maxPoints: int,
|
|
402
403
|
) -> tuple["FeArray", "FeArray", int]:
|
|
403
404
|
r"""Per-element adaptive strain-path quadrature — the ``tol``-driven path of :func:`TimeQuadratureStressTensor`.
|
|
404
405
|
|
|
405
|
-
Each element refines along the nested chain ``1, 3, 5, 9, …`` until *its own* energy defect is within ``tol
|
|
406
|
-
|
|
407
|
-
Only the still-active elements are evaluated at each level — the rule is applied to that subset via :meth:`_StrainPathState._sliced` — so the constitutive cost tracks the hard elements, not the mesh. (Nodes shared with a coarser level are re-evaluated rather than cached; the active set shrinks fast, so that stays cheap and keeps the loop plain.) Returns ``(dWde_quad, d2Wde_quad)`` — each row carrying its element's accepted rule — and ``nPts_e``, the point count each element accepted.
|
|
406
|
+
Each element refines along the nested chain ``1, 3, 5, 9, …`` (capped at ``maxPoints``) until *its own* integrated energy defect is within ``tol`` — ``Σ_p V_(ep) |S:Δe − ΔW| ≤ tol · Σ_p V_(ep) |ΔW|``, ``V_(ep)`` the Gauss-point volume — then freezes, so points are spent only where the step is nonlinear. The defect is the quadrature error of the exact identity ``S:Δe = ΔW`` (``ΔW`` known from the endpoints), so the test is absolute; taking ``|·|`` before summing bounds the element's real energy drift and is safe against Gauss-point sign cancellation. Only still-active elements are evaluated at each level (via :meth:`_StrainPathState._sliced`), so cost tracks the hard elements. Returns ``(dWde_quad, d2Wde_quad, nPts_e)``, each row carrying its element's accepted rule and point count.
|
|
408
407
|
"""
|
|
409
408
|
groupElem = state_n.groupElem # state_n and state_np1 share the group
|
|
410
409
|
dim = groupElem.dim
|
|
@@ -449,10 +448,10 @@ def __AdaptiveTimeQuadratureStressTensor(
|
|
|
449
448
|
if isAccepted.any():
|
|
450
449
|
acceptedElems = activeElements[isAccepted] # elements accepting this rule
|
|
451
450
|
dWde_quad[acceptedElems] = S[isAccepted]
|
|
452
|
-
# their tangent only: Σ_k
|
|
451
|
+
# their tangent only: Σ_k (w_k s_k / coefK) d2Wde, s=0 drops out (∂e/∂u = s B)
|
|
453
452
|
d2Wde_quad[acceptedElems] = sum(
|
|
454
453
|
(
|
|
455
|
-
(
|
|
454
|
+
(w * s / coefK) * material.Compute_d2Wde(at(s, acceptedElems))
|
|
456
455
|
for s, w in zip(nodes, weights)
|
|
457
456
|
if s
|
|
458
457
|
),
|
|
@@ -468,46 +467,46 @@ def __AdaptiveTimeQuadratureStressTensor(
|
|
|
468
467
|
def TimeQuadratureStressTensor(
|
|
469
468
|
material: "_HyperElastic",
|
|
470
469
|
state_n: "HyperElasticState",
|
|
471
|
-
|
|
470
|
+
state_t: "HyperElasticState",
|
|
472
471
|
state_np1: "HyperElasticState",
|
|
472
|
+
coefK: float,
|
|
473
473
|
nPoints: int,
|
|
474
474
|
tol: Optional[float] = None,
|
|
475
475
|
maxPoints: int = 33,
|
|
476
476
|
) -> tuple[np.ndarray, np.ndarray, int]:
|
|
477
477
|
r"""Tangent and residual for the PK2 stress **averaged along the strain path** of the step.
|
|
478
478
|
|
|
479
|
-
Returns ``(K_e, R_e)`` in ``(xi,yi,zi,...,xn,yn,zn)``. Where :func:`SecondPiolaKirchhoffStressTensor` samples the stress at one configuration, this
|
|
479
|
+
Returns ``(K_e, R_e)`` in ``(xi,yi,zi,...,xn,yn,zn)``. Where :func:`SecondPiolaKirchhoffStressTensor` samples the stress at one configuration, this averages it along the segment between the two end strains and tests it against ``B(u_t)``::
|
|
480
480
|
|
|
481
481
|
e(s) = e_n + s Δe , Δe = e_{n+1} - e_n , s ∈ [0, 1]
|
|
482
|
-
|
|
483
482
|
S_quad = ∫₀¹ ∂W/∂e(e(s)) ds ≈ Σ_k w_k ∂W/∂e(e(s_k))
|
|
484
|
-
R_e = ∫ B(
|
|
485
|
-
|
|
486
|
-
``(s_k, w_k)`` is the Clenshaw-Curtis rule on ``nPoints`` points (:func:`__clenshaw_curtis`); ``1, 2, 3`` are the midpoint, trapezoid and Simpson rules. Intermediate nodes are :class:`_StrainPathState`; ``s = 0, 1`` reuse the end states.
|
|
487
|
-
|
|
488
|
-
**Adaptive (per-element) mode.** With ``tol`` set, the rule is chosen *element by element* by :func:`__AdaptiveTimeQuadratureStressTensor`: each element walks the nested chain ``1, 3, 5, 9, 17, 33`` (up to ``maxPoints``) and freezes once *its own* energy defect is within ``tol``, so points are spent only where the step is nonlinear — a low-strain element may stop at a single midpoint while a stiff one keeps refining. The defect ``S_quad:Δe − ΔW`` is the quadrature error of the discrete-gradient identity ``S_quad:Δe = ΔW`` (with ``ΔW`` *known* from the endpoints), so the test is absolute — no consecutive-difference guess. It is the **integrated** relative error over the element, ``∫_Ωe |S_quad:Δe − ΔW| dΩ ≤ tol · ∫_Ωe |ΔW| dΩ`` — the integrals are the volume-weighted Gauss-point sums ``Σ_p V_(ep)···``, *not* a pointwise energy-density comparison — so ``tol`` reads as "conserve energy to this relative tolerance". Taking the absolute value before summing bounds the element's actual energy drift directly and guards against sign cancellation between Gauss points. Since each level is scored on its own (no comparison to a coarser one) the test accepts the coarsest rule directly: the ``1``-point midpoint is exact for a linear energy integrand, so a quadratic ``W`` converges at a single point. The tangent uses each element's accepted rule, so residual and tangent stay consistent.
|
|
483
|
+
R_e = ∫ B(u_t)ᵀ · S_quad dΩ
|
|
489
484
|
|
|
490
|
-
|
|
485
|
+
``(s_k, w_k)`` is the Clenshaw-Curtis rule on ``nPoints`` points; ``1, 2, 3`` are the midpoint, trapezoid and Simpson rules (``nPoints = 1`` is the *average-strain* stress, not the midpoint-displacement one). At midpoint ``Δe = B(ū)·Δu`` exactly, so ``S_quad:Δe = ΔW`` once the ``s``-integral is exact — a **discrete gradient** whose energy defect is just the quadrature error, driven down spectrally (a quadratic ``W`` is exact at every rule). With ``tol`` set the rule is refined *per element* by :func:`__AdaptiveTimeQuadratureStressTensor`.
|
|
491
486
|
|
|
492
|
-
Tangent — only ``e_{n+1}`` depends on ``u_{n+1}
|
|
487
|
+
Tangent — only ``e_{n+1}`` depends on ``u_{n+1}`` (``∂e(s_k)/∂u_{n+1} = s_k B_{n+1}``), so::
|
|
493
488
|
|
|
494
|
-
coefK · K_e =
|
|
495
|
-
+ ∫ B(ū)ᵀ [ Σ_k w_k s_k ℂ(e(s_k)) ] B_{n+1} dΩ # pre-doubled
|
|
489
|
+
coefK · K_e = coefK · A_geo(state_t, S_quad) + ∫ B(u_t)ᵀ [ Σ_k w_k s_k ℂ(e(s_k)) ] B_{n+1} dΩ
|
|
496
490
|
|
|
497
|
-
|
|
491
|
+
The geometric block is raw so it rides the caller's ``coefK`` chain factor ``∂u_t/∂u_{n+1}``; the material term is pre-scaled by ``1/coefK`` to survive it. Pairing ``B(u_t)`` with ``B_{n+1}`` makes ``K_e`` non-symmetric.
|
|
498
492
|
|
|
499
493
|
Parameters
|
|
500
494
|
----------
|
|
501
495
|
material
|
|
502
496
|
Hyperelastic constitutive law — supplies ``Compute_dWde(state)`` and ``Compute_d2Wde(state)``.
|
|
503
|
-
state_n,
|
|
504
|
-
Hyperelastic states at ``u_n``,
|
|
497
|
+
state_n, state_t, state_np1
|
|
498
|
+
Hyperelastic states at ``u_n``, the time-scheme base displacement ``u_t``, and ``u_{n+1}`` (same group / matrix type).
|
|
499
|
+
coefK
|
|
500
|
+
Time-scheme K-coefficient ``= ∂u_t/∂u_{n+1}`` for the base displacement ``u_t`` passed as
|
|
501
|
+
``state_t`` — ``0.5`` for :attr:`~EasyFEA.AlgoType.midpoint` (the only value that conserves energy),
|
|
502
|
+
``1`` for :attr:`~EasyFEA.AlgoType.newmark`, ``1−α`` for :attr:`~EasyFEA.AlgoType.hht`. Other schemes
|
|
503
|
+
give a consistent but non-conserving stress.
|
|
505
504
|
nPoints
|
|
506
505
|
Number of Clenshaw-Curtis points for the fixed rule (``tol is None``). Ignored when
|
|
507
506
|
adaptive, which always starts from 1 (the midpoint).
|
|
508
507
|
tol
|
|
509
508
|
If set, refine adaptively (see above) until the relative energy defect
|
|
510
|
-
|
|
509
|
+
``|S_quad:Δe − ΔW| / |ΔW|`` falls below ``tol``. ``None`` (default) keeps the fixed
|
|
511
510
|
``nPoints`` rule.
|
|
512
511
|
maxPoints
|
|
513
512
|
Adaptive only: cap on the number of points, by default 33. Refinement stops here even
|
|
@@ -516,7 +515,7 @@ def TimeQuadratureStressTensor(
|
|
|
516
515
|
Returns
|
|
517
516
|
-------
|
|
518
517
|
K_e : ndarray of shape ``(Ne, nPe·dim, nPe·dim)``
|
|
519
|
-
Consistent tangent, built
|
|
518
|
+
Consistent tangent, built so ``coefK · K_e = ∂R_e/∂u_{n+1}``.
|
|
520
519
|
R_e : ndarray of shape ``(Ne, nPe·dim)``
|
|
521
520
|
Internal residual force.
|
|
522
521
|
nPts_e : ndarray of shape ``(Ne,)``
|
|
@@ -524,12 +523,12 @@ def TimeQuadratureStressTensor(
|
|
|
524
523
|
when adaptive.
|
|
525
524
|
"""
|
|
526
525
|
|
|
527
|
-
groupElem =
|
|
528
|
-
wJ_e_pg = groupElem.Get_weightedJacobian_e_pg(
|
|
526
|
+
groupElem = state_t.groupElem
|
|
527
|
+
wJ_e_pg = groupElem.Get_weightedJacobian_e_pg(state_t.matrixType)
|
|
529
528
|
nPe = groupElem.nPe
|
|
530
529
|
dim = groupElem.dim
|
|
531
530
|
|
|
532
|
-
_,
|
|
531
|
+
_, B_t = __block_grad_B(state_t)
|
|
533
532
|
_, B_np1 = __block_grad_B(state_np1)
|
|
534
533
|
|
|
535
534
|
if tol is None:
|
|
@@ -547,7 +546,7 @@ def TimeQuadratureStressTensor(
|
|
|
547
546
|
state = _StrainPathState(state_n, state_np1, s)
|
|
548
547
|
dWde_quad += w * material.Compute_dWde(state)
|
|
549
548
|
if s != 0.0:
|
|
550
|
-
d2Wde_quad += (
|
|
549
|
+
d2Wde_quad += (w * s / coefK) * material.Compute_d2Wde(state)
|
|
551
550
|
nPts_e = np.full(groupElem.Ne, int(nPoints)) # every element uses the same rule
|
|
552
551
|
else:
|
|
553
552
|
# Adaptive: refine per element on the *energy defect*. This stress exists so that
|
|
@@ -557,15 +556,15 @@ def TimeQuadratureStressTensor(
|
|
|
557
556
|
# Refining element-by-element rather than the whole block spends points only where the
|
|
558
557
|
# material is nonlinear over the step (see __AdaptiveTimeQuadratureStressTensor).
|
|
559
558
|
dWde_quad, d2Wde_quad, nPts_e = __AdaptiveTimeQuadratureStressTensor(
|
|
560
|
-
material, state_n, state_np1, tol, int(maxPoints)
|
|
559
|
+
material, state_n, state_np1, coefK, tol, int(maxPoints)
|
|
561
560
|
)
|
|
562
561
|
|
|
563
562
|
# not __second_piola_block: that pairs one B with itself, while here the tangent is
|
|
564
|
-
# differentiated at u_{n+1} but tested against B(
|
|
565
|
-
residual_e = einsum("ep,epi,epij->ej", wJ_e_pg, dWde_quad,
|
|
563
|
+
# differentiated at u_{n+1} but tested against B(u_t) — hence the non-symmetry.
|
|
564
|
+
residual_e = einsum("ep,epi,epij->ej", wJ_e_pg, dWde_quad, B_t)
|
|
566
565
|
tangent_e = einsum(
|
|
567
|
-
"ep,epji,epjk,epkl->eil", wJ_e_pg,
|
|
568
|
-
) + __geometric_tangent(wJ_e_pg,
|
|
566
|
+
"ep,epji,epjk,epkl->eil", wJ_e_pg, B_t, d2Wde_quad, B_np1
|
|
567
|
+
) + __geometric_tangent(wJ_e_pg, state_t, dWde_quad)
|
|
569
568
|
|
|
570
569
|
if dim == 2:
|
|
571
570
|
thickness = material.thickness
|
|
@@ -181,9 +181,11 @@ class HyperElastic(_Simu):
|
|
|
181
181
|
total energy :math:`\mathrm{KE} + W` is conserved. See :class:`StressType` for the three
|
|
182
182
|
options and the operators for their construction.
|
|
183
183
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
184
|
+
Energy conservation for both non-default stresses rests on the midpoint identity
|
|
185
|
+
:math:`\Delta \eb = \Brm(\bar{\ub}) \cdot \Delta \ub`, so it holds only under
|
|
186
|
+
:attr:`~EasyFEA.AlgoType.midpoint`. ``gonzalez`` is intrinsically a midpoint discrete gradient and is
|
|
187
|
+
rejected off midpoint. ``quadrature`` also runs under any dynamic scheme — it scales its tangent by
|
|
188
|
+
that scheme's ``coefK`` and stays Newton-consistent, but conserves energy only at midpoint.
|
|
187
189
|
|
|
188
190
|
Parameters
|
|
189
191
|
----------
|
|
@@ -209,12 +211,15 @@ class HyperElastic(_Simu):
|
|
|
209
211
|
"""
|
|
210
212
|
stressType = HyperElastic.StressType(stressType)
|
|
211
213
|
|
|
212
|
-
if stressType
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
"
|
|
214
|
+
if stressType == HyperElastic.StressType.gonzalez:
|
|
215
|
+
# gonzalez is the midpoint energy-momentum stress: its discrete gradient is built on ū and
|
|
216
|
+
# conserves energy only there — intrinsically midpoint-only.
|
|
217
|
+
assert self.algo == AlgoType.midpoint, (
|
|
218
|
+
f"the 'gonzalez' stress requires AlgoType.midpoint (got {self.algo}). "
|
|
219
|
+
"Call Solver_Set_Hyperbolic_Algorithm(dt, algo=AlgoType.midpoint) first."
|
|
217
220
|
)
|
|
221
|
+
# quadrature works with any dynamic scheme (its tangent is scaled by coefK); the dynamic-scheme
|
|
222
|
+
# requirement is checked at assembly, so the algo need not be set before this call.
|
|
218
223
|
assert nPoints >= 1, f"nPoints must be >= 1 (got {nPoints})."
|
|
219
224
|
|
|
220
225
|
self.__stressParams = (stressType, nPoints, useConsistentTangent, energyTol)
|
|
@@ -254,18 +259,26 @@ class HyperElastic(_Simu):
|
|
|
254
259
|
# (Kelvin–Voigt viscosity) and the acceleration (inertia), both needed by F_e.
|
|
255
260
|
displacement = self._Solver_Get_Newton_Raphson_current_solution()
|
|
256
261
|
velocity = accel = None
|
|
257
|
-
# Both non-default stresses are built from the step endpoints (u_n, u_{n+1}) on top
|
|
258
|
-
#
|
|
259
|
-
#
|
|
260
|
-
#
|
|
261
|
-
# can't leave a stale selection.
|
|
262
|
+
# Both non-default stresses are built from the step endpoints (u_n, u_{n+1}) on top of the scheme's
|
|
263
|
+
# base point u_t (ū at midpoint), so both need u_{n+1} kept before the scheme evaluation overwrites
|
|
264
|
+
# `displacement` with u_t. Re-checked here (not only in the setter) so that re-calling
|
|
265
|
+
# Solver_Set_Hyperbolic_Algorithm with another algo can't leave a stale selection.
|
|
262
266
|
stressType, nPoints, useConsistentTangent, energyTol = (
|
|
263
267
|
self.__Solver_Get_Stress_Params()
|
|
264
268
|
)
|
|
265
269
|
isPointwise = stressType == HyperElastic.StressType.pointwise
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
270
|
+
if stressType == HyperElastic.StressType.gonzalez:
|
|
271
|
+
# gonzalez is the midpoint energy-momentum stress: its discrete gradient Ŝ = S̄ + α Δe is built
|
|
272
|
+
# on ū and conserves energy only there — it is intrinsically midpoint-only.
|
|
273
|
+
assert (
|
|
274
|
+
self.algo == AlgoType.midpoint
|
|
275
|
+
), f"the 'gonzalez' stress requires AlgoType.midpoint (got {self.algo})."
|
|
276
|
+
elif stressType == HyperElastic.StressType.quadrature:
|
|
277
|
+
# quadrature builds a consistent tangent for any dynamic scheme via its `coefK = ∂u_t/∂u_{n+1}`;
|
|
278
|
+
# energy is conserved only at midpoint (coefK = 0.5) — see TimeQuadratureStressTensor.
|
|
279
|
+
assert (
|
|
280
|
+
isDynamic
|
|
281
|
+
), f"the 'quadrature' stress requires a dynamic (hyperbolic) time scheme (got {self.algo})."
|
|
269
282
|
u_np1 = displacement
|
|
270
283
|
if isDynamic:
|
|
271
284
|
displacement, velocity, accel = self._Solver_Evaluate_u_v_a_for_time_scheme(
|
|
@@ -305,10 +318,13 @@ class HyperElastic(_Simu):
|
|
|
305
318
|
useConsistentTangent,
|
|
306
319
|
)
|
|
307
320
|
elif stressType == HyperElastic.StressType.quadrature:
|
|
321
|
+
# coefK = ∂u_t/∂u_{n+1} scales the tangent for the active scheme (0.5 at midpoint).
|
|
322
|
+
coefK = self._Solver_Get_K_C_M_coefs_for_time_scheme()[0]
|
|
308
323
|
K_e, residual_e, nPts_e = (
|
|
309
324
|
Operators.NonLinear.TimeQuadratureStressTensor(
|
|
310
325
|
self.material,
|
|
311
326
|
*hyperElasticStates,
|
|
327
|
+
coefK,
|
|
312
328
|
nPoints,
|
|
313
329
|
energyTol,
|
|
314
330
|
)
|
|
@@ -513,10 +529,14 @@ class HyperElastic(_Simu):
|
|
|
513
529
|
elif result == "W_e":
|
|
514
530
|
values = self._Calc_W(False)
|
|
515
531
|
|
|
516
|
-
elif
|
|
517
|
-
|
|
532
|
+
elif result in ["Green-Lagrange", "Piola-Kirchhoff"] or (
|
|
533
|
+
("S" in result or "E" in result) and ("_norm" not in result)
|
|
534
|
+
):
|
|
535
|
+
# Green-Lagrange (E) and second Piola-Kirchhoff (S), group by group.
|
|
536
|
+
# "Green-Lagrange" and "Piola-Kirchhoff" carry no upper-case E or S, so they have to
|
|
537
|
+
# be matched by name: they are what Results_nodeFields_elementFields hands to Paraview.
|
|
518
538
|
|
|
519
|
-
isStress = "S" in result
|
|
539
|
+
isStress = result == "Piola-Kirchhoff" or "S" in result
|
|
520
540
|
res = (
|
|
521
541
|
result
|
|
522
542
|
if result in ["Green-Lagrange", "Piola-Kirchhoff"]
|
|
@@ -687,16 +687,22 @@ class PhaseField(_Simu):
|
|
|
687
687
|
return self._Calc_Psi_Elas()
|
|
688
688
|
|
|
689
689
|
elif result == "Wdef_e":
|
|
690
|
-
values = self._Calc_Psi_Elas()
|
|
690
|
+
values = self._Calc_Psi_Elas(returnScalar=False)
|
|
691
691
|
|
|
692
692
|
elif result == "Psi_Crack":
|
|
693
693
|
return self._Calc_Psi_Crack()
|
|
694
694
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
695
|
+
elif result == "psiP":
|
|
696
|
+
# group by group, in Get_list_groupElem order, so the (Ne,) result lines up with
|
|
697
|
+
# Get_Node_Values on meshes carrying several element groups
|
|
698
|
+
values = np.concatenate(
|
|
699
|
+
[
|
|
700
|
+
np.asarray(self.__Calc_psiPlus_e_pg(groupElem)).mean(1)
|
|
701
|
+
for groupElem in self.mesh.Get_list_groupElem()
|
|
702
|
+
]
|
|
703
|
+
)
|
|
698
704
|
|
|
699
|
-
|
|
705
|
+
elif result == "damage":
|
|
700
706
|
values = self.damage # type: ignore [assignment]
|
|
701
707
|
|
|
702
708
|
elif result in ["ux", "uy", "uz"]:
|
|
@@ -1189,10 +1189,15 @@ class _Simu(_IObserver, _params.Updatable, ABC):
|
|
|
1189
1189
|
|
|
1190
1190
|
return u_t, v_t, a_t
|
|
1191
1191
|
|
|
1192
|
-
def
|
|
1192
|
+
def _Solver_Get_K_C_M_coefs_for_time_scheme(
|
|
1193
1193
|
self,
|
|
1194
1194
|
) -> tuple[float, float, float]:
|
|
1195
|
-
"""
|
|
1195
|
+
r"""The time scheme's ``(coefK, coefC, coefM)`` for ``A = coefK·K + coefC·C + coefM·M``.
|
|
1196
|
+
|
|
1197
|
+
Each is a chain-rule factor w.r.t. the step unknown ``u_{n+1}``: ``coefK = ∂u_t/∂u_{n+1}``,
|
|
1198
|
+
``coefC = ∂v_t/∂u_{n+1}``, ``coefM = ∂a_t/∂u_{n+1}`` (states from
|
|
1199
|
+
:meth:`_Solver_Evaluate_u_v_a_for_time_scheme`).
|
|
1200
|
+
"""
|
|
1196
1201
|
|
|
1197
1202
|
algotypes = AlgoType.Get_Hyperbolic_and_Parabolic_Types()
|
|
1198
1203
|
assert self.algo in algotypes, f"the current algo is not in {algotypes}."
|
|
@@ -1656,7 +1661,7 @@ class _Simu(_IObserver, _params.Updatable, ABC):
|
|
|
1656
1661
|
if self.algo is AlgoType.elliptic:
|
|
1657
1662
|
A = K
|
|
1658
1663
|
else:
|
|
1659
|
-
coefK, coefC, coefM = self.
|
|
1664
|
+
coefK, coefC, coefM = self._Solver_Get_K_C_M_coefs_for_time_scheme()
|
|
1660
1665
|
A = coefK * K + coefC * C + coefM * M
|
|
1661
1666
|
|
|
1662
1667
|
tic.Tac("Solver", f"Construct A ({problemType}, {algo})", self._verbosity)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "easyfea"
|
|
7
|
-
version = "3.2.
|
|
7
|
+
version = "3.2.2"
|
|
8
8
|
description = "User-friendly Python library that simplifies finite element analysis."
|
|
9
9
|
authors = [{ name = "Matthieu Noel", email = "matthieu.noel7@gmail.com" }]
|
|
10
10
|
readme = "README.md"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|