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.
Files changed (84) hide show
  1. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/Operators/NonLinear.py +30 -31
  2. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Simulations/_hyperelastic.py +39 -19
  3. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Simulations/_phasefield.py +11 -5
  4. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Simulations/_simu.py +8 -3
  5. {easyfea-3.2.1/easyfea.egg-info → easyfea-3.2.2}/PKG-INFO +1 -1
  6. {easyfea-3.2.1 → easyfea-3.2.2/easyfea.egg-info}/PKG-INFO +1 -1
  7. {easyfea-3.2.1 → easyfea-3.2.2}/pyproject.toml +1 -1
  8. {easyfea-3.2.1 → easyfea-3.2.2}/AUTHORS.md +0 -0
  9. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/Elems/__init__.py +0 -0
  10. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/Elems/_beam.py +0 -0
  11. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/Elems/_hexa.py +0 -0
  12. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/Elems/_point.py +0 -0
  13. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/Elems/_prism.py +0 -0
  14. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/Elems/_quad.py +0 -0
  15. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/Elems/_seg.py +0 -0
  16. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/Elems/_tetra.py +0 -0
  17. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/Elems/_tri.py +0 -0
  18. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/Operators/Bilinear.py +0 -0
  19. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/Operators/Linear.py +0 -0
  20. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/Operators/__init__.py +0 -0
  21. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/__init__.py +0 -0
  22. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/_boundary_conditions.py +0 -0
  23. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/_field.py +0 -0
  24. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/_forms.py +0 -0
  25. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/_gauss.py +0 -0
  26. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/_group_elem.py +0 -0
  27. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/_linalg.py +0 -0
  28. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/_mesh.py +0 -0
  29. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/_mesher.py +0 -0
  30. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/FEM/_utils.py +0 -0
  31. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Geoms/__init__.py +0 -0
  32. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Geoms/_circle.py +0 -0
  33. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Geoms/_contour.py +0 -0
  34. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Geoms/_domain.py +0 -0
  35. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Geoms/_geom.py +0 -0
  36. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Geoms/_line.py +0 -0
  37. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Geoms/_points.py +0 -0
  38. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Geoms/_utils.py +0 -0
  39. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Models/Beam/__init__.py +0 -0
  40. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Models/Beam/_beam.py +0 -0
  41. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Models/Elastic/__init__.py +0 -0
  42. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Models/Elastic/_laws.py +0 -0
  43. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Models/HyperElastic/__init__.py +0 -0
  44. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Models/HyperElastic/_laws.py +0 -0
  45. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Models/HyperElastic/_state.py +0 -0
  46. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Models/__init__.py +0 -0
  47. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Models/_phasefield.py +0 -0
  48. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Models/_thermal.py +0 -0
  49. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Models/_utils.py +0 -0
  50. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Models/_weakforms.py +0 -0
  51. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Simulations/Solvers.py +0 -0
  52. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Simulations/__init__.py +0 -0
  53. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Simulations/_beam.py +0 -0
  54. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Simulations/_dic.py +0 -0
  55. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Simulations/_elastic.py +0 -0
  56. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Simulations/_thermal.py +0 -0
  57. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Simulations/_utils.py +0 -0
  58. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Simulations/_weakforms.py +0 -0
  59. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/Folder.py +0 -0
  60. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/GLTF.py +0 -0
  61. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/Matplotlib.py +0 -0
  62. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/MeshIO.py +0 -0
  63. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/Paraview.py +0 -0
  64. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/PyVista.py +0 -0
  65. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/Terminal.py +0 -0
  66. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/USD.py +0 -0
  67. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/Vizir.py +0 -0
  68. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/__init__.py +0 -0
  69. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/_cache.py +0 -0
  70. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/_mpi.py +0 -0
  71. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/_observers.py +0 -0
  72. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/_params.py +0 -0
  73. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/_requires.py +0 -0
  74. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/_tic.py +0 -0
  75. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/Utilities/_types.py +0 -0
  76. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/__about__.py +0 -0
  77. {easyfea-3.2.1 → easyfea-3.2.2}/EasyFEA/__init__.py +0 -0
  78. {easyfea-3.2.1 → easyfea-3.2.2}/LICENSE.txt +0 -0
  79. {easyfea-3.2.1 → easyfea-3.2.2}/README.md +0 -0
  80. {easyfea-3.2.1 → easyfea-3.2.2}/easyfea.egg-info/SOURCES.txt +0 -0
  81. {easyfea-3.2.1 → easyfea-3.2.2}/easyfea.egg-info/dependency_links.txt +0 -0
  82. {easyfea-3.2.1 → easyfea-3.2.2}/easyfea.egg-info/requires.txt +0 -0
  83. {easyfea-3.2.1 → easyfea-3.2.2}/easyfea.egg-info/top_level.txt +0 -0
  84. {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``, then freezes — so a low-strain element stops at one point while a high-strain one keeps refining. The test is the **integrated** relative error over the element, ``∫_Ωe |S:Δe − ΔW| dΩ ≤ tol · ∫_Ωe |ΔW| dΩ``, evaluated as the Gauss-point sums ``Σ_p V_(ep) |S:Δe − ΔW| ≤ tol · Σ_p V_(ep) |ΔW|`` with ``V_(ep)`` the Gauss point's volume (weight × Jacobian) — so the ``V_(ep)`` factor makes each side a genuine *integral over the element*, **not** a pointwise energy-density comparison. This L1 (absolute) form is the tightest simple bound on the element's actual per-step energy drift ``|∫_Ωe (S:Δe − ΔW) dΩ|`` (triangle inequality, no volume factor), and taking ``|·|`` before summing makes it safe against sign cancellation between Gauss points — a well-resolved region cannot mask a coarse one. Energy-safe because ``S:Δe = ΔW`` holds per Gauss point.
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 2 w_k s_k d2Wde, s=0 drops out (∂e/∂u = s B)
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
- (2.0 * w * s) * material.Compute_d2Wde(at(s, acceptedElems))
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
- state_mid: "HyperElasticState",
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 integrates it along the straight segment joining the two end strains and contracts the result with the **midpoint** operator::
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(ū)ᵀ · S_quad dΩ
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
- Since ``Δe = B(ū)·Δu`` exactly and ``de/ds = Δe`` is constant along the segment, the fundamental theorem of calculus gives ``S_quad:Δe = ΔW`` once the ``s``-integral is exact — a **discrete gradient**. The energy defect is therefore just the quadrature error, which Clenshaw-Curtis drives down spectrally; a quadratic ``W`` is exact at every rule. Note ``nPoints = 1`` is the average-strain stress ``S(½(e_n + e_{n+1}))``, *not* the midpoint-displacement stress of :func:`SecondPiolaKirchhoffStressTensor`.
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}``, and ``∂e(s_k)/∂u_{n+1} = s_k B_{n+1}``, so every node contracts with the same ``B_{n+1}`` and the constitutive tensors collapse into one weighted sum::
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 = ½ A_geo(state_mid, S_quad) # raw
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
- Built for :attr:`~EasyFEA.AlgoType.midpoint`'s ``coefK = 0.5`` as in :func:`GonzalezStressTensor`; the doubled weights sum to 1 whatever ``nPoints``. Pairing ``B(ū)`` with ``B_{n+1}`` makes ``K_e`` non-symmetric.
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, state_mid, state_np1
504
- Hyperelastic states at ``u_n``, ``ū`` and ``u_{n+1}`` (same group / matrix type).
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
- ``‖S_quad:Δe − ΔW‖ / ‖ΔW‖`` falls below ``tol``. ``None`` (default) keeps the fixed
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 for ``coefK = 0.5``.
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 = state_mid.groupElem
528
- wJ_e_pg = groupElem.Get_weightedJacobian_e_pg(state_mid.matrixType)
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
- _, B_mid = __block_grad_B(state_mid)
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 += (2.0 * w * s) * material.Compute_d2Wde(state)
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(ū) — hence the non-symmetry.
565
- residual_e = einsum("ep,epi,epij->ej", wJ_e_pg, dWde_quad, B_mid)
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, B_mid, d2Wde_quad, B_np1
568
- ) + __geometric_tangent(wJ_e_pg, state_mid, dWde_quad)
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
- Both non-default stresses are built on the midpoint base point :math:`\bar{\ub}` — the
185
- identity :math:`\Delta \eb = \Brm(\bar{\ub}) \cdot \Delta \ub` is what makes their energy
186
- proofs work — so they require :attr:`~EasyFEA.AlgoType.midpoint`.
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 != HyperElastic.StressType.pointwise:
213
- algo = self.algo
214
- assert algo == AlgoType.midpoint, (
215
- f"the '{stressType}' stress requires AlgoType.midpoint (got {algo}); it is built "
216
- "on the midpoint base point ū. Call Solver_Set_Hyperbolic_Algorithm(dt, algo=AlgoType.midpoint) first."
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
- # of the midpoint base point, so both need u_{n+1} kept before the midpoint
259
- # evaluation overwrites `displacement` with ū. Re-checked here (not only in the
260
- # setter) so that re-calling Solver_Set_Hyperbolic_Algorithm with another algo
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
- assert (
267
- isPointwise or self.algo == AlgoType.midpoint
268
- ), f"the '{stressType}' stress requires AlgoType.midpoint (got {self.algo})."
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 ("S" in result or "E" in result) and ("_norm" not in result):
517
- # Green-Lagrange (E) and second Piola-Kirchhoff (S), group by group
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
- if result == "psiP":
696
- values_e_pg = self.__Calc_psiPlus_e_pg()
697
- values = np.mean(values_e_pg, axis=1)
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
- if result == "damage":
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 __Solver_Get_K_C_M_coefs_for_time_scheme(
1192
+ def _Solver_Get_K_C_M_coefs_for_time_scheme(
1193
1193
  self,
1194
1194
  ) -> tuple[float, float, float]:
1195
- """Returns coefK, coefC, coefM."""
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.__Solver_Get_K_C_M_coefs_for_time_scheme()
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: easyfea
3
- Version: 3.2.1
3
+ Version: 3.2.2
4
4
  Summary: User-friendly Python library that simplifies finite element analysis.
5
5
  Author-email: Matthieu Noel <matthieu.noel7@gmail.com>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: easyfea
3
- Version: 3.2.1
3
+ Version: 3.2.2
4
4
  Summary: User-friendly Python library that simplifies finite element analysis.
5
5
  Author-email: Matthieu Noel <matthieu.noel7@gmail.com>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "easyfea"
7
- version = "3.2.1"
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