diff --git a/CHANGELOG.md b/CHANGELOG.md index 78011f0ba..50cbcb35e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,10 @@ Known gaps and planned work, not yet in a release. An item moves to - The external plugins (UMEXT, UMABA, UMANS) serve the small-strain route only: a finite-strain entry in the plugin API (law fed the logarithmic strain, returning tau, as the internal kernels) would let them run under NLGEOM. +- `tangent_assembly` takes per-mechanism `std::vector` / `std::vector` + (κ, ∂Φ/∂σ, ∂Λ/∂σ): every MODUL tangent copies the 6×6 flow Hessians to the heap, the + largest remaining allocation source in MODUL plasticity. Fixed-size or view-based arguments + need an API change of `assemble_*_tangent`. - Migration of the laws to the modular engine (MODUL), state in `benchmark/README.md`. Routed through the adapter (code 201): ELISO, ELIST, ELORT, EPKCP, EPHIL, EPTRI, EPHAC, EPANI, EPDFA, EPCHG, EPHIN. To do: diff --git a/include/simcoon/Continuum_mechanics/Functions/fastor_bridge.hpp b/include/simcoon/Continuum_mechanics/Functions/fastor_bridge.hpp index 47746851a..6e02e915b 100644 --- a/include/simcoon/Continuum_mechanics/Functions/fastor_bridge.hpp +++ b/include/simcoon/Continuum_mechanics/Functions/fastor_bridge.hpp @@ -98,8 +98,8 @@ inline arma::mat::fixed<3,3> fastor2_to_arma(const Fastor::Tensor &T * or the type-free Mandel pair mandel_to_fastor4 / fastor4_to_mandel. */ inline Fastor::Tensor voigt_to_fastor4(const arma::mat::fixed<6,6> &L) { + // The four minor-symmetric writes below cover all 81 entries: no zero fill. Fastor::Tensor C; - C.zeros(); for (int i = 0; i < 3; ++i) { for (int j = i; j < 3; ++j) { @@ -210,9 +210,9 @@ inline Fastor::Tensor push_forward_4( const Fastor::Tensor &C, const Fastor::Tensor &F) { + // Every entry of each pass is written exactly once: no zero fill. // Pass 1: T1(i,J,M,N) = sum_L F(i,L) * C(L,J,M,N) Fastor::Tensor T1; - T1.zeros(); for (int i = 0; i < 3; ++i) for (int J = 0; J < 3; ++J) for (int M = 0; M < 3; ++M) @@ -225,7 +225,6 @@ inline Fastor::Tensor push_forward_4( // Pass 2: T2(i,s,M,N) = sum_J F(s,J) * T1(i,J,M,N) Fastor::Tensor T2; - T2.zeros(); for (int i = 0; i < 3; ++i) for (int s = 0; s < 3; ++s) for (int M = 0; M < 3; ++M) @@ -238,7 +237,6 @@ inline Fastor::Tensor push_forward_4( // Pass 3: T3(i,s,r,N) = sum_M F(r,M) * T2(i,s,M,N) Fastor::Tensor T3; - T3.zeros(); for (int i = 0; i < 3; ++i) for (int s = 0; s < 3; ++s) for (int r = 0; r < 3; ++r) @@ -251,7 +249,6 @@ inline Fastor::Tensor push_forward_4( // Pass 4: result(i,s,r,p) = sum_N F(p,N) * T3(i,s,r,N) Fastor::Tensor result; - result.zeros(); for (int i = 0; i < 3; ++i) for (int s = 0; s < 3; ++s) for (int r = 0; r < 3; ++r) diff --git a/include/simcoon/Continuum_mechanics/Umat/Modular/damage_mechanism.hpp b/include/simcoon/Continuum_mechanics/Umat/Modular/damage_mechanism.hpp index 7a0f3001d..5bb82f378 100644 --- a/include/simcoon/Continuum_mechanics/Umat/Modular/damage_mechanism.hpp +++ b/include/simcoon/Continuum_mechanics/Umat/Modular/damage_mechanism.hpp @@ -107,6 +107,16 @@ class DamageMechanism final : public StrainMechanism { mutable bool M_cached_valid_; ///< Whether the cached compliances are valid mutable arma::mat L_cached_; ///< Stiffness the cached compliance was inverted from std::vector kappa_cache_{tensor2::zeros(Tensor2Type::strain)}; ///< zero: no multiplier carried + size_t D_index_ = 0; ///< ivc_ index of "D", resolved by register_variables + size_t Y_max_index_ = 0; ///< ivc_ index of "Y_max", resolved by register_variables + + // last driving force and the effective stress it was evaluated at, for the cached compliance (reset with it) + mutable arma::vec::fixed<6> Y_sigma_; + mutable double Y_sigma_value_ = 0.0; + mutable bool Y_sigma_valid_ = false; + + /// compute_driving_force(sigma_eff, M_cached_), memoised on the last stress + double driving_force(const arma::vec& sigma_eff) const; public: /** diff --git a/include/simcoon/Continuum_mechanics/Umat/Modular/hardening.hpp b/include/simcoon/Continuum_mechanics/Umat/Modular/hardening.hpp index 37a97b019..5fa38fe2a 100644 --- a/include/simcoon/Continuum_mechanics/Umat/Modular/hardening.hpp +++ b/include/simcoon/Continuum_mechanics/Umat/Modular/hardening.hpp @@ -231,7 +231,8 @@ class KinematicHardening { /** * @brief Register internal variables for this hardening model - * @param ivc The owning mechanism's internal variable collection + * @param ivc The owning mechanism's internal variable collection; every other member taking a + * collection must get this same one (read by the indices resolved here, debug-checked). */ virtual void register_variables(InternalVariableCollection& ivc) = 0; @@ -410,6 +411,7 @@ class PragerHardening final : public KinematicHardening { private: double C_; ///< Kinematic hardening modulus std::string a_key_{"a"}; ///< IVC key for the back-strain α + size_t a_index_ = 0; ///< its index in the collection, resolved by register_variables public: PragerHardening() : C_(0.0) {} void configure(const arma::vec& props, int& offset) override; @@ -434,6 +436,7 @@ class ArmstrongFrederickHardening final : public KinematicHardening { double C_; ///< Hardening parameter double D_; ///< Dynamic recovery parameter std::string a_key_{"a"}; ///< IVC key for the back-strain α + size_t a_index_ = 0; ///< its index in the collection, resolved by register_variables public: ArmstrongFrederickHardening() : C_(0.0), D_(0.0) {} void configure(const arma::vec& props, int& offset) override; @@ -461,6 +464,7 @@ class ChabocheHardening final : public KinematicHardening { arma::vec C_; ///< Hardening parameters arma::vec D_; ///< Dynamic recovery parameters std::vector a_keys_; ///< IVC keys for back-strains α_i ("a_0", "a_1", ...) + std::vector a_index_; ///< their indices in the collection, resolved by register_variables public: explicit ChabocheHardening(int N = 1) : N_(N), C_(arma::zeros(N)), D_(arma::zeros(N)) {} void configure(const arma::vec& props, int& offset) override; diff --git a/include/simcoon/Continuum_mechanics/Umat/Modular/plasticity_mechanism.hpp b/include/simcoon/Continuum_mechanics/Umat/Modular/plasticity_mechanism.hpp index 49eb6f8a7..ac232f3d4 100644 --- a/include/simcoon/Continuum_mechanics/Umat/Modular/plasticity_mechanism.hpp +++ b/include/simcoon/Continuum_mechanics/Umat/Modular/plasticity_mechanism.hpp @@ -114,6 +114,9 @@ class PlasticityMechanism final : public StrainMechanism { mutable double H_total_{0.0}; ///< Hardening modulus (iso + kin) std::vector cpp_cache_{ClosestPointIngredients{}}; ///< built by refresh_state, read by closest_point_ingredients + /// Total backstress (engineering stress Voigt), summed in the X_branches_ buffer it overwrites (no allocation) + [[nodiscard]] arma::vec total_backstress_voigt() const; + public: /** * @brief Constructor diff --git a/include/simcoon/Continuum_mechanics/Umat/Modular/viscoelastic_mechanism.hpp b/include/simcoon/Continuum_mechanics/Umat/Modular/viscoelastic_mechanism.hpp index 2a5709d77..d0f6e0f13 100644 --- a/include/simcoon/Continuum_mechanics/Umat/Modular/viscoelastic_mechanism.hpp +++ b/include/simcoon/Continuum_mechanics/Umat/Modular/viscoelastic_mechanism.hpp @@ -69,6 +69,8 @@ class ViscoelasticMechanism final : public StrainMechanism { // inside the FB hot loop). std::vector ev_key_; ///< "EV_" per branch std::vector v_key_; ///< "v_" per branch + std::vector ev_index_; ///< ivc_ index of "EV_", resolved by register_variables + std::vector v_index_; ///< ivc_ index of "v_", resolved by register_variables // Per-iteration caches (CCP: frozen flow direction from previous iteration) mutable std::vector kappa_t_; ///< Zero fluxes: the rows carry no multiplier diff --git a/include/simcoon/Continuum_mechanics/Umat/Modular/yield_criterion.hpp b/include/simcoon/Continuum_mechanics/Umat/Modular/yield_criterion.hpp index 812162577..a82071c9c 100644 --- a/include/simcoon/Continuum_mechanics/Umat/Modular/yield_criterion.hpp +++ b/include/simcoon/Continuum_mechanics/Umat/Modular/yield_criterion.hpp @@ -55,6 +55,8 @@ class YieldCriterion { private: YieldType type_; arma::vec params_; ///< Criterion-specific parameters + arma::mat P_; ///< HILL / DFA / ANISOTROPIC: quadratic-form matrix of params_ + ///< (P_Hill / P_DFA / P_Ani), built once by configure_* bool configured_; /** @@ -63,6 +65,18 @@ class YieldCriterion { */ std::string type_string() const; + /** + * @brief equivalent_stress(sigma) and flow_direction(sigma) in one call (the quadratic criteria share P sigma). + */ + void equivalent_stress_and_flow(const arma::vec& sigma, double& sigma_eq, arma::vec& flow) const; + + /** + * @brief The same plus flow_hessian(sigma) (criteria with a flow Hessian only, else its exception). + */ + void equivalent_stress_flow_hessian(const arma::vec& sigma, double& sigma_eq, arma::vec& flow, + arma::mat& hessian) const; + friend class PlasticityMechanism; + public: /** * @brief Default constructor (von Mises) diff --git a/include/simcoon/Simulation/Maths/small_dense.hpp b/include/simcoon/Simulation/Maths/small_dense.hpp new file mode 100644 index 000000000..0a24b3c3b --- /dev/null +++ b/include/simcoon/Simulation/Maths/small_dense.hpp @@ -0,0 +1,85 @@ +/* This file is part of simcoon. + + simcoon is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + simcoon is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with simcoon. If not, see . + + */ + +///@file small_dense.hpp +///@brief Fixed-order 6x6 products for the per-point kernels +///@version 1.0 + +#pragma once +#include + +namespace simcoon{ + +/** @addtogroup maths + * @{ + */ + +/** + * @brief \f$ \mathbf{y} = \mathbf{A}\,\mathbf{x} \f$ for a column-major 6x6 @p A and a 6-vector @p x. + * + * Armadillo hands every product above 4x4 to BLAS; at this size the call costs more than the + * 36 multiply-adds, and the summation order depends on the BLAS backend. This loop has a fixed + * order (column by column), so the result is the same on every platform. + * @p y may alias @p x. + */ +inline void mul6(const double *A, const double *x, double *y) { + double r[6]; + for (int i = 0; i < 6; ++i) r[i] = A[i]*x[0]; + for (int k = 1; k < 6; ++k) + for (int i = 0; i < 6; ++i) r[i] += A[i + 6*k]*x[k]; + for (int i = 0; i < 6; ++i) y[i] = r[i]; +} + +/// \f$ \mathbf{y} = \mathbf{A}\,\mathbf{x} \f$ for a 6x6 @p A and a 6-vector @p x (see the pointer form). +inline void mul6(const arma::mat &A, const arma::vec &x, arma::vec &y) { + if (&y != &x) y.set_size(6); + mul6(A.memptr(), x.memptr(), y.memptr()); +} + +/// \f$ \mathbf{C} = \mathbf{A}\,\mathbf{B} \f$ for 6x6 operands, fixed summation order (see mul6). +inline arma::mat::fixed<6,6> mul66(const arma::mat &A, const arma::mat &B) { + const double *a = A.memptr(); + const double *b = B.memptr(); + arma::mat::fixed<6,6> C; + double *c = C.memptr(); + for (int j = 0; j < 6; ++j) + for (int i = 0; i < 6; ++i) { + double s = 0.0; + for (int k = 0; k < 6; ++k) s += a[i + 6*k]*b[k + 6*j]; + c[i + 6*j] = s; + } + return C; +} + +/// \f$ \mathbf{C} = \mathbf{A}\,\mathbf{B}^T \f$ for 6x6 operands, without forming the transpose. +inline arma::mat::fixed<6,6> mul66_ABt(const arma::mat &A, const arma::mat &B) { + const double *a = A.memptr(); + const double *b = B.memptr(); + arma::mat::fixed<6,6> C; + double *c = C.memptr(); + for (int j = 0; j < 6; ++j) + for (int i = 0; i < 6; ++i) { + double s = 0.0; + for (int k = 0; k < 6; ++k) s += a[i + 6*k]*b[j + 6*k]; + c[i + 6*j] = s; + } + return C; +} + +/** @} */ // end of maths group + +} //namespace simcoon diff --git a/python-setup/simcoon/rotation.py b/python-setup/simcoon/rotation.py index 70dd44a04..06c1ab7bc 100644 --- a/python-setup/simcoon/rotation.py +++ b/python-setup/simcoon/rotation.py @@ -7,6 +7,7 @@ methods (``apply_stress``, ``apply_stiffness``, etc.). """ +import math import warnings from typing import Dict, Sequence, Union @@ -81,7 +82,16 @@ def from_mrp(cls, mrp): @classmethod def identity(cls, num=None): - return cls._ensure_cls(super().identity(num)) + r = super().identity(num) + if type(r) is not cls: + # exact unit quaternions: nothing to renormalize through from_quat (the default + # orientation of every phase, built with each phase object) + r.__class__ = cls + if num is None: + # a single rotation cannot be modified in place: euler_angles reads its angles + # without decomposing it + r._exact_identity = True + return r @classmethod def random(cls, num=None, random_state=None): @@ -501,17 +511,23 @@ def as_rotation(value: Orientation) -> Rotation: return value if isinstance(value, ScipyRotation): return Rotation.from_scipy(value) + return Rotation.from_euler(EULER_SEQ, _euler_triplet(value), degrees=True) + + +def _euler_triplet(value): + """The Euler angles of a ``{"psi", "theta", "phi"}`` dict (missing angles are 0) or of a + 3-sequence, in degrees, checked: unknown keys, a count other than 3 or a non-numeric value + raise ``ValueError``.""" if isinstance(value, dict): unknown = set(value) - set(_ANGLES) if unknown: raise ValueError(f"orientation: unknown keys {sorted(unknown)}; expected {_ANGLES}") - angles = [float(value.get(k, 0.0)) for k in _ANGLES] - else: - angles = np.asarray(value, dtype=float).ravel() - if angles.size != 3: - raise ValueError(f"orientation: 3 Euler angles (psi, theta, phi) in degrees " - f"expected, got {angles.size} values") - return Rotation.from_euler(EULER_SEQ, angles, degrees=True) + return [float(value.get(k, 0.0)) for k in _ANGLES] + angles = np.asarray(value, dtype=float).ravel() + if angles.size != 3: + raise ValueError(f"orientation: 3 Euler angles (psi, theta, phi) in degrees " + f"expected, got {angles.size} values") + return angles def euler_angles(rotation: Orientation) -> Dict[str, float]: @@ -525,11 +541,15 @@ def euler_angles(rotation: Orientation) -> Dict[str, float]: if not isinstance(rotation, ScipyRotation) and rotation is not None: # angles given as angles are written as given: no detour through a quaternion # (float noise, and a gimbal-locked triplet rewritten) for a no-op - as_rotation(rotation) # validates the dict keys / the 3 values - values = ([float(rotation.get(k, 0.0)) for k in _ANGLES] if isinstance(rotation, dict) - else [float(a) for a in np.asarray(rotation, dtype=float).ravel()]) + values = [float(a) for a in _euler_triplet(rotation)] # validates the dict keys / the 3 values + if not all(math.isfinite(a) for a in values): + as_rotation(rotation) # the error of a rotation built from non-finite angles return dict(zip(_ANGLES, values)) rot = as_rotation(rotation) + if getattr(rot, "_exact_identity", False): + # Rotation.identity() (the default orientation of every phase): the angles scipy + # returns for it, +0, without the decomposition + return {'psi': 0.0, 'theta': 0.0, 'phi': 0.0} with warnings.catch_warnings(): warnings.simplefilter('ignore') # scipy's "Gimbal lock detected" - see above psi, theta, phi = rot.as_euler(EULER_SEQ, degrees=True) diff --git a/simcoon-python-builder/src/python_wrappers/Libraries/Continuum_mechanics/pyumat.cpp b/simcoon-python-builder/src/python_wrappers/Libraries/Continuum_mechanics/pyumat.cpp index 8c39d5325..34504f610 100644 --- a/simcoon-python-builder/src/python_wrappers/Libraries/Continuum_mechanics/pyumat.cpp +++ b/simcoon-python-builder/src/python_wrappers/Libraries/Continuum_mechanics/pyumat.cpp @@ -51,6 +51,26 @@ struct step_cut_tag {}; PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store g_step_cut; static py::object *g_fn = nullptr; // touched only with the GIL held +// The keyword names of the call, interned once (same lifetime rule as g_step_cut). The call +// passes them with the values in one vector (PyObject_Vectorcall): building a kwargs dict +// key by key, as a pybind11 keyword call does, was a third of the bridge. +constexpr const char *k_kwnames[] = {"Etot", "DEtot", "sigma", "DR", "props", "statev", "T", "DT", + "Time", "DTime", "Wm", "ndi", "nshr", "start", "tangent_mode"}; +constexpr std::size_t k_nkw = sizeof(k_kwnames) / sizeof(k_kwnames[0]); +PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store g_kwnames; + +const py::tuple &kwnames() { + return g_kwnames.call_once_and_store_result([]() { + py::tuple names(k_nkw); + for (std::size_t k = 0; k < k_nkw; k++) { + PyObject *s = PyUnicode_InternFromString(k_kwnames[k]); + if (!s) throw py::error_already_set(); + names[k] = py::reinterpret_steal(s); + } + return names; + }).get_stored(); +} + using farr = py::array_t; // (rows, cols) column-major copy — cheaper than simpy::arma_to_numpy::mat_to_arr(arma::mat(m)), which @@ -126,20 +146,28 @@ void python_umat_bridge(const std::string &umat_name, const arma::vec &Etot, con if (!g_fn || g_fn->is_none()) { throw simcoon::exception_solver("PYEXT: no Python UMAT registered"); } + // own reference for the call: a law that unregisters or replaces itself must stay alive + const py::object fn = *g_fn; py::array_t Wm4(4); { double *w = Wm4.mutable_data(); w[0] = Wm; w[1] = Wm_r; w[2] = Wm_ir; w[3] = Wm_d; } + // keyword arguments, in the order of k_kwnames + const py::object values[k_nkw] = {np1d(Etot), np1d(DEtot), np1d(sigma), np2d(DR), np1d(props), + np1d(statev), py::float_(T), py::float_(DT), py::float_(Time), + py::float_(DTime), Wm4, py::int_(ndi), py::int_(nshr), + py::bool_(start), py::int_(tangent_mode)}; py::object ret; try { - ret = (*g_fn)("Etot"_a = np1d(Etot), "DEtot"_a = np1d(DEtot), "sigma"_a = np1d(sigma), - "DR"_a = np2d(DR), "props"_a = np1d(props), - "statev"_a = np1d(statev), - "T"_a = T, "DT"_a = DT, "Time"_a = Time, "DTime"_a = DTime, - "Wm"_a = Wm4, "ndi"_a = ndi, "nshr"_a = nshr, "start"_a = start, - "tangent_mode"_a = tangent_mode); + PyObject *argv[k_nkw + 1]; // argv[0]: the slot PY_VECTORCALL_ARGUMENTS_OFFSET lends the callee + argv[0] = nullptr; + for (std::size_t k = 0; k < k_nkw; k++) argv[k + 1] = values[k].ptr(); + PyObject *res = PyObject_Vectorcall(fn.ptr(), argv + 1, PY_VECTORCALL_ARGUMENTS_OFFSET, + kwnames().ptr()); + if (!res) throw py::error_already_set(); + ret = py::reinterpret_steal(res); } catch (py::error_already_set &e) { if (e.matches(g_step_cut.get_stored())) { // Requested step cut: leave sigma/statev/Wm untouched (the solver restores the diff --git a/simcoon-python-builder/src/python_wrappers/Libraries/Continuum_mechanics/tensor.cpp b/simcoon-python-builder/src/python_wrappers/Libraries/Continuum_mechanics/tensor.cpp index eca251647..d3ef3c50b 100644 --- a/simcoon-python-builder/src/python_wrappers/Libraries/Continuum_mechanics/tensor.cpp +++ b/simcoon-python-builder/src/python_wrappers/Libraries/Continuum_mechanics/tensor.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -38,8 +39,14 @@ namespace { to_string(cols) + "), got (" + to_string(shape[0]) + ", " + to_string(shape[1]) + ")"); } } - // Helper: numpy (N,6) → arma mat (6,N) — copy + transpose + // Helper: numpy (N,6) → arma mat (6,N), read only. A C-ordered (N,6) buffer is the + // column-major (6,N) matrix itself: borrowed (the caller keeps it alive for the call); + // any other layout is copied and transposed mat np2d_to_mat6N(const py::array_t& arr) { + if (arr.ndim() == 2 && arr.size() > 0 && (arr.flags() & py::array::c_style) && arr.writeable() + && reinterpret_cast(arr.data()) % alignof(double) == 0) { + return mat(const_cast(arr.data()), arr.shape(1), arr.shape(0), false, true); + } mat m = simpy::numpy_to_arma::arr_to_mat(arr); // numpy (N,6) → arma (N,6) return m.t(); // → arma (6,N) } @@ -330,8 +337,8 @@ void register_tensor(py::module_& m) { // ================================================================ // Batch operations // Python side prepares: - // voigt as (N,6) C-order → converted via np2d_to_mat6N (copy + .t()) - // cubes as (R,C,N) F-order → zero-copy via simpy::numpy_to_arma::arr_to_cube + // voigt as (N,6) C-order → borrowed as (6,N) by np2d_to_mat6N + // cubes as (R,C,N) F-order → borrowed via simpy::numpy_to_arma::arr_to_cube_view // Each name is a pybind overload set (tensor2 + tensor4 variants), mirroring // the C++ batch_* overloads; dispatch is disambiguated by the enum argument. @@ -340,7 +347,7 @@ void register_tensor(py::module_& m) { py::array_t voigt, simcoon::Tensor2Type vtype, py::array_t rot_matrices, bool active) { mat v_cpp = np2d_to_mat6N(voigt); - cube r_cpp = simpy::numpy_to_arma::arr_to_cube(rot_matrices); + cube r_cpp = simpy::numpy_to_arma::arr_to_cube_view(rot_matrices); mat result; { py::gil_scoped_release release; @@ -355,7 +362,7 @@ void register_tensor(py::module_& m) { py::array_t voigt, simcoon::Tensor2Type vtype, py::array_t F_arr, bool metric) { mat v_cpp = np2d_to_mat6N(voigt); - cube f_cpp = simpy::numpy_to_arma::arr_to_cube(F_arr); + cube f_cpp = simpy::numpy_to_arma::arr_to_cube_view(F_arr); mat result; { py::gil_scoped_release release; @@ -370,7 +377,7 @@ void register_tensor(py::module_& m) { py::array_t voigt, simcoon::Tensor2Type vtype, py::array_t F_arr, bool metric) { mat v_cpp = np2d_to_mat6N(voigt); - cube f_cpp = simpy::numpy_to_arma::arr_to_cube(F_arr); + cube f_cpp = simpy::numpy_to_arma::arr_to_cube_view(F_arr); mat result; { py::gil_scoped_release release; @@ -400,7 +407,7 @@ void register_tensor(py::module_& m) { []( py::array_t t4_arr, simcoon::Tensor4Type t4type, py::array_t t2_arr, simcoon::Tensor2Type t2_vtype) { - cube t4_cpp = simpy::numpy_to_arma::arr_to_cube(t4_arr); + cube t4_cpp = simpy::numpy_to_arma::arr_to_cube_view(t4_arr); mat t2_cpp = np2d_to_mat6N(t2_arr); mat result; { @@ -416,8 +423,8 @@ void register_tensor(py::module_& m) { []( py::array_t t4_arr, simcoon::Tensor4Type t4type, py::array_t rot_matrices, bool active) { - cube t4_cpp = simpy::numpy_to_arma::arr_to_cube(t4_arr); - cube r_cpp = simpy::numpy_to_arma::arr_to_cube(rot_matrices); + cube t4_cpp = simpy::numpy_to_arma::arr_to_cube_view(t4_arr); + cube r_cpp = simpy::numpy_to_arma::arr_to_cube_view(rot_matrices); cube result; { py::gil_scoped_release release; @@ -431,8 +438,8 @@ void register_tensor(py::module_& m) { []( py::array_t t4_arr, simcoon::Tensor4Type t4type, py::array_t F_arr, bool metric) { - cube t4_cpp = simpy::numpy_to_arma::arr_to_cube(t4_arr); - cube f_cpp = simpy::numpy_to_arma::arr_to_cube(F_arr); + cube t4_cpp = simpy::numpy_to_arma::arr_to_cube_view(t4_arr); + cube f_cpp = simpy::numpy_to_arma::arr_to_cube_view(F_arr); cube result; { py::gil_scoped_release release; @@ -446,8 +453,8 @@ void register_tensor(py::module_& m) { []( py::array_t t4_arr, simcoon::Tensor4Type t4type, py::array_t F_arr, bool metric) { - cube t4_cpp = simpy::numpy_to_arma::arr_to_cube(t4_arr); - cube f_cpp = simpy::numpy_to_arma::arr_to_cube(F_arr); + cube t4_cpp = simpy::numpy_to_arma::arr_to_cube_view(t4_arr); + cube f_cpp = simpy::numpy_to_arma::arr_to_cube_view(F_arr); cube result; { py::gil_scoped_release release; @@ -460,7 +467,7 @@ void register_tensor(py::module_& m) { m.def("_batch_inverse", []( py::array_t t4_arr, simcoon::Tensor4Type t4type) { - cube t4_cpp = simpy::numpy_to_arma::arr_to_cube(t4_arr); + cube t4_cpp = simpy::numpy_to_arma::arr_to_cube_view(t4_arr); cube result; { py::gil_scoped_release release; diff --git a/simcoon-python-builder/src/python_wrappers/Libraries/Solver/solver_run.cpp b/simcoon-python-builder/src/python_wrappers/Libraries/Solver/solver_run.cpp index 42f2723f7..c541f06e6 100644 --- a/simcoon-python-builder/src/python_wrappers/Libraries/Solver/solver_run.cpp +++ b/simcoon-python-builder/src/python_wrappers/Libraries/Solver/solver_run.cpp @@ -1,3 +1,5 @@ +#include +#include #include #include #include @@ -22,6 +24,8 @@ #include #include #include +#include +#include #include #include #include @@ -124,17 +128,6 @@ void fill_step_common(StepPtr &sptr, const py::dict &sd, const unsigned int &con } } -//one row per record, vecs/mats flattened row-major (matrices reshape to -//(N, n_rows, n_cols) C-order on the Python side). -template -py::array_t rows_to_arr(const std::vector &v) { - arma::mat M(v.size(), v.empty() ? 0 : v[0].n_elem); - for (size_t i = 0; i < v.size(); i++) { - M.row(i) = arma::vectorise(v[i].t()).t(); - } - return simpy::arma_to_numpy::mat_to_arr(std::move(M)); // the heap matrix is handed over, not copied -} - template py::array_t scalars_to_arr(const std::vector &v) { py::array_t a(v.size()); @@ -142,6 +135,169 @@ py::array_t scalars_to_arr(const std::vector &v) { return a; } +//History of one recorded vector or matrix, as the (N, n_comp) Fortran-ordered array it is +//returned as: one column per component (a matrix flattened row by row), one row per record, +//filled in place. The buffer is sized at once for the expected count of records, which every +//complete run reaches exactly, so that it becomes the array itself: no copy, no growth peak +//(a doubling buffer cost 60 % more peak memory and 25 % more time on a 1e6-increment run). Its +//untouched pages cost address space only, so a run planned long that stops early stays small; +//beyond 1 GiB per field the buffer starts smaller and doubles instead. It grows on if need be +//and is cut to the records of an aborted run. A std::vector of arma objects constructed (and later destroyed) +//one object per field and record, with a heap block for each 6x6 tangent, and its +//conversion into arrays, were a quarter of an elastic increment. +class flat_history { +public: + void set_capacity(const std::size_t &n_records) { capacity_ = n_records; } + + void push(const arma::mat &m) { + if (n_records_ == 0) { + n_comp_ = m.n_elem; + const std::size_t bounded = std::max(first_rows, max_doubles/std::max(n_comp_, 1)); + buf_.set_size(std::max(std::min(capacity_, bounded), 1), n_comp_); + } + else if (m.n_elem != n_comp_) { + throw std::length_error("solver_run: a recorded quantity changed size during the run"); + } + if (n_records_ == buf_.n_rows) { + grow(); + } + const arma::uword ld = buf_.n_rows; + double *out = buf_.memptr() + n_records_; + if (m.n_rows == 1 || m.n_cols == 1) { + const double *in = m.memptr(); + for (arma::uword k = 0; k < n_comp_; k++) + out[k*ld] = in[k]; + } + else { + for (arma::uword a = 0; a < m.n_rows; a++) + for (arma::uword b = 0; b < m.n_cols; b++, out += ld) + *out = m(a, b); + } + n_records_++; + } + + //The (N, n_comp) array (Fortran order): the Python side takes .T of a vector history and + //reshapes a matrix one to (N, n_rows, n_cols), then (n_rows, n_cols, N), both views + py::array_t columns() { + if (n_records_ == 0 || n_comp_ == 0) { + return simpy::arma_to_numpy::mat_to_arr(arma::mat(n_records_, n_comp_)); + } + if (n_records_ < buf_.n_rows) { + buf_.shed_rows(n_records_, buf_.n_rows - 1); + } + return simpy::arma_to_numpy::mat_to_arr(std::move(buf_)); // handed over, not copied + } + +private: + //doubles, but stops at the expected count when it lies in between + void grow() { + std::size_t rows = 2*buf_.n_rows; + if (capacity_ > buf_.n_rows && capacity_ < rows) rows = capacity_; + arma::mat bigger(rows, n_comp_, arma::fill::none); + for (arma::uword k = 0; k < n_comp_; k++) + std::memcpy(bigger.colptr(k), buf_.colptr(k), n_records_*sizeof(double)); + buf_ = std::move(bigger); + } + + static constexpr std::size_t first_rows = 1024; ///< smallest first allocation (records) + static constexpr std::size_t max_doubles = std::size_t(1) << 27; ///< 1 GiB: larger expected histories double from here + arma::mat buf_; + std::size_t capacity_ = 0; + std::size_t n_comp_ = 0; + std::size_t n_records_ = 0; +}; + +//The memory sink of the bindings: the converged state of every output point, as +//simcoon::solver_memory_sink captures it, in flat histories that become the arrays of the +//result dict. +class history_sink : public simcoon::solver_results_sink { +public: + bool record_tangent = true; + int sv_type = 0; ///< 1: mechanical, 2: thermomechanical (set by init()) + + explicit history_sink(const std::size_t &n_hint) : n_hint_(n_hint) {} + + void init(simcoon::phase_characteristics &rve) override { + auto *sv = rve.sptr_sv_global.get(); + if (dynamic_cast(sv) != nullptr) + sv_type = 2; + else if (dynamic_cast(sv) != nullptr) + sv_type = 1; + for (auto *v : {&blocks_i, &cycles_i, &steps_i, &incs_i}) v->reserve(n_hint_); + for (auto *v : {&time, &T, &Q, &r}) v->reserve(n_hint_); + for (auto *h : {&Etot, &etot, &PKII, &tau, &sigma, &F1, &R, &DR, &statev, &Wm, &Wt, + &Lt, &dSdE, &dSdT, &drdE, &drdT}) h->set_capacity(n_hint_); + } + + void record(simcoon::phase_characteristics &rve, const simcoon::solver_output &, const int &kblock, + const int &kcycle, const int &kstep, const int &kinc, const double &Time) override { + simcoon::state_variables *sv = rve.sptr_sv_global.get(); + // defensive re-detection of the state-variable type, as solver_memory_sink + auto *sv_T = dynamic_cast(sv); + auto *sv_M = (sv_T == nullptr) ? dynamic_cast(sv) : nullptr; + sv_type = (sv_T != nullptr) ? 2 : ((sv_M != nullptr) ? 1 : 0); + + blocks_i.push_back(kblock); + cycles_i.push_back(kcycle); + steps_i.push_back(kstep); + incs_i.push_back(kinc); + time.push_back(Time); + + Etot.push(sv->Etot); + etot.push(sv->etot); + PKII.push(sv->PKII); + tau.push(sv->tau); + // Cauchy stress, as solver_memory_sink (o_stress_type 4 of phase_characteristics::output) + sigma.push(sv->tau / det(sv->F1)); + F1.push(sv->F1); + R.push(sv->R); + DR.push(sv->DR); + T.push_back(sv->T); + statev.push(sv->statev); + + if (sv_T != nullptr) { + Wm.push(sv_T->Wm); + Wt.push(sv_T->Wt); + Q.push_back(sv_T->Q); + r.push_back(sv_T->r); + if (record_tangent) { + dSdE.push(sv_T->dSdE); + dSdT.push(sv_T->dSdT); + drdE.push(sv_T->drdE); + drdT.push(sv_T->drdT); + } + } + else if (sv_M != nullptr) { + Wm.push(sv_M->Wm); + if (record_tangent) { + Lt.push(sv_M->Lt); + } + } + } + + std::vector blocks_i, cycles_i, steps_i, incs_i; + std::vector time, T, Q, r; + flat_history Etot, etot, PKII, tau, sigma, F1, R, DR, statev, Wm, Wt, Lt, dSdE, dSdT, drdE, drdT; + +private: + std::size_t n_hint_; +}; + +//Records of a complete run, every increment being an output point here (as step::generate() +//counts the increments): the capacity of the histories +std::size_t expected_records(const std::vector &blocks) { + std::size_t n = 0; + for (const auto &bl : blocks) { + std::size_t n_inc = 0; + for (const auto &sptr : bl.steps) { + if (sptr->mode == 3) n_inc += sptr->tab_data.n_rows; + else if (sptr->Dn_inc > 0.) n_inc += static_cast(std::round(1./sptr->Dn_inc)); + } + n += n_inc*bl.ncycle; + } + return n; +} + } //anonymous namespace py::dict solver_run(const py::list &blocks_py, const double &T_init, @@ -235,7 +391,7 @@ py::dict solver_run(const py::list &blocks_py, const double &T_init, } simcoon::check_path_output(blocks, so); - simcoon::solver_memory_sink sink; + history_sink sink(expected_records(blocks)); sink.record_tangent = record_tangent; //Sub-phases of a mean-field model, built HERE, while the GIL is still held: they are read @@ -251,7 +407,8 @@ py::dict solver_run(const py::list &blocks_py, const double &T_init, sub_phases); } - //Assemble the results (canonical state; measure transforms happen in Python) + //Assemble the results (canonical state; measure transforms happen in Python): one row per + //record, (N, n) in Fortran order, a matrix flattened row by row py::dict res; res["status"] = status; res["sv_type"] = sink.sv_type; @@ -260,30 +417,30 @@ py::dict solver_run(const py::list &blocks_py, const double &T_init, res["step"] = scalars_to_arr(sink.steps_i); res["inc"] = scalars_to_arr(sink.incs_i); res["time"] = scalars_to_arr(sink.time); - res["Etot"] = rows_to_arr(sink.Etot); - res["etot"] = rows_to_arr(sink.etot); - res["PKII"] = rows_to_arr(sink.PKII); - res["tau"] = rows_to_arr(sink.tau); - res["sigma"] = rows_to_arr(sink.sigma); - res["F1"] = rows_to_arr(sink.F1); - res["R"] = rows_to_arr(sink.R); - res["DR"] = rows_to_arr(sink.DR); + res["Etot"] = sink.Etot.columns(); + res["etot"] = sink.etot.columns(); + res["PKII"] = sink.PKII.columns(); + res["tau"] = sink.tau.columns(); + res["sigma"] = sink.sigma.columns(); + res["F1"] = sink.F1.columns(); + res["R"] = sink.R.columns(); + res["DR"] = sink.DR.columns(); res["T"] = scalars_to_arr(sink.T); - res["Wm"] = rows_to_arr(sink.Wm); - res["statev"] = rows_to_arr(sink.statev); + res["Wm"] = sink.Wm.columns(); + res["statev"] = sink.statev.columns(); if (sink.sv_type == 2) { res["Q"] = scalars_to_arr(sink.Q); res["r"] = scalars_to_arr(sink.r); - res["Wt"] = rows_to_arr(sink.Wt); + res["Wt"] = sink.Wt.columns(); if (record_tangent) { - res["dSdE"] = rows_to_arr(sink.dSdE); - res["dSdT"] = rows_to_arr(sink.dSdT); - res["drdE"] = rows_to_arr(sink.drdE); - res["drdT"] = rows_to_arr(sink.drdT); + res["dSdE"] = sink.dSdE.columns(); + res["dSdT"] = sink.dSdT.columns(); + res["drdE"] = sink.drdE.columns(); + res["drdT"] = sink.drdT.columns(); } } else if (record_tangent) { - res["Lt"] = rows_to_arr(sink.Lt); + res["Lt"] = sink.Lt.columns(); } return res; } diff --git a/simcoon-python-builder/test/test_core/test_solver_history.py b/simcoon-python-builder/test/test_core/test_solver_history.py new file mode 100644 index 000000000..1bf936ede --- /dev/null +++ b/simcoon-python-builder/test/test_core/test_solver_history.py @@ -0,0 +1,111 @@ +"""The recorded history of _core.solver_run, field by field. + +The binding records the solver state into its own sink (solver_run.cpp, history_sink) rather +than through simcoon::solver_memory_sink, the sink of the C++ API. Both capture the same +converged state at every output point; these tests pin that contract on the arrays the binding +returns: one record per increment, the Cauchy stress as tau / det(F1), matrices flattened row +by row, the fields of each state-variable type, and the (N, n) Fortran layout SolverResults +takes views of. +""" + +import numpy as np +import pytest + +import simcoon as sim +from simcoon import _core +from simcoon.solver import Block, StepMeca, StepThermomeca, SolverResults + +MIXED = ["strain"] + ["stress"] * 5 +EPICP_PROPS = [70000.0, 0.3, 1.0e-5, 300.0, 1000.0, 0.3] +EPICP_T_PROPS = [7800.0, 500.0, 70000.0, 0.3, 1.0e-5, 300.0, 1000.0, 0.3] +VECTORS_M = {"Etot": 6, "etot": 6, "PKII": 6, "tau": 6, "sigma": 6, "Wm": 4} +MATRICES = {"F1": 9, "R": 9, "DR": 9} + + +def run(blocks, umat, props, nstatev, record_tangent=True, corate=3, **params): + blocks_py = [b.to_dict(290.0) for b in blocks] + return _core.solver_run(blocks_py, 290.0, umat, np.asarray(props, dtype=float), nstatev, None, 0, + corate, dict(tangent_mode=2, **params), record_tangent, None) + + +def check_common(raw, n, nstatev): + for key in ("block", "cycle", "step", "inc", "time", "T"): + assert raw[key].shape == (n,) + for key, ncomp in {**VECTORS_M, **MATRICES}.items(): + a = raw[key] + assert a.shape == (n, ncomp), key + assert a.flags.f_contiguous, key + assert raw["statev"].shape == (n, nstatev) + # the Cauchy stress of every record is tau / det(F1) + F = raw["F1"].reshape(n, 3, 3) + np.testing.assert_allclose(raw["sigma"], raw["tau"] / np.linalg.det(F)[:, None], rtol=1e-14, atol=0.0) + + +CASES = { + "small_strain": (lambda: [Block(steps=[StepMeca(control=MIXED, value=[0.01, 0, 0, 0, 0, 0], ninc=12), + StepMeca(control=MIXED, value=[-0.005, 0, 0, 0, 0, 0], ninc=8)], + ncycle=2)], 40), + "logarithmic": (lambda: [Block(steps=[StepMeca(control=MIXED, value=[0.2, 0, 0, 0, 0, 0], ninc=15)], + control_type="logarithmic")], 15), +} + + +@pytest.mark.parametrize("case", sorted(CASES)) +@pytest.mark.parametrize("record_tangent", [True, False]) +def test_mechanical_history(case, record_tangent): + make, n = CASES[case] + raw = run(make(), "EPICP", EPICP_PROPS, 8, record_tangent) + assert raw["status"] == 0 and raw["sv_type"] == 1 + check_common(raw, n, 8) + assert "dSdE" not in raw and "Q" not in raw + if record_tangent: + assert raw["Lt"].shape == (n, 36) and raw["Lt"].flags.f_contiguous + else: + assert "Lt" not in raw + res = SolverResults(raw, [case != "small_strain"], 3) + assert res["Stress"].shape == (6, n) and res["Stress"].flags.c_contiguous + assert np.shares_memory(res["Stress"], raw["sigma"]) + + +def test_matrices_are_flattened_row_by_row(): + # a non-symmetric deformation gradient prescribed in full: the last F1 is the target, + # written row by row in BC_meca and in the record alike + target = np.array([[1.2, 0.1, 0.0], [0.05, 0.9, 0.02], [0.0, 0.03, 0.95]]) + raw = run([Block(steps=[StepMeca(control="F", value=target.ravel(), ninc=10)], control_type="F")], + "NEOHC", [1000.0, 10000.0], 1) + assert raw["status"] == 0 + check_common(raw, 10, 1) + np.testing.assert_allclose(raw["F1"][-1].reshape(3, 3), target, rtol=0.0, atol=1e-12) + res = SolverResults(raw, [True], 3) + np.testing.assert_array_equal(res["F"][:, :, -1], raw["F1"][-1].reshape(3, 3)) + np.testing.assert_array_equal(res["TangentMatrix"][:, :, 3], raw["Lt"][3].reshape(6, 6)) + + +@pytest.mark.parametrize("record_tangent", [True, False]) +def test_thermomechanical_history(record_tangent): + n = 10 + step = StepThermomeca(control=MIXED, value=[0.01, 0, 0, 0, 0, 0], ninc=n, T_final=320.0) + raw = run([Block(steps=[step])], "EPICP", EPICP_T_PROPS, 8, record_tangent) + assert raw["status"] == 0 and raw["sv_type"] == 2 + check_common(raw, n, 8) + assert raw["Q"].shape == (n,) and raw["r"].shape == (n,) and raw["Wt"].shape == (n, 3) + assert "Lt" not in raw + if record_tangent: + assert raw["dSdE"].shape == (n, 36) + for key in ("dSdT", "drdE"): + assert raw[key].shape == (n, 6) + assert raw["drdT"].shape == (n, 1) + else: + assert "dSdE" not in raw + + +def test_aborted_run_keeps_its_records(): + # a stress beyond the saturation of a perfectly plastic law, without inforce: the run stops + # early and every history holds the same records + step = StepMeca(control=["stress"] * 6, value=[900.0, 0, 0, 0, 0, 0], ninc=50, Dn_mini=0.25) + raw = run([Block(steps=[step])], "EPICP", [70000.0, 0.3, 1.0e-5, 300.0, 0.0, 1.0], 8, inforce=0) + assert raw["status"] == 1 + n = raw["time"].shape[0] + assert 0 < n < 50 + check_common(raw, n, 8) + assert raw["Lt"].shape == (n, 36) diff --git a/src/Continuum_mechanics/Functions/constitutive.cpp b/src/Continuum_mechanics/Functions/constitutive.cpp index 6e7863c2b..10bb91224 100755 --- a/src/Continuum_mechanics/Functions/constitutive.cpp +++ b/src/Continuum_mechanics/Functions/constitutive.cpp @@ -30,6 +30,7 @@ along with simcoon. If not, see . #include #include #include +#include using namespace std; using namespace arma; @@ -615,15 +616,28 @@ static vec condensed_pred(const mat &L, const vec &E, const int &ndi, const int vec el_pred(const vec &sigma_start, const mat &L, const vec &DE_el, const int &ndi, const int &nshr) { - if (ndi == 3) + if (ndi == 3) { + if (L.n_rows == 6 && L.n_cols == 6 && DE_el.n_elem == 6 && sigma_start.n_elem == 6) { + vec sigma(6); + mul6(L.memptr(), DE_el.memptr(), sigma.memptr()); + sigma += sigma_start; + return sigma; + } return sigma_start + L*DE_el; + } return sigma_start + condensed_pred(L, DE_el, ndi, nshr); } vec el_pred(const mat &L, const vec &E_el, const int &ndi, const int &nshr) { - if (ndi == 3) + if (ndi == 3) { + if (L.n_rows == 6 && L.n_cols == 6 && E_el.n_elem == 6) { + vec sigma(6); + mul6(L.memptr(), E_el.memptr(), sigma.memptr()); + return sigma; + } return L*E_el; + } return condensed_pred(L, E_el, ndi, nshr); } diff --git a/src/Continuum_mechanics/Functions/contimech.cpp b/src/Continuum_mechanics/Functions/contimech.cpp index 89b604283..2d06ea039 100755 --- a/src/Continuum_mechanics/Functions/contimech.cpp +++ b/src/Continuum_mechanics/Functions/contimech.cpp @@ -33,10 +33,55 @@ using namespace arma; namespace simcoon{ +namespace { + +// dev(vec) of a Voigt 6-vector and its partner with shears scaled by s_shear (2 stress, 0.5 strain) +inline void dev_voigt(const vec &v, const double &s_shear, double d[6], double d2[6]) { + const double sph = (1./3.)*(v(0) + v(1) + v(2)); + for (int i=0; i<3; i++) { + d[i] = v(i) - sph; + d2[i] = d[i]; + } + for (int i=3; i<6; i++) { + d[i] = v(i); + d2[i] = s_shear*v(i); + } +} + +// sum(a % b) with the two interleaved partial sums of Armadillo's dot (same rounding as sum()) +inline double dot_voigt(const double a[6], const double b[6]) { + double val1 = 0.; + double val2 = 0.; + for (int i=0; i<6; i+=2) { + val1 += (a[i] * b[i]); + val2 += (a[i+1] * b[i+1]); + } + return (val1 + val2); +} + +} // namespace + mat dev(const mat &m) { assert((m.n_cols==3)&&(m.n_rows==3)); - return m - sph(m); + if ((m.n_rows != 3) || (m.n_cols != 3)) { + return m - sph(m); + } + // m - sph(m) entry by entry; off-diagonals subtract 0*tr/3 to keep signed zeros and non-finite traces + double val1 = 0.; + double val2 = 0.; + val1 += m(0,0); + val2 += m(1,1); + val1 += m(2,2); + const double k = (1./3.)*(val1 + val2); + const double zero_k = 0.*k; + mat d(3, 3, fill::none); + for (int j=0; j<3; j++) { + for (int i=0; i<3; i++) { + d(i,j) = m(i,j) - ((i == j) ? k : zero_k); + } + } + return d; } mat sph(const mat &m) { @@ -70,26 +115,27 @@ vec dev(const vec &v) { double Mises_stress(const vec &v) { assert(v.size()==6); - vec vdev = dev(v); - vec vdev2 = vdev; - for (int i=3; i<6; i++) - vdev2(i) = 2.*vdev2(i); + double vdev[6], vdev2[6]; + dev_voigt(v, 2., vdev, vdev2); - return sqrt(3./2.*sum(vdev%vdev2)); + return sqrt(3./2.*dot_voigt(vdev, vdev2)); } vec eta_stress(const vec &v) { assert(v.size()==6); - vec vdev = dev(v); - vec vdev2 = vdev; - for (int i=3; i<6; i++) - vdev2(i) = 2.*vdev2(i); + double vdev[6], vdev2[6]; + dev_voigt(v, 2., vdev, vdev2); - double n = sqrt(3./2.*sum(vdev%vdev2)); + double n = sqrt(3./2.*dot_voigt(vdev, vdev2)); - if (n > 0.) { - return (3./2.)*vdev2*(1./n); + if (n > 0.) { + // (3/2) vdev2 (1/n), evaluated as the expression template did + const double inv_n = 1./n; + vec eta(6, fill::none); + for (int i=0; i<6; i++) + eta(i) = ((3./2.)*vdev2[i])*inv_n; + return eta; } else { return zeros(6); @@ -100,33 +146,52 @@ vec eta_stress(const vec &v) { mat deta_stress(const vec &v) { assert(v.size()==6); - double sigeq = Mises_stress(v); + double vdev[6], vdev2[6]; + dev_voigt(v, 2., vdev, vdev2); + double sigeq = sqrt(3./2.*dot_voigt(vdev, vdev2)); if (sigeq < simcoon::iota) { return zeros(6,6); } - vec eta = eta_stress(v); + double eta[6]; + const double inv_n = 1./sigeq; + for (int i=0; i<6; i++) + eta[i] = (sigeq > 0.) ? ((3./2.)*vdev2[i])*inv_n : 0.; // P_Mises in simcoon's engineering-shear Voigt basis: normal block = (3/2) dev, // shear diagonal = 3 (the factor 2 is already embedded, as in eta_stress / Mises_stress). - mat Pm = { { 1., -0.5, -0.5, 0., 0., 0.}, - {-0.5, 1., -0.5, 0., 0., 0.}, - {-0.5, -0.5, 1., 0., 0., 0.}, - { 0., 0., 0., 3., 0., 0.}, - { 0., 0., 0., 0., 3., 0.}, - { 0., 0., 0., 0., 0., 3.} }; - return (Pm - eta*eta.t())/sigeq; + // the product stays its own statement: rounded before the subtraction, as in the matrix temporary + static const double Pm[6][6] = { { 1., -0.5, -0.5, 0., 0., 0.}, + {-0.5, 1., -0.5, 0., 0., 0.}, + {-0.5, -0.5, 1., 0., 0., 0.}, + { 0., 0., 0., 3., 0., 0.}, + { 0., 0., 0., 0., 3., 0.}, + { 0., 0., 0., 0., 0., 3.} }; + mat deta(6, 6, fill::none); + for (int j=0; j<6; j++) { + for (int i=0; i<6; i++) { + const double eta_ij = eta[i]*eta[j]; + deta(i,j) = (Pm[i][j] - eta_ij)/sigeq; + } + } + return deta; } vec eta_norm_stress(const vec &v) { assert(v.size()==6); - vec v2 = v; - for (int i=3; i<6; i++) - v2(i) = 2.*v2(i); + double v1[6], v2[6]; + for (int i=0; i<6; i++) { + v1[i] = v(i); + v2[i] = (i < 3) ? v1[i] : 2.*v1[i]; + } - double n = sqrt(sum(v%v2)); + double n = sqrt(dot_voigt(v1, v2)); if (n > 0.) { - return v2*(1./n); + const double inv_n = 1./n; + vec eta(6, fill::none); + for (int i=0; i<6; i++) + eta(i) = v2[i]*inv_n; + return eta; } else { return zeros(6); @@ -136,14 +201,20 @@ vec eta_norm_stress(const vec &v) { vec eta_norm_strain(const vec &v) { assert(v.size()==6); - vec v2 = v; - for (int i=3; i<6; i++) - v2(i) = 0.5*v2(i); + double v1[6], v2[6]; + for (int i=0; i<6; i++) { + v1[i] = v(i); + v2[i] = (i < 3) ? v1[i] : 0.5*v1[i]; + } - double n = sqrt(sum(v%v2)); + double n = sqrt(dot_voigt(v1, v2)); if (n > 0.) { - return v*(1./n); + const double inv_n = 1./n; + vec eta(6, fill::none); + for (int i=0; i<6; i++) + eta(i) = v1[i]*inv_n; + return eta; } else { return zeros(6); @@ -154,47 +225,52 @@ double norm_stress(const vec &v) { assert(v.size()==6); - vec v2 = v; - for (int i=3; i<6; i++) - v2(i) = 2.*v2(i); + double v1[6], v2[6]; + for (int i=0; i<6; i++) { + v1[i] = v(i); + v2[i] = (i < 3) ? v1[i] : 2.*v1[i]; + } - return sqrt(sum(v%v2)); + return sqrt(dot_voigt(v1, v2)); } double norm_strain(const vec &v) { assert(v.size()==6); - vec v2 = v; - for (int i=3; i<6; i++) - v2(i) = 0.5*v2(i); + double v1[6], v2[6]; + for (int i=0; i<6; i++) { + v1[i] = v(i); + v2[i] = (i < 3) ? v1[i] : 0.5*v1[i]; + } - return sqrt(sum(v%v2)); + return sqrt(dot_voigt(v1, v2)); } double Mises_strain(const vec &v) { assert(v.size()==6); - vec vdev = dev(v); - vec vdev2 = vdev; - for (int i=3; i<6; i++) - vdev2(i) = 0.5*vdev2(i); + double vdev[6], vdev2[6]; + dev_voigt(v, 0.5, vdev, vdev2); - return sqrt(2./3.*sum(vdev%vdev2)); + return sqrt(2./3.*dot_voigt(vdev, vdev2)); } vec eta_strain(const vec &v) { assert(v.size()==6); - vec vdev = dev(v); - vec vdev2 = vdev; - for (int i=3; i<6; i++) - vdev2(i) = 0.5*vdev2(i); + double vdev[6], vdev2[6]; + dev_voigt(v, 0.5, vdev, vdev2); - double n = sqrt(2./3.*sum(vdev%vdev2)); + double n = sqrt(2./3.*dot_voigt(vdev, vdev2)); if (n > 0.) { - return (2./3.)*vdev*(1./n); + // (2/3) vdev (1/n), evaluated as the expression template did + const double inv_n = 1./n; + vec eta(6, fill::none); + for (int i=0; i<6; i++) + eta(i) = ((2./3.)*vdev[i])*inv_n; + return eta; } else { return zeros(6); @@ -205,24 +281,20 @@ vec eta_strain(const vec &v) { double J2_stress(const vec &v) { assert(v.size()==6); - vec vdev = dev(v); - vec vdev2 = vdev; - for (int i=3; i<6; i++) - vdev2(i) = 2.*vdev2(i); + double vdev[6], vdev2[6]; + dev_voigt(v, 2., vdev, vdev2); - return 0.5*sum(vdev%vdev2); + return 0.5*dot_voigt(vdev, vdev2); } double J2_strain(const vec &v) { assert(v.size()==6); - vec vdev = dev(v); - vec vdev2 = vdev; - for (int i=3; i<6; i++) - vdev2(i) = 0.5*vdev2(i); + double vdev[6], vdev2[6]; + dev_voigt(v, 0.5, vdev, vdev2); - return 0.5*sum(vdev%vdev2); + return 0.5*dot_voigt(vdev, vdev2); } double J3_stress(const vec &v) { @@ -347,18 +419,29 @@ mat p_ikjl(const vec &a) { return F; } +// outer product entry by entry: one rounded product per entry, as the rank-one BLAS call +static mat outer_voigt(const vec &a, const vec &b) { + mat C(6, 6, fill::none); + for (int j=0; j<6; j++) { + for (int i=0; i<6; i++) { + C(i,j) = a(i)*b(j); + } + } + return C; +} + mat auto_sym_dyadic(const mat &A) { vec A_v = t2v_sym(A); - return A_v * A_v.t(); + return outer_voigt(A_v, A_v); } mat sym_dyadic(const mat &A, const mat &B) { vec A_v = t2v_sym(A); vec B_v = t2v_sym(B); - - return A_v * B_v.t(); + + return outer_voigt(A_v, B_v); } mat auto_dyadic(const mat &A) { diff --git a/src/Continuum_mechanics/Functions/criteria.cpp b/src/Continuum_mechanics/Functions/criteria.cpp index e403b07dd..d477a0ae3 100755 --- a/src/Continuum_mechanics/Functions/criteria.cpp +++ b/src/Continuum_mechanics/Functions/criteria.cpp @@ -45,19 +45,20 @@ namespace simcoon assert(n > 0.); double temp; double m; + const double Mises = Mises_stress(v); // Below iota the invariants are roundoff and 1 + b J3/J2^1.5 can leave its bounds: NaN - if (Mises_stress(v) > simcoon::iota) + if (Mises > simcoon::iota) { if (n < 10.) { m = 1. / n; - temp = Mises_stress(v) * pow((1. + b * J3_stress(v) / pow(J2_stress(v), 1.5)), m); + temp = Mises * pow((1. + b * J3_stress(v) / pow(J2_stress(v), 1.5)), m); } else { m = 0.; - temp = Mises_stress(v); + temp = Mises; } } else @@ -75,19 +76,6 @@ namespace simcoon assert(n > 0.); double Mises = Mises_stress(v); - vec dMises = eta_stress(v); - - double J2 = J2_stress(v); - double J3 = J3_stress(v); - double J2_3_2 = pow(J2, 1.5); - double J2_5_2 = pow(J2, 2.5); - - mat sigma = v2t_stress(v); - - mat dJ2_mat=dJ2DS(sigma); - mat dJ3_mat=dJ3DS(sigma); - vec dJ2 = t2v_strain(dJ2_mat); - vec dJ3 = t2v_strain(dJ3_mat); double m; vec temp; @@ -98,6 +86,17 @@ namespace simcoon { m = 1. / n; + vec dMises = eta_stress(v); + double J2 = J2_stress(v); + double J3 = J3_stress(v); + double J2_3_2 = pow(J2, 1.5); + double J2_5_2 = pow(J2, 2.5); + mat sigma = v2t_stress(v); + mat dJ2_mat=dJ2DS(sigma); + mat dJ3_mat=dJ3DS(sigma); + vec dJ2 = t2v_strain(dJ2_mat); + vec dJ3 = t2v_strain(dJ3_mat); + temp = pow((1. + b * J3 / J2_3_2), m) * dMises + Mises * b * m * (pow((1. + b * J3 / J2_3_2), (m-1))* (dJ3/J2_3_2-1.5*J3*dJ2/J2_5_2)); } else @@ -227,20 +226,6 @@ namespace simcoon assert(n > 0.); double Dfa_stress = DFA_stress(v, params); - vec dDfa_stress = dDFA_stress(v,params); - - double J2 = J2_stress(v); - double J3 = J3_stress(v); - double J2_3_2 = pow(J2, 1.5); - double J2_5_2 = pow(J2, 2.5); - - mat sigma = v2t_stress(v); - - mat dJ2_mat=dJ2DS(sigma); - mat dJ3_mat=dJ3DS(sigma); - vec dJ2 = t2v_strain(dJ2_mat); - vec dJ3 = t2v_strain(dJ3_mat); - double m; vec temp; @@ -251,6 +236,17 @@ namespace simcoon { m = 1. / n; + vec dDfa_stress = dDFA_stress(v,params); + double J2 = J2_stress(v); + double J3 = J3_stress(v); + double J2_3_2 = pow(J2, 1.5); + double J2_5_2 = pow(J2, 2.5); + mat sigma = v2t_stress(v); + mat dJ2_mat=dJ2DS(sigma); + mat dJ3_mat=dJ3DS(sigma); + vec dJ2 = t2v_strain(dJ2_mat); + vec dJ3 = t2v_strain(dJ3_mat); + temp = pow((1. + b * J3 / J2_3_2), m) * dDfa_stress + Dfa_stress * b * m * (pow((1. + b * J3 / J2_3_2), (m-1))* (dJ3/J2_3_2-1.5*J3*dJ2/J2_5_2)); } else @@ -267,10 +263,12 @@ namespace simcoon return temp; } - mat P_Ani(const vec ¶ms) + namespace { + // anisotropy operators into a caller-provided 6x6 (stack in the criteria, heap in P_Ani/P_Hill/P_DFA) + void fill_P_Ani(const vec ¶ms, mat &P) { assert(params.n_elem == 9); // P_11,P_22,P_33,P_12,P_13,P_23,P_44,P_55,P_66 - mat P = zeros(6, 6); + P.zeros(); P(0, 0) = params(0); // P_11 P(1, 1) = params(1); // P_22 P(2, 2) = params(2); // P_33 @@ -283,14 +281,12 @@ namespace simcoon P(3, 3) = 2. * params(6); // P_44 = P_1212 P(4, 4) = 2. * params(7); // P_55 = P_1313 P(5, 5) = 2. * params(8); // P_66 = P_2323 - - return P; } - mat P_Hill(const vec ¶ms) + void fill_P_Hill(const vec ¶ms, mat &P) { assert(params.n_elem == 6); // F,G,H,L,M,N - mat P = zeros(6, 6); + P.zeros(); // params = (F, G, H, L, M, N) P(0, 0) = params(1) + params(2); // P_11 = G+H P(1, 1) = params(0) + params(2); // P_22 = F+H @@ -304,14 +300,12 @@ namespace simcoon P(3, 3) = 2. * params(5); // P_44 = N P(4, 4) = 2. * params(4); // P_55 = M P(5, 5) = 2. * params(3); // P_66 = L - - return P; } - mat P_DFA(const vec ¶ms) + void fill_P_DFA(const vec ¶ms, mat &P) { // Deshpande–Fleck–Ashby assert(params.n_elem == 7); // F,G,H,L,M,N,K - mat P = zeros(6, 6); + P.zeros(); // params = (F, G, H, L, M, N, K) P(0, 0) = params(1) + params(2) + params(6) / 9.; // P_11 = G+H+K/9 P(1, 1) = params(0) + params(2) + params(6) / 9.; // P_22 = F+H+K/9 @@ -325,7 +319,27 @@ namespace simcoon P(3, 3) = 2. * params(5); // P_44 = N P(4, 4) = 2. * params(4); // P_55 = M P(5, 5) = 2. * params(3); // P_66 = L + } + } + + mat P_Ani(const vec ¶ms) + { + mat P(6, 6); + fill_P_Ani(params, P); + return P; + } + mat P_Hill(const vec ¶ms) + { + mat P(6, 6); + fill_P_Hill(params, P); + return P; + } + + mat P_DFA(const vec ¶ms) + { + mat P(6, 6); + fill_P_DFA(params, P); return P; } @@ -347,7 +361,8 @@ namespace simcoon if (norm(v, 2) > simcoon::iota) { - return (H * v) / Eq_stress_P(v, H); + const vec Hv = H * v; // shared by the gradient and the norm (Eq_stress_P) + return Hv / pow(sum(v % Hv), 0.5); } else { @@ -361,63 +376,76 @@ namespace simcoon { return zeros(6, 6); } - double sigeq = Eq_stress_P(v, H); + const vec Hv = H * v; + double sigeq = pow(sum(v % Hv), 0.5); // Eq_stress_P(v, H) if (sigeq < simcoon::iota) { return zeros(6, 6); } - vec Lambda = (H * v) / sigeq; // == dEq_stress_P(v, H) + vec Lambda = Hv / sigeq; // == dEq_stress_P(v, H) return (H - Lambda * Lambda.t()) / sigeq; } mat ddHill_stress(const vec &v, const vec ¶ms) { - return ddEq_stress_P(v, P_Hill(params)); + mat::fixed<6, 6> P; + fill_P_Hill(params, P); + return ddEq_stress_P(v, P); } mat ddDFA_stress(const vec &v, const vec ¶ms) { - return ddEq_stress_P(v, P_DFA(params)); + mat::fixed<6, 6> P; + fill_P_DFA(params, P); + return ddEq_stress_P(v, P); } mat ddAni_stress(const vec &v, const vec ¶ms) { - return ddEq_stress_P(v, P_Ani(params)); + mat::fixed<6, 6> P; + fill_P_Ani(params, P); + return ddEq_stress_P(v, P); } double Hill_stress(const vec &v, const vec ¶ms) { - mat P = P_Hill(params); + mat::fixed<6, 6> P; + fill_P_Hill(params, P); return Eq_stress_P(v, P); } vec dHill_stress(const vec &v, const vec ¶ms) { - mat P = P_Hill(params); + mat::fixed<6, 6> P; + fill_P_Hill(params, P); return dEq_stress_P(v, P); } double DFA_stress(const vec &v, const vec ¶ms) { - mat P = P_DFA(params); + mat::fixed<6, 6> P; + fill_P_DFA(params, P); return Eq_stress_P(v, P); } vec dDFA_stress(const vec &v, const vec ¶ms) { - mat P = P_DFA(params); + mat::fixed<6, 6> P; + fill_P_DFA(params, P); return dEq_stress_P(v, P); } double Ani_stress(const vec &v, const vec ¶ms) { - mat P = P_Ani(params); + mat::fixed<6, 6> P; + fill_P_Ani(params, P); return Eq_stress_P(v, P); } vec dAni_stress(const vec &v, const vec ¶ms) { - mat P = P_Ani(params); + mat::fixed<6, 6> P; + fill_P_Ani(params, P); return dEq_stress_P(v, P); } diff --git a/src/Continuum_mechanics/Functions/hyperelastic.cpp b/src/Continuum_mechanics/Functions/hyperelastic.cpp index d09eb6999..7b53014aa 100755 --- a/src/Continuum_mechanics/Functions/hyperelastic.cpp +++ b/src/Continuum_mechanics/Functions/hyperelastic.cpp @@ -34,27 +34,277 @@ using namespace arma; namespace simcoon{ -vec isochoric_invariants(const mat &b, const double &mJ) { +namespace { + +// Fixed-size kernels of the isochoric-invariant functions. Each one reproduces the armadillo +// expression it replaces operation by operation (same products, summed in the same order), with +// the 3x3 and 6x6 temporaries on the stack. Bit identity with the armadillo expressions was +// established with Apple clang (-ffp-contract=on: a product is fused only with an addition of the +// same statement) and Armadillo 15.6.0 (the simcoon-prof environment); it rests on internal details +// of that version (tiny-matrix product kernel, two-accumulator trace, diagonal shortcut of powmat). +// A compiler that contracts across statements (GCC's default -ffp-contract=fast in gnu++ mode, on +// FMA targets) or another Armadillo may round differently, at the epsilon level; the +// tolerance-based tests remain the guarantee of correctness. +typedef mat::fixed<3,3> m33; +typedef mat::fixed<6,6> m66; + +// trace(X), summed as armadillo's trace (15.6.0): two accumulators, (X00 + X22) + X11 +inline double trace3(const m33 &X) { + double val1 = 0.; + double val2 = 0.; + val1 += X(0,0); + val2 += X(1,1); + val1 += X(2,2); + return val1 + val2; +} + +// dev(X) = X - ((1/3) tr X) I +inline m33 dev3(const m33 &X) { + const double s = (1./3.)*trace3(X); + m33 D; + for (int j = 0; j < 3; ++j) { + for (int i = 0; i < 3; ++i) { + const double e = (i == j) ? 1. : 0.; + const double sph = e*s; + D(i,j) = X(i,j) - sph; + } + } + return D; +} +// powmat(X, 2): X*X as armadillo's tiny-matrix kernel (15.6.0); a diagonal X takes armadillo's own +// shortcut (a pow call per entry, not always the rounded square) +inline m33 square3(const m33 &X) { + m33 P; + if (X.is_diagmat()) { + P = powmat(X, 2); + return P; + } + for (int j = 0; j < 3; ++j) { + const double x0 = X(0,j), x1 = X(1,j), x2 = X(2,j); + for (int i = 0; i < 3; ++i) { + P(i,j) = X(i,0)*x0 + X(i,1)*x1 + X(i,2)*x2; + } + } + return P; +} + +// t2v_sym(A) +inline void sym_voigt(const m33 &A, double v[6]) { + v[0] = A(0,0); + v[1] = A(1,1); + v[2] = A(2,2); + v[3] = 0.5*(A(0,1) + A(1,0)); + v[4] = 0.5*(A(0,2) + A(2,0)); + v[5] = 0.5*(A(1,2) + A(2,1)); +} + +// t2v_sym of the identity +const double I_voigt[6] = {1., 1., 1., 0., 0., 0.}; + +// Idev(), Ivol() and Ireal() entries (constitutive.cpp) +inline double Idev_at(const int i, const int j) { + if (i < 3 && j < 3) return (i == j) ? 1.*(1.-1./3.) : 1.*(0.-1./3.); + return (i == j) ? 0.5 : 0.; +} +inline double Ivol_at(const int i, const int j) { + return (i < 3 && j < 3) ? (1./3.) : 0.; +} +inline double Ireal_at(const int i, const int j) { + return (i == j) ? ((i < 3) ? 1. : 0.5) : 0.; +} + +// b_bar = J^(-2/3) b +inline m33 b_bar_of(const mat &b, const double &J) { + const double k = pow(J,-2./3.); + m33 b_bar; + for (int e = 0; e < 9; ++e) b_bar(e) = b(e)*k; + return b_bar; +} + +// The first two isochoric invariants of b_bar, as isochoric_invariants +inline void bar_invariants(const m33 &b_bar, double &I1, double &I2) { + I1 = trace3(b_bar); + I2 = 0.5*(pow(trace3(b_bar),2.) - trace3(square3(b_bar))); +} + +double J_of_b(const mat &b, const double &mJ, const char *where) { double J=mJ; if (fabs(mJ) < simcoon::iota) { try { J = sqrt(det(b)); } catch (const std::runtime_error &e) { cerr << "Error in det: " << e.what() << endl; - throw simcoon::exception_det("Error in det function inside isochoric_invariants."); - } + throw simcoon::exception_det(std::string("Error in det function inside ") + where + "."); + } } - mat b_bar = pow(J,-2./3.)*b; - vec I = zeros(3); + return J; +} - I(0) = trace(b_bar); - try { - I(1) = 0.5*(pow(trace(b_bar),2.)-trace(powmat(b_bar,2))); - } catch (const std::runtime_error &e) { - cerr << "Error in det: " << e.what() << endl; - throw simcoon::exception_powmat("Error in powmat function inside isochoric_invariants."); - } +// 2 dW/dI1 dev(b_bar) + 2 dW/dI2 (tr(b_bar) dev(b_bar) - dev(b_bar^2)); the I_2 part is skipped +// when its derivative is zero (an exact zero term) +m33 tau_iso_invariants_fixed(const double &dWdI_1_bar, const double &dWdI_2_bar, const m33 &b_bar) { + const m33 dev_b_bar = dev3(b_bar); + const double c1 = 2.*dWdI_1_bar; + m33 tau; + for (int e = 0; e < 9; ++e) tau(e) = dev_b_bar(e)*c1; + if (dWdI_2_bar != 0.) { + const m33 dev_b_bar2 = dev3(square3(b_bar)); + const double tr = trace3(b_bar); + const double c2 = 2.*dWdI_2_bar; + for (int e = 0; e < 9; ++e) { + const double p = dev_b_bar(e)*tr; + const double d = p - dev_b_bar2(e); + const double q = d*c2; + tau(e) = tau(e) + q; + } + } + return tau; +} + +// gamma_linear: (4/3) (tr_A I_dev - (dev_A (x) I + I (x) dev_A)), scaled by d1, added to Lt +// (Lt = that product when first), with dev_A in Voigt form +void add_gamma_linear(m66 &Lt, const double a[6], const double &tr_A, const double &d1, const bool &first) { + for (int j = 0; j < 6; ++j) { + for (int i = 0; i < 6; ++i) { + const double s1 = a[i]*I_voigt[j]; + const double s2 = I_voigt[i]*a[j]; + const double s = s1 + s2; + const double x = Idev_at(i,j)*tr_A; + const double g = (x - s)*(4./3.); + const double t = g*d1; + Lt(i,j) = first ? t : Lt(i,j) + t; + } + } +} + +// Lt += (4 a (x) a) d2, auto_sym_dyadic of the Voigt vector a +void add_gamma_quadratic(m66 &Lt, const double a[6], const double &d2) { + for (int j = 0; j < 6; ++j) { + for (int i = 0; i < 6; ++i) { + const double g = (a[i]*a[j])*4.; + const double t = g*d2; + Lt(i,j) = Lt(i,j) + t; + } + } +} + +// L_iso_hyper_invariants without the 1/J, on the stack +m66 L_iso_invariants_fixed(const double &dWdI_1_bar, const double &dWdI_2_bar, const double &dW2dI_11_bar, const double &dW2dI_12_bar, const double &dW2dI_22_bar, const m33 &b_bar) { + const m33 dev_b_bar = dev3(b_bar); + double I1, I2; + bar_invariants(b_bar, I1, I2); + double a[6]; + sym_voigt(dev_b_bar, a); + + // Only the terms of a non-zero potential derivative are built, summed in the order of the + // full expression (a skipped term adds an exact zero): a Neo-Hookean potential needs gamma_1 + // alone, and the I_2 terms cost b_bar^2 and five more dyadic products. + m66 Lt; + add_gamma_linear(Lt, a, I1, dWdI_1_bar, true); + if ((dWdI_2_bar != 0.) || (dW2dI_12_bar != 0.) || (dW2dI_22_bar != 0.)) { + const m33 dev_b_bar2 = dev3(square3(b_bar)); + m33 devdevbb2; // I1 dev(b_bar) - dev(b_bar^2) + for (int e = 0; e < 9; ++e) { + const double p = dev_b_bar(e)*I1; + devdevbb2(e) = p - dev_b_bar2(e); + } + if (dWdI_2_bar != 0.) { + double a2[6], bv[6]; + sym_voigt(dev_b_bar2, a2); + sym_voigt(b_bar, bv); + static const int Id[3][3] = {{0, 3, 4}, {3, 1, 5}, {4, 5, 2}}; + m66 H_bar; // auto_sym_dyadic_operator(b_bar) + for (int i=0; i<3; i++) { + for (int j=i; j<3; j++) { + for (int k=0; k<3; k++) { + for (int l=k; l<3; l++) { + H_bar(Id[i][j],Id[k][l]) = 0.5*(b_bar(i,k)*b_bar(j,l) + b_bar(i,l)*b_bar(j,k)); + } + } + } + } + // gamma_2 = (8/3) (I2 (I_real - 2 I_vol) - I1 (dev(b_bar) (x) I + I (x) dev(b_bar)) + // + (dev(b_bar^2) (x) I + I (x) dev(b_bar^2))) + 4 (b_bar (x) b_bar - H_bar) + for (int j = 0; j < 6; ++j) { + for (int i = 0; i < 6; ++i) { + const double v2 = Ivol_at(i,j)*2.; + const double r = Ireal_at(i,j) - v2; + const double X = r*I2; + const double s1 = a[i]*I_voigt[j]; + const double s2 = I_voigt[i]*a[j]; + const double Y = (s1 + s2)*I1; + const double z1 = a2[i]*I_voigt[j]; + const double z2 = I_voigt[i]*a2[j]; + const double Z = z1 + z2; + const double T = ((X - Y) + Z)*(8./3.); + const double bb = bv[i]*bv[j]; + const double U = (bb - H_bar(i,j))*4.; + const double g = T + U; + const double t = g*dWdI_2_bar; + Lt(i,j) = Lt(i,j) + t; + } + } + } + if (dW2dI_11_bar != 0.) { + add_gamma_quadratic(Lt, a, dW2dI_11_bar); + } + double dd[6]; + sym_voigt(devdevbb2, dd); + if (dW2dI_12_bar != 0.) { + for (int j = 0; j < 6; ++j) { + for (int i = 0; i < 6; ++i) { + const double s1 = a[i]*dd[j]; + const double s2 = dd[i]*a[j]; + const double g = (s1 + s2)*4.; + const double t = g*dW2dI_12_bar; + Lt(i,j) = Lt(i,j) + t; + } + } + } + if (dW2dI_22_bar != 0.) { + add_gamma_quadratic(Lt, dd, dW2dI_22_bar); + } + } + else if (dW2dI_11_bar != 0.) { + add_gamma_quadratic(Lt, a, dW2dI_11_bar); + } + return Lt; +} + +// L_vol_hyper on the stack: (dU/dJ + J d2U/dJ2) 3 I_vol - 2 dU/dJ I_real +m66 L_vol_fixed(const double &dUdJ, const double &dU2dJ2, const double &J) { + const double k1 = (dUdJ+dU2dJ2*J)*3.; + const double k2 = 2.*dUdJ; + m66 L; + for (int j = 0; j < 6; ++j) { + for (int i = 0; i < 6; ++i) { + const double p1 = Ivol_at(i,j)*k1; + const double p2 = Ireal_at(i,j)*k2; + L(i,j) = p1 - p2; + } + } + return L; +} + +// N_k = n_k n_k^T, the eigenprojectors of the columns of n (n_k(a) n_k(b), as their outer product) +void eigen_projectors(std::vector &N_projectors, const mat &n) { + for (int k = 0; k < 3; k++) { + mat P(3,3); + for (int q = 0; q < 3; q++) + for (int p = 0; p < 3; p++) + P(p,q) = n(p,k)*n(q,k); + N_projectors[k] = P; + } +} + +} // namespace + +vec isochoric_invariants(const mat &b, const double &mJ) { + + const double J = J_of_b(b, mJ, "isochoric_invariants"); + vec I = zeros(3); + bar_invariants(b_bar_of(b, J), I(0), I(1)); I(2) = 1.; return I; } @@ -167,9 +417,7 @@ void pstretch(vec &lambda, mat &n_pvectors, const mat &input, const string &inpu void pstretch(vec &lambda, mat &n_pvectors, std::vector &N_projectors, const mat &input, const string &input_tensor, const double &mJ) { pstretch(lambda, n_pvectors, input, input_tensor, mJ); - N_projectors[0] = n_pvectors.col(0)*(n_pvectors.col(0)).t(); - N_projectors[1] = n_pvectors.col(1)*(n_pvectors.col(1)).t(); - N_projectors[2] = n_pvectors.col(2)*(n_pvectors.col(2)).t(); + eigen_projectors(N_projectors, n_pvectors); } void isochoric_pstretch(vec &lambda_bar, mat &n_pvectors, const mat &input, const string &input_tensor, const double &mJ) { @@ -215,9 +463,7 @@ void isochoric_pstretch(vec &lambda_bar, mat &n_pvectors, const mat &input, cons void isochoric_pstretch(vec &lambda_bar, mat &n_pvectors, std::vector &N_projectors, const mat &input, const string &input_tensor, const double &mJ) { isochoric_pstretch(lambda_bar, n_pvectors, input, input_tensor, mJ); - N_projectors[0] = n_pvectors.col(0)*(n_pvectors.col(0)).t(); - N_projectors[1] = n_pvectors.col(1)*(n_pvectors.col(1)).t(); - N_projectors[2] = n_pvectors.col(2)*(n_pvectors.col(2)).t(); + eigen_projectors(N_projectors, n_pvectors); } vec beta_coefs(const vec &dWdlambda_bar, const vec &lambda_bar) { @@ -338,26 +584,8 @@ vec delta_coefs(const vec &a_coefs, const vec &b_coefs, const mat &b) { }*/ mat tau_iso_hyper_invariants(const double &dWdI_1_bar, const double &dWdI_2_bar, const mat &b, const double &mJ) { - double J=mJ; - if (fabs(mJ) < simcoon::iota) { - try { - J = sqrt(det(b)); - } catch (const std::runtime_error &e) { - cerr << "Error in det: " << e.what() << endl; - throw simcoon::exception_det("Error in det function inside tau_iso_hyper_invariants."); - } - } - mat b_bar = pow(J,-2./3.)*b; - - mat b_bar2; - try { - b_bar2 = powmat(b_bar,2); - } catch (const std::runtime_error &e) { - cerr << "Error in det: " << e.what() << endl; - throw simcoon::exception_powmat("Error in powmat function inside tau_iso_hyper_invariants."); - } - - return 2.*dWdI_1_bar*dev(b_bar) + 2.*dWdI_2_bar*(trace(b_bar)*dev(b_bar) - dev(b_bar2)); + const double J = J_of_b(b, mJ, "tau_iso_hyper_invariants"); + return tau_iso_invariants_fixed(dWdI_1_bar, dWdI_2_bar, b_bar_of(b, J)); } mat tau_vol_hyper(const double &dUdJ, const mat &b, const double &mJ) { @@ -376,25 +604,12 @@ mat tau_vol_hyper(const double &dUdJ, const mat &b, const double &mJ) { } mat sigma_iso_hyper_invariants(const double &dWdI_1_bar, const double &dWdI_2_bar, const mat &b, const double &mJ) { - double J=mJ; - if (fabs(mJ) < simcoon::iota) { - try { - J = sqrt(det(b)); - } catch (const std::runtime_error &e) { - cerr << "Error in det: " << e.what() << endl; - throw simcoon::exception_det("Error in det function inside sigma_iso_hyper_invariants."); - } - } - mat b_bar = pow(J,-2./3.)*b; - mat b_bar2; - try { - b_bar2 = powmat(b_bar,2); - } catch (const std::runtime_error &e) { - cerr << "Error in det: " << e.what() << endl; - throw simcoon::exception_powmat("Error in powmat function inside sigma_iso_hyper_invariants."); - } - - return (1./J)*(2.*dWdI_1_bar*dev(b_bar) + 2.*dWdI_2_bar*(trace(b_bar)*dev(b_bar) - dev(b_bar2))); + const double J = J_of_b(b, mJ, "sigma_iso_hyper_invariants"); + const m33 tau = tau_iso_invariants_fixed(dWdI_1_bar, dWdI_2_bar, b_bar_of(b, J)); + const double invJ = 1./J; + mat sigma(3,3); + for (int e = 0; e < 9; ++e) sigma(e) = tau(e)*invJ; + return sigma; } mat sigma_vol_hyper(const double &dUdJ, const mat &b, const double &mJ) { @@ -466,15 +681,31 @@ mat L_iso_hyper_pstretch(const vec &dWdlambda_bar, const mat &dW2dlambda_bar2, c vec beta = beta_coefs(dWdlambda_bar, lambda_bar); mat gamma = gamma_coefs(dWdlambda_bar, dW2dlambda_bar2, lambda_bar); - mat c = zeros(6,6); mat delta = eye(3,3); + const m33 n(n_pvectors); + // c accumulated on the stack, the dyadic products built in place: the same products, rounded + // and summed in the same order as dyadic_4vectors_sym and auto_sym_dyadic (bit identity: see the + // note on the fixed-size kernels at the top of this file) + static const int Id[3][3] = {{0, 3, 4}, {3, 1, 5}, {4, 5, 2}}; + m66 c(fill::zeros); double factor = 0.; for(unsigned int i=0; i<3; i++) { for(unsigned int j=0; j<3; j++) { - c += (gamma(i,j) - 2*delta(i,j)*beta(i))*dyadic_4vectors_sym(n_pvectors.col(i), n_pvectors.col(j), "aabb") ; - } + const double coef = (gamma(i,j) - 2*delta(i,j)*beta(i)); + for (int p=0; p<3; p++) { + for (int q=p; q<3; q++) { + for (int k=0; k<3; k++) { + for (int l=k; l<3; l++) { + const double D = n(p,i)*n(q,i)*n(k,j)*n(l,j); // aabb dyadic of (n_i, n_j) + const double t = D*coef; + c(Id[p][q],Id[k][l]) = c(Id[p][q],Id[k][l]) + t; + } + } + } + } + } // ordered pairs a!=b: the symmetrized dyadic collapses to s s^T, s = sym dyadic of (n_i n_j^T + n_j n_i^T) for(unsigned int j=i+1; j<3; j++) { @@ -485,10 +716,28 @@ mat L_iso_hyper_pstretch(const vec &dWdlambda_bar, const mat &dW2dlambda_bar2, c factor = (beta(j)*pow(lambda_bar(i),2.) - beta(i)*pow(lambda_bar(j),2.))/(pow(lambda_bar(j),2.)-pow(lambda_bar(i),2.)); } - c += factor*auto_sym_dyadic(n_pvectors.col(i)*(n_pvectors.col(j)).t() + n_pvectors.col(j)*(n_pvectors.col(i)).t()); + m33 M; + for (int q=0; q<3; q++) { + for (int p=0; p<3; p++) { + const double m1 = n(p,i)*n(q,j); + const double m2 = n(p,j)*n(q,i); + M(p,q) = m1 + m2; + } + } + double s[6]; + sym_voigt(M, s); + for (int q=0; q<6; q++) { + for (int p=0; p<6; p++) { + const double t = (s[p]*s[q])*factor; + c(p,q) = c(p,q) + t; + } + } } } - return (1./J)*c; + const double invJ = 1./J; + mat L(6,6); + for (int e = 0; e < 36; ++e) L(e) = c(e)*invJ; + return L; } mat L_iso_hyper_pstretch(const vec &dWdlambda_bar, const mat &dW2dlambda_bar2, const mat &b, const double &mJ) { @@ -522,75 +771,16 @@ static mat gamma_quadratic(const mat &dev_A) { } mat L_iso_hyper_invariants(const double &dWdI_1_bar, const double &dWdI_2_bar, const double &dW2dI_11_bar, const double &dW2dI_12_bar, const double &dW2dI_22_bar, const mat &b, const double &mJ) { - double J=mJ; - if (fabs(mJ) < simcoon::iota) { - try { - J = sqrt(det(b)); - } catch (const std::runtime_error &e) { - cerr << "Error in det: " << e.what() << endl; - throw simcoon::exception_det("Error in det function inside L_iso_hyper_invariants."); - } - } - mat b_bar = pow(J,-2./3.)*b; - mat dev_b_bar = dev(b_bar); - vec I_bar = isochoric_invariants(b,J); - mat Id = eye(3,3); - - // no static arma object: its destructor runs at DLL unload (Windows) - mat I_dev = Idev(); - - // terms of a zero potential derivative skipped (exact zeros) - mat Lt = gamma_linear(dev_b_bar, I_bar(0), I_dev, Id)*dWdI_1_bar; - if ((dWdI_2_bar != 0.) || (dW2dI_12_bar != 0.) || (dW2dI_22_bar != 0.)) { - mat b_bar2; - try { - b_bar2 = powmat(b_bar,2); - } catch (const std::runtime_error &e) { - cerr << "Error in det: " << e.what() << endl; - throw simcoon::exception_powmat("Error in powmat function inside L_iso_hyper_invariants."); - } - mat dev_b_bar2 = dev(b_bar2); - mat devdevbb2 = I_bar(0)*dev(b_bar) - dev(b_bar2); - if (dWdI_2_bar != 0.) { - mat I_real = Ireal(); - mat I_vol = Ivol(); - mat H_bar = auto_sym_dyadic_operator(b_bar); - mat gamma_2 = (8./3.)*(I_bar(1)*(I_real - 2.*I_vol) - I_bar(0)*(sym_dyadic(dev_b_bar,Id)+sym_dyadic(Id,dev_b_bar)) - + (sym_dyadic(dev_b_bar2,Id)+sym_dyadic(Id,dev_b_bar2))) + 4*(auto_sym_dyadic(b_bar)-H_bar); - Lt += gamma_2*dWdI_2_bar; - } - if (dW2dI_11_bar != 0.) { - Lt += gamma_quadratic(dev_b_bar)*dW2dI_11_bar; - } - if (dW2dI_12_bar != 0.) { - mat gamma_12 = 4.*(sym_dyadic(dev_b_bar,devdevbb2)+sym_dyadic(devdevbb2,dev_b_bar)); - Lt += gamma_12*dW2dI_12_bar; - } - if (dW2dI_22_bar != 0.) { - mat gamma_22 = 4.*auto_sym_dyadic(devdevbb2); - Lt += gamma_22*dW2dI_22_bar; - } - } - else if (dW2dI_11_bar != 0.) { - Lt += gamma_quadratic(dev_b_bar)*dW2dI_11_bar; - } - - return (1./J)*Lt; + const double J = J_of_b(b, mJ, "L_iso_hyper_invariants"); + const m66 Lt = L_iso_invariants_fixed(dWdI_1_bar, dWdI_2_bar, dW2dI_11_bar, dW2dI_12_bar, dW2dI_22_bar, b_bar_of(b, J)); + const double invJ = 1./J; + mat L(6,6); + for (int e = 0; e < 36; ++e) L(e) = Lt(e)*invJ; + return L; } mat L_vol_hyper(const double &dUdJ, const double &dU2dJ2, const mat &b, const double &mJ) { - double J=mJ; - if (fabs(mJ) < simcoon::iota) { - try { - J = sqrt(det(b)); - } catch (const std::runtime_error &e) { - cerr << "Error in det: " << e.what() << endl; - throw simcoon::exception_det("Error in det function inside L_vol_hyper."); - } - } - mat I_real = Ireal(); // never static: see L_iso_hyper above - mat I_vol = Ivol(); - return (dUdJ+dU2dJ2*J)*3.*I_vol - 2.*dUdJ*I_real; + return L_vol_fixed(dUdJ, dU2dJ2, J_of_b(b, mJ, "L_vol_hyper")); } namespace { @@ -838,15 +1028,22 @@ void hyper_invariants_response(const hyper_invariants_dW &dW, const mat &b, cons } // Kirchhoff throughout: Cauchy = tau/J only at the output boundary - mat m_tau_iso = tau_iso_hyper_invariants(dW.dWdI_1_bar, dW.dWdI_2_bar, b, J); + const double Jb = J_of_b(b, J, "hyper_invariants_response"); + const m33 b_bar = b_bar_of(b, Jb); + mat m_tau_iso = tau_iso_invariants_fixed(dW.dWdI_1_bar, dW.dWdI_2_bar, b_bar); mat m_tau_vol = tau_vol_hyper(dW.dUdJ, b, J); mat m_tau = m_tau_iso + m_tau_vol; // builders return c = (1/J) d(L_v tau)/dD (not d(L_v sigma)/dD), with J written asymmetrically // (1/J in L_iso, none in L_vol): scale them together as a sum, never one at a time - mat Lt_iso = L_iso_hyper_invariants(dW.dWdI_1_bar, dW.dWdI_2_bar, dW.dW2dI_11_bar, dW.dW2dI_12_bar, dW.dW2dI_22_bar, b, J); - mat Lt_vol = L_vol_hyper(dW.dUdJ, dW.dU2dJ2, b, J); - mat Lt_spatial = Lt_iso + Lt_vol; + const m66 Lt_iso = L_iso_invariants_fixed(dW.dWdI_1_bar, dW.dWdI_2_bar, dW.dW2dI_11_bar, dW.dW2dI_12_bar, dW.dW2dI_22_bar, b_bar); + const m66 Lt_vol = L_vol_fixed(dW.dUdJ, dW.dU2dJ2, Jb); + const double invJb = 1./Jb; + m66 Lt_spatial; + for (int e = 0; e < 36; ++e) { + const double l_iso = Lt_iso(e)*invJb; + Lt_spatial(e) = l_iso + Lt_vol(e); + } // fibre terms: I_1 algebra with b_bar -> structure tensor (kappa_d folded into A); zero below the switch if (!A.empty()) { @@ -868,7 +1065,8 @@ void hyper_invariants_response(const hyper_invariants_dW &dW, const mat &b, cons // Box tangent Lt = d(tau_hat)/d(De) in corate_type (Kirchhoff, no J). J*Lt_spatial is the // Kirchhoff-Lie tangent d(L_v tau)/dD that the map consumes. - Lt_box = Dtau_LieDD_2_DtauDe_corate(J*Lt_spatial, corate_type, F, m_tau); + for (int e = 0; e < 36; ++e) Lt_spatial(e) = Lt_spatial(e)*J; + Lt_box = Dtau_LieDD_2_DtauDe_corate(Lt_spatial, corate_type, F, m_tau); } } //namespace simcoon diff --git a/src/Continuum_mechanics/Functions/objective_rates.cpp b/src/Continuum_mechanics/Functions/objective_rates.cpp index c253e51ac..d8250173b 100755 --- a/src/Continuum_mechanics/Functions/objective_rates.cpp +++ b/src/Continuum_mechanics/Functions/objective_rates.cpp @@ -70,12 +70,192 @@ static inline Fastor::Tensor pullback_4( return push_forward_4(C, invF); } -// Helper: 4th-order push-forward DSDE_ijkl = F_iL F_jH F_kM F_lN C_LHMN -static inline Fastor::Tensor pushforward_4( - const Fastor::Tensor &C, - const Fastor::Tensor &F) -{ - return push_forward_4(C, F); +typedef mat::fixed<3,3> m33; + +// Inline 3x3 products, the same arithmetic as armadillo's tiny-matrix kernel (each entry the +// three-term dot product A(i,0)*x0 + A(i,1)*x1 + A(i,2)*x2 written as it does) without its call and +// dispatch overhead. A chain A*B*C is (A*B)*C, as armadillo evaluates it for square operands. +// Bit identity with the armadillo expressions, here and in the Voigt-native push-forward and +// tangent corrections below (which reproduce the Fastor loops they replace), was established with +// Apple clang (-ffp-contract=on: a product is fused only with an addition of the same statement) +// and Armadillo 15.6.0 (the simcoon-prof environment), whose kernel shapes it relies on. A compiler +// that contracts across statements (GCC's default -ffp-contract=fast in gnu++ mode, on FMA targets) +// or another Armadillo may round differently, at the epsilon level; the tolerance-based tests +// remain the guarantee of correctness. +static inline m33 mm(const m33 &A, const m33 &B) { // A*B + m33 C; + for (int j = 0; j < 3; ++j) { + const double x0 = B(0,j), x1 = B(1,j), x2 = B(2,j); + for (int i = 0; i < 3; ++i) C(i,j) = A(i,0)*x0 + A(i,1)*x1 + A(i,2)*x2; + } + return C; +} +static inline m33 mtm(const m33 &A, const m33 &B) { // A^T*B + m33 C; + for (int j = 0; j < 3; ++j) { + const double x0 = B(0,j), x1 = B(1,j), x2 = B(2,j); + for (int i = 0; i < 3; ++i) C(i,j) = A(0,i)*x0 + A(1,i)*x1 + A(2,i)*x2; + } + return C; +} +static inline m33 mmt(const m33 &A, const m33 &B) { // A*B^T + m33 C; + for (int j = 0; j < 3; ++j) { + const double x0 = B(j,0), x1 = B(j,1), x2 = B(j,2); + for (int i = 0; i < 3; ++i) C(i,j) = A(i,0)*x0 + A(i,1)*x1 + A(i,2)*x2; + } + return C; +} + +// alpha*X entrywise: the scaling armadillo applies after a product carrying a scalar factor +static inline m33 alpha_times(const double alpha, const m33 &X) { + m33 Y; + for (int e = 0; e < 9; ++e) Y(e) = alpha*X(e); + return Y; +} + +// 2nd-order centered velocity gradient L = (2/dt)(F1-F0)(F0+F1)^-1, as the armadillo expression +// (the inverse throws std::runtime_error on a singular F0+F1) +static inline m33 velocity_gradient(const mat &F0, const mat &F1, const double &DTime) { + const m33 Fsum_inv = inv(F0+F1); + return alpha_times(2./DTime, mm(m33(F1-F0), Fsum_inv)); +} + +// Helper: push-forward of a stiffness-convention Voigt 6x6, C'_isrp = F_iL F_sJ F_rM F_pN C_LJMN, +// the same as push_forward_4(voigt_to_fastor4(L), F) (to the bit: see the note above): the four +// one-index contractions run in the same order over the same values, but only for the entries a +// Voigt result reads. C is symmetric in (M,N) exactly, and so are the first two partial +// contractions; only the pairs i <= s of the result are read. 198 contracted entries instead of +// 324, no 3x3x3x3 temporaries. +// R[is][r][p] = C'(i,s,r,p) for the Voigt pair is = (i <= s), unaveraged. +static void push_forward_voigt_core(double R[6][3][3], const mat::fixed<6,6> &L, const mat::fixed<3,3> &F) { + static const int vm[3][3] = {{0, 3, 4}, {3, 1, 5}, {4, 5, 2}}; // Voigt index of (i,j) + static const int vi[6] = {0, 1, 2, 0, 0, 1}, vj[6] = {0, 1, 2, 1, 2, 2}; + + // T1(i,J,MN) = sum_L F(i,L) C(L,J,M,N) + double T1[3][3][6]; + for (int i = 0; i < 3; ++i) + for (int J = 0; J < 3; ++J) + for (int MN = 0; MN < 6; ++MN) { + double sum = 0.0; + for (int l = 0; l < 3; ++l) + sum += F(i,l) * L(vm[l][J], MN); + T1[i][J][MN] = sum; + } + // T2(is,MN) = sum_J F(s,J) T1(i,J,MN), i <= s + double T2[6][6]; + for (int is = 0; is < 6; ++is) + for (int MN = 0; MN < 6; ++MN) { + const int i = vi[is], s = vj[is]; + double sum = 0.0; + for (int J = 0; J < 3; ++J) + sum += F(s,J) * T1[i][J][MN]; + T2[is][MN] = sum; + } + // T3(is,r,N) = sum_M F(r,M) T2(is,M,N) + double T3[6][3][3]; + for (int is = 0; is < 6; ++is) + for (int r = 0; r < 3; ++r) + for (int N = 0; N < 3; ++N) { + double sum = 0.0; + for (int M = 0; M < 3; ++M) + sum += F(r,M) * T2[is][vm[M][N]]; + T3[is][r][N] = sum; + } + // R(is,r,p) = sum_N F(p,N) T3(is,r,N) + for (int is = 0; is < 6; ++is) + for (int r = 0; r < 3; ++r) + for (int p = 0; p < 3; ++p) { + double sum = 0.0; + for (int N = 0; N < 3; ++N) + sum += F(p,N) * T3[is][r][N]; + R[is][r][p] = sum; + } +} + +// The Voigt average of the minor pair (r,p), as fastor4_to_voigt +static mat::fixed<6,6> voigt_average(const double R[6][3][3]) { + static const int vi[6] = {0, 1, 2, 0, 0, 1}, vj[6] = {0, 1, 2, 1, 2, 2}; + mat::fixed<6,6> out; + for (int I = 0; I < 6; ++I) + for (int J = 0; J < 6; ++J) + out(I, J) = 0.5 * (R[I][vi[J]][vj[J]] + R[I][vj[J]][vi[J]]); + return out; +} + +// fastor4_to_voigt(push_forward_4(voigt_to_fastor4(L), F)) (to the bit, see the note above) +static mat::fixed<6,6> push_forward_voigt(const mat::fixed<6,6> &L, const mat::fixed<3,3> &F) { + double R[6][3][3]; + push_forward_voigt_core(R, L, F); + return voigt_average(R); +} + +// The Jaumann correction of apply_jaumann_correction on the entries a Voigt result reads: +// R[is][p][r] += sign*corr(i,s,p,r), the same terms added in the same order. tau_f is indexed as +// the Fastor copy of tau (to_fastor2 with the symmetric memcpy: tau_f(a,b) = tau(b,a)). +static void add_jaumann_correction(double R[6][3][3], const mat::fixed<3,3> &tau_f, const bool add) { + static const int vi[6] = {0, 1, 2, 0, 0, 1}, vj[6] = {0, 1, 2, 1, 2, 2}; + const double sign = add ? 0.5 : -0.5; + for (int is = 0; is < 6; ++is) { + const int i = vi[is], s = vj[is]; + for (int p = 0; p < 3; ++p) + for (int r = 0; r < 3; ++r) { + double corr = 0.0; + if (i == r) corr += tau_f(p,s); + if (i == p) corr += tau_f(r,s); + if (s == p) corr += tau_f(i,r); + if (s == r) corr += tau_f(i,p); + R[is][p][r] += sign * corr; + } + } +} + +// R[is][p][r] = A(i,s,p,r) of voigt_to_fastor4(L), for the Voigt pairs is = (i <= s) +static void voigt_expand(double R[6][3][3], const mat::fixed<6,6> &L) { + static const int vm[3][3] = {{0, 3, 4}, {3, 1, 5}, {4, 5, 2}}; + for (int is = 0; is < 6; ++is) + for (int p = 0; p < 3; ++p) + for (int r = 0; r < 3; ++r) + R[is][p][r] = L(is, vm[p][r]); +} + +// The B-correction of apply_B_correction on the entries a Voigt result reads (pairs i <= j): +// R[ij][k][l] = A(i,j,k,l) + sign*(sum1 + sum2), with B - I and the sums as there; A and B are +// stiffness-convention Voigt 6x6, tau_f indexed as the Fastor copy of tau. +static void add_B_correction(double R[6][3][3], const mat::fixed<6,6> &A, const mat::fixed<6,6> &B, const mat::fixed<3,3> &tau_f, const bool add) { + static const int vm[3][3] = {{0, 3, 4}, {3, 1, 5}, {4, 5, 2}}; + static const int vi[6] = {0, 1, 2, 0, 0, 1}, vj[6] = {0, 1, 2, 1, 2, 2}; + // B - I, I the symmetric 4th-order identity of sym_identity_4 (entries 0, 0.5 or 1) + auto BmI = [&](const int a, const int b, const int c, const int d) { + double I = 0.; + if (c == a && d == b) I += 0.5; + if (c == b && d == a) I += 0.5; + return B(vm[a][b], vm[c][d]) - I; + }; + const double sign = add ? 1.0 : -1.0; + for (int ij = 0; ij < 6; ++ij) { + const int i = vi[ij], j = vj[ij]; + for (int k = 0; k < 3; ++k) + for (int l = 0; l < 3; ++l) { + double sum1 = 0.0, sum2 = 0.0; + for (int p = 0; p < 3; ++p) { + sum1 += BmI(i,p,k,l) * tau_f(p,j); + sum2 += tau_f(i,p) * BmI(j,p,k,l); + } + double r = A(ij, vm[k][l]); + r += sign * (sum1 + sum2); + R[ij][k][l] = r; + } + } +} + +// The Fastor copy of a symmetric 3x3 (to_fastor2 with the symmetric memcpy), as an arma matrix +static inline mat::fixed<3,3> fastor_sym_copy(const mat &tau) { + return mat::fixed<3,3>(tau).t(); +} + +static inline bool is_exactly_symmetric(const mat &X) { + return X(0,1) == X(1,0) && X(0,2) == X(2,0) && X(1,2) == X(2,1); } // Helper: B-correction for objective rate conversions @@ -185,7 +365,7 @@ void Jaumann(mat &DR, mat &D, mat &W, const double &DTime, const mat &F0, const if(DTime > simcoon::iota) { try { // 2nd-order midpoint L = (2/dt)(F1-F0)(F0+F1)^-1, shared by every rate so D = sym(L) agrees - L = (2./DTime)*(F1-F0)*inv(F0+F1); + L = velocity_gradient(F0, F1, DTime); } catch (const std::runtime_error &e) { cerr << "Error in inv: " << e.what() << endl; throw simcoon::exception_inv("Error in inv function inside Jaumann (L)."); @@ -206,7 +386,7 @@ void Green_Naghdi(mat &DR, mat &D, mat &Omega, const double &DTime, const mat &F try { // Same 2nd-order centered velocity gradient as the other rate functions (see Jaumann), // so that D=sym(L) matches across all rates. - L = (2./DTime)*(F1-F0)*inv(F0+F1); + L = velocity_gradient(F0, F1, DTime); } catch (const std::runtime_error &e) { cerr << "Error in inv: " << e.what() << endl; throw simcoon::exception_inv("Error in inv function inside Green_Naghdi (L)."); @@ -227,7 +407,7 @@ void logarithmic_R_kinematics(mat &DR, mat &D, mat &Omega, vec::fixed<3> &b1, ma if(DTime > simcoon::iota) { try { // 2nd-order centered velocity gradient (see Jaumann); D=sym(L) is shared by all rates. - L = (2./DTime)*(F1-F0)*inv(F0+F1); + L = velocity_gradient(F0, F1, DTime); } catch (const std::runtime_error &e) { cerr << "Error in inv: " << e.what() << endl; throw simcoon::exception_inv("Error in inv function inside logarithmic_R (L)."); @@ -242,8 +422,8 @@ void logarithmic_R_kinematics(mat &DR, mat &D, mat &Omega, vec::fixed<3> &b1, ma vec::fixed<3> c0; RU_spectral(R0, c0, N0, F0); RU_spectral(R1, b1, N1, F1); - DR = R1*R0.t(); - Nb = R1*N1; // B1 = R1 C1 R1^T + DR = mmt(R1, R0); + Nb = mm(R1, N1); // B1 = R1 C1 R1^T if (DTime <= simcoon::iota) { Omega = zeros(3,3); @@ -254,7 +434,7 @@ void logarithmic_R_kinematics(mat &DR, mat &D, mat &Omega, vec::fixed<3> &b1, ma if (!inv(DRpI_inv, DRpI)) { throw simcoon::exception_inv("Error in inv function inside finite_rotation."); } - Omega = (2./DTime)*(DR-eye(3,3))*DRpI_inv; + Omega = alpha_times(2./DTime, mm(m33(DR-eye(3,3)), DRpI_inv)); } // De = A^R : D on the eigenbasis (b, Nb) of B, by congruence (t/sinh t kernel) @@ -262,7 +442,7 @@ mat::fixed<3,3> A_R_D(const vec::fixed<3> &b, const mat::fixed<3,3> &Nb, const m mat::fixed<3,3> K; for (int i = 0; i < 3; i++) for (int j = 0; j < 3; j++) - K(i, j) = kappa_R(b(i), b(j)); + K(i, j) = (i != j) ? kappa_R(b(i), b(j)) : 1.; // kappa_R(b, b) = 1 exactly return spectral_congruence(Nb, K, D); } @@ -281,7 +461,7 @@ void logarithmic_F(mat &DF, mat &N_1, mat &N_2, mat &D, mat &L, const double &DT try { // 2nd-order centered velocity gradient (see Jaumann): D=sym(L) is shared by all rates, // and exp(L*dt) approximates F1 F0^-1 used below for the transport DF. - L = (2./DTime)*(F1-F0)*inv(F0+F1); + L = velocity_gradient(F0, F1, DTime); } catch (const std::runtime_error &e) { cerr << "Error in inv: " << e.what() << endl; throw simcoon::exception_inv("Error in inv function inside logarithmic_F (L)."); @@ -306,7 +486,7 @@ void Truesdell(mat &DF, mat &D, mat &L, const double &DTime, const mat &F0, cons try { // 2nd-order centered velocity gradient (see Jaumann): D=sym(L) is shared by all rates, // and exp(L*dt) approximates F1 F0^-1 used below for the transport DF. - L = (2./DTime)*(F1-F0)*inv(F0+F1); + L = velocity_gradient(F0, F1, DTime); } catch (const std::runtime_error &e) { cerr << "Error in inv: " << e.what() << endl; throw simcoon::exception_inv("Error in inv function inside Truesdell (L)."); @@ -393,7 +573,7 @@ mat A_F(const mat &F) { mat::fixed<6,6> AF(fill::zeros); for (unsigned int i=0; i<3; i++) { for (unsigned int j=0; j<3; j++) { - double t = 0.5*log(bi(i)/bi(j)); + double t = (i != j) ? 0.5*log(bi(i)/bi(j)) : 0.; // log(b/b) = 0 exactly double tcoth; if (fabs(t) > 1.e-4) { tcoth = t/tanh(t); @@ -414,7 +594,7 @@ void logarithmic(mat &DR, mat &D, mat &Omega, const double &DTime, const mat &F0 if(DTime > simcoon::iota) { try { // 2nd-order centered velocity gradient (see Jaumann); D=sym(L) is shared by all rates. - L = (2./DTime)*(F1-F0)*inv(F0+F1); + L = velocity_gradient(F0, F1, DTime); } catch (const std::runtime_error &e) { cerr << "Error in inv: " << e.what() << endl; throw simcoon::exception_inv("Error in inv function inside logarithmic (L)."); @@ -439,8 +619,8 @@ void logarithmic(mat &DR, mat &D, mat &Omega, const double &DTime, const mat &F0 } mat Delta_log_strain(const mat &D, const mat &Omega, const double &DTime) { - const mat DR = Hughes_Winget(Omega, DTime); - return 0.5*(D+(DR*D*DR.t()))*DTime; + const m33 DR = Hughes_Winget(Omega, DTime), D_(D); + return 0.5*(D_+mmt(mm(DR, D_), DR))*DTime; } mat Delta_log_strain_F(const mat &D, const mat &L, const double &DTime) { @@ -474,7 +654,8 @@ void corate_kinematics(const int &corate_type, mat &DR, mat &D, mat &Omega, mat mat::fixed<3,3> Nb; logarithmic_R_kinematics(DR, D, Omega, b1, Nb, DTime, F0, F1); const mat::fixed<3,3> Dh = A_R_D(b1, Nb, D); - De = 0.5*(Dh + DR*Dh*DR.t())*DTime; + const m33 DR_(DR); + De = 0.5*(Dh + mmt(mm(DR_, Dh), DR_))*DTime; return; } corate_kinematics(corate_type, DR, D, Omega, F0, F1, DTime); @@ -531,7 +712,7 @@ double Delta_work_conjugacy(const vec &tau_start, const vec &tau_start_tr, const mat::fixed<3,3> Fsum_inv; if (!inv(Fsum_inv, mat::fixed<3,3>(F1 + F0))) return 0.; - const mat::fixed<3,3> LDt = 2.*(F1 - F0)*Fsum_inv; // midpoint velocity gradient times DTime + const mat::fixed<3,3> LDt = alpha_times(2., mm(m33(F1 - F0), Fsum_inv)); // midpoint velocity gradient times DTime // both stresses in the lab frame: a start stress carried to the end frame against a midpoint // D would leave a first-order [W, tau] : D error, even for isotropic elasticity return 0.5*dot(tau_start + tau, t2v_strain(0.5*(LDt + LDt.t()))) @@ -548,12 +729,12 @@ struct dk_spectral { arma::mat::fixed<3,3> N; // eigenvectors of C (columns) arma::mat::fixed<3,3> G_h; // DK coefficients of h(C) = 1/2 ln C arma::mat::fixed<3,3> G_iU; // DK coefficients of C^{-1/2} - arma::mat::fixed<3,3> U; // C^{1/2} + arma::mat::fixed<3,3> U; // C^{1/2} (built when asked: only the inverse map reads it) arma::mat::fixed<3,3> invU; // C^{-1/2} arma::mat::fixed<3,3> R; // polar rotation F U^{-1} arma::mat::fixed<3,3> tau_hat; // corotational Kirchhoff stress R^T tau R - void build(const arma::mat &F, const arma::mat &tau) { + void build(const arma::mat &F, const arma::mat &tau, const bool &with_U = true) { const arma::mat::fixed<3,3> F_(F); const bool success_eig_sym = eig_sym_3x3(lam, N, F_.t()*F_); if (!success_eig_sym) { @@ -569,10 +750,10 @@ struct dk_spectral { [](double l) { return 0.5 / l; }); G_iU = gmat([](double l) { return 1.0 / std::sqrt(l); }, [](double l) { return -0.5 * std::pow(l, -1.5); }); - U = spectral_compose_3x3(N, sqrt(lam)); + if (with_U) U = spectral_compose_3x3(N, sqrt(lam)); invU = spectral_compose_3x3(N, 1.0 / sqrt(lam)); - R = F_ * invU; - tau_hat = R.t() * arma::mat::fixed<3,3>(tau) * R; + R = mm(F_, invU); + tau_hat = mm(mtm(R, arma::mat::fixed<3,3>(tau)), R); } // f' at the midpoint under the 1e-4 relative coincidence guard (as get_BBBB / A_R) @@ -598,6 +779,7 @@ struct dk_spectral { // stack and the 6x6 box applied by hand (a BLAS gemv call per column otherwise). typedef arma::mat::fixed<3,3> m33; + // engineering-Voigt unit strain e_j as a tensor (v2t_strain of the j-th basis vector) inline m33 unit_strain_tensor(const int j) { static const int ia[6] = {0, 1, 2, 0, 0, 1}, ib[6] = {0, 1, 2, 1, 2, 2}; @@ -608,6 +790,17 @@ inline m33 unit_strain_tensor(const int j) { return e; } +// Q^T e_j Q for the unit strain e_j of unit_strain_tensor, from two rows of Q (e_j has one or two entries) +inline m33 congruence_unit_strain(const m33 &Q, const int j) { + static const int ia[6] = {0, 1, 2, 0, 0, 1}, ib[6] = {0, 1, 2, 1, 2, 2}; + const int a = ia[j], b = ib[j]; + m33 D; + for (int l = 0; l < 3; ++l) + for (int k = 0; k < 3; ++k) + D(k, l) = (j < 3) ? Q(a, k)*Q(a, l) : 0.5*(Q(a, k)*Q(b, l) + Q(b, k)*Q(a, l)); + return D; +} + // y = Lt t2v_strain(e) inline void box_apply(const arma::mat::fixed<6,6> &Lt, const m33 &e, double y[6]) { const double x[6] = {e(0,0), e(1,1), e(2,2), e(0,1) + e(1,0), e(0,2) + e(2,0), e(1,2) + e(2,1)}; @@ -686,11 +879,19 @@ struct logU_spectral { R = F_*spectral_compose_3x3(N, 1.0/sqrt(c)); for (int a = 0; a < 3; ++a) { for (int b = 0; b < 3; ++b) { - kappa(a, b) = kappa_of(c(a), c(b)); + kappa(a, b) = (a != b) ? kappa_of(c(a), c(b)) : 1.; // kappa_of(c, c) = 1 exactly if (!tangent) continue; G(a, b) = kappa(a, b)/(2.*std::sqrt(c(a)*c(b))); - for (int k = 0; k < 3; ++k) { - Gam[a][b][k] = dd2(c(a), c(b), c(k)); + } + } + if (!tangent) return; + // dd2 sorts its arguments, so Gam is the same, to the bit, for every permutation of + // (a, b, k): the 10 index multisets are evaluated once each, instead of 27 calls + for (int a = 0; a < 3; ++a) { + for (int b = a; b < 3; ++b) { + for (int k = b; k < 3; ++k) { + const double g = dd2(c(a), c(b), c(k)); + Gam[a][b][k] = Gam[a][k][b] = Gam[b][a][k] = Gam[b][k][a] = Gam[k][a][b] = Gam[k][b][a] = g; } } } @@ -723,13 +924,16 @@ mat logU_box_2_DSDE(const mat &Lt, const mat &F, const mat &tau) { logU_spectral sp; sp.build(F, true); const mat That = sp.That_of_tau(tau); + const arma::mat::fixed<6,6> Lt_(Lt); mat DSDE(6, 6); vec ej(6); for (int j = 0; j < 6; ++j) { ej.zeros(); ej(j) = 1.; const mat dCp = sp.N.t()*(2.*v2t_strain(ej))*sp.N; const mat dH = sp.N*(sp.G % dCp)*sp.N.t(); - const mat dTp = sp.N.t()*v2t_stress(Lt*t2v_strain(dH))*sp.N; + double y[6]; + box_apply(Lt_, dH, y); // Lt t2v_strain(dH) by hand (a BLAS gemv call per column otherwise) + const mat dTp = sp.N.t()*v2t_stress_arr(y)*sp.N; DSDE.col(j) = t2v_stress(2.*sp.N*(sp.G % dTp + sp.geometric(That, dCp))*sp.N.t()); } return DSDE; @@ -740,12 +944,15 @@ mat DSDE_2_logU_box(const mat &DSDE, const mat &F, const mat &tau) { logU_spectral sp; sp.build(F, true); const mat That = sp.That_of_tau(tau); + const arma::mat::fixed<6,6> DSDE_(DSDE); mat Lt(6, 6); vec ej(6); for (int j = 0; j < 6; ++j) { ej.zeros(); ej(j) = 1.; const mat dCp = (sp.N.t()*v2t_strain(ej)*sp.N)/sp.G; - const mat dS = v2t_stress(DSDE*t2v_strain(0.5*sp.N*dCp*sp.N.t())); + double y[6]; + box_apply(DSDE_, m33(0.5*sp.N*dCp*sp.N.t()), y); // DSDE t2v_strain(.) by hand, as above + const mat dS = v2t_stress_arr(y); const mat dTp = (0.5*sp.N.t()*dS*sp.N - sp.geometric(That, dCp))/sp.G; Lt.col(j) = t2v_stress(sp.N*dTp*sp.N.t()); } @@ -770,22 +977,23 @@ mat Kirchoff2LogStressU(const mat &tau, const mat &F) { mat DtauDe_2_DSDE(const mat &Lt, const mat &F, const mat &tau){ dk_spectral sp; - sp.build(F, tau); + sp.build(F, tau, false); const arma::mat::fixed<6,6> Lt_(Lt); + const m33 invU_tau_hat = mm(sp.invU, sp.tau_hat); // column-independent factor of dS + // column-independent products: Q = R N (spatial frame of the eigenbasis), W = R U^-1, T2 = N^T tau_hat U^-1 + const m33 Q = mm(sp.R, sp.N); + const m33 W = mm(sp.R, sp.invU); + const m33 T2 = mm(mtm(sp.N, sp.tau_hat), sp.invU); mat DSDE(6, 6); double y[6]; for (int j = 0; j < 6; ++j) { - const m33 dC = 2. * unit_strain_tensor(j); // engineering Voigt basis - const m33 dCp = sp.N.t() * dC * sp.N; // shared eigenbasis transform - const m33 dh = sp.N * (sp.G_h % dCp) * sp.N.t(); // d(1/2 ln C) = d(ln U) - // The box acts in the spatial frame (d ln V = R d(ln U) R^T at fixed R): - // conjugate in, apply Lt, conjugate back to the corotational frame. - box_apply(Lt_, sp.R * dh * sp.R.t(), y); - const m33 dtau_hat = sp.R.t() * v2t_stress_arr(y) * sp.R; - const m33 dinvU = sp.N * (sp.G_iU % dCp) * sp.N.t(); // d(C^{-1/2}) - const m33 dS = sp.invU * dtau_hat * sp.invU - + dinvU * sp.tau_hat * sp.invU + sp.invU * sp.tau_hat * dinvU; + const m33 dCp = 2. * congruence_unit_strain(sp.N, j); // N^T dC N, dC = 2 e_j (engineering Voigt) + // the box acts in the spatial frame: d ln V = R d(ln U) R^T = Q (G_h % dCp) Q^T + box_apply(Lt_, mmt(mm(Q, m33(sp.G_h % dCp)), Q), y); + const m33 A = mm(sp.N, m33(sp.G_iU % dCp)); // d(C^{-1/2}) = A N^T + // dS = U^-1 dtau_hat U^-1 + d(U^-1) tau_hat U^-1 + U^-1 tau_hat d(U^-1), dtau_hat = R^T dtau R + const m33 dS = mm(mtm(W, v2t_stress_arr(y)), W) + mm(A, T2) + mm(invU_tau_hat, mmt(A, sp.N)); set_stress_col(DSDE, j, dS); } return DSDE; @@ -799,12 +1007,9 @@ mat Dtau_LieDD_2_DSDE(const mat &Lt, const mat &F){ } catch (const std::runtime_error &e) { cerr << "Error in inv: " << e.what() << endl; throw simcoon::exception_inv("Error in inv function inside Dtau_LieDD_2_DSDE."); - } + } - auto invF_ = to_fastor2(invF, false); - auto Dtau_LieDD = to_fastor4(Lt); - auto DSDE = pullback_4(Dtau_LieDD, invF_); - return fastor4_to_voigt(DSDE); + return push_forward_voigt(mat::fixed<6,6>(Lt), mat::fixed<3,3>(invF)); // pull-back by F^-1 } mat DtauDe_JaumannDD_2_DSDE(const mat &Lt, const mat &F, const mat &tau){ @@ -816,6 +1021,22 @@ mat DtauDe_JaumannDD_2_DSDE(const mat &Lt, const mat &F, const mat &tau){ cerr << "Error in inv: " << e.what() << endl; throw simcoon::exception_inv("Error in inv function inside DtauDe_JaumannDD_2_DSDE."); } + if (is_exactly_symmetric(tau)) { + // Dtau_LieDD = Dtau_JaumannDD - Jaumann correction: with a symmetric tau it keeps both minor + // symmetries exactly, so its Voigt form (the corrected entries i <= s) pulled back is the + // Fastor route (to the bit with Apple clang, see the note on the inline products) + double R[6][3][3]; + voigt_expand(R, mat::fixed<6,6>(Lt)); + add_jaumann_correction(R, fastor_sym_copy(tau), false); + static const int vm[3][3] = {{0, 3, 4}, {3, 1, 5}, {4, 5, 2}}; + mat::fixed<6,6> Dtau_LieDD; + for (int is = 0; is < 6; ++is) + for (int p = 0; p < 3; ++p) + for (int r = p; r < 3; ++r) + Dtau_LieDD(is, vm[p][r]) = R[is][p][r]; + return push_forward_voigt(Dtau_LieDD, mat::fixed<3,3>(invF)); // pull-back by F^-1 + } + auto invF_ = to_fastor2(invF, false); auto tau_ = to_fastor2(tau); auto Dtau_JaumannDD = to_fastor4(Lt); @@ -872,6 +1093,20 @@ mat DtauDe_GreenNaghdiDD_2_DSDE(const mat &Lt, const mat &F, const mat &tau){ cerr << "Error in inv: " << e.what() << endl; throw simcoon::exception_inv("Error in inv function inside DtauDe_GreenNaghdiDD_2_DSDE."); } + if (is_exactly_symmetric(tau)) { + // with a symmetric tau the corrected tangent keeps both minor symmetries exactly: its Voigt + // form (the corrected entries i <= j), pulled back, is the Fastor route (to the bit with + // Apple clang, see the note on the inline products) + double R[6][3][3]; + add_B_correction(R, mat::fixed<6,6>(Lt), mat::fixed<6,6>(get_BBBB_GN(F)), fastor_sym_copy(tau), true); + static const int vm[3][3] = {{0, 3, 4}, {3, 1, 5}, {4, 5, 2}}; + mat::fixed<6,6> Dtau_LieDD; + for (int ij = 0; ij < 6; ++ij) + for (int k = 0; k < 3; ++k) + for (int l = k; l < 3; ++l) + Dtau_LieDD(ij, vm[k][l]) = R[ij][k][l]; + return push_forward_voigt(Dtau_LieDD, mat::fixed<3,3>(invF)); // pull-back by F^-1 + } auto invF_ = to_fastor2(invF, false); auto tau_ = to_fastor2(tau); auto Dtau_GN = to_fastor4(Lt); @@ -912,20 +1147,22 @@ mat DSDE_2_DtauDe(const mat &DSDE, const mat &F, const mat &tau) { const m33 G_h_inv = 1.0 / sp.G_h; // elementwise; G_h > 0 (1/2 ln strictly increasing) const m33 G_comp = sp.G_iU % G_h_inv; // composed d(C^{-1/2}) o [d(1/2 ln C)]^{-1} const arma::mat::fixed<6,6> DSDE_(0.5 * DSDE); + const m33 invU_tau_hat = mm(sp.invU, sp.tau_hat); // column-independent factor + // column-independent products: Q = R N, RU = R U, T2 = N^T tau_hat U^-1 + const m33 Q = mm(sp.R, sp.N); + const m33 RU = mm(sp.R, sp.U); + const m33 T2 = mm(mtm(sp.N, sp.tau_hat), sp.invU); mat Lt(6, 6); double y[6]; for (int j = 0; j < 6; ++j) { - // box argument is ln V: pull dh back by R (inverse of the forward R conjugation) - const m33 dh = sp.R.t() * unit_strain_tensor(j) * sp.R; - const m33 dhp = sp.N.t() * dh * sp.N; // eigenframe - const m33 dC = sp.N * (G_h_inv % dhp) * sp.N.t(); // inverse of d(1/2 ln C) + // box argument is ln V: its eigenframe image N^T R^T e_j R N = Q^T e_j Q + const m33 dhp = congruence_unit_strain(Q, j); + const m33 dC = mmt(mm(sp.N, m33(G_h_inv % dhp)), sp.N); // inverse of d(1/2 ln C) box_apply(DSDE_, dC, y); - const m33 dS = v2t_stress_arr(y); - const m33 dinvU = sp.N * (G_comp % dhp) * sp.N.t(); - const m33 dtau_hat = - sp.U * (dS - dinvU * sp.tau_hat * sp.invU - sp.invU * sp.tau_hat * dinvU) * sp.U; - set_stress_col(Lt, j, sp.R * dtau_hat * sp.R.t()); + const m33 A = mm(sp.N, m33(G_comp % dhp)); // d(C^{-1/2}) = A N^T + const m33 inner = v2t_stress_arr(y) - mm(A, T2) - mm(invU_tau_hat, mmt(A, sp.N)); + set_stress_col(Lt, j, mmt(mm(RU, inner), RU)); // R U inner U R^T } return Lt; } @@ -944,10 +1181,7 @@ mat DSDE_2_DsigmaDe(const mat &DSDE, const mat &F, const mat &sigma) { mat DSDE_2_Dtau_LieDD(const mat &DSDE, const mat &F) { - auto F_ = to_fastor2(F, false); - auto DSDE_ = to_fastor4(DSDE); - auto C = pushforward_4(DSDE_, F_); - return fastor4_to_voigt(C); + return push_forward_voigt(mat::fixed<6,6>(DSDE), mat::fixed<3,3>(F)); } mat DSDE_2_Dsigma_LieDD(const mat &DSDE, const mat &F) { @@ -959,19 +1193,17 @@ mat DSDE_2_Dsigma_LieDD(const mat &DSDE, const mat &F) { cerr << "Error in det: " << e.what() << endl; throw simcoon::exception_det("Error in det function inside DSDE_2_DsigmaDe_LieDD."); } - return (1./J)*DSDE_2_Dtau_LieDD(DSDE, F); + return (1./J)*push_forward_voigt(mat::fixed<6,6>(DSDE), mat::fixed<3,3>(F)); // DSDE_2_Dtau_LieDD / J } mat DSDE_2_Dtau_JaumannDD(const mat &DSDE, const mat &F, const mat &tau) { - auto F_ = to_fastor2(F, false); - auto tau_ = to_fastor2(tau); - auto DSDE_ = to_fastor4(DSDE); - - // Push-forward + Jaumann correction - auto Dtau_LieDD = pushforward_4(DSDE_, F_); - auto C = apply_jaumann_correction(Dtau_LieDD, tau_, true); - return fastor4_to_voigt(C); + // Push-forward + Jaumann correction, on the Voigt entries only (the same sums as the + // 3x3x3x3 Fastor route) + double R[6][3][3]; + push_forward_voigt_core(R, mat::fixed<6,6>(DSDE), mat::fixed<3,3>(F)); + add_jaumann_correction(R, fastor_sym_copy(tau), true); + return voigt_average(R); } mat DSDE_2_Dsigma_JaumannDD(const mat &DSDE, const mat &F, const mat &sigma) { @@ -1112,7 +1344,7 @@ mat Dtau_LieDD_2_DtauDe_stretch(const mat &Dtau_LieDD, const vec::fixed<3> &e, c for (int j = 0; j < 6; ++j) { const m33 dt = spectral_congruence(N, H, unit_strain_tensor(j)); box_apply(Jc, dt, y); - const m33 dtau = v2t_stress_arr(y) + dt * tau_ + tau_ * dt; + const m33 dtau = v2t_stress_arr(y) + mm(dt, tau_) + mm(tau_, dt); set_stress_col(Lt, j, dtau); } return Lt; @@ -1126,21 +1358,20 @@ mat box_DtauDe_from_dSdE(const mat &dSdE, const mat &F, const vec &sigma) { mat Dtau_LieDD_Dtau_JaumannDD(const mat &Dtau_LieDD, const mat &tau) { - auto tau_ = to_fastor2(tau); - auto Dtau_LieDD_ = to_fastor4(Dtau_LieDD); - auto result = apply_jaumann_correction(Dtau_LieDD_, tau_, true); - return fastor4_to_voigt(result); + // the correction on the Voigt entries only (the same sums as the 3x3x3x3 Fastor route) + double R[6][3][3]; + voigt_expand(R, mat::fixed<6,6>(Dtau_LieDD)); + add_jaumann_correction(R, fastor_sym_copy(tau), true); + return voigt_average(R); } mat Dtau_LieDD_Dtau_objectiveDD(const mat &Dtau_LieDD, const mat &B, const mat &tau) { - auto tau_ = to_fastor2(tau); - auto Dtau_LieDD_ = to_fastor4(Dtau_LieDD); - auto B_ = to_fastor4(B); - - // Dtau_objectiveDD = Dtau_LieDD - B-correction - auto result = apply_B_correction(Dtau_LieDD_, B_, tau_, false); - return fastor4_to_voigt(result); + // Dtau_objectiveDD = Dtau_LieDD - B-correction, on the Voigt entries only (the same sums as + // the 3x3x3x3 Fastor route) + double R[6][3][3]; + add_B_correction(R, mat::fixed<6,6>(Dtau_LieDD), mat::fixed<6,6>(B), fastor_sym_copy(tau), false); + return voigt_average(R); } mat Dtau_LieDD_Dtau_GreenNaghdiDD(const mat &Dtau_LieDD, const mat &F, const mat &tau) { @@ -1157,21 +1388,19 @@ mat Dtau_LieDD_Dtau_logarithmicDD(const mat &Dtau_LieDD, const mat &F, const mat mat Dsigma_LieDD_Dsigma_JaumannDD(const mat &Dsigma_LieDD, const mat &sigma) { - auto sigma_ = to_fastor2(sigma); - auto Dsigma_LieDD_ = to_fastor4(Dsigma_LieDD); - auto result = apply_jaumann_correction(Dsigma_LieDD_, sigma_, true); - return fastor4_to_voigt(result); + // as Dtau_LieDD_Dtau_JaumannDD + double R[6][3][3]; + voigt_expand(R, mat::fixed<6,6>(Dsigma_LieDD)); + add_jaumann_correction(R, fastor_sym_copy(sigma), true); + return voigt_average(R); } mat Dsigma_LieDD_Dsigma_objectiveDD(const mat &Dsigma_LieDD, const mat &B, const mat &sigma) { - auto sigma_ = to_fastor2(sigma); - auto Dsigma_LieDD_ = to_fastor4(Dsigma_LieDD); - auto B_ = to_fastor4(B); - - // Dsigma_objectiveDD = Dsigma_LieDD - B-correction - auto result = apply_B_correction(Dsigma_LieDD_, B_, sigma_, false); - return fastor4_to_voigt(result); + // Dsigma_objectiveDD = Dsigma_LieDD - B-correction, as Dtau_LieDD_Dtau_objectiveDD + double R[6][3][3]; + add_B_correction(R, mat::fixed<6,6>(Dsigma_LieDD), mat::fixed<6,6>(B), fastor_sym_copy(sigma), false); + return voigt_average(R); } mat Dsigma_LieDD_Dsigma_GreenNaghdiDD(const mat &Dsigma_LieDD, const mat &F, const mat &sigma) { @@ -1196,35 +1425,50 @@ mat Dsigma_LieDD_Dsigma_logarithmicDD(const mat &Dsigma_LieDD, const mat &F, con mat DSDE_DBiotStressDU(const mat &DSDE, const mat &U, const mat &S) { - auto U_ = to_fastor2(U); - auto S_ = to_fastor2(S); - auto DSDE_ = to_fastor4(DSDE); - // C_sjpr = 0.5*δ_is*(U_lj*U_mp*δ_nr*DSDE_ilmn + U_lj*δ_mr*U_np*DSDE_ilmn) // + 0.5*S_sp*δ_rj + 0.5*S_rj*δ_sp - Fastor::Tensor C; - C.zeros(); - for (int s = 0; s < 3; ++s) - for (int j = 0; j < 3; ++j) - for (int p = 0; p < 3; ++p) - for (int r = 0; r < 3; ++r) { - double sum = 0.0; - // i=s (delta_is), contract over l,m,n - for (int l = 0; l < 3; ++l) - for (int m = 0; m < 3; ++m) - for (int n = 0; n < 3; ++n) { - double dsde_val = DSDE_(s,l,m,n); - // term1: U_lj * U_mp * δ_nr - if (n == r) sum += 0.5 * U_(l,j) * U_(m,p) * dsde_val; - // term2: U_lj * δ_mr * U_np - if (m == r) sum += 0.5 * U_(l,j) * U_(n,p) * dsde_val; + // Computed straight from the Voigt DSDE and for the entries the Voigt result reads only + // (s <= j), visiting the (l, m, n) terms that the deltas keep in their original order: the + // same sums as the full 3x3x3x3 Fastor contraction this replaces (to the bit with Apple clang, + // see the note on the inline products). + static const int vm[3][3] = {{0, 3, 4}, {3, 1, 5}, {4, 5, 2}}; + static const int vi[6] = {0, 1, 2, 0, 0, 1}, vj[6] = {0, 1, 2, 1, 2, 2}; + const mat::fixed<6,6> D(DSDE); + // U and S as the Fastor copies read them (a plain memcpy of the column-major storage) + const mat::fixed<3,3> U_t = mat::fixed<3,3>(U).t(), S_t = mat::fixed<3,3>(S).t(); + double C[6][3][3]; + for (int I = 0; I < 6; ++I) { + const int s = vi[I], j = vj[I]; + for (int p = 0; p < 3; ++p) { + for (int r = 0; r < 3; ++r) { + double sum = 0.0; + for (int l = 0; l < 3; ++l) { + for (int m = 0; m < 3; ++m) { + if (m != r) { // only n == r contributes + const int n = r; + const double dsde_val = D(vm[s][l], vm[m][n]); + sum += 0.5 * U_t(l,j) * U_t(m,p) * dsde_val; + continue; + } + for (int n = 0; n < 3; ++n) { + const double dsde_val = D(vm[s][l], vm[m][n]); + if (n == r) sum += 0.5 * U_t(l,j) * U_t(m,p) * dsde_val; + sum += 0.5 * U_t(l,j) * U_t(n,p) * dsde_val; + } + } + } + // Jaumann-like terms + if (r == j) sum += 0.5 * S_t(s,p); + if (s == p) sum += 0.5 * S_t(r,j); + C[I][p][r] = sum; + } } - // Jaumann-like terms - if (r == j) sum += 0.5 * S_(s,p); - if (s == p) sum += 0.5 * S_(r,j); - C(s,j,p,r) = sum; } - return fastor4_to_voigt(C); + mat L(6,6); + for (int I = 0; I < 6; ++I) + for (int J = 0; J < 6; ++J) + L(I, J) = 0.5 * (C[I][vi[J]][vj[J]] + C[I][vj[J]][vi[J]]); + return L; } diff --git a/src/Continuum_mechanics/Functions/tensor.cpp b/src/Continuum_mechanics/Functions/tensor.cpp index 77fd14ceb..e0cb7ce52 100644 --- a/src/Continuum_mechanics/Functions/tensor.cpp +++ b/src/Continuum_mechanics/Functions/tensor.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -124,30 +125,6 @@ static arma::mat::fixed<6,6> mandel_rotation(const arma::mat::fixed<3,3> &Q) { return R6; } -// scalar 6x6 products: dgemm at this size costs more than the flops and contends across threads -static arma::mat::fixed<6,6> mul66(const arma::mat::fixed<6,6> &A, const arma::mat::fixed<6,6> &B) { - arma::mat::fixed<6,6> C; - for (int j = 0; j < 6; ++j) - for (int i = 0; i < 6; ++i) { - double s = 0.0; - for (int k = 0; k < 6; ++k) s += A(i,k) * B(k,j); - C(i,j) = s; - } - return C; -} - -// A * B^T without forming the transpose. -static arma::mat::fixed<6,6> mul66_ABt(const arma::mat::fixed<6,6> &A, const arma::mat::fixed<6,6> &B) { - arma::mat::fixed<6,6> C; - for (int j = 0; j < 6; ++j) - for (int i = 0; i < 6; ++i) { - double s = 0.0; - for (int k = 0; k < 6; ++k) s += A(i,k) * B(j,k); - C(i,j) = s; - } - return C; -} - // Volumetric projector (Mandel == engineering here: it has no shear entries). static arma::mat::fixed<6,6> base_volumetric() { arma::mat::fixed<6,6> m; @@ -174,22 +151,29 @@ tensor2::tensor2(const arma::mat &m, Tensor2Type vtype) _mat = m; } -tensor2 tensor2::from_voigt(const arma::vec::fixed<6> &v, Tensor2Type vtype) { - arma::mat::fixed<3,3> m; +// engineering Voigt (6 contiguous values) -> symmetric 3x3 of a stress/strain tensor; false for none +static bool fill_from_voigt(arma::mat::fixed<3,3> &m, const double *v, Tensor2Type vtype) { if (vtype == Tensor2Type::strain) { - m(0,0) = v(0); m(1,1) = v(1); m(2,2) = v(2); - m(0,1) = 0.5 * v(3); m(1,0) = 0.5 * v(3); - m(0,2) = 0.5 * v(4); m(2,0) = 0.5 * v(4); - m(1,2) = 0.5 * v(5); m(2,1) = 0.5 * v(5); - } else if (vtype == Tensor2Type::stress || vtype == Tensor2Type::symmetric) { - m(0,0) = v(0); m(1,1) = v(1); m(2,2) = v(2); - m(0,1) = v(3); m(1,0) = v(3); - m(0,2) = v(4); m(2,0) = v(4); - m(1,2) = v(5); m(2,1) = v(5); - } else { - throw std::runtime_error("Cannot construct tensor2 from Voigt with Tensor2Type::none"); + m(0,0) = v[0]; m(1,1) = v[1]; m(2,2) = v[2]; + m(0,1) = 0.5 * v[3]; m(1,0) = 0.5 * v[3]; + m(0,2) = 0.5 * v[4]; m(2,0) = 0.5 * v[4]; + m(1,2) = 0.5 * v[5]; m(2,1) = 0.5 * v[5]; + return true; + } + if (vtype == Tensor2Type::stress || vtype == Tensor2Type::symmetric) { + m(0,0) = v[0]; m(1,1) = v[1]; m(2,2) = v[2]; + m(0,1) = v[3]; m(1,0) = v[3]; + m(0,2) = v[4]; m(2,0) = v[4]; + m(1,2) = v[5]; m(2,1) = v[5]; + return true; } - tensor2 result(m, vtype); + return false; +} + +tensor2 tensor2::from_voigt(const arma::vec::fixed<6> &v, Tensor2Type vtype) { + tensor2 result(vtype); + if (!fill_from_voigt(result._mat, v.memptr(), vtype)) + throw std::runtime_error("Cannot construct tensor2 from Voigt with Tensor2Type::none"); return result; } @@ -197,8 +181,10 @@ tensor2 tensor2::from_voigt(const arma::vec &v, Tensor2Type vtype) { if (v.n_elem != 6) throw std::invalid_argument("tensor2: expected 6-element vector, got " + std::to_string(v.n_elem)); - arma::vec::fixed<6> vf(v.memptr()); - return from_voigt(vf, vtype); + tensor2 result(vtype); + if (!fill_from_voigt(result._mat, v.memptr(), vtype)) + throw std::runtime_error("Cannot construct tensor2 from Voigt with Tensor2Type::none"); + return result; } tensor2 tensor2::from_voigt(const arma::vec &v, const std::string &type_str) { @@ -250,27 +236,16 @@ void tensor2::set_mat(const arma::mat &m) { } void tensor2::set_voigt(const arma::vec::fixed<6> &v) { - if (_vtype == Tensor2Type::strain) { - _mat(0,0) = v(0); _mat(1,1) = v(1); _mat(2,2) = v(2); - _mat(0,1) = 0.5 * v(3); _mat(1,0) = 0.5 * v(3); - _mat(0,2) = 0.5 * v(4); _mat(2,0) = 0.5 * v(4); - _mat(1,2) = 0.5 * v(5); _mat(2,1) = 0.5 * v(5); - } else if (_vtype == Tensor2Type::stress || _vtype == Tensor2Type::symmetric) { - _mat(0,0) = v(0); _mat(1,1) = v(1); _mat(2,2) = v(2); - _mat(0,1) = v(3); _mat(1,0) = v(3); - _mat(0,2) = v(4); _mat(2,0) = v(4); - _mat(1,2) = v(5); _mat(2,1) = v(5); - } else { + if (!fill_from_voigt(_mat, v.memptr(), _vtype)) throw std::runtime_error("Cannot set_voigt on Tensor2Type::none tensor"); - } } void tensor2::set_voigt(const arma::vec &v) { if (v.n_elem != 6) throw std::invalid_argument("tensor2::set_voigt: expected 6-element vector, got " + std::to_string(v.n_elem)); - arma::vec::fixed<6> vf(v.memptr()); - set_voigt(vf); + if (!fill_from_voigt(_mat, v.memptr(), _vtype)) + throw std::runtime_error("Cannot set_voigt on Tensor2Type::none tensor"); } arma::vec::fixed<6> tensor2::voigt() const { @@ -583,12 +558,20 @@ tensor4::tensor4(Tensor4Type type) : _mandel(arma::fill::zeros), _type(type) {} tensor4::tensor4(const arma::mat::fixed<6,6> &m, Tensor4Type type) : _mandel(eng_to_mandel(m, type)), _type(type) {} +// eng_to_mandel in place on the stored matrix (same products) +static void eng_to_mandel_inplace(arma::mat::fixed<6,6> &X, Tensor4Type type) { + double r, c; mandel_factors(type, r, c); + for (int I = 3; I < 6; ++I) X.row(I) *= r; + for (int J = 3; J < 6; ++J) X.col(J) *= c; +} + tensor4::tensor4(const arma::mat &m, Tensor4Type type) : _type(type) { if (m.n_rows != 6 || m.n_cols != 6) throw std::invalid_argument("tensor4: expected 6x6 matrix, got " + std::to_string(m.n_rows) + "x" + std::to_string(m.n_cols)); - _mandel = eng_to_mandel(arma::mat::fixed<6,6>(m), type); + _mandel = m; + eng_to_mandel_inplace(_mandel, type); } tensor4::tensor4(const arma::mat &m, const std::string &type_str) @@ -646,7 +629,8 @@ void tensor4::set_mat(const arma::mat &m) { if (m.n_rows != 6 || m.n_cols != 6) throw std::invalid_argument("tensor4: expected 6x6 matrix, got " + std::to_string(m.n_rows) + "x" + std::to_string(m.n_cols)); - _mandel = eng_to_mandel(arma::mat::fixed<6,6>(m), _type); + _mandel = m; + eng_to_mandel_inplace(_mandel, _type); _invalidate_fastor(); } diff --git a/src/Continuum_mechanics/Homogenization/eshelby.cpp b/src/Continuum_mechanics/Homogenization/eshelby.cpp index 8572eac34..1d986c7f5 100755 --- a/src/Continuum_mechanics/Homogenization/eshelby.cpp +++ b/src/Continuum_mechanics/Homogenization/eshelby.cpp @@ -202,7 +202,23 @@ void calG(const double &pt, const double &a1, const double &a2, const double &a3 //Voigt index of the pair (i,j): the table that Eshelby and T_II hand to Gauss as Id static const int Voigt_index[3][3] = {{0,3,4},{3,1,5},{4,5,2}}; -// sum of calG in the per-entry operation order of the point loop (bitwise); sin/cos hoisted +// two rows of G per operation (vector type on GCC/Clang, else a pair); each lane runs the scalar expressions +#if defined(__GNUC__) || defined(__clang__) +typedef double lane2 __attribute__((vector_size(16))); +static inline lane2 lanes(const double &a, const double &b) { lane2 r = {a, b}; return r; } +static inline lane2 mul(const lane2 &a, const lane2 &b) { return a*b; } +static inline lane2 mul_add(const lane2 &a, const lane2 &b, const lane2 &c) { return a*b + c; } +#else +struct lane2 { + double v[2]; + double operator[](int i) const { return v[i]; } +}; +static inline lane2 lanes(const double &a, const double &b) { lane2 r = {{a, b}}; return r; } +static inline lane2 mul(const lane2 &a, const lane2 &b) { lane2 r = {{a.v[0]*b.v[0], a.v[1]*b.v[1]}}; return r; } +static inline lane2 mul_add(const lane2 &a, const lane2 &b, const lane2 &c) { lane2 r = {{a.v[0]*b.v[0] + c.v[0], a.v[1]*b.v[1] + c.v[1]}}; return r; } +#endif + +// sum of calG in the per-entry operation order of the point loop (bitwise); sin/cos hoisted, two rows of G at a time void Gauss(Mat &Id, const mat &Lt, mat &G, const double &a1, const double &a2, const double &a3, const vec &x, const vec &wx, const vec &y, const vec &wy, const int &mp, const int &np) { (void)Id; //always the Voigt table (Voigt_index) @@ -210,10 +226,13 @@ void Gauss(Mat &Id, const mat &Lt, mat &G, const double &a1, const double & for (int i=0; i<6; i++) for (int j=0; j<6; j++) L[i][j] = Lt(i,j); - double Gl[6][6]; - for (int i=0; i<6; i++) - for (int j=0; j<6; j++) - Gl[i][j] = G(i,j); + //rows ij of G in pairs, (00, 01), (02, 11), (12, 22); columns kl in the order 00, 01, 02, 11, 12, 22 + static const int row_ij[6] = {0, 3, 4, 1, 5, 2}; + static const int col_kl[6] = {0, 3, 4, 1, 5, 2}; + lane2 Gp[3][6]; + for (int q=0; q<3; q++) + for (int c=0; c<6; c++) + Gp[q][c] = lanes(G(row_ij[2*q],col_kl[c]), G(row_ij[2*q+1],col_kl[c])); std::vector cos_y(np), sin_y(np); for (int i=0; i &Id, const mat &Lt, mat &G, const double &a1, const double & const double inv_a1 = 1./a1; const double inv_a2 = 1./a2; const double inv_a3 = 1./a3; + const double *wy_ = wy.memptr(); //per point of the ring: X and the upper triangle of inv(K), K_ik = L_ijkl X_j X_l std::vector ring(9*np); double *X[3] = {&ring[0], &ring[np], &ring[2*np]}; double *R00 = &ring[3*np], *R01 = &ring[4*np], *R02 = &ring[5*np]; double *R11 = &ring[6*np], *R12 = &ring[7*np], *R22 = &ring[8*np]; - const double *R[3][3] = {{R00, R01, R02}, {nullptr, R11, R12}, {nullptr, nullptr, R22}}; + const double *R[6] = {R00, R01, R02, R11, R12, R22}; //rows in the order of row_ij for (int l=0; l &Id, const mat &Lt, mat &G, const double &a1, const double & R22[p] = rn22*inv_D; } - const double wx_l = wx(l); + const lane2 wx_l = lanes(wx(l), wx(l)); + const lane2 zero = lanes(0., 0.); + //a sequential sum per entry: an ordered vectorised reduction over p could split the + //contracted multiply-adds +#if defined(__clang__) + #pragma clang loop vectorize(disable) +#endif for (int p=0; p Id(6,6); - Id.zeros(); - - mat S = zeros(6,6); - int ij=0; - int mn=0; - int pq=0; - int ip=0; - int jq=0; - int jp=0; - int iq=0; - - Id(0,0) = 0; - Id(0,1) = 3; - Id(0,2) = 4; - Id(1,0) = 3; - Id(1,1) = 1; - Id(1,2) = 5; - Id(2,0) = 4; - Id(2,1) = 5; - Id(2,2) = 2; - - Gauss(Id, Lt, G, a1, a2, a3, x, wx, y, wy, mp, np); - - for (int i=0; i<3; i++) { - for (int j=i; j<3; j++) { - ij = Id(i,j); - for (int m=0; m<3; m++) { - for (int n=m; n<3; n++) { - mn = Id(m,n); - for (int p=0; p<3; p++) { - for (int q=0; q<3; q++) { - pq = Id(p,q); - ip = Id(i,p); - jq = Id(j,q); - jp = Id(j,p); - iq = Id(i,q); - S(ij,mn) = S(ij,mn)+Lt(pq,mn)*(G(ip,jq)+G(jp,iq)); - } - } - } - } - } - } - - for (int i=0; i<3; i++) { - for (int j=0; j<6; j++) { - S(i,j) = S(i,j)*(1./(8.*simcoon::pi)); - } - } - - for (int i=3; i<6; i++) { - for (int j=0; j<6; j++) { - S(i,j) = S(i,j)*(1./(4.*simcoon::pi)); - } - } - return S; + Mat Id; //unused by Gauss (always the Voigt table) + Gauss(Id, Lt, G, a1, a2, a3, x, wx, y, wy, mp, np); + return contract_G(Lt, G); } mat Eshelby(const mat &Lt, const double &a1, const double &a2, const double &a3, const int &mp, const int &np) { @@ -362,67 +375,11 @@ mat Eshelby(const mat &Lt, const double &a1, const double &a2, const double &a3, mat T_II(const mat &Lt, const double &a1, const double &a2, const double &a3, const vec &x, const vec &wx, const vec &y, const vec &wy, const int &mp, const int &np) -{ - mat G = zeros(6,6); - Mat Id(6,6); - Id.zeros(); - - mat T_II = zeros(6,6); - mat I = eye(6,6); - - int ij=0; - int mn=0; - int pq=0; - int ip=0; - int jq=0; - int jp=0; - int iq=0; - - Id(0,0) = 0; - Id(0,1) = 3; - Id(0,2) = 4; - Id(1,0) = 3; - Id(1,1) = 1; - Id(1,2) = 5; - Id(2,0) = 4; - Id(2,1) = 5; - Id(2,2) = 2; - - Gauss(Id, Lt, G, a1, a2, a3, x, wx, y, wy, mp, np); - - for (int i=0; i<3; i++) { - for (int j=i; j<3; j++) { - ij = Id(i,j); - for (int m=0; m<3; m++) { - for (int n=m; n<3; n++) { - mn = Id(m,n); - for (int p=0; p<3; p++) { - for (int q=0; q<3; q++) { - pq = Id(p,q); - ip = Id(i,p); - jq = Id(j,q); - jp = Id(j,p); - iq = Id(i,q); - T_II(ij,mn) = T_II(ij,mn)+I(pq,mn)*(G(ip,jq)+G(jp,iq)); - } - } - } - } - } - } - - for (int i=0; i<3; i++) { - for (int j=0; j<6; j++) { - T_II(i,j) = T_II(i,j)*(1./(8.*simcoon::pi)); - } - } - - for (int i=3; i<6; i++) { - for (int j=0; j<6; j++) { - T_II(i,j) = T_II(i,j)*(1./(4.*simcoon::pi)); - } - } - return T_II; +{ + mat G = zeros(6,6); + Mat Id; //unused by Gauss (always the Voigt table) + Gauss(Id, Lt, G, a1, a2, a3, x, wx, y, wy, mp, np); + return contract_G(eye(6,6), G); } mat T_II(const mat &Lt, const double &a1, const double &a2, const double &a3, const int &mp, const int &np) { diff --git a/src/Continuum_mechanics/Micromechanics/multiphase.cpp b/src/Continuum_mechanics/Micromechanics/multiphase.cpp index 1792eeff5..e25affad7 100755 --- a/src/Continuum_mechanics/Micromechanics/multiphase.cpp +++ b/src/Continuum_mechanics/Micromechanics/multiphase.cpp @@ -164,8 +164,7 @@ void umat_multi(phase_characteristics &phase, const mat &DR, const double &Time, while ((error > simcoon::precision_micro)&&(nbiter <= simcoon::maxiter_micro)) { for(int i=0; i(phase.sub_phases[i].sptr_sv_global); - DE_N[i] = sv_r->DEtot; + DE_N[i] = phase.sub_phases[i].sptr_sv_global->DEtot; //a state_variables member: no cast } //Compute the strain concentration tensor for each phase: @@ -210,8 +209,7 @@ void umat_multi(phase_characteristics &phase, const mat &DR, const double &Time, error = 0.; for(int i=0; i(phase.sub_phases[i].sptr_sv_global); - error += norm(DE_N[i] - sv_r->DEtot,2); + error += norm(DE_N[i] - phase.sub_phases[i].sptr_sv_global->DEtot,2); } error*=(1./nphases); nbiter++; @@ -249,13 +247,13 @@ void umat_multi(phase_characteristics &phase, const mat &DR, const double &Time, // Homogenization //Compute the effective stress umat_phase_M->sigma = zeros(6); - for (auto r : phase.sub_phases) { + for (auto &r : phase.sub_phases) { umat_phase_M->sigma += r.sptr_shape->concentration*r.sptr_sv_global->sigma; } umat_phase_M->Lt = zeros(6,6); // Compute the effective tangent modulus, and the effective stress - for (auto r : phase.sub_phases) { + for (auto &r : phase.sub_phases) { umat_sub_phases_M = std::dynamic_pointer_cast(r.sptr_sv_global); umat_phase_M->Lt += r.sptr_shape->concentration*(umat_sub_phases_M->Lt*r.sptr_multi->A); } diff --git a/src/Continuum_mechanics/Micromechanics/schemes.cpp b/src/Continuum_mechanics/Micromechanics/schemes.cpp index d77b4466f..59707a5b3 100755 --- a/src/Continuum_mechanics/Micromechanics/schemes.cpp +++ b/src/Continuum_mechanics/Micromechanics/schemes.cpp @@ -26,6 +26,8 @@ #include #include #include +#include +#include #include #include #include @@ -53,11 +55,33 @@ static const eshelby_quadrature &scheme_quadrature(const phase_characteristics & return eshelby_quadrature_points(static_cast(phase.sptr_matprops->props(0)), static_cast(phase.sptr_matprops->props(1)), opt.rule, opt.closed_form == 1); } - + +//inv(sumT) of the Mori-Tanaka passes, the last one of this thread kept: an unchanged medium +//(elastic phases: the localization and the tangent passes of every call) hands bitwise the same +//sumT to each pass, whose inverse is then the stored one. inv throws on a singular sumT, and +//nothing is stored then. +static void inv_sumT_memoized(const mat &sumT, mat &inv_sumT) +{ + thread_local bool valid = false; + thread_local double key[36]; + thread_local double value[36]; + if (valid && sumT.n_elem == 36 && std::memcmp(key, sumT.memptr(), sizeof(key)) == 0) { + inv_sumT.set_size(6,6); + std::memcpy(inv_sumT.memptr(), value, sizeof(value)); + return; + } + inv_sumT = inv(sumT); + if (sumT.n_elem == 36) { + std::memcpy(key, sumT.memptr(), sizeof(key)); + std::memcpy(value, inv_sumT.memptr(), sizeof(value)); + valid = true; + } +} + void Lt_Homogeneous_E(phase_characteristics &phase) { //Compute the strain concentration tensor A - for(auto r : phase.sub_phases) { + for(auto &r : phase.sub_phases) { r.sptr_multi->A = eye(6,6); } } @@ -68,7 +92,7 @@ void DE_Homogeneous_E(phase_characteristics &phase) { std::shared_ptr sv_eff = std::dynamic_pointer_cast(phase.sptr_sv_local); //Compute the strain concentration tensor A - for(auto r : phase.sub_phases) { + for(auto &r : phase.sub_phases) { sv_r = std::dynamic_pointer_cast(r.sptr_sv_global); r.sptr_multi->A = eye(6,6); sv_r->DEtot = r.sptr_multi->A*sv_eff->DEtot; //Recall that the global coordinates of subphases is the local coordinates of the generic phase @@ -77,8 +101,8 @@ void DE_Homogeneous_E(phase_characteristics &phase) { void Lt_Mori_Tanaka(phase_characteristics &phase, const int &n_matrix) { - mat sumT = zeros(6,6); - mat inv_sumT = zeros(6,6); + mat::fixed<6,6> sumT(fill::zeros); //fixed size: no heap allocation per pass + mat::fixed<6,6> inv_sumT(fill::zeros); std::shared_ptr elli_multi; std::shared_ptr elli; @@ -91,7 +115,7 @@ void Lt_Mori_Tanaka(phase_characteristics &phase, const int &n_matrix) { //(one Eshelby integration per distinct inclusion geometry in this pass: eshelby_memo) eshelby_memo memo; const eshelby_quadrature &quad = scheme_quadrature(phase); - for(auto r : phase.sub_phases) { + for(auto &r : phase.sub_phases) { elli_multi = std::dynamic_pointer_cast(r.sptr_multi); elli = std::dynamic_pointer_cast(r.sptr_shape); sv_r = std::dynamic_pointer_cast(r.sptr_sv_global); @@ -108,14 +132,14 @@ void Lt_Mori_Tanaka(phase_characteristics &phase, const int &n_matrix) { } try { - inv_sumT = inv(sumT); + inv_sumT_memoized(sumT, inv_sumT); } catch (const std::runtime_error &e) { cerr << "Error in inv: " << e.what() << endl; throw simcoon::exception_inv("Error in inv function inside Lt_Mori_Tanaka."); } //Compute the strain concentration tensor A - for(auto r : phase.sub_phases) { + for(auto &r : phase.sub_phases) { elli_multi = std::dynamic_pointer_cast(r.sptr_multi); elli_multi->A = elli_multi->T*inv_sumT; } @@ -123,8 +147,8 @@ void Lt_Mori_Tanaka(phase_characteristics &phase, const int &n_matrix) { void Lt_Mori_Tanaka_iso(phase_characteristics &phase, const int &n_matrix) { - mat sumT = zeros(6,6); - mat inv_sumT = zeros(6,6); + mat::fixed<6,6> sumT(fill::zeros); //fixed size: no heap allocation per pass + mat::fixed<6,6> inv_sumT(fill::zeros); std::shared_ptr elli_multi; std::shared_ptr elli; @@ -137,7 +161,7 @@ void Lt_Mori_Tanaka_iso(phase_characteristics &phase, const int &n_matrix) { //(one Eshelby integration per distinct inclusion geometry in this pass: eshelby_memo) eshelby_memo memo; const eshelby_quadrature &quad = scheme_quadrature(phase); - for(auto r : phase.sub_phases) { + for(auto &r : phase.sub_phases) { elli_multi = std::dynamic_pointer_cast(r.sptr_multi); elli = std::dynamic_pointer_cast(r.sptr_shape); sv_r = std::dynamic_pointer_cast(r.sptr_sv_global); @@ -154,14 +178,14 @@ void Lt_Mori_Tanaka_iso(phase_characteristics &phase, const int &n_matrix) { } try { - inv_sumT = inv(sumT); + inv_sumT_memoized(sumT, inv_sumT); } catch (const std::runtime_error &e) { cerr << "Error in inv: " << e.what() << endl; throw simcoon::exception_inv("Error in inv function inside Lt_Mori_Tanaka_iso."); } //Compute the strain concentration tensor A - for(auto r : phase.sub_phases) { + for(auto &r : phase.sub_phases) { elli_multi = std::dynamic_pointer_cast(r.sptr_multi); elli_multi->A = elli_multi->T*inv_sumT; } @@ -169,8 +193,8 @@ void Lt_Mori_Tanaka_iso(phase_characteristics &phase, const int &n_matrix) { void DE_Mori_Tanaka(phase_characteristics &phase, const int &n_matrix) { - mat sumT = zeros(6,6); - mat inv_sumT = zeros(6,6); + mat::fixed<6,6> sumT(fill::zeros); //fixed size: no heap allocation per pass + mat::fixed<6,6> inv_sumT(fill::zeros); std::shared_ptr elli_multi; std::shared_ptr elli; @@ -183,7 +207,7 @@ void DE_Mori_Tanaka(phase_characteristics &phase, const int &n_matrix) { //(one Eshelby integration per distinct inclusion geometry in this pass: eshelby_memo) eshelby_memo memo; const eshelby_quadrature &quad = scheme_quadrature(phase); - for(auto r : phase.sub_phases) { + for(auto &r : phase.sub_phases) { elli_multi = std::dynamic_pointer_cast(r.sptr_multi); elli = std::dynamic_pointer_cast(r.sptr_shape); sv_r = std::dynamic_pointer_cast(r.sptr_sv_global); @@ -199,14 +223,14 @@ void DE_Mori_Tanaka(phase_characteristics &phase, const int &n_matrix) { } try { - inv_sumT = inv(sumT); + inv_sumT_memoized(sumT, inv_sumT); } catch (const std::runtime_error &e) { cerr << "Error in inv: " << e.what() << endl; throw simcoon::exception_inv("Error in inv function inside DE_Mori_Tanaka."); } //Compute the strain concentration tensor A - for(auto r : phase.sub_phases) { + for(auto &r : phase.sub_phases) { elli_multi = std::dynamic_pointer_cast(r.sptr_multi); sv_r = std::dynamic_pointer_cast(r.sptr_sv_global); @@ -217,8 +241,8 @@ void DE_Mori_Tanaka(phase_characteristics &phase, const int &n_matrix) { void DE_Mori_Tanaka_iso(phase_characteristics &phase, const int &n_matrix) { - mat sumT = zeros(6,6); - mat inv_sumT = zeros(6,6); + mat::fixed<6,6> sumT(fill::zeros); //fixed size: no heap allocation per pass + mat::fixed<6,6> inv_sumT(fill::zeros); std::shared_ptr elli_multi; std::shared_ptr elli; @@ -231,7 +255,7 @@ void DE_Mori_Tanaka_iso(phase_characteristics &phase, const int &n_matrix) { //(one Eshelby integration per distinct inclusion geometry in this pass: eshelby_memo) eshelby_memo memo; const eshelby_quadrature &quad = scheme_quadrature(phase); - for(auto r : phase.sub_phases) { + for(auto &r : phase.sub_phases) { elli_multi = std::dynamic_pointer_cast(r.sptr_multi); elli = std::dynamic_pointer_cast(r.sptr_shape); sv_r = std::dynamic_pointer_cast(r.sptr_sv_global); @@ -247,14 +271,14 @@ void DE_Mori_Tanaka_iso(phase_characteristics &phase, const int &n_matrix) { } try { - inv_sumT = inv(sumT); + inv_sumT_memoized(sumT, inv_sumT); } catch (const std::runtime_error &e) { cerr << "Error in inv: " << e.what() << endl; throw simcoon::exception_inv("Error in inv function inside DE_Mori_Tanaka_iso."); } //Compute the strain concentration tensor A - for(auto r : phase.sub_phases) { + for(auto &r : phase.sub_phases) { elli_multi = std::dynamic_pointer_cast(r.sptr_multi); sv_r = std::dynamic_pointer_cast(r.sptr_sv_global); @@ -293,7 +317,7 @@ void Lt_Self_Consistent(phase_characteristics &phase, const int &n_matrix, const sv_eff->Lt = Lt_eff; } - mat sumA = zeros(6,6); + mat::fixed<6,6> sumA(fill::zeros); //fixed size: no heap allocation per pass //Compute the Eshelby tensor and the interaction tensor for each phase //(one Eshelby integration per distinct inclusion geometry in this pass: eshelby_memo) eshelby_memo memo; @@ -347,14 +371,14 @@ void DE_Self_Consistent(phase_characteristics &phase, const int &n_matrix, const //Compute the effective tensor from the previous strain localization tensors mat Lt_eff = zeros(6,6); - for(auto r : phase.sub_phases) { + for(auto &r : phase.sub_phases) { sv_r = std::dynamic_pointer_cast(r.sptr_sv_global); Lt_eff += r.sptr_shape->concentration*r.sptr_multi->A*sv_r->Lt; } sv_eff->Lt = Lt_eff; } - mat sumA = zeros(6,6); + mat::fixed<6,6> sumA(fill::zeros); //fixed size: no heap allocation per pass //Compute the Eshelby tensor and the interaction tensor for each phase //(one Eshelby integration per distinct inclusion geometry in this pass: eshelby_memo) eshelby_memo memo; @@ -400,7 +424,7 @@ void dE_Periodic_Layer(phase_characteristics &phase, const int &nbiter) { mat Lt_loc = zeros(6,6); if (nbiter == 0) { - for (auto r : phase.sub_phases) { + for(auto &r : phase.sub_phases) { r.sptr_sv_global->DEtot = sv_eff->DEtot; } } @@ -408,13 +432,20 @@ void dE_Periodic_Layer(phase_characteristics &phase, const int &nbiter) { //Compute the increment of strain mat sumDnn = zeros(3,3); vec sumcDsig = zeros(3); - for(auto r : phase.sub_phases) { + //Voigt operators of each layer, built once per call: the matrices that + //Rotation::apply_stiffness, apply_stress and apply_strain build at each use, applied + //through the same products + std::vector strain_to_global; + strain_to_global.reserve(phase.sub_phases.size()); + for(auto &r : phase.sub_phases) { sv_r = std::dynamic_pointer_cast(r.sptr_sv_global); lay_multi = std::dynamic_pointer_cast(r.sptr_multi); lay = std::dynamic_pointer_cast(r.sptr_shape); - Rotation rot_layer = Rotation::from_euler(lay->psi_geom, lay->theta_geom, lay->phi_geom, "zxz"); - Lt_loc = rot_layer.inv().apply_stiffness(sv_r->Lt); + const Rotation rot_layer = Rotation::from_euler(lay->psi_geom, lay->theta_geom, lay->phi_geom, "zxz"); + const mat vs_to_local = rot_layer.inv().as_voigt_stress_rotation(); + strain_to_global.push_back(rot_layer.as_voigt_strain_rotation()); + Lt_loc = vs_to_local * (sv_r->Lt * trans(vs_to_local)); lay_multi->Dnn(0,0) = Lt_loc(0,0); lay_multi->Dnn(0,1) = Lt_loc(0,3); @@ -426,14 +457,14 @@ void dE_Periodic_Layer(phase_characteristics &phase, const int &nbiter) { lay_multi->Dnn(2,1) = Lt_loc(4,3); lay_multi->Dnn(2,2) = Lt_loc(4,4); - vec sigma_local = rot_layer.inv().apply_stress(sv_r->sigma); + vec sigma_local = vs_to_local * sv_r->sigma; lay_multi->sigma_hat(0) = sigma_local(0); lay_multi->sigma_hat(1) = sigma_local(3); lay_multi->sigma_hat(2) = sigma_local(4); } try { - for(auto r : phase.sub_phases) { + for(auto &r : phase.sub_phases) { lay_multi = std::dynamic_pointer_cast(r.sptr_multi); lay = std::dynamic_pointer_cast(r.sptr_shape); const mat inv_Dnn = inv(lay_multi->Dnn); @@ -453,7 +484,8 @@ void dE_Periodic_Layer(phase_characteristics &phase, const int &nbiter) { throw simcoon::exception_inv("Error in inv function inside dE_Periodic_Layer."); } - for(auto r : phase.sub_phases) { + for(unsigned int i=0; i(r.sptr_sv_global); lay_multi = std::dynamic_pointer_cast(r.sptr_multi); @@ -464,14 +496,12 @@ void dE_Periodic_Layer(phase_characteristics &phase, const int &nbiter) { cerr << "Error in inv: " << e.what() << endl; throw simcoon::exception_inv("Error in inv function inside dE_Periodic_Layer."); } - lay = std::dynamic_pointer_cast(r.sptr_shape); vec dEtot_local = zeros(6); dEtot_local(0) = lay_multi->dzdx1(0); dEtot_local(3) = lay_multi->dzdx1(1); dEtot_local(4) = lay_multi->dzdx1(2); - Rotation rot_l2g = Rotation::from_euler(lay->psi_geom, lay->theta_geom, lay->phi_geom, "zxz"); - vec dEtot_global = rot_l2g.apply_strain(dEtot_local); + vec dEtot_global = strain_to_global[i] * dEtot_local; sv_r->DEtot += dEtot_global; } @@ -488,15 +518,15 @@ void Lt_Periodic_Layer(phase_characteristics &phase) { mat A_loc = zeros(6,6); //Compute the strain concentration tensor A - for(auto r : phase.sub_phases) { + std::vector rotations; //of each layer, for both of its uses + rotations.reserve(phase.sub_phases.size()); + for(auto &r : phase.sub_phases) { sv_r = std::dynamic_pointer_cast(r.sptr_sv_global); lay_multi = std::dynamic_pointer_cast(r.sptr_multi); lay = std::dynamic_pointer_cast(r.sptr_shape); - { - Rotation rot = Rotation::from_euler(lay->psi_geom, lay->theta_geom, lay->phi_geom, "zxz"); - Lt_loc = rot.inv().apply_stiffness(sv_r->Lt); - } + rotations.push_back(Rotation::from_euler(lay->psi_geom, lay->theta_geom, lay->phi_geom, "zxz")); + Lt_loc = rotations.back().inv().apply_stiffness(sv_r->Lt); lay_multi->Dnn(0,0) = Lt_loc(0,0); lay_multi->Dnn(0,1) = Lt_loc(0,3); @@ -523,7 +553,7 @@ void Lt_Periodic_Layer(phase_characteristics &phase) { mat sumDnt = zeros(3,3); try { - for(auto r : phase.sub_phases) { + for(auto &r : phase.sub_phases) { lay_multi = std::dynamic_pointer_cast(r.sptr_multi); lay = std::dynamic_pointer_cast(r.sptr_shape); sumDnn += lay->concentration*inv(lay_multi->Dnn); @@ -544,9 +574,8 @@ void Lt_Periodic_Layer(phase_characteristics &phase) { mat m_t = m_n*sumDnt; - for(auto r : phase.sub_phases) { - lay_multi = std::dynamic_pointer_cast(r.sptr_multi); - lay = std::dynamic_pointer_cast(r.sptr_shape); + for(unsigned int i=0; i(phase.sub_phases[i].sptr_multi); try { lay_multi->dXn = inv(lay_multi->Dnn)*(m_n-lay_multi->Dnn); @@ -579,10 +608,7 @@ void Lt_Periodic_Layer(phase_characteristics &phase) { A_loc(4,4) += lay_multi->dXn(2,2); A_loc(4,5) += lay_multi->dXt(2,2); - { - Rotation rot = Rotation::from_euler(lay->psi_geom, lay->theta_geom, lay->phi_geom, "zxz"); - lay_multi->A = rot.apply_strain_concentration(A_loc); - } + lay_multi->A = rotations[i].apply_strain_concentration(A_loc); } } diff --git a/src/Continuum_mechanics/Umat/Finite/Neo_hookean_incomp.cpp b/src/Continuum_mechanics/Umat/Finite/Neo_hookean_incomp.cpp index 0cc5067fe..35c3e5ced 100755 --- a/src/Continuum_mechanics/Umat/Finite/Neo_hookean_incomp.cpp +++ b/src/Continuum_mechanics/Umat/Finite/Neo_hookean_incomp.cpp @@ -114,8 +114,10 @@ void umat_neo_hookean_incomp(const string &umat_name, const vec &etot, const vec double dU2dJ2 = 2./D_1; // The sum is the spatial elasticity c = (1/J) d(L_v tau)/dD; the J that turns it into the // Kirchhoff-Lie tangent is applied once here -- see the note on L_vol_hyper. - mat Lt_spatial = L_iso_hyper_invariants(dWdI_1_bar, 0., 0., 0., 0., b, J) + L_vol_hyper(dUdJ, dU2dJ2, b, J); - Lt = Dtau_LieDD_2_DtauDe_corate(J*Lt_spatial, corate_type, F1, tau_t); + mat Lt_spatial = L_iso_hyper_invariants(dWdI_1_bar, 0., 0., 0., 0., b, J); + Lt_spatial += L_vol_hyper(dUdJ, dU2dJ2, b, J); + Lt_spatial *= J; // the Kirchhoff-Lie tangent, in place + Lt = Dtau_LieDD_2_DtauDe_corate(Lt_spatial, corate_type, F1, tau_t); if(start) { L = Lt; diff --git a/src/Continuum_mechanics/Umat/Finite/generic_hyper_pstretch.cpp b/src/Continuum_mechanics/Umat/Finite/generic_hyper_pstretch.cpp index 276a1d99c..91879a986 100755 --- a/src/Continuum_mechanics/Umat/Finite/generic_hyper_pstretch.cpp +++ b/src/Continuum_mechanics/Umat/Finite/generic_hyper_pstretch.cpp @@ -127,7 +127,8 @@ void umat_generic_hyper_pstretch(const std::string &umat_name, const vec &etot, mat Lt_spatial = Lt_iso + Lt_vol; // Standardize to the canonical box convention Lt = d(tau_hat)/d(De) -- see generic_hyper_invariants. - Lt = Dtau_LieDD_2_DtauDe_corate(J*Lt_spatial, corate_type, F1, m_tau); + Lt_spatial *= J; // the Kirchhoff-Lie tangent, in place + Lt = Dtau_LieDD_2_DtauDe_corate(Lt_spatial, corate_type, F1, m_tau); if(start) { L = Lt; diff --git a/src/Continuum_mechanics/Umat/Mechanical/Damage/damage_LLD_0.cpp b/src/Continuum_mechanics/Umat/Mechanical/Damage/damage_LLD_0.cpp index 3873e58b8..e17395aa2 100755 --- a/src/Continuum_mechanics/Umat/Mechanical/Damage/damage_LLD_0.cpp +++ b/src/Continuum_mechanics/Umat/Mechanical/Damage/damage_LLD_0.cpp @@ -47,15 +47,16 @@ constexpr double d_broken = 0.99; struct damaged_elasticity { double EL, ET, nuTL, nuTT, GLT; - mat L(const double &d22, const double &d12) const { + mat::fixed<6, 6> L(const double &d22, const double &d12) const { const double ET_d = ET*(1. - d22); mat::fixed<3, 3> S; S(0, 0) = 1./EL; S(0, 1) = S(1, 0) = S(0, 2) = S(2, 0) = -nuTL/ET; S(1, 1) = S(2, 2) = 1./ET_d; S(1, 2) = S(2, 1) = -nuTT/ET_d; - mat L = zeros(6, 6); - L.submat(0, 0, 2, 2) = inv(S); + mat::fixed<6, 6> L = zeros(6, 6); + const mat::fixed<3, 3> S_inv = inv(S); + L.submat(0, 0, 2, 2) = S_inv; L(3, 3) = L(4, 4) = GLT*(1. - d12); L(5, 5) = ET_d/(2.*(1. + nuTT)); return L; @@ -175,7 +176,7 @@ void umat_damage_LLD_0(const string &umat_name, const vec &Etot, const vec &DEto sigma = zeros(6); } - const mat L0 = C.L(0., 0.); // undamaged: the effective-stress plasticity + const mat::fixed<6, 6> L0 = C.L(0., 0.); // undamaged: the effective-stress plasticity vec alpha = alphaT*Ith(); alpha(0) += alphaL - alphaT; @@ -193,7 +194,8 @@ void umat_damage_LLD_0(const string &umat_name, const vec &Etot, const vec &DEto const vec theta = {0., A_ts, A_ts, 1., 1., 0.}; // the diagonal of Theta // ---- 1. Plasticity in effective stress (independent of the damage) ---- - vec Eel = Etot_f + DEtot_f - alpha*(T + DT - Tinit) - EP; + const vec Eel_0 = Etot_f + DEtot_f - alpha*(T + DT - Tinit); // the strain minus its thermal part + vec Eel = Eel_0 - EP; vec Lambda_p = zeros(6); vec Phi_p = zeros(1), Dp = zeros(1), dp = zeros(1); const vec Y_pcrit = {sigma_ts_0}; @@ -208,7 +210,7 @@ void umat_damage_LLD_0(const string &umat_name, const vec &Etot, const vec &DEto Fischer_Burmeister_m(Phi_p, Y_pcrit, denom_p, Dp, dp, error); p_ts += dp(0); EP += dp(0)*Lambda_p; - Eel = Etot_f + DEtot_f - alpha*(T + DT - Tinit) - EP; + Eel = Eel_0 - EP; } if (compteur == simcoon::maxiter_umat) tnew_dt = 0.2; @@ -219,12 +221,12 @@ void umat_damage_LLD_0(const string &umat_name, const vec &Etot, const vec &DEto const double Y0[2] = {Y_22_0, Y_12_0}; vec d = {d_22, d_12}; vec Dd_all = zeros(2); - mat L_t = C.L(d(0), d(1)); + mat::fixed<6, 6> L_t = C.L(d(0), d(1)); sigma = L_t*Eel; - // dPhi_l/dd_j at fixed strain: through sigma = L~(d) Eel and explicitly through Y_ts - auto dPhi_dd = [&](const driving_force &Y, const std::array, 2> &Lambda_d, const int &l, const int &j) { + // dPhi_l/dd_j at fixed strain, via sigma = L~(d) Eel and explicitly via Y_ts; LLambda_d[j] = L~ Lambda_j + auto dPhi_dd = [&](const driving_force &Y, const std::array, 2> &LLambda_d, const int &l, const int &j) { const double dYts_dd = (j == 0) ? Y.dYts_dd22 : Y.dYts_dd12; - double J = ((Y.Y_ts > Y0[l]) ? (-sum(Y.dYts_dsigma % (L_t*Lambda_d[j])) + dYts_dd)/Yc[l] : 0.); + double J = ((Y.Y_ts > Y0[l]) ? (-sum(Y.dYts_dsigma % LLambda_d[j]) + dYts_dd)/Yc[l] : 0.); if (l == j) J -= dlagrange_pow_1(d(l), c_lambda, p0_lambda, n_lambda, alpha_lambda) + 1.; return J; @@ -240,9 +242,18 @@ void umat_damage_LLD_0(const string &umat_name, const vec &Etot, const vec &DEto sigma = L_t*Eel; continue; } - const std::vector mech = broken_22 ? std::vector{1} : std::vector{0, 1}; - const int n = mech.size(); + const int mech[2] = {broken_22 ? 1 : 0, 1}; + const int n = broken_22 ? 1 : 2; const std::array, 2> Lambda_d = {C.Lambda_22(sigma, d(0)), C.Lambda_12(sigma, d(1))}; + // L~ Lambda_j enters a row only above its damage threshold (dPhi_dd) + bool above = false; + for (int a = 0; a < n; a++) + above = above || (Y.Y_ts > Y0[mech[a]]); + std::array, 2> LLambda_d; + if (above) { + for (int a = 0; a < n; a++) + LLambda_d[mech[a]] = L_t*Lambda_d[mech[a]]; + } vec Phi_d(n), Y_dcrit = ones(n), Dd(n), dd(n); mat denom_d(n, n); for (int a = 0; a < n; a++) { @@ -250,7 +261,7 @@ void umat_damage_LLD_0(const string &umat_name, const vec &Etot, const vec &DEto Phi_d(a) = Macaulay_p(Y.Y_ts - Y0[l])/Yc[l] - lagrange_pow_1(d(l), c_lambda, p0_lambda, n_lambda, alpha_lambda) - d(l); Dd(a) = Dd_all(l); for (int c = 0; c < n; c++) - denom_d(a, c) = dPhi_dd(Y, Lambda_d, l, mech[c]); + denom_d(a, c) = dPhi_dd(Y, LLambda_d, l, mech[c]); } Fischer_Burmeister_m(Phi_d, Y_dcrit, denom_d, Dd, dd, error); for (int a = 0; a < n; a++) { @@ -267,7 +278,7 @@ void umat_damage_LLD_0(const string &umat_name, const vec &Etot, const vec &DEto // ---- 3. Tangent of the two-stage update (plasticity, then damage) ---- // plastic flow direction held fixed (continuum), then the active damage set linearized exactly - mat A = eye(6, 6); + mat::fixed<6, 6> A = eye(6, 6); if (Dp(0) > simcoon::iota) { const double h_p = sum(Lambda_p % (L0*Lambda_p)) + dhardening(p_ts); A -= Lambda_p*(L0*Lambda_p).t()/h_p; @@ -282,15 +293,19 @@ void umat_damage_LLD_0(const string &umat_name, const vec &Etot, const vec &DEto act.push_back(l); const int n = act.size(); if (n > 0) { + std::array, 2> LLambda_d; + for (int a = 0; a < n; a++) + LLambda_d[act[a]] = L_t*Lambda_d[act[a]]; + const rowvec dYts_deps = (A.t()*(L_t*Y.dYts_dsigma)).t(); // the same for every row mat J(n, n), G(n, 6); for (int a = 0; a < n; a++) { for (int c = 0; c < n; c++) - J(a, c) = dPhi_dd(Y, Lambda_d, act[a], act[c]); - G.row(a) = (A.t()*(L_t*Y.dYts_dsigma)).t()/Yc[act[a]]; + J(a, c) = dPhi_dd(Y, LLambda_d, act[a], act[c]); + G.row(a) = dYts_deps/Yc[act[a]]; } const mat dd_deps = -solve(J, G); for (int a = 0; a < n; a++) - Lt -= (L_t*Lambda_d[act[a]])*dd_deps.row(a); + Lt -= LLambda_d[act[a]]*dd_deps.row(a); } } diff --git a/src/Continuum_mechanics/Umat/Mechanical/Plasticity/plastic_chaboche.cpp b/src/Continuum_mechanics/Umat/Mechanical/Plasticity/plastic_chaboche.cpp index 82c6e07b7..0f82ec150 100755 --- a/src/Continuum_mechanics/Umat/Mechanical/Plasticity/plastic_chaboche.cpp +++ b/src/Continuum_mechanics/Umat/Mechanical/Plasticity/plastic_chaboche.cpp @@ -180,12 +180,11 @@ void umat_plasticity_chaboche(const string &umat_name, const vec &Etot, const ve vec dPhida_1 = zeros(6); vec dPhida_2 = zeros(6); vec dPhidsigma = zeros(6); - double dPhidtheta = 0.; //Compute the explicit flow direction vec Lambdap = eta_stress(stress-X); - vec Lambdaa_1 = eta_stress(stress-X) - D_1*a_1; - vec Lambdaa_2 = eta_stress(stress-X) - D_2*a_2; + vec Lambdaa_1 = Lambdap - D_1*a_1; + vec Lambdaa_2 = Lambdap - D_2*a_2; std::vector kappa_j(1); kappa_j[0] = L*Lambdap; mat K = zeros(1,1); @@ -227,23 +226,26 @@ void umat_plasticity_chaboche(const string &umat_name, const vec &Etot, const ve } } else { + const vec Ir05_v = Ir05(); + const vec Eel_0 = Etot + DEtot - alpha*(T + DT - T_init); //Loop for (compteur = 0; ((compteur < simcoon::maxiter_umat) && (error > simcoon::precision_umat)); compteur++) { p = s_j(0); Hp = hardening.R(p); dHpdp = hardening.dR_dp(p); - dPhidsigma = eta_stress(stress-X); + const vec sigma_eff = stress-X; + dPhidsigma = eta_stress(sigma_eff); dPhidp = -1.*dHpdp; - dPhida_1 = -1.*(2./3.)*C_1*(eta_stress(stress-X)%Ir05()); - dPhida_2 = -1.*(2./3.)*C_2*(eta_stress(stress-X)%Ir05()); + dPhida_1 = -1.*(2./3.)*C_1*(dPhidsigma%Ir05_v); + dPhida_2 = -1.*(2./3.)*C_2*(dPhidsigma%Ir05_v); //compute Phi and the derivatives - Phi(0) = Mises_stress(stress-X) - Hp - sigmaY; + Phi(0) = Mises_stress(sigma_eff) - Hp - sigmaY; - Lambdap = eta_stress(stress-X); - Lambdaa_1 = eta_stress(stress-X) - D_1*a_1; - Lambdaa_2 = eta_stress(stress-X) - D_2*a_2; + Lambdap = dPhidsigma; + Lambdaa_1 = dPhidsigma - D_1*a_1; + Lambdaa_2 = dPhidsigma - D_2*a_2; kappa_j[0] = L*Lambdap; K(0,0) = dPhidp + sum(dPhida_1%Lambdaa_1) + sum(dPhida_2%Lambdaa_2); @@ -256,12 +258,12 @@ void umat_plasticity_chaboche(const string &umat_name, const vec &Etot, const ve EP = EP + ds_j(0)*Lambdap; a_1 = a_1 + ds_j(0)*Lambdaa_1; a_2 = a_2 + ds_j(0)*Lambdaa_2; - X_1 += ds_j(0)*(2./3.)*C_1*(Lambdaa_1%Ir05()); - X_2 += ds_j(0)*(2./3.)*C_2*(Lambdaa_2%Ir05()); + X_1 += ds_j(0)*(2./3.)*C_1*(Lambdaa_1%Ir05_v); + X_2 += ds_j(0)*(2./3.)*C_2*(Lambdaa_2%Ir05_v); X = X_1 + X_2; //the stress is now computed using the relationship stress = L(E-Ep) - Eel = Etot + DEtot - alpha*(T + DT - T_init) - EP; + Eel = Eel_0 - EP; stress = el_pred(L, Eel); } // the loop reads p at the top and corrects s_j at the bottom @@ -270,7 +272,6 @@ void umat_plasticity_chaboche(const string &umat_name, const vec &Etot, const ve } //Computation of the increments of variables - vec Dsigma = stress - stress_start; vec DEP = EP - EP_start; double Dp = Ds_j[0]; vec Da_1 = a_1 - a_1start; @@ -285,13 +286,11 @@ void umat_plasticity_chaboche(const string &umat_name, const vec &Etot, const ve tangent_mode, Bhat, kappa_j, dPhidsigma_l, Ds_j, L, [&]() -> std::vector { // lazy: evaluated only in algorithmic mode // J2 flow on sigma-X; the backstress coupling is only in the closest-point branch - const std::vector dLambda_dsigma_l = { deta_stress(stress - X) }; + std::vector dLambda_dsigma_l(1); + dLambda_dsigma_l[0] = deta_stress(stress - X); return dLambda_dsigma_l; }); Lt = ct.Lt; - - std::vector P_theta(1); - P_theta[0] = dPhidtheta - sum(dPhidsigma%(L*alpha)); double A_p = -Hp; vec A_a1 = -X_1; diff --git a/src/Continuum_mechanics/Umat/Mechanical/Plasticity/plastic_isotropic.cpp b/src/Continuum_mechanics/Umat/Mechanical/Plasticity/plastic_isotropic.cpp index f64765a91..4800360be 100755 --- a/src/Continuum_mechanics/Umat/Mechanical/Plasticity/plastic_isotropic.cpp +++ b/src/Continuum_mechanics/Umat/Mechanical/Plasticity/plastic_isotropic.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -133,7 +134,7 @@ void umat_plasticity_iso(const string &umat_name, const vec &Etot, const vec &DE //Compute the explicit flow direction vec Lambdap = eta_stress(stress); std::vector kappa_j(1); - kappa_j[0] = L*Lambdap; + mul6(L, Lambdap, kappa_j[0]); mat K = zeros(1,1); //Loop parameters @@ -178,7 +179,7 @@ void umat_plasticity_iso(const string &umat_name, const vec &Etot, const vec &DE Phi(0) = Mises_stress(stress) - Hp - sigmaY; Lambdap = eta_stress(stress); - kappa_j[0] = L*Lambdap; + mul6(L, Lambdap, kappa_j[0]); K(0,0) = dPhidp; B(0, 0) = -1.*sum(dPhidsigma%kappa_j[0]) + K(0,0); @@ -241,7 +242,9 @@ void umat_plasticity_iso(const string &umat_name, const vec &Etot, const vec &DE } std::vector P_theta(1); - P_theta[0] = dPhidtheta - sum(dPhidsigma%(L*alpha)); + vec L_alpha; + mul6(L, alpha, L_alpha); + P_theta[0] = dPhidtheta - sum(dPhidsigma%L_alpha); double A_p = -Hp; double Dgamma_loc = 0.5*sum((stress_start+stress)%DEP) + 0.5*(A_p_start + A_p)*Dp; diff --git a/src/Continuum_mechanics/Umat/Mechanical/Plasticity/plastic_johnson_cook_ccp.cpp b/src/Continuum_mechanics/Umat/Mechanical/Plasticity/plastic_johnson_cook_ccp.cpp index 4e1ea827b..5ad3de93f 100644 --- a/src/Continuum_mechanics/Umat/Mechanical/Plasticity/plastic_johnson_cook_ccp.cpp +++ b/src/Continuum_mechanics/Umat/Mechanical/Plasticity/plastic_johnson_cook_ccp.cpp @@ -131,7 +131,8 @@ void umat_plasticity_johnson_cook_CCP(const string &umat_name, const vec &Etot, vec ds_j = zeros(1); //Elastic prediction - Accounting for the thermal prediction - vec Eel = Etot + DEtot - alpha*(T+DT-T_init) - EP; + const vec Eel_0 = Etot + DEtot - alpha*(T+DT-T_init); + vec Eel = Eel_0 - EP; sigma = el_pred(L, Eel); //Define the plastic function and the stress @@ -159,7 +160,7 @@ void umat_plasticity_johnson_cook_CCP(const string &umat_name, const vec &Etot, Ds_j(0) = x0; s_j(0) += x0; EP = EP + x0*Lambda_trial; - Eel = Etot + DEtot - alpha*(T + DT - T_init) - EP; + Eel = Eel_0 - EP; sigma = el_pred(L, Eel); } } @@ -215,7 +216,7 @@ void umat_plasticity_johnson_cook_CCP(const string &umat_name, const vec &Etot, EP = EP + ds_j(0)*Lambdap; //the stress is now computed using the relationship sigma = L(E-Ep) - Eel = Etot + DEtot - alpha*(T + DT - T_init) - EP; + Eel = Eel_0 - EP; sigma = el_pred(L, Eel); } @@ -248,7 +249,8 @@ void umat_plasticity_johnson_cook_CCP(const string &umat_name, const vec &Etot, tangent_mode, Bhat, kappa_j, dPhidsigma_l, Ds_j, L, [&]() -> std::vector { // lazy: evaluated only in algorithmic mode // J2 associated flow: dLambda/dsigma = deta_stress, complete for one isotropic mechanism - const std::vector dLambda_dsigma_l = { deta_stress(sigma) }; + std::vector dLambda_dsigma_l(1); + dLambda_dsigma_l[0] = deta_stress(sigma); return dLambda_dsigma_l; }); Lt = ct.Lt; diff --git a/src/Continuum_mechanics/Umat/Mechanical/SMA/SMA_mono.cpp b/src/Continuum_mechanics/Umat/Mechanical/SMA/SMA_mono.cpp index d29208543..0deb0c74a 100644 --- a/src/Continuum_mechanics/Umat/Mechanical/SMA/SMA_mono.cpp +++ b/src/Continuum_mechanics/Umat/Mechanical/SMA/SMA_mono.cpp @@ -179,7 +179,6 @@ void umat_sma_mono(const string &umat_name, const vec &Etot, const vec &DEtot, v vec ET_start = ET; double xi = statev(nvariants+7); - double xi_start = xi; double dxi = 0.; //Rotation of internal variables (tensors) @@ -211,7 +210,7 @@ void umat_sma_mono(const string &umat_name, const vec &Etot, const vec &DEtot, v vec PhiR(nvariants); vec PhiR_start(nvariants); - std::vector transfo_actif(24); + std::vector transfo_actif(nvariants); ///Elastic prediction - Accounting for the thermal prediction vec Eel = Etot + DEtot - alpha*(T+DT-T_init) - ET; @@ -299,6 +298,8 @@ void umat_sma_mono(const string &umat_name, const vec &Etot, const vec &DEtot, v lambda1 = lagrange_pow_1(xi, c_lambda1, p0_lambda1, n_lambda1, alpha_lambda1); dlambda1dxin = dlagrange_pow_1(xi, c_lambda1, p0_lambda1, n_lambda1, alpha_lambda1); + Hnmxim = Hnm*xin; + for(int i=0; i 1.E-8) { - - xi_path = xi - xi_start; - - for(int i=0; i 0.) { @@ -420,13 +390,6 @@ void umat_sma_mono(const string &umat_name, const vec &Etot, const vec &DEtot, v throw simcoon::exception_inv("Error in inv function inside umat_sma_mono."); } - -/* ///Note : To use with Self-consistent micromechanics only! - vec eigval = eig_sym(Lt); - while(eigval(0) < 0.) { - Lt = 0.99*Lt + 0.01*Lt_eff; - eigval = eig_sym(Lt); - }*/ } else { diff --git a/src/Continuum_mechanics/Umat/Mechanical/SMA/unified_T.cpp b/src/Continuum_mechanics/Umat/Mechanical/SMA/unified_T.cpp index ca059cb95..ce38784ad 100755 --- a/src/Continuum_mechanics/Umat/Mechanical/SMA/unified_T.cpp +++ b/src/Continuum_mechanics/Umat/Mechanical/SMA/unified_T.cpp @@ -266,9 +266,9 @@ void umat_sma_unified_T(const string &umat_name, const vec &Etot, const vec &DEt vec lambdaTR = -1.*ETMean; - //Definition of the modified Y function - double YtF = Y0t + D*Hcur*Mises_stress(stress); - double YtR = Y0t + D*sum(stress%ETMean); + //Modified Y functions, Lagrange multipliers and criteria: set by the transformation loop before any use + double YtF = 0.; + double YtR = 0.; double HfF = 0.; double HfR = 0.; @@ -304,9 +304,9 @@ void umat_sma_unified_T(const string &umat_name, const vec &Etot, const vec &DEt } } - //Set the Lagrange multiplliers due to Physical limitations - double lambda0 = -1.*lagrange_pow_0(xi, c_lambda, p0_lambda, n_lambda, alpha_lambda); - double lambda1 = lagrange_pow_1(xi, c_lambda, p0_lambda, n_lambda, alpha_lambda); + //Lagrange multiplliers due to Physical limitations + double lambda0 = 0.; + double lambda1 = 0.; //Define the value of DM_sig vec DM_sig = (DM*stress_start); @@ -320,14 +320,8 @@ void umat_sma_unified_T(const string &umat_name, const vec &Etot, const vec &DEt double A_xiR_start = -1.*rhoDs0*(T) + rhoDE0 - 0.5*sum(stress_start%DM_sig) - sum(stress_start%Dalpha)*(T-T_init) + HfR; //Transformation criteria - double PhihatF; - if (aniso_criteria) { - PhihatF = Hcur*Drucker_ani_stress(stress, DFA_params, prager_b, prager_n); - } - else { - PhihatF = Hcur*Drucker_stress(stress, prager_b, prager_n); - } - double PhihatR = sum(stress%ETMean); + double PhihatF = 0.; + double PhihatR = 0.; //Variables required for the loop vec s_j = zeros(2); @@ -541,14 +535,22 @@ void umat_sma_unified_T(const string &umat_name, const vec &Etot, const vec &DEt L = inv_cubic(M_eff); DM_sig = DM*stress; - Dalpha_T = Dalpha*(T+DT-T_init); + // equivalent stresses and gradients at the current stress, shared below (Dalpha_T is loop-invariant) + const double sig_eq = Mises_stress(stress); + const vec eta_sig = eta_stress(stress); + double Dr_sig; + vec dDr_sig; if (aniso_criteria) { - lambdaTF = Hcur * dDrucker_ani_stress(stress, DFA_params, prager_b, prager_n); + Dr_sig = Drucker_ani_stress(stress, DFA_params, prager_b, prager_n); + dDr_sig = dDrucker_ani_stress(stress, DFA_params, prager_b, prager_n); } else { - lambdaTF = Hcur * dDrucker_stress(stress, prager_b, prager_n); + Dr_sig = Drucker_stress(stress, prager_b, prager_n); + dDr_sig = dDrucker_stress(stress, prager_b, prager_n); } + + lambdaTF = Hcur * dDr_sig; lambdaTR = -1. * ETMean; kappa_j[0] = L*(lambdaTF + DM_sig + Dalpha_T); @@ -586,23 +588,18 @@ void umat_sma_unified_T(const string &umat_name, const vec &Etot, const vec &DEt } // Find Hcur explicit - if (Mises_stress(stress) > sigmacrit) - sigmastar = Mises_stress(stress) - sigmacrit; + if (sig_eq > sigmacrit) + sigmastar = sig_eq - sigmacrit; else sigmastar = 0.; Hcur = Hmin + (Hmax - Hmin)*(1. - exp(-1.*k1*sigmastar)); //Forward transformation thermodynamic force - if (aniso_criteria) { - PhihatF = Hcur*Drucker_ani_stress(stress, DFA_params, prager_b, prager_n); - } - else { - PhihatF = Hcur*Drucker_stress(stress, prager_b, prager_n); - } + PhihatF = Hcur*Dr_sig; A_xiF = rhoDs0*(T + DT) - rhoDE0 + 0.5*sum(stress%DM_sig) + sum(stress%Dalpha)*(T + DT - T_init) - HfF; lambda1 = lagrange_pow_1(xi, c_lambda, p0_lambda, n_lambda, alpha_lambda); - const double DHM = D*Hcur*Mises_stress(stress); + const double DHM = D*Hcur*sig_eq; YtF = Y0t + DHM; Phi(0) = PhihatF + A_xiF - lambda1 - YtF; @@ -644,27 +641,22 @@ void umat_sma_unified_T(const string &umat_name, const vec &Etot, const vec &DEt } } - dHcurdsigma = k1*(Hmax - Hmin)*exp(-1.*k1*sigmastar)*eta_stress(stress); + dHcurdsigma = k1*(Hmax - Hmin)*exp(-1.*k1*sigmastar)*eta_sig; //Related to forward transformation - if (aniso_criteria) { - dPhihatFdsigma = dHcurdsigma * Drucker_ani_stress(stress, DFA_params, prager_b, prager_n) + Hcur * dDrucker_ani_stress(stress, DFA_params, prager_b, prager_n); - } - else { - dPhihatFdsigma = dHcurdsigma * Drucker_stress(stress, prager_b, prager_n) + Hcur * dDrucker_stress(stress, prager_b, prager_n); - } + dPhihatFdsigma = dHcurdsigma * Dr_sig + Hcur * dDr_sig; dPhihatFdxiF = 0.; dPhihatFdxiR = 0.; - dA_xiFdsigma = DM_sig + Dalpha*(T+DT-T_init); + dA_xiFdsigma = DM_sig + Dalpha_T; dA_xiFdxiF = -dHfF; dA_xiFdxiR = dHfF; dlambda1dsigma = zeros(6); dlambda1dxiF = dlagrange_pow_1(xi, c_lambda, p0_lambda, n_lambda, alpha_lambda); - dlambda1dxiR = -1.*dlagrange_pow_1(xi, c_lambda, p0_lambda, n_lambda, alpha_lambda); + dlambda1dxiR = -1.*dlambda1dxiF; - dYtFdsigma = D*(dHcurdsigma * Mises_stress(stress) + Hcur * eta_stress(stress)); + dYtFdsigma = D*(dHcurdsigma * sig_eq + Hcur * eta_sig); dYtFdxiF = 0.; dYtFdxiR = 0.; @@ -674,13 +666,13 @@ void umat_sma_unified_T(const string &umat_name, const vec &Etot, const vec &DEt //Relative to reverse transformation dPhihatRdsigma = ETMean; - dA_xiRdsigma = -1.*DM_sig -1.*Dalpha*(T+DT-T_init); + dA_xiRdsigma = -1.*DM_sig - Dalpha_T; dA_xiRdxiF = dHfR; dA_xiRdxiR = -dHfR; dlambda0dsigma = zeros(6); - dlambda0dxiF = -1.*dlagrange_pow_0(xi, c_lambda, p0_lambda, n_lambda, alpha_lambda); - dlambda0dxiR = dlagrange_pow_0(xi, c_lambda, p0_lambda, n_lambda, alpha_lambda); + dlambda0dxiR = dlagrange_pow_0(xi, c_lambda, p0_lambda, n_lambda, alpha_lambda); + dlambda0dxiF = -1.*dlambda0dxiR; dYtRdsigma = D*ETMean; dPhiRdsigma = -1.*dPhihatRdsigma + dA_xiRdsigma + dlambda0dsigma - dYtRdsigma; diff --git a/src/Continuum_mechanics/Umat/Mechanical/SMA/unified_TR.cpp b/src/Continuum_mechanics/Umat/Mechanical/SMA/unified_TR.cpp index e82a1a9c3..72853dcd9 100644 --- a/src/Continuum_mechanics/Umat/Mechanical/SMA/unified_TR.cpp +++ b/src/Continuum_mechanics/Umat/Mechanical/SMA/unified_TR.cpp @@ -264,19 +264,9 @@ void umat_sma_unified_TR(const string &umat_name, const vec &Etot, const vec &DE double lambda1Reo = lagrange_pow_1(a_eq/ETRmax, c_lambdaReo, p0_lambdaReo, n_lambdaReo, alpha_lambdaReo); vec sigma_eff = stress - (1. + lambda1Reo) * X; - vec lambdaReo; - vec etaReo; - if (Mises_stress(sigma_eff) > simcoon::iota) { - if (aniso_criteria) { - lambdaReo = dDrucker_ani_stress(sigma_eff, DFA_params, prager_b, prager_n); - } else { - lambdaReo = dDrucker_stress(sigma_eff, prager_b, prager_n); - } - etaReo = lambdaReo; - } else { - lambdaReo = zeros(6); - etaReo = zeros(6); - } + // reorientation flow and criterion gradient: set by the local loop before any use + vec lambdaReo = zeros(6); + vec etaReo = zeros(6); // Back-strain channel flows: each shadows its own leading-mechanism flow vec lambda_areo = lambdaReo; @@ -301,8 +291,9 @@ void umat_sma_unified_TR(const string &umat_name, const vec &Etot, const vec &DE double HfF = 0., HfR = 0.; compute_Hf(xi, HfF, HfR); - double lambda0 = -1.*lagrange_pow_0(xi, c_lambda, p0_lambda, n_lambda, alpha_lambda); - double lambda1 = lagrange_pow_1(xi, c_lambda, p0_lambda, n_lambda, alpha_lambda); + // Lagrange multipliers and criteria: set by the local loop before any use + double lambda0 = 0.; + double lambda1 = 0.; vec DM_sig = DM*stress_start; vec Dalpha_T = Dalpha*(T+DT-T_init); @@ -312,8 +303,8 @@ void umat_sma_unified_TR(const string &umat_name, const vec &Etot, const vec &DE double A_xiR = -1.*rhoDs0*(T+DT) + rhoDE0 - 0.5*sum(stress%DM_sig) - sum(stress%Dalpha)*(T+DT-T_init) + HfR; double A_xiR_start = -1.*rhoDs0*(T) + rhoDE0 - 0.5*sum(stress_start%DM_sig) - sum(stress_start%Dalpha)*(T-T_init) + HfR; - double PhihatF = build_PhihatF(stress); - double PhihatR = sum(stress%ETMean); + double PhihatF = 0.; + double PhihatR = 0.; // Variables required for the local loop — 3 leading mechanisms vec s_j = zeros(3); @@ -576,6 +567,7 @@ void umat_sma_unified_TR(const string &umat_name, const vec &Etot, const vec &DE } } else { + const vec Ir05_v = Ir05(); // engineering -> tensorial shear for (compteur = 0; ((compteur < simcoon::maxiter_umat) && (error > simcoon::precision_umat)); compteur++) { // Reuss mixing @@ -583,18 +575,30 @@ void umat_sma_unified_TR(const string &umat_name, const vec &Etot, const vec &DE L = inv_cubic(M_eff); DM_sig = DM*stress; - Dalpha_T = Dalpha*(T+DT-T_init); + + // equivalent stresses and gradients at the current stress, shared below (Dalpha_T is loop-invariant) + const double sig_eq = Mises_stress(stress); + const vec eta_sig = eta_stress(stress); + double Dr_sig; + vec dDr_sig; + if (aniso_criteria) { + Dr_sig = Drucker_ani_stress(stress, DFA_params, prager_b, prager_n); + dDr_sig = dDrucker_ani_stress(stress, DFA_params, prager_b, prager_n); + } else { + Dr_sig = Drucker_stress(stress, prager_b, prager_n); + dDr_sig = dDrucker_stress(stress, prager_b, prager_n); + } // Hcur explicit (depends on \sigma ) - if (Mises_stress(stress) > sigmacrit) sigmastar = Mises_stress(stress) - sigmacrit; - else sigmastar = 0.; + if (sig_eq > sigmacrit) sigmastar = sig_eq - sigmacrit; + else sigmastar = 0.; Hcur = Hmin + (Hmax - Hmin)*(1. - exp(-1.*k1*sigmastar)); - // Re-evaluate flows at current \sigma , areo, \lambda_1Reo - lambdaTF = build_lambdaTF(stress); + // Re-evaluate flows at current \sigma , areo, \lambda_1Reo (build_lambdaTF at stress) + lambdaTF = Hcur * dDr_sig; lambdaTR = -1. * ETMean; - X = HReo * (areo % Ir05()); + X = HReo * (areo % Ir05_v); a_eq = Mises_strain(areo); // intrinsic saturation argument ||v^re||/ETRmax, no xi: the penalty would blow up as xi -> 0 lambda1Reo = lagrange_pow_1(a_eq/ETRmax, c_lambdaReo, p0_lambdaReo, n_lambdaReo, alpha_lambdaReo); @@ -623,11 +627,11 @@ void umat_sma_unified_TR(const string &umat_name, const vec &Etot, const vec &DE // Hardening compute_Hf(xi, HfF, HfR); - // Forward thermodynamic force - PhihatF = build_PhihatF(stress); + // Forward thermodynamic force (build_PhihatF at stress) + PhihatF = Hcur * Dr_sig; A_xiF = rhoDs0*(T + DT) - rhoDE0 + 0.5*sum(stress%DM_sig) + sum(stress%Dalpha)*(T + DT - T_init) - HfF; lambda1 = lagrange_pow_1(xi, c_lambda, p0_lambda, n_lambda, alpha_lambda); - const double DHM = D*Hcur*Mises_stress(stress); + const double DHM = D*Hcur*sig_eq; double YtF = Y0t + DHM; Phi(0) = PhihatF + A_xiF - lambda1 - YtF; @@ -653,17 +657,13 @@ void umat_sma_unified_TR(const string &umat_name, const vec &Etot, const vec &DE else dHfR = 1.E12; // d(Hcur)/d\sigma - dHcurdsigma = k1*(Hmax - Hmin)*exp(-1.*k1*sigmastar)*eta_stress(stress); + dHcurdsigma = k1*(Hmax - Hmin)*exp(-1.*k1*sigmastar)*eta_sig; // ---- Forward derivatives ---- - if (aniso_criteria) { - dPhihatFdsigma = dHcurdsigma * Drucker_ani_stress(stress, DFA_params, prager_b, prager_n) + Hcur * dDrucker_ani_stress(stress, DFA_params, prager_b, prager_n); - } else { - dPhihatFdsigma = dHcurdsigma * Drucker_stress(stress, prager_b, prager_n) + Hcur * dDrucker_stress(stress, prager_b, prager_n); - } - dA_xiFdsigma = DM_sig + Dalpha*(T+DT-T_init); + dPhihatFdsigma = dHcurdsigma * Dr_sig + Hcur * dDr_sig; + dA_xiFdsigma = DM_sig + Dalpha_T; dlambda1dsigma = zeros(6); - dYtFdsigma = D*(dHcurdsigma * Mises_stress(stress) + Hcur * eta_stress(stress)); + dYtFdsigma = D*(dHcurdsigma * sig_eq + Hcur * eta_sig); dPhiFdsigma = dPhihatFdsigma + dA_xiFdsigma - dlambda1dsigma - dYtFdsigma; double dlambda1dxi = dlagrange_pow_1(xi, c_lambda, p0_lambda, n_lambda, alpha_lambda); @@ -674,7 +674,7 @@ void umat_sma_unified_TR(const string &umat_name, const vec &Etot, const vec &DE // ---- Reverse derivatives ---- const double xi_div = xi_safe(xi); dPhihatRdsigma = ETMean; - dA_xiRdsigma = -1.*DM_sig - 1.*Dalpha*(T+DT-T_init); + dA_xiRdsigma = -1.*DM_sig - Dalpha_T; double dlambda0dxi = dlagrange_pow_0(xi, c_lambda, p0_lambda, n_lambda, alpha_lambda); dlambda0dsigma = zeros(6); dlambda0dxiF = -1.*dlambda0dxi; @@ -694,12 +694,12 @@ void umat_sma_unified_TR(const string &umat_name, const vec &Etot, const vec &DE dlambda1Reo_arg = dlagrange_pow_1(a_eq/ETRmax, c_lambdaReo, p0_lambdaReo, n_lambdaReo, alpha_lambdaReo); const double eta_dot_X = sum(etaReo % X); // \eta_Reo : X (tensor double contraction) // eta_strain has engineering shears; the gradient of a_eq carries half shears, hence % Ir05 - const vec eta_strain_areo = (a_eq > simcoon::iota) ? vec(eta_strain(areo) % Ir05()) : vec(zeros(6)); + const vec eta_strain_areo = (a_eq > simcoon::iota) ? vec(eta_strain(areo) % Ir05_v) : vec(zeros(6)); const vec dlambda1Reo_dareo = dlambda1Reo_arg * (1./ETRmax) * eta_strain_areo; dPhiReodsigma = etaReo; - dPhiReodareo = -(1. + lambda1Reo) * HReo * (etaReo % Ir05()) + dPhiReodareo = -(1. + lambda1Reo) * HReo * (etaReo % Ir05_v) - eta_dot_X * dlambda1Reo_dareo; dPhiReodpTR = 0.; diff --git a/src/Continuum_mechanics/Umat/Mechanical/Viscoelasticity/Zener_fast.cpp b/src/Continuum_mechanics/Umat/Mechanical/Viscoelasticity/Zener_fast.cpp index 3124a1d1a..dc741eb4b 100755 --- a/src/Continuum_mechanics/Umat/Mechanical/Viscoelasticity/Zener_fast.cpp +++ b/src/Continuum_mechanics/Umat/Mechanical/Viscoelasticity/Zener_fast.cpp @@ -79,9 +79,11 @@ void umat_zener_fast(const string &umat_name, const vec &Etot, const vec &DEtot, //Define the viscoelastic stiffness mat L0 = L_iso(E0, nu0, "Enu"); L = L0; - mat L1 = L_iso(E1, nu1, "Enu"); - - mat H1 = H_iso(etaB1, etaS1); //dimension of stiffness tensor + // the branch operators are built in the lists kelvin_series_step reads (no copies) + std::vector L_branch(1), H_branch(1); + L_branch[0] = L_iso(E1, nu1, "Enu"); + H_branch[0] = H_iso(etaB1, etaS1); //dimension of stiffness tensor + const mat &L1 = L_branch[0]; if(start) { //Initialization T_init = T; @@ -101,7 +103,7 @@ void umat_zener_fast(const string &umat_name, const vec &Etot, const vec &DEtot, // backward Euler in closed form, with its consistent tangent (linear_viscoelastic.hpp) const vec eps_e = Etot + DEtot - alpha*(T + DT - T_init); - const LinearViscoStep st = kelvin_series_step(L0, {L1}, {H1}, {EV1_start}, eps_e, alpha, DTime); + const LinearViscoStep st = kelvin_series_step(L0, L_branch, H_branch, {EV1_start}, eps_e, alpha, DTime); EV1 = st.EV_i[0]; const vec DEV1 = EV1 - EV1_start; v += norm_strain(DEV1); diff --git a/src/Continuum_mechanics/Umat/Mechanical/Viscoelasticity/linear_viscoelastic.cpp b/src/Continuum_mechanics/Umat/Mechanical/Viscoelasticity/linear_viscoelastic.cpp index 1230a5127..bed0f8630 100644 --- a/src/Continuum_mechanics/Umat/Mechanical/Viscoelasticity/linear_viscoelastic.cpp +++ b/src/Continuum_mechanics/Umat/Mechanical/Viscoelasticity/linear_viscoelastic.cpp @@ -18,6 +18,7 @@ ///@file linear_viscoelastic.cpp ///@brief Closed-form backward-Euler step of the linear viscoelastic rheologies (see the header) +#include #include #include #include @@ -29,10 +30,34 @@ namespace simcoon { namespace { -// B_i^-1 EV = (H + dt L)^-1 H EV and C_i = dt (H + dt L)^-1; dt = 0 gives EV and 0 (inactive branch) +// per-thread memo of the step inverses keyed on the matrix bits: inv() is deterministic, a hit is exact +struct inverse_memo { + static constexpr int size = 8; + double key[size][36]; + double value[size][36]; + int used; + int next; +}; +thread_local inverse_memo memo; + +mat::fixed<6,6> memo_inv(const mat::fixed<6,6> &M) { + for (int k = 0; k < memo.used; k++) { + if (std::memcmp(memo.key[k], M.memptr(), sizeof(memo.key[k])) == 0) + return mat::fixed<6,6>(memo.value[k]); + } + const mat::fixed<6,6> M_inv = inv(M); // throws on a singular matrix: nothing is stored + std::memcpy(memo.key[memo.next], M.memptr(), sizeof(memo.key[memo.next])); + std::memcpy(memo.value[memo.next], M_inv.memptr(), sizeof(memo.value[memo.next])); + memo.next = (memo.next + 1) % inverse_memo::size; + if (memo.used < inverse_memo::size) memo.used++; + return M_inv; +} + +// B_i^-1 EV = (H + dt L)^-1 H EV and C_i = dt (H + dt L)^-1; dt = 0 gives EV and 0 (inactive branch). void branch_operators(const mat &L, const mat &H, const vec &EV_start, const double &DTime, vec &EV_relaxed, mat &C) { - const mat HdtL_inv = inv(H + DTime*L); + const mat::fixed<6,6> HdtL = H + DTime*L; + const mat::fixed<6,6> HdtL_inv = memo_inv(HdtL); EV_relaxed = HdtL_inv*(H*EV_start); C = DTime*HdtL_inv; } @@ -43,21 +68,22 @@ LinearViscoStep kelvin_series_step(const mat &L0, const vector &L_i, const const vector &EV_i_start, const vec &eps_e, const vec &alpha, const double &DTime) { const size_t N = L_i.size(); - vector C(N); + vector> C(N); vector EV_relaxed(N); // B_i^-1 EV_i,n - mat sumC = zeros(6,6); + mat::fixed<6,6> sumC = zeros(6,6); vec rhs = eps_e; for (size_t i=0; i S = eye(6,6) + L0*sumC; + const mat::fixed<6,6> S_inv = memo_inv(S); LinearViscoStep st; st.sigma = S_inv*(L0*rhs); - st.dSdE = S_inv*L0; - st.dSdE = 0.5*(st.dSdE + st.dSdE.t()); // (L0^-1 + sum C)^-1: symmetric up to round-off + const mat::fixed<6,6> dSdE = S_inv*L0; + st.dSdE = 0.5*(dSdE + dSdE.t()); // (L0^-1 + sum C)^-1: symmetric up to round-off st.dSdT = -st.dSdE*alpha; st.EV_i.resize(N); st.dEVdE_i.resize(N); @@ -82,16 +108,17 @@ LinearViscoStep maxwell_parallel_step(const mat &L0, const vector &L_i, con st.dEVdT_i.resize(N); for (size_t i=0; i C; branch_operators(L_i[i], H_i[i], EV_i_start[i], DTime, EV_relaxed, C); - const mat G = C*L_i[i]; + const mat::fixed<6,6> G = C*L_i[i]; st.EV_i[i] = EV_relaxed + G*eps_e; st.dEVdE_i[i] = G; st.dEVdT_i[i] = -G*alpha; st.sigma -= L_i[i]*st.EV_i[i]; st.dSdE -= L_i[i]*G; } - st.dSdE = 0.5*(st.dSdE + st.dSdE.t()); // L0 - sum L C L: symmetric up to round-off + const mat::fixed<6,6> dSdE = st.dSdE; + st.dSdE = 0.5*(dSdE + dSdE.t()); // L0 - sum L C L: symmetric up to round-off st.dSdT = -st.dSdE*alpha; return st; } diff --git a/src/Continuum_mechanics/Umat/Modular/damage_mechanism.cpp b/src/Continuum_mechanics/Umat/Modular/damage_mechanism.cpp index 89c3bbde6..051f44c53 100644 --- a/src/Continuum_mechanics/Umat/Modular/damage_mechanism.cpp +++ b/src/Continuum_mechanics/Umat/Modular/damage_mechanism.cpp @@ -25,6 +25,7 @@ along with simcoon. If not, see . #include #include #include +#include namespace simcoon { @@ -89,6 +90,8 @@ void DamageMechanism::configure(const arma::vec& props, int& offset) { void DamageMechanism::register_variables() { ivc_.add_scalar("D", 0.0); ivc_.add_scalar("Y_max", 0.0); + D_index_ = ivc_.index_of("D"); + Y_max_index_ = ivc_.index_of("Y_max"); } // ========== Constitutive Computations ========== @@ -98,6 +101,18 @@ double DamageMechanism::compute_driving_force(const arma::vec& sigma_eff, const return 0.5 * arma::dot(sigma_eff, (M_cached_t_ * stress(sigma_eff)).to_arma_voigt()); } +double DamageMechanism::driving_force(const arma::vec& sigma_eff) const { + if (sigma_eff.n_elem != 6) { + return compute_driving_force(sigma_eff, M_cached_); + } + if (!Y_sigma_valid_ || std::memcmp(Y_sigma_.memptr(), sigma_eff.memptr(), 6 * sizeof(double)) != 0) { + Y_sigma_value_ = compute_driving_force(sigma_eff, M_cached_); + std::memcpy(Y_sigma_.memptr(), sigma_eff.memptr(), 6 * sizeof(double)); + Y_sigma_valid_ = true; + } + return Y_sigma_value_; +} + double DamageMechanism::compute_damage(double Y_eff) const { // No damage below threshold if (Y_eff <= Y_0_) { @@ -129,7 +144,7 @@ double DamageMechanism::compute_damage(double Y_eff) const { } double DamageMechanism::get_damage() const { - return ivc_.get("D").scalar(); + return ivc_.at(D_index_).scalar(); } void DamageMechanism::compute_constraints( @@ -144,8 +159,8 @@ void DamageMechanism::compute_constraints( Y_crit.set_size(1); // Get current damage and history - D_current_ = ivc_.get("D").scalar(); - double Y_max = ivc_.get("Y_max").scalar(); + D_current_ = ivc_.at(D_index_).scalar(); + double Y_max = ivc_.at(Y_max_index_).scalar(); // compliance cache keyed on L: a hyperelastic block moves L at every refresh, a linear one inverts once if (!M_cached_valid_ || !arma::approx_equal(L, L_cached_, "absdiff", 0.0)) { @@ -153,10 +168,11 @@ void DamageMechanism::compute_constraints( M_cached_ = arma::inv(L); M_cached_t_ = tensor4(M_cached_, Tensor4Type::compliance); M_cached_valid_ = true; + Y_sigma_valid_ = false; } // Compute current driving force - Y_current_ = compute_driving_force(sigma, M_cached_); + Y_current_ = driving_force(sigma); const double Y_eff = std::max(Y_current_, Y_max); @@ -169,7 +185,7 @@ void DamageMechanism::compute_constraints( // dD/dY for the tangent, only while damage grows in this increment (Y beyond the history // maximum at the start of the step): under unloading the response is (1-D) L, no softening - const bool loading = Y_current_ > ivc_.get("Y_max").scalar_start(); + const bool loading = Y_current_ > ivc_.at(Y_max_index_).scalar_start(); if (loading && Y_eff > Y_0_ && D_current_ < D_c_) { switch (damage_type_) { case DamageType::LINEAR: @@ -220,23 +236,23 @@ double DamageMechanism::consistency_residual(const arma::vec& sigma_eff) const { if (!M_cached_valid_) { return 0.; } - const double Y = compute_driving_force(sigma_eff, M_cached_); - const double Y_used = ivc_.get("Y_max").scalar(); - const double target = std::max(ivc_.get("Y_max").scalar_start(), Y); + const double Y = driving_force(sigma_eff); + const double Y_used = ivc_.at(Y_max_index_).scalar(); + const double target = std::max(ivc_.at(Y_max_index_).scalar_start(), Y); return std::abs(target - Y_used) / std::max({std::abs(target), Y_0_, 1e-12}); } arma::mat DamageMechanism::stress_map(const arma::vec& sigma_eff) const { // sigma = (1 - D) sigma_eff with dD = D'(Y) (S sigma_eff) . d sigma_eff while damage grows arma::mat Q = stiffness_reduction() * arma::eye(6, 6); - if (dD_dY_ > simcoon::iota && ivc_.get("D").scalar() < D_c_ && M_cached_valid_) { + if (dD_dY_ > simcoon::iota && ivc_.at(D_index_).scalar() < D_c_ && M_cached_valid_) { Q -= dD_dY_ * (sigma_eff * (M_cached_ * sigma_eff).t()); } return Q; } double DamageMechanism::stiffness_reduction() const { - return 1.0 - ivc_.get("D").scalar(); + return 1.0 - ivc_.at(D_index_).scalar(); } arma::vec DamageMechanism::inelastic_strain() const { @@ -250,9 +266,9 @@ void DamageMechanism::update( int /*offset*/ ) { // history from the step START, never the running max over iterates (an overshooting iterate freezes damage) - double& Y_max = ivc_.get("Y_max").scalar(); - Y_max = std::max(ivc_.get("Y_max").scalar_start(), Y_current_); - double& D = ivc_.get("D").scalar(); + double& Y_max = ivc_.at(Y_max_index_).scalar(); + Y_max = std::max(ivc_.at(Y_max_index_).scalar_start(), Y_current_); + double& D = ivc_.at(D_index_).scalar(); D = compute_damage(Y_max); } @@ -277,7 +293,7 @@ void DamageMechanism::compute_work( double& Wm_d ) const { // Get damage increment - double dD = ivc_.get("D").delta_scalar(); + double dD = ivc_.at(D_index_).delta_scalar(); Wm_r = 0.0; Wm_ir = 0.0; diff --git a/src/Continuum_mechanics/Umat/Modular/elasticity_module.cpp b/src/Continuum_mechanics/Umat/Modular/elasticity_module.cpp index 991572003..d07628a29 100644 --- a/src/Continuum_mechanics/Umat/Modular/elasticity_module.cpp +++ b/src/Continuum_mechanics/Umat/Modular/elasticity_module.cpp @@ -20,6 +20,7 @@ along with simcoon. If not, see . * @brief Implementation of ElasticityModule class */ +#include #include #include #include @@ -290,7 +291,11 @@ void ElasticityModule::evaluate(const arma::vec& eps_el, int ndi, int nshr, throw std::invalid_argument("ElasticityModule: not configured"); } if (type_ != ElasticityType::HYPER_INVARIANTS) { - sigma = L_ * eps_el; + if (L_.n_rows == 6 && L_.n_cols == 6 && eps_el.n_elem == 6 ) { + mul6(L_, eps_el, sigma); + } else { + sigma = L_ * eps_el; + } Lt = L_; return; } diff --git a/src/Continuum_mechanics/Umat/Modular/hardening.cpp b/src/Continuum_mechanics/Umat/Modular/hardening.cpp index 650aeb219..237274ebe 100644 --- a/src/Continuum_mechanics/Umat/Modular/hardening.cpp +++ b/src/Continuum_mechanics/Umat/Modular/hardening.cpp @@ -23,16 +23,91 @@ along with simcoon. If not, see . #include #include #include +#include #include #include +#include namespace simcoon { namespace { +// typed-path operations in order, one rounding per statement: bit-identical unless FMA crosses statements + +// back-strain registered under key at index; the name is checked in debug builds only +template +auto& back_strain(Collection& ivc, size_t index, const std::string& key) { + assert(index < ivc.size() && ivc.at(index).name() == key); + (void)key; + return ivc.at(index); +} + +// tensor2::from_voigt(v, Tensor2Type::strain).mat() +inline arma::mat::fixed<3,3> strain_mat(const arma::vec& v) { + if (v.n_elem != 6) { + throw std::invalid_argument("tensor2: expected 6-element vector, got " + std::to_string(v.n_elem)); + } + arma::mat::fixed<3,3> m; + m(0,0) = v(0); m(1,1) = v(1); m(2,2) = v(2); + m(0,1) = 0.5 * v(3); m(1,0) = 0.5 * v(3); + m(0,2) = 0.5 * v(4); m(2,0) = 0.5 * v(4); + m(1,2) = 0.5 * v(5); m(2,1) = 0.5 * v(5); + return m; +} + +// tensor2(m, Tensor2Type::strain).voigt() +inline void store_strain(const arma::mat::fixed<3,3>& m, arma::vec& v) { + v.set_size(6); + v(0) = m(0,0); v(1) = m(1,1); v(2) = m(2,2); + v(3) = m(0,1) + m(1,0); + v(4) = m(0,2) + m(2,0); + v(5) = m(1,2) + m(2,1); +} + // X = (2/3) C alpha, re-tagged STRESS through the 3x3: alpha is strain-typed (factor-2 shear), // so a strain-typed X would double-count the shear terms in sigma - X and X:n -inline tensor2 backstress_t(double C, const tensor2& alpha) { - return stress(arma::mat::fixed<3,3>(((2.0 / 3.0) * C * alpha).mat())); +inline tensor2 backstress_of(double C, const arma::vec& a) { + const double k = (2.0 / 3.0) * C; + arma::mat::fixed<3,3> X = strain_mat(a); + for (arma::uword i = 0; i < 9; ++i) { + X(i) = X(i) * k; + } + return stress(X); +} + +// alpha <- alpha_from + dp n +inline void add_flow(const arma::vec& alpha_from, double dp, const tensor2& n, arma::vec& alpha) { + arma::mat::fixed<3,3> a = strain_mat(alpha_from); + const arma::mat::fixed<3,3>& nm = n.mat(); + for (arma::uword i = 0; i < 9; ++i) { + const double t = nm(i) * dp; + a(i) = a(i) + t; + } + store_strain(a, alpha); +} + +// alpha <- alpha + dp (n - D alpha) +inline void add_flow_recovery(InternalVariable& a_var, double dp, double D, const tensor2& n) { + arma::mat::fixed<3,3> a = strain_mat(a_var.raw_voigt()); + const arma::mat::fixed<3,3>& nm = n.mat(); + for (arma::uword i = 0; i < 9; ++i) { + const double t1 = a(i) * D; + const double t2 = nm(i) - t1; + const double t3 = t2 * dp; + a(i) = a(i) + t3; + } + store_strain(a, a_var.raw_voigt()); +} + +// alpha <- s (alpha_n + dp n), the Armstrong-Frederick backward-Euler closed form +inline void refresh_from_start(InternalVariable& a_var, double dp, double s, const tensor2& n) { + arma::mat::fixed<3,3> a = strain_mat(a_var.raw_voigt_start()); + const arma::mat::fixed<3,3>& nm = n.mat(); + for (arma::uword i = 0; i < 9; ++i) { + const double t = nm(i) * dp; + const double u = a(i) + t; + a(i) = u * s; + } + store_strain(a, a_var.raw_voigt()); } } // namespace @@ -168,12 +243,13 @@ void PragerHardening::configure(const arma::vec& props, int& offset) { void PragerHardening::register_variables(InternalVariableCollection& ivc) { ivc.add_vec(a_key_, arma::zeros(6), true); // back-strain (strain-like) + a_index_ = ivc.index_of(a_key_); } void PragerHardening::compute_backstresses(const InternalVariableCollection& ivc, std::vector& X_i) const { X_i.resize(1, tensor2(Tensor2Type::stress)); - X_i[0] = backstress_t(C_, ivc.get(a_key_).as_tensor2()); + X_i[0] = backstress_of(C_, back_strain(ivc, a_index_, a_key_).raw_voigt()); } double PragerHardening::hardening_modulus(const tensor2& n, @@ -183,15 +259,15 @@ double PragerHardening::hardening_modulus(const tensor2& n, } void PragerHardening::update(double dp, const tensor2& n, InternalVariableCollection& ivc) { - auto& a_var = ivc.get(a_key_); - a_var.set_tensor2(a_var.as_tensor2() + dp * n); + auto& a_var = back_strain(ivc, a_index_, a_key_); + add_flow(a_var.raw_voigt(), dp, n, a_var.raw_voigt()); // alpha + dp n } void PragerHardening::refresh_state(double dp, const tensor2& n, InternalVariableCollection& ivc) const { // Linear: alpha = alpha_n + dp n (exact backward Euler). - auto& a_var = ivc.get(a_key_); - a_var.set_tensor2(a_var.as_tensor2_start() + dp * n); + auto& a_var = back_strain(ivc, a_index_, a_key_); + add_flow(a_var.raw_voigt_start(), dp, n, a_var.raw_voigt()); } void PragerHardening::backward_euler_factors(double dp, const arma::vec& n, @@ -210,34 +286,32 @@ void ArmstrongFrederickHardening::configure(const arma::vec& props, int& offset) void ArmstrongFrederickHardening::register_variables(InternalVariableCollection& ivc) { ivc.add_vec(a_key_, arma::zeros(6), true); // back-strain (strain-like) + a_index_ = ivc.index_of(a_key_); } void ArmstrongFrederickHardening::compute_backstresses( const InternalVariableCollection& ivc, std::vector& X_i) const { X_i.resize(1, tensor2(Tensor2Type::stress)); - X_i[0] = backstress_t(C_, ivc.get(a_key_).as_tensor2()); + X_i[0] = backstress_of(C_, back_strain(ivc, a_index_, a_key_).raw_voigt()); } double ArmstrongFrederickHardening::hardening_modulus( const tensor2& n, const std::vector& X_i) const { // H_kin = (2/3) C - D (X:n); X:n = stress-Voigt . engineering-strain-Voigt const double nn = arma::accu(n.mat() % n.mat()); - return (2.0 / 3.0) * C_ * nn - D_ * arma::dot(X_i[0].to_arma_voigt(), n.to_arma_voigt()); + return (2.0 / 3.0) * C_ * nn - D_ * arma::dot(X_i[0].voigt(), n.voigt()); } void ArmstrongFrederickHardening::update(double dp, const tensor2& n, InternalVariableCollection& ivc) { // dα = (n − D α) dp (AF in α-form; equivalent to dX/dp = (2/3)C n − D X) - auto& a_var = ivc.get(a_key_); - const tensor2 a_t = a_var.as_tensor2(); - a_var.set_tensor2(a_t + dp * (n - D_ * a_t)); + add_flow_recovery(back_strain(ivc, a_index_, a_key_), dp, D_, n); } void ArmstrongFrederickHardening::refresh_state(double dp, const tensor2& n, InternalVariableCollection& ivc) const { // Backward Euler closed form: alpha = (alpha_n + dp n) / (1 + D dp). - auto& a_var = ivc.get(a_key_); - a_var.set_tensor2((1.0 / (1.0 + D_ * dp)) * (a_var.as_tensor2_start() + dp * n)); + refresh_from_start(back_strain(ivc, a_index_, a_key_), dp, (1.0 / (1.0 + D_ * dp)), n); } void ArmstrongFrederickHardening::backward_euler_factors(double dp, const arma::vec& n, @@ -245,7 +319,7 @@ void ArmstrongFrederickHardening::backward_euler_factors(double dp, const arma:: double& gamma, arma::vec& beta) const { const double f = (2.0 / 3.0) * C_ / (1.0 + D_ * dp); gamma = dp * f; - beta = f * (n - D_ * ivc.get(a_key_).raw_voigt()); + beta = f * (n - D_ * back_strain(ivc, a_index_, a_key_).raw_voigt()); } // ChabocheHardening @@ -259,18 +333,20 @@ void ChabocheHardening::configure(const arma::vec& props, int& offset) { void ChabocheHardening::register_variables(InternalVariableCollection& ivc) { a_keys_.resize(static_cast(N_)); + a_index_.resize(static_cast(N_)); for (int i = 0; i < N_; ++i) { a_keys_[i] = "a_" + std::to_string(i); ivc.add_vec(a_keys_[i], arma::zeros(6), true); + a_index_[i] = ivc.index_of(a_keys_[i]); } } void ChabocheHardening::compute_backstresses(const InternalVariableCollection& ivc, std::vector& X_i) const { - // X_i stress-typed (backstress_t): a strain-typed carrier would put the factor-2 shear back + // X_i stress-typed (backstress_of): a strain-typed carrier would put the factor-2 shear back X_i.resize(static_cast(N_), tensor2(Tensor2Type::stress)); for (int i = 0; i < N_; ++i) { - X_i[static_cast(i)] = backstress_t(C_(i), ivc.get(a_keys_[i]).as_tensor2()); + X_i[static_cast(i)] = backstress_of(C_(i), back_strain(ivc, a_index_[i], a_keys_[i]).raw_voigt()); } } @@ -278,12 +354,12 @@ double ChabocheHardening::hardening_modulus(const tensor2& n, const std::vector& X_i) const { // H_kin = Σ_i [ (2/3) C_i − D_i (X_i : n) ] — exact slope per // branch (see ArmstrongFrederick). - const arma::vec n_v = n.to_arma_voigt(); + const arma::vec::fixed<6> n_v = n.voigt(); const double nn = arma::accu(n.mat() % n.mat()); double H_kin = 0.0; for (int i = 0; i < N_; ++i) { H_kin += (2.0 / 3.0) * C_(i) * nn - - D_(i) * arma::dot(X_i[static_cast(i)].to_arma_voigt(), n_v); + - D_(i) * arma::dot(X_i[static_cast(i)].voigt(), n_v); } return H_kin; } @@ -292,9 +368,7 @@ void ChabocheHardening::update(double dp, const tensor2& n, InternalVariableCollection& ivc) { // dα_i = (n − D_i α_i) dp for (int i = 0; i < N_; ++i) { - auto& a_var = ivc.get(a_keys_[i]); - const tensor2 a_t = a_var.as_tensor2(); - a_var.set_tensor2(a_t + dp * (n - D_(i) * a_t)); + add_flow_recovery(back_strain(ivc, a_index_[i], a_keys_[i]), dp, D_(i), n); } } @@ -302,9 +376,7 @@ void ChabocheHardening::refresh_state(double dp, const tensor2& n, InternalVariableCollection& ivc) const { // Per-branch backward Euler closed form (see ArmstrongFrederick). for (int i = 0; i < N_; ++i) { - auto& a_var = ivc.get(a_keys_[i]); - a_var.set_tensor2((1.0 / (1.0 + D_(i) * dp)) - * (a_var.as_tensor2_start() + dp * n)); + refresh_from_start(back_strain(ivc, a_index_[i], a_keys_[i]), dp, (1.0 / (1.0 + D_(i) * dp)), n); } } @@ -316,7 +388,7 @@ void ChabocheHardening::backward_euler_factors(double dp, const arma::vec& n, for (int i = 0; i < N_; ++i) { const double f = (2.0 / 3.0) * C_(i) / (1.0 + D_(i) * dp); gamma += dp * f; - beta += f * (n - D_(i) * ivc.get(a_keys_[i]).raw_voigt()); + beta += f * (n - D_(i) * back_strain(ivc, a_index_[i], a_keys_[i]).raw_voigt()); } } diff --git a/src/Continuum_mechanics/Umat/Modular/internal_variable.cpp b/src/Continuum_mechanics/Umat/Modular/internal_variable.cpp index ec7dab910..a7290e425 100644 --- a/src/Continuum_mechanics/Umat/Modular/internal_variable.cpp +++ b/src/Continuum_mechanics/Umat/Modular/internal_variable.cpp @@ -23,6 +23,7 @@ along with simcoon. If not, see . #include #include #include +#include namespace simcoon { @@ -312,7 +313,11 @@ void InternalVariable::pack(arma::vec& statev) const { statev(offset) = scalar_value_; break; case IVarType::VECTOR_6: - statev.subvec(offset, offset + 5) = vec_value_; + if (vec_value_.n_elem == 6) { + std::copy(vec_value_.begin(), vec_value_.end(), statev.begin() + offset); + } else { + statev.subvec(offset, offset + 5) = vec_value_; // reports the size mismatch + } break; case IVarType::MATRIX_6x6: for (unsigned int i = 0; i < 6; ++i) { @@ -337,8 +342,10 @@ void InternalVariable::unpack(const arma::vec& statev) { scalar_start_ = scalar_value_; break; case IVarType::VECTOR_6: - vec_value_ = statev.subvec(offset, offset + 5); - vec_start_ = vec_value_; + vec_value_.set_size(6); + vec_start_.set_size(6); + std::copy(statev.begin() + offset, statev.begin() + offset + 6, vec_value_.begin()); + std::copy(statev.begin() + offset, statev.begin() + offset + 6, vec_start_.begin()); break; case IVarType::MATRIX_6x6: for (unsigned int i = 0; i < 6; ++i) { diff --git a/src/Continuum_mechanics/Umat/Modular/legacy_adapters.cpp b/src/Continuum_mechanics/Umat/Modular/legacy_adapters.cpp index 02a14c976..566029a03 100644 --- a/src/Continuum_mechanics/Umat/Modular/legacy_adapters.cpp +++ b/src/Continuum_mechanics/Umat/Modular/legacy_adapters.cpp @@ -22,8 +22,10 @@ along with simcoon. If not, see . #include #include +#include #include #include +#include using namespace arma; @@ -200,12 +202,29 @@ void umat_legacy_modular( const int& ndi, const int& nshr, const bool& start, double& tnew_dt, const int& tangent_mode, const int& ncp) { - const auto it = registry().find(umat_name); - if (it == registry().end()) { - throw std::invalid_argument("umat_legacy_modular: no adapter registered for '" - + umat_name + "'"); + // per-thread memo of the last (name, props) translation; the key is written last so a throw leaves no stale pair + struct translation { + std::string name; + std::vector props; + vec props_mod; + }; + thread_local translation last; + const bool hit = !last.name.empty() && umat_name == last.name && props.n_elem == last.props.size() + && (last.props.empty() + || std::memcmp(last.props.data(), props.memptr(), last.props.size() * sizeof(double)) == 0); + if (!hit) { + const auto it = registry().find(umat_name); + if (it == registry().end()) { + throw std::invalid_argument("umat_legacy_modular: no adapter registered for '" + + umat_name + "'"); + } + vec props_mod = it->second(props); + last.name.clear(); + last.props.assign(props.begin(), props.end()); + last.props_mod = std::move(props_mod); + last.name = umat_name; } - const vec props_mod = it->second(props); + const vec& props_mod = last.props_mod; try { umat_modular(umat_name, Etot, DEtot, sigma, Lt, L, DR, static_cast(props_mod.n_elem), props_mod, diff --git a/src/Continuum_mechanics/Umat/Modular/modular_umat.cpp b/src/Continuum_mechanics/Umat/Modular/modular_umat.cpp index 6e82b574e..a4435c8c1 100644 --- a/src/Continuum_mechanics/Umat/Modular/modular_umat.cpp +++ b/src/Continuum_mechanics/Umat/Modular/modular_umat.cpp @@ -35,10 +35,30 @@ along with simcoon. If not, see . #include #include #include +#include #include namespace simcoon { +namespace { + +/// approx_equal(DR, eye(3, 3), "absdiff", 0.0) without building the identity (NaN or wrong size: false) +bool is_identity_rotation(const arma::mat& DR) { + if (DR.n_rows != 3 || DR.n_cols != 3) { + return false; + } + for (arma::uword j = 0; j < 3; ++j) { + for (arma::uword i = 0; i < 3; ++i) { + if (DR(i, j) != ((i == j) ? 1.0 : 0.0)) { + return false; + } + } + } + return true; +} + +} // namespace + // ========== Constructor ========== ModularUMAT::ModularUMAT() @@ -312,7 +332,7 @@ void ModularUMAT::run( } // Apply rotation for objectivity (DR = I under small strain: nothing to do) - const bool rotate = !arma::approx_equal(DR, arma::eye(3, 3), "absdiff", 0.0); + const bool rotate = !is_identity_rotation(DR); if (rotate) { for (auto& mech : mechanisms_) { mech->rotate(DR); @@ -371,11 +391,14 @@ void ModularUMAT::run( // Wm_r closes the balance Wm = Wm_r + Wm_ir + Wm_d double Wm_ir_inc = 0.0, Wm_d_inc = 0.0; - for (const auto& mech : mechanisms_) { - double Wm_r_m = 0.0, Wm_ir_m = 0.0, Wm_d_m = 0.0; - mech->compute_work(sigma_start_, sigma, Etot, Etot + DEtot, Wm_r_m, Wm_ir_m, Wm_d_m); - Wm_ir_inc += Wm_ir_m; - Wm_d_inc += Wm_d_m; + if (!mechanisms_.empty()) { + const arma::vec::fixed<6> Etot_end = Etot + DEtot; // once, not per mechanism + for (const auto& mech : mechanisms_) { + double Wm_r_m = 0.0, Wm_ir_m = 0.0, Wm_d_m = 0.0; + mech->compute_work(sigma_start_, sigma, Etot, Etot_end, Wm_r_m, Wm_ir_m, Wm_d_m); + Wm_ir_inc += Wm_ir_m; + Wm_d_inc += Wm_d_m; + } } Wm += Wm_inc; @@ -392,11 +415,18 @@ void ModularUMAT::run( void ModularUMAT::refresh_stress(const arma::vec& Etot_end, double DT_init, int ndi, int nshr, arma::vec& sigma) { - arma::vec E_inel = arma::zeros(6); - for (const auto& mech : mechanisms_) { - E_inel += mech->inelastic_strain(); + // no mechanism: the inelastic sum is +0 and x - (+0) = x bit for bit, so it is skipped + if (!elasticity_.is_configured()) { + throw std::runtime_error("ElasticityModule: not configured"); // thermal_strain's guard + } + arma::vec::fixed<6> Eel = Etot_end - elasticity_.alpha() * DT_init; + if (!mechanisms_.empty()) { + arma::vec::fixed<6> E_inel(arma::fill::zeros); + for (const auto& mech : mechanisms_) { + E_inel += mech->inelastic_strain(); + } + Eel -= E_inel; } - const arma::vec Eel = Etot_end - elasticity_.thermal_strain(DT_init) - E_inel; elasticity_.evaluate(Eel, ndi, nshr, sigma_eff_, L_cur_); // strain equivalence (Lemaitre): the mechanisms work on sigma_eff_, damage scales it sigma = stiffness_reduction() * sigma_eff_; @@ -424,7 +454,7 @@ bool ModularUMAT::return_mapping( } // with no constraints this elastic call is the whole response - const arma::vec Etot_end = Etot + DEtot; + const arma::vec::fixed<6> Etot_end = Etot + DEtot; for (auto& mech : mechanisms_) { mech->predict(Etot_end, DTime); // closed-form parts first (viscoelastic branches) } @@ -514,8 +544,15 @@ void ModularUMAT::return_mapping_ccp( hooks.update = [&](const arma::vec& ds) { for (size_t m = 0; m < mechanisms_.size(); ++m) mechanisms_[m]->update(ds, mech_offset_[m]); }; - // D may have evolved in update: the reduction factor is re-evaluated with the stress - hooks.refresh = [&]() { refresh_stress(Etot_end, DT_init, ndi, nshr, sigma); }; + // D may have evolved in update: the reduction factor is re-evaluated with the stress. + // one captured reference keeps the closure in std::function's small buffer (no heap per call) + struct refresh_args { + const arma::vec& Etot_end; + double DT_init; + int ndi, nshr; + arma::vec& sigma; + } const ra{Etot_end, DT_init, ndi, nshr, sigma}; + hooks.refresh = [this, &ra]() { refresh_stress(ra.Etot_end, ra.DT_init, ra.ndi, ra.nshr, ra.sigma); }; hooks.consistency = [&]() { double res = 0.0; for (const auto& mech : mechanisms_) res += mech->consistency_residual(sigma_eff_); @@ -658,28 +695,30 @@ void ModularUMAT::assemble_jacobian( // Phase 2: B_lj = -dPhi^l/dsigma . kappa^j + K^lj (engineering Voigt dot); strain-form Phi // (viscoelastic) returns an empty dPhi_dsigma and contributes no row B.zeros(); - // Fetch each mechanism's kappa list once — the const-refs stay valid - // for the whole phase (no compute_constraints call in between). - std::vector*> kappa_all(mechanisms_.size()); - for (size_t jm = 0; jm < mechanisms_.size(); ++jm) { - kappa_all[jm] = &mechanisms_[jm]->kappa(sigma_eff_, DT, L_cur_); - } - for (size_t lm = 0; lm < mechanisms_.size(); ++lm) { - const auto& dPhi_l_all = mechanisms_[lm]->dPhi_dsigma(sigma_eff_); - if (dPhi_l_all.empty()) continue; - for (size_t l_c = 0; l_c < dPhi_l_all.size(); ++l_c) { - const int row = mech_offset_[lm] + static_cast(l_c); - const arma::vec::fixed<6> dPhi_l = dPhi_l_all[l_c].voigt(); - for (size_t jm = 0; jm < mechanisms_.size(); ++jm) { - const auto& kappa_j_all = *kappa_all[jm]; - for (size_t j_c = 0; j_c < kappa_j_all.size(); ++j_c) { - if (lm == jm && l_c == j_c) continue; // diagonal last - const int col = mech_offset_[jm] + static_cast(j_c); - B(row, col) = -arma::dot(dPhi_l, kappa_j_all[j_c].voigt()) - + mechanisms_[lm]->K_cross( - static_cast(l_c), - *mechanisms_[jm], - static_cast(j_c)); + // single row: no off-diagonal entry, phase 2 skipped (compute_tangent fetches dPhi and kappa itself) + if (B.n_rows > 1) { + // kappa lists fetched once: the refs stay valid, nothing below calls compute_constraints + std::vector*> kappa_all(mechanisms_.size()); + for (size_t jm = 0; jm < mechanisms_.size(); ++jm) { + kappa_all[jm] = &mechanisms_[jm]->kappa(sigma_eff_, DT, L_cur_); + } + for (size_t lm = 0; lm < mechanisms_.size(); ++lm) { + const auto& dPhi_l_all = mechanisms_[lm]->dPhi_dsigma(sigma_eff_); + if (dPhi_l_all.empty()) continue; + for (size_t l_c = 0; l_c < dPhi_l_all.size(); ++l_c) { + const int row = mech_offset_[lm] + static_cast(l_c); + const arma::vec::fixed<6> dPhi_l = dPhi_l_all[l_c].voigt(); + for (size_t jm = 0; jm < mechanisms_.size(); ++jm) { + const auto& kappa_j_all = *kappa_all[jm]; + for (size_t j_c = 0; j_c < kappa_j_all.size(); ++j_c) { + if (lm == jm && l_c == j_c) continue; // diagonal last + const int col = mech_offset_[jm] + static_cast(j_c); + B(row, col) = -arma::dot(dPhi_l, kappa_j_all[j_c].voigt()) + + mechanisms_[lm]->K_cross( + static_cast(l_c), + *mechanisms_[jm], + static_cast(j_c)); + } } } } @@ -726,20 +765,20 @@ void ModularUMAT::compute_tangent( // mechanisms see sigma_eff_; damage enters last through its stress_map(). const arma::vec& sigma_eff = sigma_eff_; - // algorithmic assembly only for stress-dependent Phi with an analytic flow Hessian - std::vector algo; + // algorithmic assembly only for stress-dependent Phi with an analytic flow Hessian (the Hessians are kept) + std::vector*>> algo; if (tangent_mode == tangent_closest_point) { // Exact operator of the closest-point solve; every multiplier-carrying row is inside it. for (size_t m = 0; m < mechanisms_.size(); ++m) { - if (mechanisms_[m]->carries_multipliers()) algo.push_back(m); + if (mechanisms_[m]->carries_multipliers()) algo.emplace_back(m, nullptr); } Lt = cpp_consistent_tangent(cpp_result_, L_cur_).Lt; } else if (tangent_mode == tangent_algorithmic && arma::any(Ds_total > simcoon::iota)) { // no active multiplier: the assembly masks everything and returns L, skip the Hessians for (size_t m = 0; m < mechanisms_.size(); ++m) { - if (mechanisms_[m]->dLambda_dsigma(sigma_eff) != nullptr && - !mechanisms_[m]->dPhi_dsigma(sigma_eff).empty()) { - algo.push_back(m); + const std::vector* hess = mechanisms_[m]->dLambda_dsigma(sigma_eff); + if (hess != nullptr && !mechanisms_[m]->dPhi_dsigma(sigma_eff).empty()) { + algo.emplace_back(m, hess); } } } @@ -757,10 +796,14 @@ void ModularUMAT::compute_tangent( std::vector kappa_j; std::vector dPhi_l; std::vector dLambda_l; - for (size_t m : algo) { + rows.reserve(n_total); + kappa_j.reserve(n_total); + dPhi_l.reserve(n_total); + dLambda_l.reserve(n_total); + for (const auto& [m, hess] : algo) { const auto& dPhi_all = mechanisms_[m]->dPhi_dsigma(sigma_eff); const auto& kappa_all = mechanisms_[m]->kappa(sigma_eff, 0.0, L_cur_); - const auto& hess_all = *mechanisms_[m]->dLambda_dsigma(sigma_eff); + const auto& hess_all = *hess; for (size_t c = 0; c < dPhi_all.size(); ++c) { rows.push_back(mech_offset_[m] + static_cast(c)); dPhi_l.emplace_back(dPhi_all[c].voigt()); @@ -783,7 +826,7 @@ void ModularUMAT::compute_tangent( } for (size_t m = 0; m < mechanisms_.size(); ++m) { - if (std::find(algo.begin(), algo.end(), m) != algo.end()) { + if (std::find_if(algo.begin(), algo.end(), [m](const auto& a) { return a.first == m; }) != algo.end()) { continue; } mechanisms_[m]->tangent_contribution( diff --git a/src/Continuum_mechanics/Umat/Modular/plasticity_mechanism.cpp b/src/Continuum_mechanics/Umat/Modular/plasticity_mechanism.cpp index 270ed1597..b73f71c01 100644 --- a/src/Continuum_mechanics/Umat/Modular/plasticity_mechanism.cpp +++ b/src/Continuum_mechanics/Umat/Modular/plasticity_mechanism.cpp @@ -20,6 +20,7 @@ along with simcoon. If not, see . * @brief Implementation of PlasticityMechanism class */ +#include #include #include #include @@ -32,8 +33,27 @@ along with simcoon. If not, see . namespace simcoon { namespace { + // state_drift denominator floor: rejection at near-zero Dp needs a flow-rule error > tol * 1e-4 strain constexpr double drift_scale_floor = 1.0e-4; + +// HT = H.each_row() % T.t(), A = I + gamma HT in the expressions' order: bit-identical unless FMA crosses statements +void fill_HT(const arma::mat& H, const arma::vec& T, arma::mat::fixed<6,6>& HT) { + for (arma::uword j = 0; j < 6; ++j) { + for (arma::uword i = 0; i < 6; ++i) { + HT(i, j) = H(i, j) * T(j); + } + } +} + +void fill_I_plus_gamma(const arma::mat::fixed<6,6>& HT, double gamma, arma::mat::fixed<6,6>& A) { + for (arma::uword j = 0; j < 6; ++j) { + for (arma::uword i = 0; i < 6; ++i) { + const double g = HT(i, j) * gamma; + A(i, j) = ((i == j) ? 1.0 : 0.0) + g; + } + } +} } // namespace // ========== Constructor ========== @@ -110,11 +130,20 @@ void PlasticityMechanism::register_variables() { // ========== Yield-side queries ========== +arma::vec PlasticityMechanism::total_backstress_voigt() const { + kin_hard_->compute_backstresses(ivc_, X_branches_); + tensor2 X_t = tensor2::zeros(Tensor2Type::stress); + for (const auto& x : X_branches_) { + X_t += x; + } + return X_t.to_arma_voigt(); +} + double PlasticityMechanism::equivalent_stress(const arma::vec& sigma) const { if (kin_hard_->num_backstresses() == 0) { return yield_->equivalent_stress(sigma); } - return yield_->equivalent_stress(sigma - kin_hard_->total_backstress(ivc_).to_arma_voigt()); + return yield_->equivalent_stress(sigma - total_backstress_voigt()); } double PlasticityMechanism::equivalent_stress(const tensor2& sigma) const { @@ -134,7 +163,7 @@ arma::vec PlasticityMechanism::flow_direction(const arma::vec& sigma) const { if (kin_hard_->num_backstresses() == 0) { return yield_->flow_direction(sigma); } - return yield_->flow_direction(sigma - kin_hard_->total_backstress(ivc_).to_arma_voigt()); + return yield_->flow_direction(sigma - total_backstress_voigt()); } tensor2 PlasticityMechanism::flow_direction(const tensor2& sigma) const { @@ -164,11 +193,9 @@ void PlasticityMechanism::compute_constraints( X_t += x; } const arma::vec sigma_eff = sigma - X_t.to_arma_voigt(); - sigma_eq = yield_->equivalent_stress(sigma_eff); - flow_dir_ = yield_->flow_direction(sigma_eff); + yield_->equivalent_stress_and_flow(sigma_eff, sigma_eq, flow_dir_); } else { - sigma_eq = yield_->equivalent_stress(sigma); - flow_dir_ = yield_->flow_direction(sigma); + yield_->equivalent_stress_and_flow(sigma, sigma_eq, flow_dir_); } const double R = iso_hard_->R(p); @@ -181,7 +208,11 @@ void PlasticityMechanism::compute_constraints( Y_crit.set_size(1); Y_crit(0) = std::max(sigma_Y_, 1.0); - kappa_ = L * flow_dir_; + if (L.n_rows == 6 && L.n_cols == 6 && flow_dir_.n_elem == 6) { + mul6(L, flow_dir_, kappa_); + } else { + kappa_ = L * flow_dir_; + } H_total_ = dR_dp + (has_kin ? kin_hard_->hardening_modulus(strain(flow_dir_), X_branches_) : 0.0); @@ -208,7 +239,7 @@ const std::vector* PlasticityMechanism::dLambda_dsigma( } const bool has_kin = kin_hard_->num_backstresses() > 0; const arma::vec sigma_eff = - has_kin ? arma::vec(sigma - kin_hard_->total_backstress(ivc_).to_arma_voigt()) : sigma; + has_kin ? arma::vec(sigma - total_backstress_voigt()) : sigma; hessian_cache_[0] = tensor4(arma::mat(yield_->flow_hessian(sigma_eff)), Tensor4Type::compliance); return &hessian_cache_; @@ -223,36 +254,41 @@ bool PlasticityMechanism::refresh_state( const arma::vec& Ds_total, int offset) { const double dp = std::max(Ds_total(offset), 0.0); // the integrator projects Ds >= 0 too - const arma::vec T = Ir05(); // engineering -> tensorial shear + static const arma::vec T = Ir05(); // engineering -> tensorial shear const bool has_kin = kin_hard_->num_backstresses() > 0; auto& p_var = ivc_.at(p_index_); p_var.scalar() = p_var.scalar_start() + dp; - // Newton on the n <-> X coupling (class note in the header). + // Newton on the n <-> X coupling (see header); flow and Hessian of the final xi feed the derivatives below arma::vec n(6), xi(6); double gamma = 0.0; arma::vec beta = arma::zeros(6); + double sigma_eq = 0.0; + arma::mat H; if (!has_kin) { - n = yield_->flow_direction(sigma); xi = sigma; + yield_->equivalent_stress_flow_hessian(xi, sigma_eq, n, H); } else { // Start = normal of the dp-relaxed start backstress: exact for von Mises (zero steps); // warm-starting from the previous normal measured worse (J2 pays real steps). - kin_hard_->refresh_state(dp, strain(arma::vec(arma::zeros(6))), ivc_); // alpha_i^n / (1 + D_i dp) - n = yield_->flow_direction(sigma - kin_hard_->total_backstress(ivc_).to_arma_voigt()); + kin_hard_->refresh_state(dp, tensor2::zeros(Tensor2Type::strain), ivc_); // alpha_i^n / (1 + D_i dp) + n = yield_->flow_direction(sigma - total_backstress_voigt()); bool converged = false; for (int it = 0; it < 25; ++it) { kin_hard_->refresh_state(dp, strain(n), ivc_); - xi = sigma - kin_hard_->total_backstress(ivc_).to_arma_voigt(); - const arma::vec G = n - yield_->flow_direction(xi); + xi = sigma - total_backstress_voigt(); + arma::vec flow; + yield_->equivalent_stress_flow_hessian(xi, sigma_eq, flow, H); + const arma::vec G = n - flow; if (arma::norm(G, 2) <= 1e-12 * std::max(1.0, arma::norm(n, 2))) { converged = true; break; } kin_hard_->backward_euler_factors(dp, n, ivc_, gamma, beta); - const arma::mat H = yield_->flow_hessian(xi); - const arma::mat A = arma::eye(6, 6) + gamma * (H.each_row() % T.t()); // I + gamma H T + arma::mat::fixed<6,6> HT, A; + fill_HT(H, T, HT); + fill_I_plus_gamma(HT, gamma, A); // I + gamma H T small_lu<6> lu_A; // on the stack: a LAPACK 6x6 solve per step otherwise if (!lu_A.factor(A)) { return false; @@ -270,9 +306,8 @@ bool PlasticityMechanism::refresh_state( // Total derivatives at this state (class note in the header). ClosestPointIngredients& out = cpp_cache_[0]; - const arma::mat H = yield_->flow_hessian(xi); const double dR_dp = iso_hard_->dR_dp(p_var.scalar()); - out.Phi = yield_->equivalent_stress(xi) - iso_hard_->R(p_var.scalar()) - sigma_Y_; + out.Phi = sigma_eq - iso_hard_->R(p_var.scalar()) - sigma_Y_; out.Lambda = n; if (!has_kin) { out.dPhi_dsigma = n; @@ -281,9 +316,11 @@ bool PlasticityMechanism::refresh_state( out.K = -dR_dp; return true; } - const arma::mat HT = H.each_row() % T.t(); + arma::mat::fixed<6,6> HT, A; + fill_HT(H, T, HT); + fill_I_plus_gamma(HT, gamma, A); small_lu<6> lu_A; // A = I + gamma H T on the stack: solves instead of a LAPACK inverse - if (!lu_A.factor(arma::eye(6, 6) + gamma * HT)) { + if (!lu_A.factor(A)) { return false; } const arma::vec Tn = T % n; @@ -402,24 +439,31 @@ void PlasticityMechanism::compute_work( ) const { // Get increments double Dp = ivc_.at(p_index_).delta_scalar(); - arma::vec DEP = ivc_.at(EP_index_).delta_vec(); - if (Dp == 0.0 && DEP.is_zero()) { - Wm_r = Wm_ir = Wm_d = 0.0; // both dots below would be (signed) zeros - return; + const auto& EP_var = ivc_.at(EP_index_); + if (Dp == 0.0) { + // DEP.is_zero() on the differences, before building DEP (a NaN difference is not zero) + bool moved = false; + for (arma::uword i = 0; i < 6 && !moved; ++i) { + const double d = EP_var.raw_voigt()(i) - EP_var.raw_voigt_start()(i); + moved = (d != 0.0); + } + if (!moved) { + Wm_r = Wm_ir = Wm_d = 0.0; // both dots below would be (signed) zeros + return; + } } + arma::vec DEP = EP_var.delta_vec(); // Average stress during increment arma::vec sigma_avg = 0.5 * (sigma_start + sigma); - // Get backstress (if any) - const arma::vec X = kin_hard_->total_backstress(ivc_).to_arma_voigt(); - // Dissipated work: sigma : dEP Wm_d = arma::dot(sigma_avg, DEP); // For kinematic hardening, part of the work is stored // Stored work in backstress: X : dEP (approximately) if (kin_hard_->num_backstresses() > 0) { + const arma::vec X = total_backstress_voigt(); Wm_ir = arma::dot(X, DEP); Wm_d -= Wm_ir; } else { diff --git a/src/Continuum_mechanics/Umat/Modular/viscoelastic_mechanism.cpp b/src/Continuum_mechanics/Umat/Modular/viscoelastic_mechanism.cpp index 9f37b0f63..ee2536a28 100644 --- a/src/Continuum_mechanics/Umat/Modular/viscoelastic_mechanism.cpp +++ b/src/Continuum_mechanics/Umat/Modular/viscoelastic_mechanism.cpp @@ -42,6 +42,8 @@ ViscoelasticMechanism::ViscoelasticMechanism(int N_prony) , M0_L_i_(N_prony) , ev_key_(N_prony) , v_key_(N_prony) + , ev_index_(N_prony, 0) + , v_index_(N_prony, 0) , kappa_t_(N_prony, tensor2::zeros(Tensor2Type::stress)) , dEVtilde_dE_(arma::zeros(6, 6)) { @@ -67,13 +69,13 @@ void ViscoelasticMechanism::configure(const arma::vec& props, int& offset) { } void ViscoelasticMechanism::register_variables() { - // Resolve and cache the keys once — they're used every FB iteration in - // compute_constraints / inelastic_strain / update. for (int i = 0; i < N_prony_; ++i) { v_key_[i] = "v_" + std::to_string(i); ev_key_[i] = "EV_" + std::to_string(i); ivc_.add_scalar(v_key_[i], 0.0); ivc_.add_vec (ev_key_[i], arma::zeros(6), true); + v_index_[i] = ivc_.index_of(v_key_[i]); + ev_index_[i] = ivc_.index_of(ev_key_[i]); } } @@ -122,7 +124,7 @@ arma::vec ViscoelasticMechanism::inelastic_strain() const { // EV_tilde = sum_i (M_0 L_i) EV_i (Prony_Nfast form); M0_L_i_ precomputed in set_reference_stiffness arma::vec EV_tilde = arma::zeros(6); for (int i = 0; i < N_prony_; ++i) { - EV_tilde += M0_L_i_[i] * ivc_.get(ev_key_[i]).raw_voigt(); + EV_tilde += M0_L_i_[i] * ivc_.at(ev_index_[i]).raw_voigt(); } return EV_tilde; } @@ -138,15 +140,16 @@ void ViscoelasticMechanism::predict(const arma::vec& E_total_end, double DTime) // closed-form backward Euler from the rotated start, final before the other mechanisms; DTime = 0 -> inactive std::vector EV_start(N_prony_); for (int i = 0; i < N_prony_; ++i) { - EV_start[i] = ivc_.get(ev_key_[i]).raw_voigt_start(); + EV_start[i] = ivc_.at(ev_index_[i]).raw_voigt_start(); } - const LinearViscoStep st = maxwell_parallel_step(L_0_.is_empty() ? arma::mat(arma::zeros(6, 6)) : L_0_, - L_i_, H_i_, EV_start, E_total_end, arma::zeros(6), DTime); + static const arma::mat zero_L0(6, 6, arma::fill::zeros); + const arma::mat& L0 = L_0_.is_empty() ? zero_L0 : L_0_; // a reference: no 6x6 copy per call + const LinearViscoStep st = maxwell_parallel_step(L0, L_i_, H_i_, EV_start, E_total_end, arma::zeros(6), DTime); dEVtilde_dE_.zeros(); for (int i = 0; i < N_prony_; ++i) { - InternalVariable& ev = ivc_.get(ev_key_[i]); + InternalVariable& ev = ivc_.at(ev_index_[i]); ev.raw_voigt() = st.EV_i[i]; - InternalVariable& v = ivc_.get(v_key_[i]); + InternalVariable& v = ivc_.at(v_index_[i]); v.scalar() = v.scalar_start() + norm_strain(st.EV_i[i] - EV_start[i]); dEVtilde_dE_ += M0_L_i_[i] * st.dEVdE_i[i]; } @@ -183,7 +186,7 @@ void ViscoelasticMechanism::compute_work( Wm_d = 0.0; for (int i = 0; i < N_prony_; ++i) { - const InternalVariable& ev = ivc_.get(ev_key_[i]); + const InternalVariable& ev = ivc_.at(ev_index_[i]); const arma::vec A_start = L_i_[i] * (E_start - ev.raw_voigt_start()); const arma::vec A_end = L_i_[i] * (E_end - ev.raw_voigt()); Wm_d += 0.5 * arma::dot(A_start + A_end, ev.delta_vec()); diff --git a/src/Continuum_mechanics/Umat/Modular/yield_criterion.cpp b/src/Continuum_mechanics/Umat/Modular/yield_criterion.cpp index 9a1f21347..5a277bdb8 100644 --- a/src/Continuum_mechanics/Umat/Modular/yield_criterion.cpp +++ b/src/Continuum_mechanics/Umat/Modular/yield_criterion.cpp @@ -23,6 +23,8 @@ along with simcoon. If not, see . #include #include #include +#include +#include #include namespace simcoon { @@ -62,30 +64,35 @@ std::string YieldCriterion::type_string() const { void YieldCriterion::configure_von_mises() { type_ = YieldType::VON_MISES; params_ = arma::zeros(1); // No parameters needed + P_.reset(); configured_ = true; } void YieldCriterion::configure_tresca() { type_ = YieldType::TRESCA; params_ = arma::zeros(1); // No parameters needed + P_.reset(); configured_ = true; } void YieldCriterion::configure_drucker(double b, double n) { type_ = YieldType::DRUCKER; params_ = arma::vec({b, n}); + P_.reset(); configured_ = true; } void YieldCriterion::configure_hill(double F, double G, double H, double L, double M, double N) { type_ = YieldType::HILL; params_ = arma::vec({F, G, H, L, M, N}); + P_ = P_Hill(params_); configured_ = true; } void YieldCriterion::configure_dfa(double F, double G, double H, double L, double M, double N, double K) { type_ = YieldType::DFA; params_ = arma::vec({F, G, H, L, M, N, K}); + P_ = P_DFA(params_); configured_ = true; } @@ -94,6 +101,7 @@ void YieldCriterion::configure_anisotropic(double P11, double P22, double P33, double P44, double P55, double P66) { type_ = YieldType::ANISOTROPIC; params_ = arma::vec({P11, P22, P33, P12, P13, P23, P44, P55, P66}); + P_ = P_Ani(params_); configured_ = true; } @@ -158,18 +166,101 @@ void YieldCriterion::configure(YieldType type, const arma::vec& props, int& offs // ========== Yield Function Computations ========== +// the criteria.cpp kernels called by type, the quadratic ones on P_ built at configuration + double YieldCriterion::equivalent_stress(const arma::vec& sigma) const { if (!configured_) { throw std::invalid_argument("YieldCriterion: not configured"); } - return Eq_stress(sigma, type_string(), params_); + switch (type_) { + case YieldType::TRESCA: + return Tresca_stress(sigma); + case YieldType::DRUCKER: + return Drucker_stress(sigma, params_(0), params_(1)); + case YieldType::HILL: + case YieldType::DFA: + case YieldType::ANISOTROPIC: + return Eq_stress_P(sigma, P_); + default: // VON_MISES (type_string's fallback too) + return Mises_stress(sigma); + } } arma::vec YieldCriterion::flow_direction(const arma::vec& sigma) const { if (!configured_) { throw std::invalid_argument("YieldCriterion: not configured"); } - return dEq_stress(sigma, type_string(), params_); + switch (type_) { + case YieldType::TRESCA: + return dTresca_stress(sigma); + case YieldType::DRUCKER: + return dDrucker_stress(sigma, params_(0), params_(1)); + case YieldType::HILL: + case YieldType::DFA: + case YieldType::ANISOTROPIC: + return dEq_stress_P(sigma, P_); + default: // VON_MISES + return eta_stress(sigma); + } +} + +void YieldCriterion::equivalent_stress_and_flow(const arma::vec& sigma, double& sigma_eq, + arma::vec& flow) const { + switch (type_) { + case YieldType::HILL: + case YieldType::DFA: + case YieldType::ANISOTROPIC: { + if (!configured_) { + throw std::runtime_error("YieldCriterion: not configured"); + } + // Eq_stress_P and dEq_stress_P on one P sigma: the latter is P sigma over the former + if (arma::norm(sigma, 2) > simcoon::iota) { + const arma::vec P_sigma = P_ * sigma; + sigma_eq = std::pow(arma::sum(sigma % P_sigma), 0.5); + flow = P_sigma / sigma_eq; + } else { + sigma_eq = 0.; + flow = arma::zeros(6); + } + return; + } + default: + sigma_eq = equivalent_stress(sigma); + flow = flow_direction(sigma); + } +} + +void YieldCriterion::equivalent_stress_flow_hessian(const arma::vec& sigma, double& sigma_eq, + arma::vec& flow, arma::mat& hessian) const { + switch (type_) { + case YieldType::HILL: + case YieldType::DFA: + case YieldType::ANISOTROPIC: { + if (!configured_) { + throw std::runtime_error("YieldCriterion: not configured"); + } + // Eq_stress_P, dEq_stress_P and ddEq_stress_P(sigma, P_) (criteria.cpp) on one P sigma + if (arma::norm(sigma, 2) > simcoon::iota) { + const arma::vec P_sigma = P_ * sigma; + sigma_eq = std::pow(arma::sum(sigma % P_sigma), 0.5); + flow = P_sigma / sigma_eq; + if (sigma_eq < simcoon::iota) { + hessian = arma::zeros(6, 6); + } else { + hessian = (P_ - flow * flow.t()) / sigma_eq; // flow is ddEq_stress_P's Lambda + } + } else { + sigma_eq = 0.; + flow = arma::zeros(6); + hessian = arma::zeros(6, 6); + } + return; + } + default: + sigma_eq = equivalent_stress(sigma); + flow = flow_direction(sigma); + hessian = flow_hessian(sigma); + } } arma::vec YieldCriterion::plastic_flow(const arma::vec& sigma) const { @@ -212,11 +303,9 @@ arma::mat YieldCriterion::flow_hessian(const arma::vec& sigma) const { case YieldType::VON_MISES: return deta_stress(sigma); case YieldType::HILL: - return ddHill_stress(sigma, params_); case YieldType::DFA: - return ddDFA_stress(sigma, params_); case YieldType::ANISOTROPIC: - return ddAni_stress(sigma, params_); + return ddEq_stress_P(sigma, P_); // ddHill_stress / ddDFA_stress / ddAni_stress default: throw std::invalid_argument( "YieldCriterion: no analytic flow Hessian for " + type_string()); diff --git a/src/Continuum_mechanics/Umat/Thermomechanical/Plasticity/plastic_isotropic.cpp b/src/Continuum_mechanics/Umat/Thermomechanical/Plasticity/plastic_isotropic.cpp index 1777ab4d3..526684547 100755 --- a/src/Continuum_mechanics/Umat/Thermomechanical/Plasticity/plastic_isotropic.cpp +++ b/src/Continuum_mechanics/Umat/Thermomechanical/Plasticity/plastic_isotropic.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -59,9 +60,8 @@ void umat_plasticity_iso_T(const vec &Etot, const vec &DEtot, vec &sigma, double //definition of the CTE tensor vec alpha = alpha_iso*Ith(); - //Elastic stiffness tensor and thermal tensor + //Elastic stiffness tensor mat L = L_iso(E, nu, "Enu"); - mat M = M_iso(E, nu, "Enu"); ///@brief Temperature initialization double T_init = statev(0); @@ -137,7 +137,7 @@ void umat_plasticity_iso_T(const vec &Etot, const vec &DEtot, vec &sigma, double //Compute the explicit flow direction vec Lambdap = eta_stress(sigma); std::vector kappa_j(1); - kappa_j[0] = L*Lambdap; + mul6(L, Lambdap, kappa_j[0]); mat K = zeros(1,1); //Loop parameters @@ -185,8 +185,8 @@ void umat_plasticity_iso_T(const vec &Etot, const vec &DEtot, vec &sigma, double //compute Phi and the derivatives Phi(0) = Mises_stress(sigma) - Hp - sigmaY; - Lambdap = eta_stress(sigma); - kappa_j[0] = L*Lambdap; + Lambdap = dPhidsigma; // associated J2 flow + mul6(L, Lambdap, kappa_j[0]); K(0,0) = dPhidp; B(0, 0) = -1.*sum(dPhidsigma%kappa_j[0]) + K(0,0); @@ -228,9 +228,11 @@ void umat_plasticity_iso_T(const vec &Etot, const vec &DEtot, vec &sigma, double std::vector P_theta(1); // P_theta = invBhat (dPhi/dtheta - dPhi/dsigma:L:alpha), 1/K; invBhat masked so elastic gives 0 - P_theta[0] = ct.invBhat(0, 0) * (dPhidtheta - sum(dPhidsigma%(L*alpha))); + vec L_alpha; + mul6(L, alpha, L_alpha); + P_theta[0] = ct.invBhat(0, 0) * (dPhidtheta - sum(dPhidsigma%L_alpha)); - dSdT = -1.*L*alpha - (kappa_j[0]*P_theta[0]); + dSdT = -1.*L_alpha - (kappa_j[0]*P_theta[0]); //computation of the internal energy production double eta_r = c_0*log((T+DT)/T_init) + sum(alpha%sigma); diff --git a/src/Continuum_mechanics/Umat/Thermomechanical/Plasticity/plastic_kin_iso.cpp b/src/Continuum_mechanics/Umat/Thermomechanical/Plasticity/plastic_kin_iso.cpp index 4f2fc978b..c53184398 100755 --- a/src/Continuum_mechanics/Umat/Thermomechanical/Plasticity/plastic_kin_iso.cpp +++ b/src/Continuum_mechanics/Umat/Thermomechanical/Plasticity/plastic_kin_iso.cpp @@ -60,9 +60,8 @@ void umat_plasticity_kin_iso_T(const vec &Etot, const vec &DEtot, vec &sigma, do //definition of the CTE tensor vec alpha = alpha_iso*Ith(); - //Elastic stiffness tensor and thermal tensor + //Elastic stiffness tensor mat L = L_iso(E, nu, "Enu"); - mat M = M_iso(E, nu, "Enu"); ///@brief Temperature initialization double T_init = statev(0); @@ -151,7 +150,7 @@ void umat_plasticity_kin_iso_T(const vec &Etot, const vec &DEtot, vec &sigma, do //Compute the explicit flow direction vec Lambdap = eta_stress(sigma-X); - vec Lambdaa = eta_stress(sigma-X); + vec Lambdaa = Lambdap; std::vector kappa_j(1); kappa_j[0] = L*Lambdap; mat K = zeros(1,1); @@ -199,15 +198,16 @@ void umat_plasticity_kin_iso_T(const vec &Etot, const vec &DEtot, vec &sigma, do p = s_j(0); Hp = hardening.R(p); dHpdp = hardening.dR_dp(p); + // associated J2 flow on sigma - X, shared by the criterion gradient and both flows dPhidsigma = eta_stress(sigma-X); dPhidp = -1.*dHpdp; - dPhida = -1.*kX*(eta_stress(sigma - X)%Ir05()); + dPhida = -1.*kX*(dPhidsigma%Ir05()); //compute Phi and the derivatives Phi(0) = Mises_stress(sigma-X) - Hp - sigmaY; - Lambdap = eta_stress(sigma-X); - Lambdaa = eta_stress(sigma-X); + Lambdap = dPhidsigma; + Lambdaa = dPhidsigma; kappa_j[0] = L*Lambdap; K(0,0) = dPhidp + sum(dPhida%Lambdaa); diff --git a/src/Continuum_mechanics/Umat/Thermomechanical/SMA/unified_T.cpp b/src/Continuum_mechanics/Umat/Thermomechanical/SMA/unified_T.cpp index 1119a5178..12474e17f 100755 --- a/src/Continuum_mechanics/Umat/Thermomechanical/SMA/unified_T.cpp +++ b/src/Continuum_mechanics/Umat/Thermomechanical/SMA/unified_T.cpp @@ -272,9 +272,9 @@ void umat_sma_unified_T_T(const string &umat_name, const vec &Etot, const vec &D vec lambdaTR = -1.*ETMean; - //Definition of the modified Y function - double YtF = Y0t + D*Hcur*Mises_stress(sigma); - double YtR = Y0t + D*sum(sigma%ETMean); //*D Changed by D. + //Modified Y functions, Lagrange multipliers and criteria: set by the transformation loop before any use + double YtF = 0.; + double YtR = 0.; double HfF = 0.; double HfR = 0.; @@ -297,9 +297,9 @@ void umat_sma_unified_T_T(const string &umat_name, const vec &Etot, const vec &D HfR = - a3; } - //Set the Lagrange multiplliers due to Physical limitations - double lambda0 = -1.*lagrange_pow_0(xi, c_lambda, p0_lambda, n_lambda, alpha_lambda); - double lambda1 = lagrange_pow_1(xi, c_lambda, p0_lambda, n_lambda, alpha_lambda); + //Lagrange multiplliers due to Physical limitations + double lambda0 = 0.; + double lambda1 = 0.; //Define the value of DM_sig vec DM_sig = (DM*sigma_start); @@ -313,14 +313,8 @@ void umat_sma_unified_T_T(const string &umat_name, const vec &Etot, const vec &D double A_xiR_start = -1.*rhoDs0*(T) + rhoDE0 - 0.5*sum(sigma_start%DM_sig) - sum(sigma_start%Dalpha)*(T-T_init) + HfR; //Transformation criteria - double PhihatF; - if (aniso_criteria) { - PhihatF = Hcur*Drucker_ani_stress(sigma, DFA_params, prager_b, prager_n); - } - else { - PhihatF = Hcur*Drucker_stress(sigma, prager_b, prager_n); - } - double PhihatR = sum(sigma%ETMean); + double PhihatF = 0.; + double PhihatR = 0.; //Variables required for the loop vec s_j = zeros(2); @@ -390,14 +384,22 @@ void umat_sma_unified_T_T(const string &umat_name, const vec &Etot, const vec &D L = inv_cubic(M_eff); DM_sig = DM*sigma; - Dalpha_T = Dalpha*(T+DT-T_init); + // equivalent stresses and gradients at the current stress, shared below (Dalpha_T is loop-invariant) + const double sig_eq = Mises_stress(sigma); + const vec eta_sig = eta_stress(sigma); + double Dr_sig; + vec dDr_sig; if (aniso_criteria) { - lambdaTF = Hcur * dDrucker_ani_stress(sigma, DFA_params, prager_b, prager_n); + Dr_sig = Drucker_ani_stress(sigma, DFA_params, prager_b, prager_n); + dDr_sig = dDrucker_ani_stress(sigma, DFA_params, prager_b, prager_n); } else { - lambdaTF = Hcur * dDrucker_stress(sigma, prager_b, prager_n); + Dr_sig = Drucker_stress(sigma, prager_b, prager_n); + dDr_sig = dDrucker_stress(sigma, prager_b, prager_n); } + + lambdaTF = Hcur * dDr_sig; lambdaTR = -1. * ETMean; kappa_j[0] = L*(lambdaTF + DM_sig + Dalpha_T); @@ -430,23 +432,18 @@ void umat_sma_unified_T_T(const string &umat_name, const vec &Etot, const vec &D HfR = - a3; // Find Hcur explicit - if (Mises_stress(sigma) > sigmacrit) - sigmastar = Mises_stress(sigma) - sigmacrit; + if (sig_eq > sigmacrit) + sigmastar = sig_eq - sigmacrit; else sigmastar = 0.; Hcur = Hmin + (Hmax - Hmin)*(1. - exp(-1.*k1*sigmastar)); //Forward transformation thermodynamic force - if (aniso_criteria) { - PhihatF = Hcur*Drucker_ani_stress(sigma, DFA_params, prager_b, prager_n); - } - else { - PhihatF = Hcur*Drucker_stress(sigma, prager_b, prager_n); - } + PhihatF = Hcur*Dr_sig; A_xiF = rhoDs0*(T + DT) - rhoDE0 + 0.5*sum(sigma%DM_sig) + sum(sigma%Dalpha)*(T + DT - T_init) - HfF; lambda1 = lagrange_pow_1(xi, c_lambda, p0_lambda, n_lambda, alpha_lambda); - const double DHM = D*Hcur*Mises_stress(sigma); + const double DHM = D*Hcur*sig_eq; YtF = Y0t + DHM; Phi(0) = PhihatF + A_xiF - lambda1 - YtF; @@ -485,27 +482,22 @@ void umat_sma_unified_T_T(const string &umat_name, const vec &Etot, const vec &D dHfR = 0.; } - dHcurdsigma = k1*(Hmax - Hmin)*exp(-1.*k1*sigmastar)*eta_stress(sigma); + dHcurdsigma = k1*(Hmax - Hmin)*exp(-1.*k1*sigmastar)*eta_sig; //Related to forward transformation - if (aniso_criteria) { - dPhihatFdsigma = dHcurdsigma * Drucker_ani_stress(sigma, DFA_params, prager_b, prager_n) + Hcur * dDrucker_ani_stress(sigma, DFA_params, prager_b, prager_n); - } - else { - dPhihatFdsigma = dHcurdsigma * Drucker_stress(sigma, prager_b, prager_n) + Hcur * dDrucker_stress(sigma, prager_b, prager_n); - } + dPhihatFdsigma = dHcurdsigma * Dr_sig + Hcur * dDr_sig; dPhihatFdxiF = 0.; dPhihatFdxiR = 0.; - dA_xiFdsigma = DM_sig + Dalpha*(T+DT-T_init); + dA_xiFdsigma = DM_sig + Dalpha_T; dA_xiFdxiF = -dHfF; dA_xiFdxiR = dHfF; dlambda1dsigma = zeros(6); dlambda1dxiF = dlagrange_pow_1(xi, c_lambda, p0_lambda, n_lambda, alpha_lambda); - dlambda1dxiR = -1.*dlagrange_pow_1(xi, c_lambda, p0_lambda, n_lambda, alpha_lambda); + dlambda1dxiR = -1.*dlambda1dxiF; - dYtFdsigma = D*(dHcurdsigma * Mises_stress(sigma) + Hcur * eta_stress(sigma)); + dYtFdsigma = D*(dHcurdsigma * sig_eq + Hcur * eta_sig); dYtFdxiF = 0.; dYtFdxiR = 0.; @@ -515,13 +507,13 @@ void umat_sma_unified_T_T(const string &umat_name, const vec &Etot, const vec &D //Relative to reverse transformation dPhihatRdsigma = ETMean; - dA_xiRdsigma = -1.*DM_sig -1.*Dalpha*(T+DT-T_init); + dA_xiRdsigma = -1.*DM_sig - Dalpha_T; dA_xiRdxiF = dHfR; dA_xiRdxiR = -dHfR; dlambda0dsigma = zeros(6); - dlambda0dxiF = -1.*dlagrange_pow_0(xi, c_lambda, p0_lambda, n_lambda, alpha_lambda); - dlambda0dxiR = dlagrange_pow_0(xi, c_lambda, p0_lambda, n_lambda, alpha_lambda); + dlambda0dxiR = dlagrange_pow_0(xi, c_lambda, p0_lambda, n_lambda, alpha_lambda); + dlambda0dxiF = -1.*dlambda0dxiR; dYtRdsigma = D*ETMean; dPhiRdsigma = -1.*dPhihatRdsigma + dA_xiRdsigma + dlambda0dsigma - dYtRdsigma; @@ -629,8 +621,9 @@ void umat_sma_unified_T_T(const string &umat_name, const vec &Etot, const vec &D // Delta s = invBhat * rhs, Bhat(l, j) = criterion l, mechanism j std::vector P_theta(2); - const double rhsF = dPhiFdtheta - sum(dPhiFdsigma%(L*alpha)); - const double rhsR = dPhiRdtheta - sum(dPhiRdsigma%(L*alpha)); + const vec L_alpha = L*alpha; + const double rhsF = dPhiFdtheta - sum(dPhiFdsigma%L_alpha); + const double rhsR = dPhiRdtheta - sum(dPhiRdsigma%L_alpha); P_theta[0] = invBhat(0, 0)*rhsF + invBhat(0, 1)*rhsR; P_theta[1] = invBhat(1, 0)*rhsF + invBhat(1, 1)*rhsR; diff --git a/src/Continuum_mechanics/Umat/fea_transfer.cpp b/src/Continuum_mechanics/Umat/fea_transfer.cpp index a9400da62..530a3c469 100644 --- a/src/Continuum_mechanics/Umat/fea_transfer.cpp +++ b/src/Continuum_mechanics/Umat/fea_transfer.cpp @@ -194,10 +194,19 @@ bool abaqus_nlgeom(const double *dfgrd1) { } mat abaqus_jacobian(const mat &Lt, const vec &sigma) { - // symmetric part of sigma (x) I: what Abaqus's default (symmetric) solver keeps - const vec I = {1., 1., 1., 0., 0., 0.}; - const mat sigma_I = sigma * I.t(); - return Lt + 0.5*(sigma_I + sigma_I.t()); + // symmetric part of sigma (x) I: what Abaqus's default (symmetric) solver keeps, built in + // place (the products and sums of Lt + 0.5*(sigma I^T + I sigma^T), without the temporaries) + static const double I[6] = {1., 1., 1., 0., 0., 0.}; + mat J(6, 6); + for (int j = 0; j < 6; j++) { + for (int i = 0; i < 6; i++) { + const double s1 = sigma(i)*I[j]; + const double s2 = sigma(j)*I[i]; + const double h = (s1 + s2)*0.5; + J(i, j) = Lt(i, j) + h; + } + } + return J; } int abaqus_kinematic_form(const char *cmname) { diff --git a/src/Continuum_mechanics/Umat/return_mapping.cpp b/src/Continuum_mechanics/Umat/return_mapping.cpp index 2119d599e..2986707a5 100644 --- a/src/Continuum_mechanics/Umat/return_mapping.cpp +++ b/src/Continuum_mechanics/Umat/return_mapping.cpp @@ -111,12 +111,12 @@ ReturnMappingResult closest_point_return_mapping( // Tangent ingredients at the converged iterate from the pieces the Newton just built // (kappa_eff = kappa + c: the multiplier-side state chain enters through the flux). - auto fill_tangent_pieces = [&](const mat &K, const std::vector &D, const std::vector &c) { + auto fill_tangent_pieces = [&](const mat &K, std::vector &&D, const std::vector &c) { r.Bhat_continuum.set_size(N, N); r.kappa_j.assign(kappa.begin(), kappa.end()); for (int j = 0; j < N; j++) r.kappa_j[j] += c[j]; r.dPhidsigma_l.assign(n_l.begin(), n_l.end()); - r.dLambda_dsigma_l = D; + r.dLambda_dsigma_l = std::move(D); for (int l = 0; l < N; l++) { for (int j = 0; j < N; j++) { r.Bhat_continuum(l, j) = sum(n_l[l] % r.kappa_j[j]) - K(l, j); @@ -133,6 +133,7 @@ ReturnMappingResult closest_point_return_mapping( } const int snap_niter = 20; // plain Newton gets this many iterations before the snap may act + r.error_history.reserve(8); // a converging solve takes a handful of iterations // buffers sized once; M factorized on the stack (small_lu) std::vector c(N, zeros(6)); @@ -183,7 +184,7 @@ ReturnMappingResult closest_point_return_mapping( r.error_history.push_back(err); r.error = err; if (err < precision) { - fill_tangent_pieces(K, D_j, c); + fill_tangent_pieces(K, std::move(D_j), c); r.converged = true; return r; } diff --git a/src/Continuum_mechanics/Umat/tangent_assembly.cpp b/src/Continuum_mechanics/Umat/tangent_assembly.cpp index b81c91099..d09d21a7a 100644 --- a/src/Continuum_mechanics/Umat/tangent_assembly.cpp +++ b/src/Continuum_mechanics/Umat/tangent_assembly.cpp @@ -25,6 +25,8 @@ #include #include +#include +#include #include namespace simcoon { @@ -65,19 +67,44 @@ ContinuumTangent assemble_tangent( } arma::mat invBhat = mask % invBbar; // zero out inactive rows/cols - // P = (L dPhi) invBhat^T: Bhat rows = criteria, columns = mechanisms - arma::mat DP(6, Nmech); - for (arma::uword m = 0; m < Nmech; ++m) DP.col(m) = dPhidsigma_l[m]; - const arma::mat P = (Lt_dPhi * DP) * invBhat.t(); // 6 \times Nmech - - // Stack \kappa^l into a 6\times Nmech matrix for the rank-Nmech update Lt = L - \kappa P^T. - arma::mat K6(6, Nmech); - for (arma::uword l = 0; l < Nmech; ++l) K6.col(l) = kappa_j[l]; + // elastic increment: every product below sums signed zeros, so Lt = L bit for bit; skipped when no term can be inf/NaN + if (!any(op) && Lt_dPhi.is_finite()) { + double max_dPhi = 0.; + bool finite = true; + for (arma::uword m = 0; m < Nmech; ++m) { + finite = finite && dPhidsigma_l[m].is_finite() && kappa_j[m].is_finite(); + if (finite) max_dPhi = std::max(max_dPhi, arma::abs(dPhidsigma_l[m]).max()); + } + if (finite && arma::abs(Lt_dPhi).max()*max_dPhi < 1.E300) { + ContinuumTangent out; + out.Lt = L; + out.P_epsilon.assign(Nmech, arma::zeros(6)); + out.invBhat = std::move(invBhat); + return out; + } + } + // P^l = sum_m invBhat(l,m) Lt_dPhi dPhi^m (Bhat rows = criteria, columns = mechanisms), then Lt = L - sum_l kappa^l P^l^T + assert(L.n_rows == 6 && L.n_cols == 6 && Lt_dPhi.n_rows == 6 && Lt_dPhi.n_cols == 6); + arma::mat LD(6, Nmech); + for (arma::uword m = 0; m < Nmech; ++m) mul6(Lt_dPhi.memptr(), dPhidsigma_l[m].memptr(), LD.colptr(m)); ContinuumTangent out; - out.Lt = L - K6 * P.t(); - out.P_epsilon.resize(Nmech); - for (arma::uword l = 0; l < Nmech; ++l) out.P_epsilon[l] = P.col(l); + out.P_epsilon.assign(Nmech, arma::vec(6, arma::fill::zeros)); + for (arma::uword l = 0; l < Nmech; ++l) { + double* p = out.P_epsilon[l].memptr(); + for (arma::uword m = 0; m < Nmech; ++m) { + const double b = invBhat(l, m); + const double* d = LD.colptr(m); + for (int i = 0; i < 6; ++i) p[i] += d[i]*b; + } + } + out.Lt = L; + for (arma::uword j = 0; j < 6; ++j) + for (arma::uword i = 0; i < 6; ++i) { + double s = 0.; + for (arma::uword l = 0; l < Nmech; ++l) s += kappa_j[l](i)*out.P_epsilon[l](j); + out.Lt(i, j) -= s; + } out.invBhat = std::move(invBhat); return out; } @@ -135,8 +162,8 @@ ContinuumTangent assemble_algorithmic_tangent( assert(dLambda_dsigma_l.size() == Nmech); // M = I + L \cdot \sum_j (\Delta s^j \cdot \partial \Lambda^j/\partial \sigma ), summed over the active set only. - const arma::mat I6 = arma::eye(6, 6); - arma::mat sumDsLambdaSigma = arma::zeros(6, 6); + const arma::mat::fixed<6,6> I6 = arma::eye(6, 6); + arma::mat::fixed<6,6> sumDsLambdaSigma = arma::zeros(6, 6); for (arma::uword j = 0; j < Nmech; ++j) { if (Ds_j(j) > simcoon::iota) { sumDsLambdaSigma += Ds_j(j) * dLambda_dsigma_l[j]; @@ -149,28 +176,33 @@ ContinuumTangent assemble_algorithmic_tangent( } // singular M (FD dLambda/dsigma near activation, LAPACK-dependent): continuum fallback - arma::mat Minv; - if (!arma::inv(Minv, I6 + L * sumDsLambdaSigma)) { + arma::mat::fixed<6,6> M = mul66(L, sumDsLambdaSigma); + M += I6; + small_lu<6> lu; + if (!lu.factor(M)) { return assemble_continuum_tangent(Bhat_continuum, kappa_j, dPhidsigma_l, Ds_j, L); } // Algorithmic operators: \tilde{L} = M^{-1} L, \tilde{\kappa}^j = M^{-1} \kappa^j, // \tilde{B}^{lj} = Bhat_continuum^{lj} + \partial \Phi^l \cdot (M^{-1} - I) \cdot \kappa^j (Bhat_continuum = \partial \Phi \cdot \kappa - K). - const arma::mat L_tilde = Minv * L; + arma::mat::fixed<6,6> L_tilde = L; + for (int c = 0; c < 6; ++c) lu.solve_in_place(L_tilde.colptr(c)); std::vector kappa_tilde(Nmech); for (arma::uword j = 0; j < Nmech; ++j) { - kappa_tilde[j] = Minv * kappa_j[j]; + kappa_tilde[j] = kappa_j[j]; + lu.solve_in_place(kappa_tilde[j].memptr()); } arma::mat Bhat_tilde = Bhat_continuum; - const arma::mat Minv_minus_I = Minv - I6; - for (arma::uword l = 0; l < Nmech; ++l) { - for (arma::uword j = 0; j < Nmech; ++j) { - Bhat_tilde(l, j) += arma::dot(dPhidsigma_l[l], Minv_minus_I * kappa_j[j]); + for (arma::uword j = 0; j < Nmech; ++j) { + const arma::vec::fixed<6> dkappa_j = kappa_tilde[j] - kappa_j[j]; // (M^-1 - I) kappa_j, shared by every row l + for (arma::uword l = 0; l < Nmech; ++l) { + Bhat_tilde(l, j) += arma::dot(dPhidsigma_l[l], dkappa_j); } } // L~ = M^-1 L is not symmetric: its transpose acts on dPhi - return assemble_tangent(Bhat_tilde, kappa_tilde, dPhidsigma_l, Ds_j, L_tilde, L_tilde.t()); + const arma::mat::fixed<6,6> L_tilde_t = L_tilde.t(); + return assemble_tangent(Bhat_tilde, kappa_tilde, dPhidsigma_l, Ds_j, L_tilde, L_tilde_t); } ContinuumTangent assemble_algorithmic_tangent( @@ -236,9 +268,12 @@ ContinuumTangent compute_tangent_operator( } if (tangent_mode == simcoon::tangent_algorithmic || tangent_mode == simcoon::tangent_closest_point) { // closest-point kernels use cpp_consistent_tangent() and never come here; for the others mode 3 = algorithmic - if (!dLambda_dsigma) { - // Flow independent of stress: the algorithmic operator IS the - // continuum one. + bool active = false; + for (arma::uword j = 0; j < Ds_j.n_elem; ++j) { + active = active || (Ds_j(j) > simcoon::iota); + } + if (!dLambda_dsigma || !active) { + // stress-independent flow or no active mechanism: the algorithmic operator IS the continuum one return assemble_continuum_tangent(Bhat, kappa_j, dPhidsigma_l, Ds_j, L); } return assemble_algorithmic_tangent(Bhat, kappa_j, dPhidsigma_l, Ds_j, L, diff --git a/src/Continuum_mechanics/Umat/umat_L_elastic.cpp b/src/Continuum_mechanics/Umat/umat_L_elastic.cpp index b5b7641c5..ef69cbc4a 100755 --- a/src/Continuum_mechanics/Umat/umat_L_elastic.cpp +++ b/src/Continuum_mechanics/Umat/umat_L_elastic.cpp @@ -131,7 +131,7 @@ void get_L_elastic(phase_characteristics &rve) } Lt_Self_Consistent(rve, n_matrix, false, self_consistent_start(rve)); umat_M->Lt = zeros(6,6); - for (auto r : rve.sub_phases) { + for (auto &r : rve.sub_phases) { umat_sub_phases_M = std::dynamic_pointer_cast(r.sptr_sv_global); umat_M->Lt += r.sptr_shape->concentration*(umat_sub_phases_M->Lt*r.sptr_multi->A); } @@ -190,7 +190,7 @@ void get_L_elastic(phase_characteristics &rve) // Compute the effective tangent modulus, and the effective stress umat_M->Lt = zeros(6,6); - for (auto r : rve.sub_phases) { + for (auto &r : rve.sub_phases) { umat_sub_phases_M = std::dynamic_pointer_cast(r.sptr_sv_global); umat_M->Lt += r.sptr_shape->concentration*(umat_sub_phases_M->Lt*r.sptr_multi->A); } diff --git a/src/Continuum_mechanics/Umat/umat_smart.cpp b/src/Continuum_mechanics/Umat/umat_smart.cpp index 942bbaece..4edc1a8cc 100644 --- a/src/Continuum_mechanics/Umat/umat_smart.cpp +++ b/src/Continuum_mechanics/Umat/umat_smart.cpp @@ -98,7 +98,7 @@ namespace simcoon{ void size_statev(phase_characteristics &rve, unsigned int &size) { - for (auto r:rve.sub_phases) { + for (auto &r:rve.sub_phases) { // by reference: a copy would duplicate the whole sub-tree size = size + r.sptr_sv_local->nstatev + 57; size_statev(r,size); } @@ -106,7 +106,7 @@ void size_statev(phase_characteristics &rve, unsigned int &size) { void statev_2_phases(phase_characteristics &rve, unsigned int &pos, const vec &statev) { - for(auto r : rve.sub_phases) { + for(auto &r : rve.sub_phases) { shared_ptr umat_phase_M = std::dynamic_pointer_cast(r.sptr_sv_global); unsigned int nstatev = umat_phase_M->statev.n_elem; @@ -130,7 +130,7 @@ void statev_2_phases(phase_characteristics &rve, unsigned int &pos, const vec &s void phases_2_statev(vec &statev, unsigned int &pos, const phase_characteristics &rve) { - for(auto r : rve.sub_phases) { + for(const auto &r : rve.sub_phases) { shared_ptr umat_phase_M = std::dynamic_pointer_cast(r.sptr_sv_global); unsigned int nstatev = umat_phase_M->statev.n_elem; @@ -229,7 +229,11 @@ const std::map &umat_conventions() } // namespace -umat_convention output_convention_of(const std::string &umat_name) +namespace { + +// The declaration itself, by reference into the table (no copy of its statev tensor list on the +// per-point finite dispatch) +const umat_convention &declared_convention(const std::string &umat_name) { const auto &conventions = umat_conventions(); auto it = conventions.find(umat_name); @@ -243,6 +247,13 @@ umat_convention output_convention_of(const std::string &umat_name) return it->second; } +} // namespace + +umat_convention output_convention_of(const std::string &umat_name) +{ + return declared_convention(umat_name); +} + bool stress_output_is_kirchhoff(const std::string &umat_name) { // shares the table instead of calling output_convention_of: no thrown exception per call diff --git a/src/Simulation/Maths/num_solve.cpp b/src/Simulation/Maths/num_solve.cpp index 134d6eac3..a8dcfa492 100755 --- a/src/Simulation/Maths/num_solve.cpp +++ b/src/Simulation/Maths/num_solve.cpp @@ -249,6 +249,40 @@ void Fischer_Burmeister_limits(const vec &Phi_p, const vec &Phi_l, const vec &Y_ } +namespace { + +// row i of the normalised FB system (Dp* = Dp |B_ii|): residual and Jacobian entry, bit for bit +inline double fb_m_value(const double &Phi, const double &Dpstar) +{ + if ((fabs(Phi) > 0.)&&(fabs(Dpstar) > 0.)) { + return sqrt(pow(Phi,2.) + pow(Dpstar,2.)) + Phi - Dpstar; + } + else if(fabs(Phi) > 0.) { + return sqrt(pow(Phi,2.)) + Phi; + } + else if(fabs(Dpstar) > 0.) { + return sqrt(pow(Dpstar,2.)) - Dpstar; + } + return 0.; +} + +inline double fb_m_jacobian(const double &Phi, const double &Dpstar, const double &dDpstar, const double &denom_ij, const bool &diag) +{ + const double delta = diag ? 1. : 0.; + if ((fabs(Phi) > 0.)&&(fabs(Dpstar) > 0.)) { + return (Phi/(sqrt(pow(Phi,2.) + pow(Dpstar,2.)))+1.)*denom_ij + delta*dDpstar*(Dpstar/(sqrt(pow(Phi,2.) + pow(Dpstar,2.))) - 1.); + } + else if(fabs(Phi) > 0.) { + return (Phi/(sqrt(pow(Phi,2.)))+1.)*denom_ij - delta*dDpstar; + } + else if(fabs(Dpstar) > 0.) { + return denom_ij + delta*dDpstar*(Dpstar/(sqrt(pow(Dpstar,2.))) - 1.); + } + return diag ? 1.E12 : 0.; +} + +} // namespace + double Fischer_Burmeister_residual(const vec &Phi, const vec &Y_crit, const mat &denom, const vec &Dp) { // Same branches and expressions as the FB vector of Fischer_Burmeister_m below. @@ -281,9 +315,44 @@ void Fischer_Burmeister_m(const vec &Phi, const vec &Y_crit, const mat &denom, v assert(fabs(Y_crit(i)) > 0); } + // one or two mechanisms: system on the stack, closed forms, no LAPACK call + if (n == 1 || n == 2) { + double FB[2], denomFB[2][2]; + for (int i=0; i simcoon::limit) { + dp(0) = -FB[0]/a; + } + } + else { + const double det_FB = denomFB[0][0]*denomFB[1][1] - denomFB[0][1]*denomFB[1][0]; + if (fabs(det_FB) > simcoon::limit) { + dp(0) = -(denomFB[1][1]*FB[0] - denomFB[0][1]*FB[1])/det_FB; // Cramer + dp(1) = -(denomFB[0][0]*FB[1] - denomFB[1][0]*FB[0])/det_FB; + } + } + //New update of the transformation/orientation multipliers + Dp += dp; + // the entering iterate's residual: Fischer_Burmeister_residual, kept in step + error = 0.; + for (int i=0; i 0.)&&(fabs(Dpstar(i)) > 0.)) { - FB(i) = sqrt(pow(Phi(i),2.) + pow(Dpstar(i),2.)) + Phi(i) - Dpstar(i); - } - else if(fabs(Phi(i)) > 0.) { - FB(i) = sqrt(pow(Phi(i),2.)) + Phi(i); - } - else if(fabs(Dpstar(i)) > 0.) { - FB(i) = sqrt(pow(Dpstar(i),2.)) - Dpstar(i); - } - else { - FB(i) = 0.; - } - + FB(i) = fb_m_value(Phi(i), Dpstar(i)); for (int j=0; j 0.)&&(fabs(Dpstar(i)) > 0.)) { - denomFB(i,j) = (Phi(i)/(sqrt(pow(Phi(i),2.) + pow(Dpstar(i),2.)))+1.)*denom(i,j) + delta(i,j)*dDpstar(i)*(Dpstar(i)/(sqrt(pow(Phi(i),2.) + pow(Dpstar(i),2.))) - 1.); - } - else if(fabs(Phi(i)) > 0.) { - denomFB(i,j) = (Phi(i)/(sqrt(pow(Phi(i),2.)))+1.)*denom(i,j) - delta(i,j)*dDpstar(i); - } - else if(fabs(Dpstar(i)) > 0.) { - denomFB(i,j) = denom(i,j) + delta(i,j)*dDpstar(i)*(Dpstar(i)/(sqrt(pow(Dpstar(i),2.))) - 1.); - } - else { - if (i==j) - denomFB(i,j) = 1.E12; - else - denomFB(i,j) = 0.; - } + denomFB(i,j) = fb_m_jacobian(Phi(i), Dpstar(i), dDpstar(i), denom(i,j), i == j); } - } - // One or two mechanisms (the common cases): closed forms, no LAPACK call. - if (n == 1) { - const double a = denomFB(0,0); - if (fabs(a) > simcoon::limit) { - dp.set_size(1); - dp(0) = -FB(0)/a; - } - else { - dp = zeros(n); - } - } - else if (n == 2) { - const double det_FB = denomFB(0,0)*denomFB(1,1) - denomFB(0,1)*denomFB(1,0); - if (fabs(det_FB) > simcoon::limit) { - dp.set_size(2); - dp(0) = -(denomFB(1,1)*FB(0) - denomFB(0,1)*FB(1))/det_FB; // Cramer - dp(1) = -(denomFB(0,0)*FB(1) - denomFB(1,0)*FB(0))/det_FB; + try { + if (fabs(det(denomFB)) > simcoon::limit) { + dp = -1.*solve(denomFB, FB); } else { dp = zeros(n); } + } catch (const std::runtime_error &e) { + cerr << "Error in det or inv: throw inv exception " << e.what() << endl; + throw simcoon::exception_det("Error in det function inside Fischer_Burmeister_m."); } - else { - try { - if (fabs(det(denomFB)) > simcoon::limit) { - dp = -1.*solve(denomFB, FB); - } - else { - dp = zeros(n); - } - } catch (const std::runtime_error &e) { - cerr << "Error in det or inv: throw inv exception " << e.what() << endl; - throw simcoon::exception_det("Error in det function inside Fischer_Burmeister_m."); - } - } - + //New update of the transformation/orientation multipliers Dp = Dp + dp; @@ -374,7 +391,7 @@ void Fischer_Burmeister_m(const vec &Phi, const vec &Y_crit, const mat &denom, v for (int i=0; i vs = fill_voigt_stress(as_matrix(), active); return vs * sigma; } @@ -747,8 +746,7 @@ vec Rotation::apply_strain(const vec& epsilon, bool active) const { if (epsilon.n_elem != 6) { throw invalid_argument("Strain vector must have 6 elements"); } - mat R = as_matrix(); - mat ve = fill_voigt_strain(R, active); + const mat::fixed<6,6> ve = fill_voigt_strain(as_matrix(), active); return ve * epsilon; } @@ -763,9 +761,10 @@ mat Rotation::apply_stiffness(const mat& L, bool active) const { if (L.n_rows != 6 || L.n_cols != 6) { throw invalid_argument("Stiffness matrix must be 6x6"); } - mat R = as_matrix(); - mat vs = fill_voigt_stress(R, active); - return vs * (L * trans(vs)); + // fixed-size operator and inner product (no heap temporaries), same association + const mat::fixed<6,6> vs = fill_voigt_stress(as_matrix(), active); + const mat::fixed<6,6> tmp = L * trans(vs); + return vs * tmp; } mat::fixed<6,6> Rotation::apply_compliance(const mat::fixed<6,6>& M, bool active) const { @@ -779,9 +778,9 @@ mat Rotation::apply_compliance(const mat& M, bool active) const { if (M.n_rows != 6 || M.n_cols != 6) { throw invalid_argument("Compliance matrix must be 6x6"); } - mat R = as_matrix(); - mat ve = fill_voigt_strain(R, active); - return ve * (M * trans(ve)); + const mat::fixed<6,6> ve = fill_voigt_strain(as_matrix(), active); + const mat::fixed<6,6> tmp = M * trans(ve); + return ve * tmp; } mat::fixed<6,6> Rotation::apply_strain_concentration(const mat::fixed<6,6>& A, bool active) const { @@ -796,10 +795,11 @@ mat Rotation::apply_strain_concentration(const mat& A, bool active) const { if (A.n_rows != 6 || A.n_cols != 6) { throw invalid_argument("Strain concentration tensor must be 6x6"); } - mat R = as_matrix(); - mat ve = fill_voigt_strain(R, active); - mat vs = fill_voigt_stress(R, active); - return ve * (A * trans(vs)); + const mat::fixed<3,3> R = as_matrix(); + const mat::fixed<6,6> ve = fill_voigt_strain(R, active); + const mat::fixed<6,6> vs = fill_voigt_stress(R, active); + const mat::fixed<6,6> tmp = A * trans(vs); + return ve * tmp; } mat::fixed<6,6> Rotation::apply_stress_concentration(const mat::fixed<6,6>& B, bool active) const { @@ -814,10 +814,11 @@ mat Rotation::apply_stress_concentration(const mat& B, bool active) const { if (B.n_rows != 6 || B.n_cols != 6) { throw invalid_argument("Stress concentration tensor must be 6x6"); } - mat R = as_matrix(); - mat vs = fill_voigt_stress(R, active); - mat ve = fill_voigt_strain(R, active); - return vs * (B * trans(ve)); + const mat::fixed<3,3> R = as_matrix(); + const mat::fixed<6,6> vs = fill_voigt_stress(R, active); + const mat::fixed<6,6> ve = fill_voigt_strain(R, active); + const mat::fixed<6,6> tmp = B * trans(ve); + return vs * tmp; } // ============================================================================= diff --git a/src/Simulation/Phase/phase_characteristics.cpp b/src/Simulation/Phase/phase_characteristics.cpp index 3c27476d7..a385be4bb 100755 --- a/src/Simulation/Phase/phase_characteristics.cpp +++ b/src/Simulation/Phase/phase_characteristics.cpp @@ -192,18 +192,11 @@ void phase_characteristics::to_start() { switch (sv_type) { - case 1: { - auto sv_M_g = std::dynamic_pointer_cast(sptr_sv_global); - auto sv_M_l = std::dynamic_pointer_cast(sptr_sv_local); - sv_M_g->to_start(); - sv_M_l->to_start(); - break; - } + case 1: case 2: { - auto sv_T_g = std::dynamic_pointer_cast(sptr_sv_global); - auto sv_T_l = std::dynamic_pointer_cast(sptr_sv_local); - sv_T_g->to_start(); - sv_T_l->to_start(); + //virtual: the rollback of state_variables_M or state_variables_T + sptr_sv_global->to_start(); + sptr_sv_local->to_start(); break; } default: { @@ -223,18 +216,11 @@ void phase_characteristics::set_start(const int &corate_type) //---------------------------------------------------------------------- { switch (sv_type) { - case 1: { - auto sv_M_g = std::dynamic_pointer_cast(sptr_sv_global); - auto sv_M_l = std::dynamic_pointer_cast(sptr_sv_local); - sv_M_g->set_start(corate_type); - sv_M_l->set_start(corate_type); - break; - } + case 1: case 2: { - auto sv_T_g = std::dynamic_pointer_cast(sptr_sv_global); - auto sv_T_l = std::dynamic_pointer_cast(sptr_sv_local); - sv_T_g->set_start(corate_type); - sv_T_l->set_start(corate_type); + //virtual: the acceptance of state_variables_M or state_variables_T + sptr_sv_global->set_start(corate_type); + sptr_sv_local->set_start(corate_type); break; } default: { @@ -255,15 +241,12 @@ void phase_characteristics::local2global() //Switch case for the state_variables type of the phase switch (sv_type) { case 1: { - auto sv_M_g = std::dynamic_pointer_cast(sptr_sv_global); - auto sv_M_l = std::dynamic_pointer_cast(sptr_sv_local); - sv_M_g->rotate_l2g(*sv_M_l, sptr_matprops->frame()); + //casts of the references: no shared_ptr copies (atomic counts) on this per-call path + dynamic_cast(*sptr_sv_global).rotate_l2g(dynamic_cast(*sptr_sv_local), sptr_matprops->frame()); break; } case 2: { - auto sv_T_g = std::dynamic_pointer_cast(sptr_sv_global); - auto sv_T_l = std::dynamic_pointer_cast(sptr_sv_local); - sv_T_g->rotate_l2g(*sv_T_l, sptr_matprops->frame()); + dynamic_cast(*sptr_sv_global).rotate_l2g(dynamic_cast(*sptr_sv_local), sptr_matprops->frame()); break; } default: { @@ -281,15 +264,12 @@ void phase_characteristics::global2local() //Switch case for the state_variables type of the phase switch (sv_type) { case 1: { - auto sv_M_g = std::dynamic_pointer_cast(sptr_sv_global); - auto sv_M_l = std::dynamic_pointer_cast(sptr_sv_local); - sv_M_l->rotate_g2l(*sv_M_g, sptr_matprops->frame()); + //casts of the references: no shared_ptr copies (atomic counts) on this per-call path + dynamic_cast(*sptr_sv_local).rotate_g2l(dynamic_cast(*sptr_sv_global), sptr_matprops->frame()); break; } case 2: { - auto sv_T_g = std::dynamic_pointer_cast(sptr_sv_global); - auto sv_T_l = std::dynamic_pointer_cast(sptr_sv_local); - sv_T_l->rotate_g2l(*sv_T_g, sptr_matprops->frame()); + dynamic_cast(*sptr_sv_local).rotate_g2l(dynamic_cast(*sptr_sv_global), sptr_matprops->frame()); break; } default: { @@ -589,7 +569,7 @@ void phase_characteristics::output(const solver_output &so, const int &kblock, c } *sptr_out_global << endl; - for(auto r : sub_phases) { + for(auto &r : sub_phases) { r.output(so, kblock, kcycle, kstep, kinc, Time, "global"); } } @@ -796,7 +776,7 @@ void phase_characteristics::output(const solver_output &so, const int &kblock, c } *sptr_out_local << endl; - for(auto r : sub_phases) { + for(auto &r : sub_phases) { r.output(so, kblock, kcycle, kstep, kinc, Time, "local"); } @@ -821,7 +801,7 @@ ostream& operator << (ostream& s, const phase_characteristics& pc) s << "Display local state variables:\n"; s << *pc.sptr_sv_local; - for(auto r : pc.sub_phases) { + for(const auto &r : pc.sub_phases) { s << r; } diff --git a/src/Simulation/Phase/state_variables.cpp b/src/Simulation/Phase/state_variables.cpp index 1ea756222..20508ffab 100755 --- a/src/Simulation/Phase/state_variables.cpp +++ b/src/Simulation/Phase/state_variables.cpp @@ -440,6 +440,8 @@ state_variables& state_variables::rotate_l2g(const state_variables& sv, const fr statev = sv.statev; statev_start = sv.statev_start; + if (frame.is_identity()) //every rotation below is then a no-op + return *this; frame.rotate_strain(Etot); frame.rotate_strain(DEtot); frame.rotate_strain(etot); @@ -485,6 +487,8 @@ state_variables& state_variables::rotate_g2l(const state_variables& sv, const fr statev_start = sv.statev_start; // inverse operators: global -> local + if (frame.is_identity()) //every rotation below is then a no-op + return *this; frame.rotate_strain(Etot, frame_rotation::inverse); frame.rotate_strain(DEtot, frame_rotation::inverse); frame.rotate_strain(etot, frame_rotation::inverse); diff --git a/src/Simulation/Solver/solver.cpp b/src/Simulation/Solver/solver.cpp index b410dc128..7fd1cda88 100755 --- a/src/Simulation/Solver/solver.cpp +++ b/src/Simulation/Solver/solver.cpp @@ -21,6 +21,7 @@ ///@version 1.9 #include +#include #include #include #include @@ -174,6 +175,23 @@ void restore_box_tangent(material_tangent_state &mt, simcoon::state_variables_M mt.held = false; } +// The rollback the Newton loops skip on their first trial (see the to_start calls below) relies +// on this invariant: every attempt of an increment begins at the start state, the current fields +// that to_start restores (PKII, tau, sigma, statev, and per type sigma_in, Wm, Wt; recursively in +// the sub-phases) being bit for bit their *_start copies. set_start (the previous increment +// accepted, or the priming call of a block) and assess_inc (set_start, or to_start on a cut, also +// after a caught exception) both end there, and nothing in between writes those fields: the solver +// only sets DEtot, Detot, DT, DR, F0, F1, U1 and Lt, and the sinks read. Debug check on the +// global state variables. +[[maybe_unused]] bool at_start_state(const simcoon::state_variables &sv) { + auto same = [](const arma::mat &a, const arma::mat &b) { + return a.n_elem == b.n_elem + && (a.n_elem == 0 || std::memcmp(a.memptr(), b.memptr(), a.n_elem*sizeof(double)) == 0); + }; + return same(sv.PKII, sv.PKII_start) && same(sv.tau, sv.tau_start) + && same(sv.sigma, sv.sigma_start) && same(sv.statev, sv.statev_start); +} + } // namespace namespace simcoon{ @@ -591,7 +609,9 @@ int solver_run(std::vector &blocks, const double &T_init, const solver_ou DTime = Dtinc*sptr_meca->times(inc); finite_kinematics(*sv_M, blocks[i].control_type, sptr_meca->BC_w, sptr_meca->BC_R, DTime, corate_type, DR); } - rve.to_start(); + // no rve.to_start(): the single kernel call of this path is the first trial of the + // attempt, which begins at the start state (invariant: see at_start_state) + assert(at_start_state(*rve.sptr_sv_global)); run_umat_M(rve, sv_M->DR, Time, DTime, ndi, nshr, start, solver_type, blocks[i].control_type, corate_type, tnew_dt); if (!sv_M->tau.is_finite()) { // non-finite answer: cut, or give up at Dn_mini if (Dtinc_cur == sptr_meca->Dn_mini) { @@ -655,7 +675,15 @@ int solver_run(std::vector &blocks, const double &T_init, const solver_ou else { finite_kinematics(*sv_M, blocks[i].control_type, sptr_meca->BC_w, sptr_meca->BC_R, DTime, corate_type, DR); } - rve.to_start(); + // compteur counts the trials of this attempt (0 on entry, see the end of the + // attempt and step_cut_or_rethrow): the first one is already at the start + // state (invariant: see at_start_state), the next ones roll back + if (compteur > 0) { + rve.to_start(); + } + else { + assert(at_start_state(*rve.sptr_sv_global)); + } run_umat_M(rve, sv_M->DR, Time, DTime, ndi, nshr, start, solver_type, blocks[i].control_type, corate_type, tnew_dt, ask_material_tangent); if (ask_material_tangent) { native_Lt.held = true; @@ -1077,7 +1105,14 @@ int solver_run(std::vector &blocks, const double &T_init, const solver_ou if (finite_ct) finite_kinematics(*sv_T, blocks[i].control_type, sptr_thermomeca->BC_w, sptr_thermomeca->BC_R, DTime, corate_type, DR); - rve.to_start(); + // as the mechanical loop: the first trial of the attempt is at the start state + // (invariant: see at_start_state), the next ones roll back + if (compteur > 0) { + rve.to_start(); + } + else { + assert(at_start_state(*rve.sptr_sv_global)); + } run_umat_T(rve, (blocks[i].control_type == 1) ? DR : sv_T->DR, Time, DTime, ndi, nshr, start, solver_type, blocks[i].control_type, corate_type, tnew_dt); // -r - DTime Q_imposed = 0, r the heat of the increment diff --git a/src/Simulation/Solver/solver_assembly.cpp b/src/Simulation/Solver/solver_assembly.cpp index 4c89e2025..52444b279 100644 --- a/src/Simulation/Solver/solver_assembly.cpp +++ b/src/Simulation/Solver/solver_assembly.cpp @@ -37,11 +37,13 @@ namespace simcoon{ void Lt_2_K(const mat &Lt, mat &K, const Col &cBC_meca, const double &lambda) { - K = zeros(6,6); + K.zeros(6,6); + // element by element: a subview row copy per stress-controlled row cost more than the copy for (int i=0; i<6; i++) { if (cBC_meca(i)) { - K.row(i) = Lt.row(i); + for (int j=0; j<6; j++) + K(i,j) = Lt(i,j); } else K(i,i) = lambda;