feat: MITC4 drilling 두 방식과 직접 스프링 기본값 구현
This commit is contained in:
@@ -0,0 +1,26 @@
|
|||||||
|
#ifndef FESA_ELEMENTS_MITC4_DRILLING_MODE_H_
|
||||||
|
#define FESA_ELEMENTS_MITC4_DRILLING_MODE_H_
|
||||||
|
|
||||||
|
namespace fesa {
|
||||||
|
|
||||||
|
/// @brief Identifies the immutable numerical MITC4 drilling formulation.
|
||||||
|
enum class Mitc4DrillingMode { kDirectSpring, kLegacyCorotatedTwist };
|
||||||
|
|
||||||
|
/// @brief Returns the stable storage name of a supported drilling formulation.
|
||||||
|
constexpr const char* Mitc4DrillingModeName(Mitc4DrillingMode mode) noexcept {
|
||||||
|
switch (mode) {
|
||||||
|
case Mitc4DrillingMode::kDirectSpring:
|
||||||
|
return "direct-initial-director-spring";
|
||||||
|
case Mitc4DrillingMode::kLegacyCorotatedTwist:
|
||||||
|
return "legacy-corotated-twist";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace mitc4_drilling_internal {
|
||||||
|
struct TestAccess;
|
||||||
|
} // namespace mitc4_drilling_internal
|
||||||
|
|
||||||
|
} // namespace fesa
|
||||||
|
|
||||||
|
#endif // FESA_ELEMENTS_MITC4_DRILLING_MODE_H_
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "fesa/core/status.h"
|
#include "fesa/core/status.h"
|
||||||
#include "fesa/elements/element.h"
|
#include "fesa/elements/element.h"
|
||||||
#include "fesa/elements/mitc4_shell_definition.h"
|
#include "fesa/elements/mitc4_shell_definition.h"
|
||||||
|
#include "fesa/elements/mitc4_drilling_mode.h"
|
||||||
#include "fesa/materials/isotropic_linear_elastic_material.h"
|
#include "fesa/materials/isotropic_linear_elastic_material.h"
|
||||||
#include "fesa/math/matrix.h"
|
#include "fesa/math/matrix.h"
|
||||||
#include "fesa/math/vector.h"
|
#include "fesa/math/vector.h"
|
||||||
@@ -92,6 +93,11 @@ class Mitc4Shell final : public Element {
|
|||||||
std::array<std::array<double, 3>, 4> initial_directors,
|
std::array<std::array<double, 3>, 4> initial_directors,
|
||||||
const ShellSection& section, const LinearElasticMaterial& material);
|
const ShellSection& section, const LinearElasticMaterial& material);
|
||||||
|
|
||||||
|
/// @brief Returns this immutable kernel's production drilling formulation.
|
||||||
|
[[nodiscard]] Mitc4DrillingMode DrillingMode() const noexcept {
|
||||||
|
return drilling_mode_;
|
||||||
|
}
|
||||||
|
|
||||||
/// @brief Returns the factory-bound MITC4 DOF order.
|
/// @brief Returns the factory-bound MITC4 DOF order.
|
||||||
const ElementDofLayout& DofLayout() const noexcept override;
|
const ElementDofLayout& DofLayout() const noexcept override;
|
||||||
|
|
||||||
@@ -163,6 +169,7 @@ class Mitc4Shell final : public Element {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
friend class ElementFactory;
|
friend class ElementFactory;
|
||||||
|
friend struct mitc4_drilling_internal::TestAccess;
|
||||||
|
|
||||||
/// @brief Binds Domain identity after the numerical candidate is valid.
|
/// @brief Binds Domain identity after the numerical candidate is valid.
|
||||||
void BindRuntime(ElementDofLayout layout, EntityIndex element_index,
|
void BindRuntime(ElementDofLayout layout, EntityIndex element_index,
|
||||||
@@ -203,6 +210,7 @@ class Mitc4Shell final : public Element {
|
|||||||
double thickness_;
|
double thickness_;
|
||||||
double youngs_modulus_;
|
double youngs_modulus_;
|
||||||
double poisson_ratio_;
|
double poisson_ratio_;
|
||||||
|
Mitc4DrillingMode drilling_mode_;
|
||||||
SourceLocation source_location_;
|
SourceLocation source_location_;
|
||||||
std::string identity_;
|
std::string identity_;
|
||||||
ElementDofLayout dof_layout_;
|
ElementDofLayout dof_layout_;
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ struct NonlinearMitc4InternalEquilibrium {
|
|||||||
Matrix drilling_metric{0U, 0U};
|
Matrix drilling_metric{0U, 0U};
|
||||||
double drilling_energy = 0.0;
|
double drilling_energy = 0.0;
|
||||||
Vector force_directional_second{0U};
|
Vector force_directional_second{0U};
|
||||||
|
/// @brief Stores D2(f_drill)[v,v] separately; direct springs give exact zero.
|
||||||
|
Vector drilling_force_directional_second{0U};
|
||||||
Vector drilling_constraint{0U};
|
Vector drilling_constraint{0U};
|
||||||
Matrix drilling_constraint_gradient{0U, 0U};
|
Matrix drilling_constraint_gradient{0U, 0U};
|
||||||
std::array<Matrix, 4> drilling_constraint_hessian{
|
std::array<Matrix, 4> drilling_constraint_hessian{
|
||||||
@@ -104,6 +106,11 @@ class NonlinearMitc4Shell {
|
|||||||
std::array<ShellNodeInitialFrame, 4> initial_frames,
|
std::array<ShellNodeInitialFrame, 4> initial_frames,
|
||||||
const ShellSection& section, const LinearElasticMaterial& material);
|
const ShellSection& section, const LinearElasticMaterial& material);
|
||||||
|
|
||||||
|
/// @brief Returns this immutable kernel's numerical drilling formulation.
|
||||||
|
[[nodiscard]] Mitc4DrillingMode DrillingMode() const noexcept {
|
||||||
|
return drilling_mode_;
|
||||||
|
}
|
||||||
|
|
||||||
/// @brief Evaluates trial physical kinematics and fixed recovery rows.
|
/// @brief Evaluates trial physical kinematics and fixed recovery rows.
|
||||||
/// @param trial Global nodal translation and fixed-global rotation vectors.
|
/// @param trial Global nodal translation and fixed-global rotation vectors.
|
||||||
/// @return Physical recovery or a fail-closed trial diagnostic.
|
/// @return Physical recovery or a fail-closed trial diagnostic.
|
||||||
@@ -134,6 +141,7 @@ class NonlinearMitc4Shell {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
friend struct nonlinear_mitc4_internal::ConfigurationSnapshot;
|
friend struct nonlinear_mitc4_internal::ConfigurationSnapshot;
|
||||||
|
friend struct mitc4_drilling_internal::TestAccess;
|
||||||
|
|
||||||
/// @brief Retains validated fixed-reference data without analysis ownership.
|
/// @brief Retains validated fixed-reference data without analysis ownership.
|
||||||
NonlinearMitc4Shell(std::array<Vector3, 4> coordinates,
|
NonlinearMitc4Shell(std::array<Vector3, 4> coordinates,
|
||||||
@@ -155,6 +163,7 @@ class NonlinearMitc4Shell {
|
|||||||
double youngs_modulus_;
|
double youngs_modulus_;
|
||||||
double poisson_ratio_;
|
double poisson_ratio_;
|
||||||
double drilling_stiffness_;
|
double drilling_stiffness_;
|
||||||
|
Mitc4DrillingMode drilling_mode_;
|
||||||
SourceLocation source_location_;
|
SourceLocation source_location_;
|
||||||
std::string identity_;
|
std::string identity_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#ifndef FESA_SRC_FESA_ELEMENTS_MITC4_DRILLING_INTERNAL_H_
|
||||||
|
#define FESA_SRC_FESA_ELEMENTS_MITC4_DRILLING_INTERNAL_H_
|
||||||
|
|
||||||
|
#include "fesa/elements/nonlinear_mitc4_shell.h"
|
||||||
|
|
||||||
|
namespace fesa::mitc4_drilling_internal {
|
||||||
|
|
||||||
|
// The sole production selection. Change this constant and rebuild all targets.
|
||||||
|
inline constexpr Mitc4DrillingMode kProductionDrillingMode =
|
||||||
|
Mitc4DrillingMode::kDirectSpring;
|
||||||
|
|
||||||
|
/// @brief Provides explicit alternate formulations only for internal tests.
|
||||||
|
/// @note Production factories use the single compile-time selection above.
|
||||||
|
struct TestAccess {
|
||||||
|
static Mitc4Shell WithMode(Mitc4Shell shell, Mitc4DrillingMode mode) {
|
||||||
|
shell.drilling_mode_ = mode;
|
||||||
|
return shell;
|
||||||
|
}
|
||||||
|
static NonlinearMitc4Shell WithMode(NonlinearMitc4Shell shell,
|
||||||
|
Mitc4DrillingMode mode) {
|
||||||
|
shell.drilling_mode_ = mode;
|
||||||
|
return shell;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace fesa::mitc4_drilling_internal
|
||||||
|
|
||||||
|
#endif // FESA_SRC_FESA_ELEMENTS_MITC4_DRILLING_INTERNAL_H_
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
#include "math/finite_values.h"
|
#include "math/finite_values.h"
|
||||||
#include "math/vector3_operations.h"
|
#include "math/vector3_operations.h"
|
||||||
#include "elements/shell_kernel_values.h"
|
#include "elements/shell_kernel_values.h"
|
||||||
|
#include "elements/mitc4_drilling_internal.h"
|
||||||
|
|
||||||
namespace fesa {
|
namespace fesa {
|
||||||
|
|
||||||
@@ -1002,6 +1003,7 @@ Mitc4Shell::Mitc4Shell(std::array<Vector3, 4> coordinates,
|
|||||||
thickness_{thickness},
|
thickness_{thickness},
|
||||||
youngs_modulus_{youngs_modulus},
|
youngs_modulus_{youngs_modulus},
|
||||||
poisson_ratio_{poisson_ratio},
|
poisson_ratio_{poisson_ratio},
|
||||||
|
drilling_mode_{mitc4_drilling_internal::kProductionDrillingMode},
|
||||||
source_location_{std::move(source_location)},
|
source_location_{std::move(source_location)},
|
||||||
identity_{std::move(identity)} {}
|
identity_{std::move(identity)} {}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ namespace fesa::nonlinear_mitc4_internal {
|
|||||||
/// key.
|
/// key.
|
||||||
struct ConfigurationSnapshot {
|
struct ConfigurationSnapshot {
|
||||||
std::array<double, 64> values{};
|
std::array<double, 64> values{};
|
||||||
|
Mitc4DrillingMode drilling_mode = Mitc4DrillingMode::kDirectSpring;
|
||||||
SourceLocation source{};
|
SourceLocation source{};
|
||||||
std::string identity;
|
std::string identity;
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
#include "fesa/elements/finite_rotation.h"
|
#include "fesa/elements/finite_rotation.h"
|
||||||
#include "elements/nonlinear_mitc4_configuration_internal.h"
|
#include "elements/nonlinear_mitc4_configuration_internal.h"
|
||||||
|
#include "elements/mitc4_drilling_internal.h"
|
||||||
#include "math/dense_blas_internal.h"
|
#include "math/dense_blas_internal.h"
|
||||||
#include "math/jet_arithmetic.h"
|
#include "math/jet_arithmetic.h"
|
||||||
#include "math/jet_linear_solve.h"
|
#include "math/jet_linear_solve.h"
|
||||||
@@ -39,6 +40,7 @@ nonlinear_mitc4_internal::ConfigurationSnapshot::Read(
|
|||||||
result.values[index++] = element.youngs_modulus_;
|
result.values[index++] = element.youngs_modulus_;
|
||||||
result.values[index++] = element.poisson_ratio_;
|
result.values[index++] = element.poisson_ratio_;
|
||||||
result.values[index] = element.drilling_stiffness_;
|
result.values[index] = element.drilling_stiffness_;
|
||||||
|
result.drilling_mode = element.drilling_mode_;
|
||||||
result.source = element.source_location_;
|
result.source = element.source_location_;
|
||||||
result.identity = element.identity_;
|
result.identity = element.identity_;
|
||||||
return result;
|
return result;
|
||||||
@@ -1227,8 +1229,91 @@ Jet DrillSpinRate(const JetVector3& phi, const Vector3& director) {
|
|||||||
return Reciprocal(Sqrt(Dot(preimage, preimage)));
|
return Reciprocal(Sqrt(Dot(preimage, preimage)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @brief Uses precisely the current inertia kernel's cubic rotation maps.
|
||||||
|
/// @note This is deliberately distinct from the legacy analytic null. Keep the
|
||||||
|
/// cubic Exp/Jacobian order synchronized with EvaluateDynamicRotation; it is
|
||||||
|
/// not an approximation to be silently replaced by the exact exponential
|
||||||
|
/// inverse.
|
||||||
|
JetVector3 InertiaDrillNull(const JetVector3& phi, const Vector3& director) {
|
||||||
|
const JetMatrix3 skew = Skew(phi);
|
||||||
|
JetMatrix3 rotation = Identity();
|
||||||
|
JetMatrix3 jacobian = Identity();
|
||||||
|
JetMatrix3 term = Identity();
|
||||||
|
double rotation_factorial = 1.0;
|
||||||
|
double jacobian_factorial = 1.0;
|
||||||
|
for (std::size_t order = 1U; order <= 3U; ++order) {
|
||||||
|
term = Multiply(term, skew);
|
||||||
|
rotation_factorial *= static_cast<double>(order);
|
||||||
|
jacobian_factorial *= static_cast<double>(order + 1U);
|
||||||
|
rotation = Add(rotation, Scale(1.0 / rotation_factorial, term));
|
||||||
|
jacobian = Add(jacobian, Scale(1.0 / jacobian_factorial, term));
|
||||||
|
}
|
||||||
|
const JetVector3 rhs = Multiply(rotation, ConstantVector(director));
|
||||||
|
Matrix primal{3U, 3U};
|
||||||
|
for (std::size_t row = 0U; row < 3U; ++row)
|
||||||
|
for (std::size_t column = 0U; column < 3U; ++column)
|
||||||
|
primal(row, column) = jacobian[row][column].value;
|
||||||
|
const auto factor = dense_blas_internal::DenseFactor::Lu(primal);
|
||||||
|
const std::array<std::size_t, 4> channels{1U, kGlobalDofCount + 1U,
|
||||||
|
kHessianSize + 1U + kGlobalDofCount,
|
||||||
|
kGlobalDofCount};
|
||||||
|
JetVector3 preimage{};
|
||||||
|
if (!factor.HasValue() ||
|
||||||
|
!math_internal::SolveJetAction(jacobian, rhs, factor.Value(), channels,
|
||||||
|
Constant, ReadChannel, WriteChannel,
|
||||||
|
preimage)) {
|
||||||
|
preimage.fill(Constant(std::numeric_limits<double>::quiet_NaN()));
|
||||||
|
return preimage;
|
||||||
|
}
|
||||||
|
return Scale(Reciprocal(Sqrt(Dot(preimage, preimage))), preimage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Evaluates the fixed initial-director spring without corotation.
|
||||||
|
Jet DirectDrillingEnergy(
|
||||||
|
const std::array<Vector3, kNodeCount>& initial_directors,
|
||||||
|
const JetTrial& trial, double drilling_stiffness, Matrix* metric,
|
||||||
|
std::array<Jet, 4>* constraints) {
|
||||||
|
Jet energy{};
|
||||||
|
double* tangent = energy.second.Data();
|
||||||
|
for (std::size_t node = 0U; node < kNodeCount; ++node) {
|
||||||
|
const auto d0 = ConstantVector(initial_directors[node]);
|
||||||
|
const Jet gamma = Dot(d0, trial.rotation[node]);
|
||||||
|
// Retain the validated spring scale through subnormal intermediates.
|
||||||
|
// Fill its linear derivatives explicitly, without differentiating a
|
||||||
|
// floating half-energy that can round kd before doubling it again.
|
||||||
|
energy.value += 0.5 * gamma.value * (drilling_stiffness * gamma.value);
|
||||||
|
energy.directional_first +=
|
||||||
|
drilling_stiffness * gamma.value * gamma.directional_first;
|
||||||
|
energy.directional_second +=
|
||||||
|
drilling_stiffness * gamma.directional_first * gamma.directional_first;
|
||||||
|
for (std::size_t i = 0U; i < 3U; ++i) {
|
||||||
|
const auto row = 6U * node + 3U + i;
|
||||||
|
energy.first[row] =
|
||||||
|
drilling_stiffness * gamma.value * initial_directors[node][i];
|
||||||
|
energy.mixed_first[row] = drilling_stiffness * gamma.directional_first *
|
||||||
|
initial_directors[node][i];
|
||||||
|
}
|
||||||
|
// D2 f_drill[v,v] is identically zero; mixed_second retains exact zeros.
|
||||||
|
if (constraints != nullptr) {
|
||||||
|
const auto null =
|
||||||
|
InertiaDrillNull(trial.rotation[node], initial_directors[node]);
|
||||||
|
(*constraints)[node] = drilling_stiffness * gamma * Dot(null, d0);
|
||||||
|
}
|
||||||
|
for (std::size_t i = 0U; i < 3U; ++i)
|
||||||
|
for (std::size_t j = 0U; j < 3U; ++j) {
|
||||||
|
const auto row = 6U * node + 3U + i;
|
||||||
|
const auto column = 6U * node + 3U + j;
|
||||||
|
const double value = drilling_stiffness * initial_directors[node][i] *
|
||||||
|
initial_directors[node][j];
|
||||||
|
tangent[row * kGlobalDofCount + column] = value;
|
||||||
|
(*metric)(row, column) = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return energy;
|
||||||
|
}
|
||||||
|
|
||||||
/// @brief Evaluates the objective numerical drilling potential and variations.
|
/// @brief Evaluates the objective numerical drilling potential and variations.
|
||||||
Jet DrillingEnergy(const std::array<Vector3, kNodeCount>& coordinates,
|
Jet LegacyDrillingEnergy(const std::array<Vector3, kNodeCount>& coordinates,
|
||||||
const std::array<Vector3, kNodeCount>& initial_directors,
|
const std::array<Vector3, kNodeCount>& initial_directors,
|
||||||
const JetTrial& trial, const double thickness,
|
const JetTrial& trial, const double thickness,
|
||||||
const double drilling_stiffness, bool& valid, Matrix* metric,
|
const double drilling_stiffness, bool& valid, Matrix* metric,
|
||||||
@@ -1323,6 +1408,7 @@ NonlinearMitc4Shell::NonlinearMitc4Shell(
|
|||||||
youngs_modulus_{youngs_modulus},
|
youngs_modulus_{youngs_modulus},
|
||||||
poisson_ratio_{poisson_ratio},
|
poisson_ratio_{poisson_ratio},
|
||||||
drilling_stiffness_{drilling_stiffness},
|
drilling_stiffness_{drilling_stiffness},
|
||||||
|
drilling_mode_{mitc4_drilling_internal::kProductionDrillingMode},
|
||||||
source_location_{std::move(source_location)},
|
source_location_{std::move(source_location)},
|
||||||
identity_{std::move(identity)} {}
|
identity_{std::move(identity)} {}
|
||||||
|
|
||||||
@@ -1640,7 +1726,10 @@ NonlinearMitc4Shell::EvaluateExternalDrillingConstraint(
|
|||||||
jet_phi[component] = Variable(phi[component], component,
|
jet_phi[component] = Variable(phi[component], component,
|
||||||
phi_dot[component]);
|
phi_dot[component]);
|
||||||
}
|
}
|
||||||
const JetVector3 null_direction = DrillNull(jet_phi, initial_directors_[node]);
|
const JetVector3 null_direction =
|
||||||
|
drilling_mode_ == Mitc4DrillingMode::kDirectSpring
|
||||||
|
? InertiaDrillNull(jet_phi, initial_directors_[node])
|
||||||
|
: DrillNull(jet_phi, initial_directors_[node]);
|
||||||
const Jet scalar = -1.0 * Dot(null_direction, ConstantVector(force));
|
const Jet scalar = -1.0 * Dot(null_direction, ConstantVector(force));
|
||||||
const Jet scalar_time =
|
const Jet scalar_time =
|
||||||
-1.0 * Dot(null_direction, ConstantVector(force_rate));
|
-1.0 * Dot(null_direction, ConstantVector(force_rate));
|
||||||
@@ -1741,10 +1830,17 @@ Result<NonlinearMitc4InternalEquilibrium> EvaluateInternalResidualAndTangent(
|
|||||||
element.poisson_ratio_, valid);
|
element.poisson_ratio_, valid);
|
||||||
Matrix drilling_metric{kGlobalDofCount, kGlobalDofCount};
|
Matrix drilling_metric{kGlobalDofCount, kGlobalDofCount};
|
||||||
std::array<Jet, 4> constraints{};
|
std::array<Jet, 4> constraints{};
|
||||||
const Jet drilling = DrillingEnergy(
|
const Jet drilling =
|
||||||
element.coordinates_, element.initial_directors_, jet_trial,
|
element.drilling_mode_ == Mitc4DrillingMode::kDirectSpring
|
||||||
element.thickness_, element.drilling_stiffness_, valid, &drilling_metric,
|
? DirectDrillingEnergy(
|
||||||
coordinate_direction != nullptr ? &constraints : nullptr);
|
element.initial_directors_, jet_trial,
|
||||||
|
element.drilling_stiffness_, &drilling_metric,
|
||||||
|
coordinate_direction != nullptr ? &constraints : nullptr)
|
||||||
|
: LegacyDrillingEnergy(
|
||||||
|
element.coordinates_, element.initial_directors_, jet_trial,
|
||||||
|
element.thickness_, element.drilling_stiffness_, valid,
|
||||||
|
&drilling_metric,
|
||||||
|
coordinate_direction != nullptr ? &constraints : nullptr);
|
||||||
const Jet internal_energy = physical + drilling;
|
const Jet internal_energy = physical + drilling;
|
||||||
if (!valid || !IsFinite(internal_energy)) {
|
if (!valid || !IsFinite(internal_energy)) {
|
||||||
return Failure<NonlinearMitc4InternalEquilibrium>(
|
return Failure<NonlinearMitc4InternalEquilibrium>(
|
||||||
@@ -1815,10 +1911,13 @@ Result<NonlinearMitc4InternalEquilibrium> EvaluateInternalResidualAndTangent(
|
|||||||
equilibrium.drilling_metric = std::move(drilling_metric);
|
equilibrium.drilling_metric = std::move(drilling_metric);
|
||||||
equilibrium.drilling_energy = drilling.value;
|
equilibrium.drilling_energy = drilling.value;
|
||||||
equilibrium.force_directional_second = Vector{kGlobalDofCount};
|
equilibrium.force_directional_second = Vector{kGlobalDofCount};
|
||||||
|
equilibrium.drilling_force_directional_second = Vector{kGlobalDofCount};
|
||||||
for (std::size_t index = 0U; index < kGlobalDofCount; ++index) {
|
for (std::size_t index = 0U; index < kGlobalDofCount; ++index) {
|
||||||
equilibrium.internal_force[index] = equilibrium.internal_residual[index];
|
equilibrium.internal_force[index] = equilibrium.internal_residual[index];
|
||||||
equilibrium.physical_force[index] = physical.first[index];
|
equilibrium.physical_force[index] = physical.first[index];
|
||||||
equilibrium.drilling_force[index] = drilling.first[index];
|
equilibrium.drilling_force[index] = drilling.first[index];
|
||||||
|
equilibrium.drilling_force_directional_second[index] =
|
||||||
|
drilling.mixed_second[index];
|
||||||
equilibrium.force_directional_second[index] =
|
equilibrium.force_directional_second[index] =
|
||||||
internal_energy.mixed_second[index];
|
internal_energy.mixed_second[index];
|
||||||
for (std::size_t column = 0U; column < kGlobalDofCount; ++column) {
|
for (std::size_t column = 0U; column < kGlobalDofCount; ++column) {
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ TEST(ElementCapabilities, ShellTrialPreservesCompleteDirectionalResponseAndFailu
|
|||||||
Same(a.drilling_metric, b.drilling_metric);
|
Same(a.drilling_metric, b.drilling_metric);
|
||||||
Same(a.drilling_energy, b.drilling_energy);
|
Same(a.drilling_energy, b.drilling_energy);
|
||||||
Same(a.force_directional_second, b.force_directional_second);
|
Same(a.force_directional_second, b.force_directional_second);
|
||||||
|
Same(a.drilling_force_directional_second, b.drilling_force_directional_second);
|
||||||
Same(a.drilling_constraint, b.drilling_constraint);
|
Same(a.drilling_constraint, b.drilling_constraint);
|
||||||
Same(a.drilling_constraint_gradient, b.drilling_constraint_gradient);
|
Same(a.drilling_constraint_gradient, b.drilling_constraint_gradient);
|
||||||
for (std::size_t n = 0; n < 4; ++n) {
|
for (std::size_t n = 0; n < 4; ++n) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
#include <cstring>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@@ -13,6 +14,8 @@
|
|||||||
#include "fesa/elements/nonlinear_mitc4_inertia.h"
|
#include "fesa/elements/nonlinear_mitc4_inertia.h"
|
||||||
#include "fesa/elements/nonlinear_mitc4_shell.h"
|
#include "fesa/elements/nonlinear_mitc4_shell.h"
|
||||||
#include "fesa/model/model_types.h"
|
#include "fesa/model/model_types.h"
|
||||||
|
#include "elements/mitc4_drilling_internal.h"
|
||||||
|
#include "elements/nonlinear_mitc4_configuration_internal.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
@@ -44,7 +47,7 @@ std::array<fesa::ShellNodeInitialFrame, 4> Frames() {
|
|||||||
{3U, {0.0, 0.0, 1.0}, {1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}}}};
|
{3U, {0.0, 0.0, 1.0}, {1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}}}};
|
||||||
}
|
}
|
||||||
|
|
||||||
fesa::NonlinearMitc4Shell Shell(const std::array<fesa::Node, 4>& nodes) {
|
fesa::NonlinearMitc4Shell ProductionShell(const std::array<fesa::Node, 4>& nodes) {
|
||||||
const fesa::ShellSection section{
|
const fesa::ShellSection section{
|
||||||
"Section", 0.2, 0U, {"nonlinear-mitc4-tangent.inp", 20U}};
|
"Section", 0.2, 0U, {"nonlinear-mitc4-tangent.inp", 20U}};
|
||||||
const fesa::LinearElasticMaterial material{
|
const fesa::LinearElasticMaterial material{
|
||||||
@@ -57,6 +60,11 @@ fesa::NonlinearMitc4Shell Shell(const std::array<fesa::Node, 4>& nodes) {
|
|||||||
return shell.Value();
|
return shell.Value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fesa::NonlinearMitc4Shell LegacyShell(const std::array<fesa::Node, 4>& nodes) {
|
||||||
|
return fesa::mitc4_drilling_internal::TestAccess::WithMode(
|
||||||
|
ProductionShell(nodes), fesa::Mitc4DrillingMode::kLegacyCorotatedTwist);
|
||||||
|
}
|
||||||
|
|
||||||
fesa::NonlinearMitc4TrialDofs Trial() {
|
fesa::NonlinearMitc4TrialDofs Trial() {
|
||||||
fesa::NonlinearMitc4TrialDofs trial{};
|
fesa::NonlinearMitc4TrialDofs trial{};
|
||||||
trial.displacement[0U] = {-0.02, 0.01, 0.03};
|
trial.displacement[0U] = {-0.02, 0.01, 0.03};
|
||||||
@@ -200,7 +208,7 @@ void ExpectRelative(const double actual, const double expected) {
|
|||||||
// NSNR-ELEM-TAN-001
|
// NSNR-ELEM-TAN-001
|
||||||
TEST(NonlinearMitc4Residual, EvaluatesResidualTangentAndRecoveryAtOneTrial) {
|
TEST(NonlinearMitc4Residual, EvaluatesResidualTangentAndRecoveryAtOneTrial) {
|
||||||
const auto nodes = Nodes();
|
const auto nodes = Nodes();
|
||||||
const auto shell = Shell(nodes);
|
const auto shell = LegacyShell(nodes);
|
||||||
const auto trial = Trial();
|
const auto trial = Trial();
|
||||||
const auto recovery = shell.RecoverState(trial);
|
const auto recovery = shell.RecoverState(trial);
|
||||||
const auto result = fesa::EvaluateInternalResidualAndTangent(shell, trial);
|
const auto result = fesa::EvaluateInternalResidualAndTangent(shell, trial);
|
||||||
@@ -230,7 +238,7 @@ TEST(NonlinearMitc4Residual, EvaluatesResidualTangentAndRecoveryAtOneTrial) {
|
|||||||
// NSNR-ELEM-TAN-002 and NSNR-ELEM-TAN-003
|
// NSNR-ELEM-TAN-002 and NSNR-ELEM-TAN-003
|
||||||
TEST(NonlinearMitc4Tangent, MatchesTheApprovedDirectionalSchedule) {
|
TEST(NonlinearMitc4Tangent, MatchesTheApprovedDirectionalSchedule) {
|
||||||
const auto nodes = Nodes();
|
const auto nodes = Nodes();
|
||||||
const auto shell = Shell(nodes);
|
const auto shell = LegacyShell(nodes);
|
||||||
const auto trial = Trial();
|
const auto trial = Trial();
|
||||||
const auto direction = WorkConjugateDirection(nodes);
|
const auto direction = WorkConjugateDirection(nodes);
|
||||||
const double length = ReferenceLength(nodes);
|
const double length = ReferenceLength(nodes);
|
||||||
@@ -295,7 +303,7 @@ TEST(NonlinearMitc4Tangent, MatchesTheApprovedDirectionalSchedule) {
|
|||||||
TEST(NonlinearMitc4Tangent, PreservesRigidTranslationNullspaceAndSymmetry) {
|
TEST(NonlinearMitc4Tangent, PreservesRigidTranslationNullspaceAndSymmetry) {
|
||||||
const auto nodes = Nodes();
|
const auto nodes = Nodes();
|
||||||
const auto equilibrium =
|
const auto equilibrium =
|
||||||
fesa::EvaluateInternalResidualAndTangent(Shell(nodes), {});
|
fesa::EvaluateInternalResidualAndTangent(LegacyShell(nodes), {});
|
||||||
ASSERT_TRUE(equilibrium.HasValue());
|
ASSERT_TRUE(equilibrium.HasValue());
|
||||||
const double length = ReferenceLength(nodes);
|
const double length = ReferenceLength(nodes);
|
||||||
|
|
||||||
@@ -326,9 +334,9 @@ TEST(NonlinearMitc4Tangent, PreservesRigidTranslationNullspaceAndSymmetry) {
|
|||||||
TEST(NonlinearMitc4Residual, DoesNotAcceptExternalLoadShares) {
|
TEST(NonlinearMitc4Residual, DoesNotAcceptExternalLoadShares) {
|
||||||
const auto nodes = Nodes();
|
const auto nodes = Nodes();
|
||||||
const auto first =
|
const auto first =
|
||||||
fesa::EvaluateInternalResidualAndTangent(Shell(nodes), Trial());
|
fesa::EvaluateInternalResidualAndTangent(LegacyShell(nodes), Trial());
|
||||||
const auto second =
|
const auto second =
|
||||||
fesa::EvaluateInternalResidualAndTangent(Shell(nodes), Trial());
|
fesa::EvaluateInternalResidualAndTangent(LegacyShell(nodes), Trial());
|
||||||
ASSERT_TRUE(first.HasValue());
|
ASSERT_TRUE(first.HasValue());
|
||||||
ASSERT_TRUE(second.HasValue());
|
ASSERT_TRUE(second.HasValue());
|
||||||
for (std::size_t index = 0U; index < 24U; ++index) {
|
for (std::size_t index = 0U; index < 24U; ++index) {
|
||||||
@@ -357,7 +365,7 @@ TEST(NonlinearMitc4Objectivity, FiniteRigidMotionHasZeroInternalForce) {
|
|||||||
trial.rotation_vector[node] = rotation;
|
trial.rotation_vector[node] = rotation;
|
||||||
}
|
}
|
||||||
const auto result =
|
const auto result =
|
||||||
fesa::EvaluateInternalResidualAndTangent(Shell(nodes), trial);
|
fesa::EvaluateInternalResidualAndTangent(LegacyShell(nodes), trial);
|
||||||
ASSERT_TRUE(result.HasValue());
|
ASSERT_TRUE(result.HasValue());
|
||||||
EXPECT_LE(result.Value().internal_residual.Norm(), kNumericalTolerance);
|
EXPECT_LE(result.Value().internal_residual.Norm(), kNumericalTolerance);
|
||||||
EXPECT_LE(result.Value().physical_strain_energy, kNumericalTolerance);
|
EXPECT_LE(result.Value().physical_strain_energy, kNumericalTolerance);
|
||||||
@@ -366,7 +374,7 @@ TEST(NonlinearMitc4Objectivity, FiniteRigidMotionHasZeroInternalForce) {
|
|||||||
// NSNR-ELEM-TAN-008
|
// NSNR-ELEM-TAN-008
|
||||||
TEST(NonlinearMitc4Patch, RetainsPhysicalRecoveryIdentities) {
|
TEST(NonlinearMitc4Patch, RetainsPhysicalRecoveryIdentities) {
|
||||||
const auto nodes = Nodes();
|
const auto nodes = Nodes();
|
||||||
const auto shell = Shell(nodes);
|
const auto shell = LegacyShell(nodes);
|
||||||
constexpr double kMembraneStrain = 1.0e-2;
|
constexpr double kMembraneStrain = 1.0e-2;
|
||||||
const double green_strain =
|
const double green_strain =
|
||||||
kMembraneStrain + 0.5 * kMembraneStrain * kMembraneStrain;
|
kMembraneStrain + 0.5 * kMembraneStrain * kMembraneStrain;
|
||||||
@@ -409,7 +417,7 @@ TEST(NonlinearMitc4Residual, RejectsNonfiniteTrialData) {
|
|||||||
trial.rotation_vector[2U] = {std::numeric_limits<double>::quiet_NaN(), 0.0,
|
trial.rotation_vector[2U] = {std::numeric_limits<double>::quiet_NaN(), 0.0,
|
||||||
0.0};
|
0.0};
|
||||||
const auto result =
|
const auto result =
|
||||||
fesa::EvaluateInternalResidualAndTangent(Shell(nodes), trial);
|
fesa::EvaluateInternalResidualAndTangent(LegacyShell(nodes), trial);
|
||||||
ASSERT_FALSE(result.HasValue());
|
ASSERT_FALSE(result.HasValue());
|
||||||
EXPECT_EQ(result.GetStatus().Diagnostics()[0U].code,
|
EXPECT_EQ(result.GetStatus().Diagnostics()[0U].code,
|
||||||
"invalid-nonlinear-shell-trial");
|
"invalid-nonlinear-shell-trial");
|
||||||
@@ -417,13 +425,13 @@ TEST(NonlinearMitc4Residual, RejectsNonfiniteTrialData) {
|
|||||||
auto overflow = Trial();
|
auto overflow = Trial();
|
||||||
overflow.displacement[1U] = {1.0e300, 0.0, 0.0};
|
overflow.displacement[1U] = {1.0e300, 0.0, 0.0};
|
||||||
const auto overflow_result =
|
const auto overflow_result =
|
||||||
fesa::EvaluateInternalResidualAndTangent(Shell(nodes), overflow);
|
fesa::EvaluateInternalResidualAndTangent(LegacyShell(nodes), overflow);
|
||||||
EXPECT_FALSE(overflow_result.HasValue());
|
EXPECT_FALSE(overflow_result.HasValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CheckThirdSweep(const fesa::NonlinearMitc4TrialDofs& trial) {
|
void CheckThirdSweep(const fesa::NonlinearMitc4TrialDofs& trial) {
|
||||||
const auto nodes = Nodes();
|
const auto nodes = Nodes();
|
||||||
const auto shell = Shell(nodes);
|
const auto shell = LegacyShell(nodes);
|
||||||
auto direction = Direction();
|
auto direction = Direction();
|
||||||
double norm = 0.0;
|
double norm = 0.0;
|
||||||
const auto unscaled = ToVector(direction);
|
const auto unscaled = ToVector(direction);
|
||||||
@@ -488,7 +496,7 @@ TEST(NonlinearMitc4Residual,
|
|||||||
nodes[1].coordinates = {4.23076923, 2.30769231, 0.0};
|
nodes[1].coordinates = {4.23076923, 2.30769231, 0.0};
|
||||||
nodes[2].coordinates = {4.23076923, 2.69230769, 0.0};
|
nodes[2].coordinates = {4.23076923, 2.69230769, 0.0};
|
||||||
nodes[3].coordinates = {3.84615385, 2.69230769, 0.0};
|
nodes[3].coordinates = {3.84615385, 2.69230769, 0.0};
|
||||||
const auto shell = Shell(nodes);
|
const auto shell = LegacyShell(nodes);
|
||||||
const auto evaluated = fesa::EvaluateInternalResidualAndTangent(shell, {});
|
const auto evaluated = fesa::EvaluateInternalResidualAndTangent(shell, {});
|
||||||
ASSERT_TRUE(evaluated.HasValue());
|
ASSERT_TRUE(evaluated.HasValue());
|
||||||
for (std::size_t i = 0; i < 24U; ++i)
|
for (std::size_t i = 0; i < 24U; ++i)
|
||||||
@@ -566,7 +574,7 @@ TEST(NonlinearMitc4Residual,
|
|||||||
for (bool warped : {false, true}) {
|
for (bool warped : {false, true}) {
|
||||||
auto nodes = Nodes();
|
auto nodes = Nodes();
|
||||||
if (warped) nodes[2U].coordinates[2U] = 0.1;
|
if (warped) nodes[2U].coordinates[2U] = 0.1;
|
||||||
const auto shell = Shell(nodes);
|
const auto shell = LegacyShell(nodes);
|
||||||
auto trial = Trial();
|
auto trial = Trial();
|
||||||
trial.rotation_vector[0U] = {.6, -.2, .1};
|
trial.rotation_vector[0U] = {.6, -.2, .1};
|
||||||
const auto direction = Direction();
|
const auto direction = Direction();
|
||||||
@@ -636,7 +644,7 @@ TEST(NonlinearMitc4Residual,
|
|||||||
TEST(NonlinearMitc4Residual,
|
TEST(NonlinearMitc4Residual,
|
||||||
ExposesExactDrillingConstraintThirdDirectionalGradient) {
|
ExposesExactDrillingConstraintThirdDirectionalGradient) {
|
||||||
const auto nodes = Nodes();
|
const auto nodes = Nodes();
|
||||||
const auto shell = Shell(nodes);
|
const auto shell = LegacyShell(nodes);
|
||||||
const auto trial = Trial();
|
const auto trial = Trial();
|
||||||
const auto direction = Direction();
|
const auto direction = Direction();
|
||||||
const auto velocity = ToVector(direction);
|
const auto velocity = ToVector(direction);
|
||||||
@@ -679,7 +687,7 @@ TEST(NonlinearMitc4Residual,
|
|||||||
TEST(NonlinearMitc4Residual,
|
TEST(NonlinearMitc4Residual,
|
||||||
ExternalDrillingConstraintIncludesForceTimeAndCurvatureTerms) {
|
ExternalDrillingConstraintIncludesForceTimeAndCurvatureTerms) {
|
||||||
const auto nodes = Nodes();
|
const auto nodes = Nodes();
|
||||||
const auto shell = Shell(nodes);
|
const auto shell = LegacyShell(nodes);
|
||||||
const fesa::Vector3 phi{.6, -.2, .1};
|
const fesa::Vector3 phi{.6, -.2, .1};
|
||||||
const fesa::Vector3 phi_dot{.2, -.1, .3};
|
const fesa::Vector3 phi_dot{.2, -.1, .3};
|
||||||
const fesa::Vector3 force{1.7, -.8, .4};
|
const fesa::Vector3 force{1.7, -.8, .4};
|
||||||
@@ -742,7 +750,7 @@ TEST(NonlinearMitc4Residual,
|
|||||||
|
|
||||||
TEST(NonlinearMitc4Residual,
|
TEST(NonlinearMitc4Residual,
|
||||||
ExternalDrillingConstraintRejectsInvalidRotationAndHasZeroForceValue) {
|
ExternalDrillingConstraintRejectsInvalidRotationAndHasZeroForceValue) {
|
||||||
const auto shell = Shell(Nodes());
|
const auto shell = LegacyShell(Nodes());
|
||||||
const auto zero = shell.EvaluateExternalDrillingConstraint(
|
const auto zero = shell.EvaluateExternalDrillingConstraint(
|
||||||
0U, {.2, -.1, .3}, {.1, .2, -.3}, {}, {}, {});
|
0U, {.2, -.1, .3}, {.1, .2, -.3}, {}, {}, {});
|
||||||
ASSERT_TRUE(zero.HasValue());
|
ASSERT_TRUE(zero.HasValue());
|
||||||
@@ -838,4 +846,470 @@ TEST(NonlinearMitc4Residual, TinyRotationPreservesPhysicalForceAndRecoveryEnergy
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Independent fixed-initial-director spring oracle: a total-chart rigid spin
|
||||||
|
// carries numerical energy even though the physical shell is undeformed.
|
||||||
|
TEST(Mitc4DrillingSelection, ProductionUsesDirectInitialDirectorSpring) {
|
||||||
|
const auto nodes = Nodes();
|
||||||
|
const auto shell = ProductionShell(nodes);
|
||||||
|
const fesa::ShellSection section{"Section", 0.2, 0U, {"spring.inp", 1U}};
|
||||||
|
const fesa::LinearElasticMaterial material{
|
||||||
|
"Material", 120.0, 0.25, {"spring.inp", 2U}};
|
||||||
|
const auto linear = fesa::Mitc4Shell::Create(
|
||||||
|
NodePointers(nodes),
|
||||||
|
{{{0., 0., 1.}, {0., 0., 1.}, {0., 0., 1.}, {0., 0., 1.}}}, section,
|
||||||
|
material);
|
||||||
|
ASSERT_TRUE(linear.HasValue());
|
||||||
|
const auto stiffness = linear.Value().Stiffness();
|
||||||
|
ASSERT_TRUE(stiffness.HasValue());
|
||||||
|
double rotary_trace = 0.0;
|
||||||
|
for (std::size_t node = 0; node < 4; ++node)
|
||||||
|
for (std::size_t c = 3; c < 5; ++c)
|
||||||
|
rotary_trace +=
|
||||||
|
stiffness.Value().physical_local20(5 * node + c, 5 * node + c);
|
||||||
|
const double kd = 1e-3 * rotary_trace / 8.;
|
||||||
|
fesa::NonlinearMitc4TrialDofs trial{};
|
||||||
|
const fesa::Vector3 phi{0., 0., 0.4};
|
||||||
|
const auto rotation = fesa::ExpSkew(phi);
|
||||||
|
for (std::size_t node = 0; node < 4; ++node) {
|
||||||
|
const fesa::Vector3 x{nodes[node].coordinates};
|
||||||
|
trial.rotation_vector[node] = phi;
|
||||||
|
fesa::Vector3 rotated{
|
||||||
|
rotation(0, 0) * x[0] + rotation(0, 1) * x[1] + rotation(0, 2) * x[2],
|
||||||
|
rotation(1, 0) * x[0] + rotation(1, 1) * x[1] + rotation(1, 2) * x[2],
|
||||||
|
rotation(2, 0) * x[0] + rotation(2, 1) * x[1] + rotation(2, 2) * x[2]};
|
||||||
|
trial.displacement[node] = rotated - x;
|
||||||
|
}
|
||||||
|
const auto actual = fesa::EvaluateInternalResidualAndTangent(shell, trial);
|
||||||
|
ASSERT_TRUE(actual.HasValue());
|
||||||
|
EXPECT_NEAR(actual.Value().physical_strain_energy, 0., 1e-25);
|
||||||
|
EXPECT_NEAR(actual.Value().drilling_energy, .5 * kd * 4 * .4 * .4, 1e-14);
|
||||||
|
for (std::size_t row = 0; row < 24; ++row) {
|
||||||
|
const double force = row % 6 == 5 ? kd * .4 : 0.;
|
||||||
|
EXPECT_NEAR(actual.Value().drilling_force[row], force, 1e-14);
|
||||||
|
for (std::size_t col = 0; col < 24; ++col) {
|
||||||
|
const double tangent = row == col && row % 6 == 5 ? kd : 0.;
|
||||||
|
EXPECT_NEAR(actual.Value().drilling_tangent(row, col), tangent, 1e-14);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Mitc4DrillingSelection,
|
||||||
|
DirectSpringUsesEachInitialDirectorAndConstantBlocks) {
|
||||||
|
for (const bool warped : {false, true}) {
|
||||||
|
auto nodes = Nodes();
|
||||||
|
if (warped) nodes[2].coordinates[2] = .1;
|
||||||
|
auto frames = Frames();
|
||||||
|
const auto transform = [](const fesa::Matrix& r, const Coordinates& x) {
|
||||||
|
Coordinates y{};
|
||||||
|
for (std::size_t i = 0; i < 3; ++i)
|
||||||
|
for (std::size_t j = 0; j < 3; ++j) y[i] += r(i, j) * x[j];
|
||||||
|
return y;
|
||||||
|
};
|
||||||
|
const auto global = fesa::ExpSkew({.4, -.3, .2});
|
||||||
|
std::array<Coordinates, 4> directors{};
|
||||||
|
for (std::size_t n = 0; n < 4; ++n) {
|
||||||
|
const auto r =
|
||||||
|
fesa::ExpSkew({0., warped ? .03 * static_cast<double>(n) : 0., 0.});
|
||||||
|
frames[n].director = transform(global, transform(r, frames[n].director));
|
||||||
|
frames[n].tangent_a =
|
||||||
|
transform(global, transform(r, frames[n].tangent_a));
|
||||||
|
frames[n].tangent_b =
|
||||||
|
transform(global, transform(r, frames[n].tangent_b));
|
||||||
|
nodes[n].coordinates = transform(global, nodes[n].coordinates);
|
||||||
|
directors[n] = frames[n].director;
|
||||||
|
}
|
||||||
|
const fesa::ShellSection section{"Section", .2, 0, {"spring.inp", 1}};
|
||||||
|
const fesa::LinearElasticMaterial material{
|
||||||
|
"Material", 120., .25, {"spring.inp", 2}};
|
||||||
|
const auto shell = fesa::NonlinearMitc4Shell::Create(
|
||||||
|
NodePointers(nodes), frames, section, material);
|
||||||
|
const auto linear = fesa::Mitc4Shell::Create(NodePointers(nodes), directors,
|
||||||
|
section, material);
|
||||||
|
ASSERT_TRUE(shell.HasValue());
|
||||||
|
ASSERT_TRUE(linear.HasValue());
|
||||||
|
const auto k = linear.Value().Stiffness();
|
||||||
|
ASSERT_TRUE(k.HasValue());
|
||||||
|
double trace = 0.;
|
||||||
|
for (std::size_t n = 0; n < 4; ++n)
|
||||||
|
for (std::size_t c = 3; c < 5; ++c)
|
||||||
|
trace += k.Value().physical_local20(5 * n + c, 5 * n + c);
|
||||||
|
const double kd = 1e-3 * trace / 8.;
|
||||||
|
auto trial = Trial();
|
||||||
|
trial.rotation_vector[0] = {.65, -.4, .3};
|
||||||
|
const auto direction = ToVector(Direction());
|
||||||
|
const auto response = fesa::EvaluateInternalResidualAndTangent(
|
||||||
|
shell.Value(), trial, &direction);
|
||||||
|
const auto shifted = fesa::EvaluateInternalResidualAndTangent(
|
||||||
|
shell.Value(), AddScaled(trial, Direction(), .2), &direction);
|
||||||
|
ASSERT_TRUE(response.HasValue());
|
||||||
|
ASSERT_TRUE(shifted.HasValue());
|
||||||
|
double energy = 0.;
|
||||||
|
for (std::size_t n = 0; n < 4; ++n) {
|
||||||
|
const double gamma =
|
||||||
|
fesa::Vector3{directors[n]}.Dot(trial.rotation_vector[n]);
|
||||||
|
energy += .5 * kd * gamma * gamma;
|
||||||
|
const fesa::Vector3 d{directors[n]};
|
||||||
|
const auto rotation = fesa::EvaluateDynamicRotation(
|
||||||
|
trial.rotation_vector[n], d, Direction().rotation_vector[n], {});
|
||||||
|
ASSERT_TRUE(rotation.HasValue());
|
||||||
|
EXPECT_NEAR(response.Value().drilling_constraint[n],
|
||||||
|
kd * gamma * rotation.Value().drill_null.Dot(d), 1e-14);
|
||||||
|
for (std::size_t c = 0; c < 3; ++c) {
|
||||||
|
double ds = 0.;
|
||||||
|
for (std::size_t j = 0; j < 3; ++j)
|
||||||
|
ds += d[j] * rotation.Value().null_gradient(j, c);
|
||||||
|
EXPECT_NEAR(
|
||||||
|
response.Value().drilling_constraint_gradient(n, 6 * n + 3 + c),
|
||||||
|
kd * (d[c] * rotation.Value().drill_null.Dot(d) + gamma * ds),
|
||||||
|
1e-14);
|
||||||
|
}
|
||||||
|
for (std::size_t c = 0; c < 3; ++c)
|
||||||
|
EXPECT_NEAR(response.Value().drilling_force[6 * n + 3 + c],
|
||||||
|
kd * gamma * directors[n][c], 1e-14);
|
||||||
|
}
|
||||||
|
EXPECT_NEAR(response.Value().drilling_energy, energy, 1e-14);
|
||||||
|
for (std::size_t i = 0; i < 24; ++i) {
|
||||||
|
EXPECT_DOUBLE_EQ(response.Value().drilling_force_directional_second[i],
|
||||||
|
0.);
|
||||||
|
if (i % 6 < 3) EXPECT_DOUBLE_EQ(response.Value().drilling_force[i], 0.);
|
||||||
|
for (std::size_t j = 0; j < 24; ++j) {
|
||||||
|
const double expected =
|
||||||
|
i / 6 == j / 6 && i % 6 >= 3 && j % 6 >= 3
|
||||||
|
? kd * directors[i / 6][i % 6 - 3] * directors[i / 6][j % 6 - 3]
|
||||||
|
: 0.;
|
||||||
|
EXPECT_NEAR(response.Value().drilling_tangent(i, j), expected, 1e-14);
|
||||||
|
EXPECT_NEAR(response.Value().drilling_metric(i, j), expected, 1e-14);
|
||||||
|
EXPECT_DOUBLE_EQ(response.Value().drilling_tangent(i, j),
|
||||||
|
shifted.Value().drilling_tangent(i, j));
|
||||||
|
if (expected == 0.)
|
||||||
|
EXPECT_DOUBLE_EQ(response.Value().drilling_tangent(i, j), 0.);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Mitc4DrillingSelection,
|
||||||
|
DirectConstraintUsesPhysicalNullAndItsHighDerivatives) {
|
||||||
|
const auto nodes = Nodes();
|
||||||
|
const auto shell = ProductionShell(nodes);
|
||||||
|
auto trial = Trial();
|
||||||
|
trial.rotation_vector[0] = {.65, -.4, .3};
|
||||||
|
const auto direction = Direction();
|
||||||
|
const auto velocity = ToVector(direction);
|
||||||
|
const auto response =
|
||||||
|
fesa::EvaluateInternalResidualAndTangent(shell, trial, &velocity);
|
||||||
|
ASSERT_TRUE(response.HasValue());
|
||||||
|
const auto zero =
|
||||||
|
fesa::EvaluateInternalResidualAndTangent(shell, {}, &velocity);
|
||||||
|
ASSERT_TRUE(zero.HasValue());
|
||||||
|
const double kd = zero.Value().drilling_tangent(5, 5);
|
||||||
|
// Independent inertia API provides n, Dn and D2n, never a twist angle.
|
||||||
|
const fesa::Vector3 d{0., 0., 1.};
|
||||||
|
const auto scalar_gradient = [&](const fesa::Vector3& phi, std::size_t c) {
|
||||||
|
const auto r = fesa::EvaluateDynamicRotation(phi, d, {}, {});
|
||||||
|
EXPECT_TRUE(r.HasValue());
|
||||||
|
double projection_derivative = 0.;
|
||||||
|
for (std::size_t k = 0; k < 3; ++k)
|
||||||
|
projection_derivative += r.Value().null_gradient(k, c) * d[k];
|
||||||
|
return kd * (d[c] * r.Value().drill_null.Dot(d) +
|
||||||
|
d.Dot(phi) * projection_derivative);
|
||||||
|
};
|
||||||
|
constexpr double h = .002;
|
||||||
|
for (std::size_t n = 0; n < 4; ++n) {
|
||||||
|
const auto phi = trial.rotation_vector[n];
|
||||||
|
const auto v = direction.rotation_vector[n];
|
||||||
|
const auto r = fesa::EvaluateDynamicRotation(phi, d, v, {});
|
||||||
|
ASSERT_TRUE(r.HasValue());
|
||||||
|
const double gamma = d.Dot(phi);
|
||||||
|
const double gamma_dot = d.Dot(v);
|
||||||
|
double projection_dot = 0.;
|
||||||
|
for (std::size_t i = 0; i < 3; ++i)
|
||||||
|
for (std::size_t j = 0; j < 3; ++j)
|
||||||
|
projection_dot += d[i] * r.Value().null_gradient(i, j) * v[j];
|
||||||
|
const double expected_second =
|
||||||
|
kd * (2. * gamma_dot * projection_dot +
|
||||||
|
gamma * r.Value().null_directional_second.Dot(d));
|
||||||
|
double actual_second = 0.;
|
||||||
|
EXPECT_NEAR(response.Value().drilling_constraint[n],
|
||||||
|
kd * gamma * r.Value().drill_null.Dot(d), 1e-14);
|
||||||
|
for (std::size_t i = 0; i < 24; ++i) {
|
||||||
|
const double gradient =
|
||||||
|
i / 6 == n && i % 6 >= 3 ? scalar_gradient(phi, i % 6 - 3) : 0.;
|
||||||
|
EXPECT_NEAR(response.Value().drilling_constraint_gradient(n, i), gradient,
|
||||||
|
1e-14);
|
||||||
|
for (std::size_t j = 0; j < 24; ++j)
|
||||||
|
actual_second += velocity[i] *
|
||||||
|
response.Value().drilling_constraint_hessian[n](i, j) *
|
||||||
|
velocity[j];
|
||||||
|
if (i / 6 == n && i % 6 >= 3) {
|
||||||
|
const auto c = i % 6 - 3;
|
||||||
|
const double fd =
|
||||||
|
(scalar_gradient(phi + h * v, c) - 2. * scalar_gradient(phi, c) +
|
||||||
|
scalar_gradient(phi - h * v, c)) /
|
||||||
|
(h * h);
|
||||||
|
const double actual =
|
||||||
|
response.Value()
|
||||||
|
.drilling_constraint_third_directional_gradient[n][i];
|
||||||
|
EXPECT_NEAR(actual, fd,
|
||||||
|
2e-5 * (std::abs(actual) + std::abs(fd) + kd * 1e-4));
|
||||||
|
} else {
|
||||||
|
EXPECT_DOUBLE_EQ(
|
||||||
|
response.Value()
|
||||||
|
.drilling_constraint_third_directional_gradient[n][i],
|
||||||
|
0.);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EXPECT_NEAR(actual_second, expected_second, 1e-14);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Mitc4DrillingSelection, DirectExternalConstraintUsesActualInertiaNull) {
|
||||||
|
const auto nodes = Nodes();
|
||||||
|
const auto shell = ProductionShell(nodes);
|
||||||
|
const fesa::Vector3 phi{.65, -.4, .3}, velocity{.2, .1, -.15};
|
||||||
|
const fesa::Vector3 force{.7, -.3, .2}, rate{-.1, .2, .4},
|
||||||
|
acceleration{.5, -.2, .1};
|
||||||
|
const auto actual = shell.EvaluateExternalDrillingConstraint(
|
||||||
|
0, phi, velocity, force, rate, acceleration);
|
||||||
|
const auto rotation =
|
||||||
|
fesa::EvaluateDynamicRotation(phi, {0., 0., 1.}, velocity, {});
|
||||||
|
ASSERT_TRUE(actual.HasValue());
|
||||||
|
ASSERT_TRUE(rotation.HasValue());
|
||||||
|
const double h = .002;
|
||||||
|
const auto plus = shell.EvaluateExternalDrillingConstraint(
|
||||||
|
0, phi + h * velocity, velocity,
|
||||||
|
force + h * rate + .5 * h * h * acceleration, rate + h * acceleration,
|
||||||
|
acceleration);
|
||||||
|
const auto minus = shell.EvaluateExternalDrillingConstraint(
|
||||||
|
0, phi - h * velocity, velocity,
|
||||||
|
force - h * rate + .5 * h * h * acceleration, rate - h * acceleration,
|
||||||
|
acceleration);
|
||||||
|
ASSERT_TRUE(plus.HasValue());
|
||||||
|
ASSERT_TRUE(minus.HasValue());
|
||||||
|
for (std::size_t c = 0; c < 3; ++c) {
|
||||||
|
EXPECT_NEAR(
|
||||||
|
actual.Value().gradient_directional_first[c],
|
||||||
|
(plus.Value().gradient[c] - minus.Value().gradient[c]) / (2. * h),
|
||||||
|
1e-7);
|
||||||
|
EXPECT_NEAR(actual.Value().gradient_directional_second[c],
|
||||||
|
(plus.Value().gradient[c] - 2. * actual.Value().gradient[c] +
|
||||||
|
minus.Value().gradient[c]) /
|
||||||
|
(h * h),
|
||||||
|
2e-7);
|
||||||
|
std::array<double, 3> basis{};
|
||||||
|
basis[c] = 1.;
|
||||||
|
constexpr double coordinate_h = 1e-4;
|
||||||
|
const auto positive = shell.EvaluateExternalDrillingConstraint(
|
||||||
|
0, phi + coordinate_h * fesa::Vector3{basis}, velocity, force, rate,
|
||||||
|
acceleration);
|
||||||
|
const auto negative = shell.EvaluateExternalDrillingConstraint(
|
||||||
|
0, phi - coordinate_h * fesa::Vector3{basis}, velocity, force, rate,
|
||||||
|
acceleration);
|
||||||
|
ASSERT_TRUE(positive.HasValue());
|
||||||
|
ASSERT_TRUE(negative.HasValue());
|
||||||
|
for (std::size_t j = 0; j < 3; ++j)
|
||||||
|
EXPECT_NEAR(
|
||||||
|
actual.Value().hessian(j, c),
|
||||||
|
(positive.Value().gradient[j] - negative.Value().gradient[j]) /
|
||||||
|
(2. * coordinate_h),
|
||||||
|
1e-7);
|
||||||
|
}
|
||||||
|
const auto& n = rotation.Value();
|
||||||
|
EXPECT_NEAR(actual.Value().value, -n.drill_null.Dot(force), 1e-14);
|
||||||
|
fesa::Vector3 n_dot{};
|
||||||
|
std::array<double, 3> values{};
|
||||||
|
for (std::size_t i = 0; i < 3; ++i)
|
||||||
|
for (std::size_t j = 0; j < 3; ++j)
|
||||||
|
values[i] += n.null_gradient(i, j) * velocity[j];
|
||||||
|
n_dot = fesa::Vector3{values};
|
||||||
|
EXPECT_NEAR(actual.Value().directional_first,
|
||||||
|
-n_dot.Dot(force) - n.drill_null.Dot(rate), 1e-14);
|
||||||
|
EXPECT_NEAR(actual.Value().directional_second,
|
||||||
|
-n.null_directional_second.Dot(force) - 2. * n_dot.Dot(rate) -
|
||||||
|
n.drill_null.Dot(acceleration),
|
||||||
|
1e-14);
|
||||||
|
for (std::size_t c = 0; c < 3; ++c) {
|
||||||
|
double expected = 0.;
|
||||||
|
for (std::size_t i = 0; i < 3; ++i)
|
||||||
|
expected -= n.null_gradient(i, c) * force[i];
|
||||||
|
EXPECT_NEAR(actual.Value().gradient[c], expected, 1e-14);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Mitc4DrillingSelection,
|
||||||
|
ModesAreImmutableReferenceInputsAndPreservePhysicalResponse) {
|
||||||
|
const auto nodes = Nodes();
|
||||||
|
const auto direct = ProductionShell(nodes);
|
||||||
|
const auto legacy = LegacyShell(nodes);
|
||||||
|
EXPECT_EQ(direct.DrillingMode(), fesa::Mitc4DrillingMode::kDirectSpring);
|
||||||
|
EXPECT_EQ(legacy.DrillingMode(),
|
||||||
|
fesa::Mitc4DrillingMode::kLegacyCorotatedTwist);
|
||||||
|
EXPECT_STREQ(fesa::Mitc4DrillingModeName(direct.DrillingMode()),
|
||||||
|
"direct-initial-director-spring");
|
||||||
|
EXPECT_STREQ(fesa::Mitc4DrillingModeName(legacy.DrillingMode()),
|
||||||
|
"legacy-corotated-twist");
|
||||||
|
const auto a =
|
||||||
|
fesa::nonlinear_mitc4_internal::ConfigurationSnapshot::Read(direct);
|
||||||
|
const auto b =
|
||||||
|
fesa::nonlinear_mitc4_internal::ConfigurationSnapshot::Read(legacy);
|
||||||
|
EXPECT_EQ(a.values, b.values);
|
||||||
|
EXPECT_NE(a.drilling_mode, b.drilling_mode);
|
||||||
|
const auto direction = ToVector(Direction());
|
||||||
|
const auto r =
|
||||||
|
fesa::EvaluateInternalResidualAndTangent(direct, Trial(), &direction);
|
||||||
|
const auto l =
|
||||||
|
fesa::EvaluateInternalResidualAndTangent(legacy, Trial(), &direction);
|
||||||
|
ASSERT_TRUE(r.HasValue());
|
||||||
|
ASSERT_TRUE(l.HasValue());
|
||||||
|
EXPECT_DOUBLE_EQ(r.Value().physical_strain_energy,
|
||||||
|
l.Value().physical_strain_energy);
|
||||||
|
EXPECT_NE(r.Value().drilling_energy, l.Value().drilling_energy);
|
||||||
|
for (std::size_t i = 0; i < 24; ++i) {
|
||||||
|
EXPECT_DOUBLE_EQ(r.Value().physical_force[i], l.Value().physical_force[i]);
|
||||||
|
for (std::size_t j = 0; j < 24; ++j)
|
||||||
|
EXPECT_NEAR(
|
||||||
|
r.Value().internal_tangent(i, j) - r.Value().drilling_tangent(i, j),
|
||||||
|
l.Value().internal_tangent(i, j) - l.Value().drilling_tangent(i, j),
|
||||||
|
1e-14);
|
||||||
|
}
|
||||||
|
for (std::size_t gp = 0; gp < 4; ++gp)
|
||||||
|
EXPECT_EQ(r.Value().recovery.gauss_points[gp].section_resultant,
|
||||||
|
l.Value().recovery.gauss_points[gp].section_resultant);
|
||||||
|
const fesa::ShellSection section{"Section", .2, 0, {"spring.inp", 1}};
|
||||||
|
const fesa::LinearElasticMaterial material{
|
||||||
|
"Material", 120., .25, {"spring.inp", 2}};
|
||||||
|
const auto linear = fesa::Mitc4Shell::Create(
|
||||||
|
NodePointers(nodes),
|
||||||
|
{{{0., 0., 1.}, {0., 0., 1.}, {0., 0., 1.}, {0., 0., 1.}}}, section,
|
||||||
|
material);
|
||||||
|
ASSERT_TRUE(linear.HasValue());
|
||||||
|
EXPECT_EQ(linear.Value().DrillingMode(), direct.DrillingMode());
|
||||||
|
const auto alternate = fesa::mitc4_drilling_internal::TestAccess::WithMode(
|
||||||
|
linear.Value(), fesa::Mitc4DrillingMode::kLegacyCorotatedTwist);
|
||||||
|
const auto k1 = linear.Value().Stiffness();
|
||||||
|
const auto k2 = alternate.Stiffness();
|
||||||
|
ASSERT_TRUE(k1.HasValue());
|
||||||
|
ASSERT_TRUE(k2.HasValue());
|
||||||
|
for (std::size_t i = 0; i < 24; ++i)
|
||||||
|
for (std::size_t j = 0; j < 24; ++j)
|
||||||
|
EXPECT_DOUBLE_EQ(k1.Value().stabilized_global24(i, j),
|
||||||
|
k2.Value().stabilized_global24(i, j));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Mitc4DrillingSelection, DirectRejectsInvalidScaleAndNonfiniteTrial) {
|
||||||
|
const auto nodes = Nodes();
|
||||||
|
const fesa::ShellSection section{"Section", .2, 0, {"spring.inp", 1}};
|
||||||
|
for (double youngs_modulus : {1e-320, (std::numeric_limits<double>::max)()}) {
|
||||||
|
const fesa::LinearElasticMaterial material{
|
||||||
|
"Material", youngs_modulus, .25, {"spring.inp", 2}};
|
||||||
|
const auto invalid = fesa::NonlinearMitc4Shell::Create(
|
||||||
|
NodePointers(nodes), Frames(), section, material);
|
||||||
|
ASSERT_FALSE(invalid.HasValue());
|
||||||
|
EXPECT_EQ(invalid.GetStatus().Diagnostics()[0].code,
|
||||||
|
youngs_modulus < 1.0 ? "invalid-nonlinear-shell-drilling"
|
||||||
|
: "invalid-shell-material");
|
||||||
|
}
|
||||||
|
const auto shell = ProductionShell(nodes);
|
||||||
|
for (double value : {std::numeric_limits<double>::quiet_NaN(),
|
||||||
|
std::numeric_limits<double>::infinity()}) {
|
||||||
|
auto trial = Trial();
|
||||||
|
trial.rotation_vector[0] = {value, 0., 0.};
|
||||||
|
EXPECT_FALSE(
|
||||||
|
fesa::EvaluateInternalResidualAndTangent(shell, trial).HasValue());
|
||||||
|
}
|
||||||
|
const auto valid = fesa::EvaluateInternalResidualAndTangent(shell, {});
|
||||||
|
ASSERT_TRUE(valid.HasValue());
|
||||||
|
EXPECT_DOUBLE_EQ(valid.Value().drilling_energy, 0.);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Mitc4DrillingSelection, DirectRetainsPositiveSubnormalSpringScale) {
|
||||||
|
const auto nodes = Nodes();
|
||||||
|
const fesa::ShellSection section{"Section", .2, 0, {"spring.inp", 1}};
|
||||||
|
unsigned accepted = 0;
|
||||||
|
for (double youngs_modulus :
|
||||||
|
{1e-315, 1e-316, 1e-317, 1e-318, 2e-318, 3e-318, 1e-319}) {
|
||||||
|
const fesa::LinearElasticMaterial material{
|
||||||
|
"Material", youngs_modulus, .25, {"spring.inp", 2}};
|
||||||
|
const auto linear = fesa::Mitc4Shell::Create(
|
||||||
|
NodePointers(nodes),
|
||||||
|
{{{0., 0., 1.}, {0., 0., 1.}, {0., 0., 1.}, {0., 0., 1.}}}, section,
|
||||||
|
material);
|
||||||
|
ASSERT_TRUE(linear.HasValue());
|
||||||
|
const auto stiffness = linear.Value().Stiffness();
|
||||||
|
const auto shell = fesa::NonlinearMitc4Shell::Create(
|
||||||
|
NodePointers(nodes), Frames(), section, material);
|
||||||
|
if (!stiffness.HasValue()) {
|
||||||
|
EXPECT_FALSE(shell.HasValue());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ASSERT_TRUE(shell.HasValue());
|
||||||
|
++accepted;
|
||||||
|
fesa::NonlinearMitc4TrialDofs trial{};
|
||||||
|
trial.rotation_vector[0] = {0., 0., 1.};
|
||||||
|
const auto actual =
|
||||||
|
fesa::EvaluateInternalResidualAndTangent(shell.Value(), trial);
|
||||||
|
ASSERT_TRUE(actual.HasValue());
|
||||||
|
// Scale is already validated. No later half-scale rounding may change K.
|
||||||
|
const double kd = stiffness.Value().drilling_stiffness;
|
||||||
|
EXPECT_EQ(actual.Value().drilling_tangent(5, 5), kd);
|
||||||
|
EXPECT_EQ(actual.Value().drilling_force[5], kd);
|
||||||
|
trial.rotation_vector[0] = {0., 0., 2.};
|
||||||
|
const auto double_angle =
|
||||||
|
fesa::EvaluateInternalResidualAndTangent(shell.Value(), trial);
|
||||||
|
ASSERT_TRUE(double_angle.HasValue());
|
||||||
|
// U(2) = 2*kd is representable even for the least positive spring scale.
|
||||||
|
EXPECT_EQ(double_angle.Value().drilling_energy, 2. * kd);
|
||||||
|
EXPECT_EQ(double_angle.Value().drilling_force[5], 2. * kd);
|
||||||
|
}
|
||||||
|
EXPECT_GT(accepted, 0U);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Mitc4DrillingSelection, LegacyHeadFrozenEvidence) {
|
||||||
|
const auto nodes = Nodes();
|
||||||
|
const auto shell = LegacyShell(nodes);
|
||||||
|
const auto direction = ToVector(Direction());
|
||||||
|
const auto response =
|
||||||
|
fesa::EvaluateInternalResidualAndTangent(shell, Trial(), &direction);
|
||||||
|
ASSERT_TRUE(response.HasValue());
|
||||||
|
const auto& r = response.Value();
|
||||||
|
std::array<std::uint64_t, 8> hashes{};
|
||||||
|
hashes.fill(14695981039346656037ULL);
|
||||||
|
const auto add = [&](std::size_t channel, double value) {
|
||||||
|
std::uint64_t bits{};
|
||||||
|
std::memcpy(&bits, &value, sizeof(bits));
|
||||||
|
for (unsigned b = 0; b < 8; ++b) {
|
||||||
|
hashes[channel] ^= (bits >> (8 * b)) & 255ULL;
|
||||||
|
hashes[channel] *= 1099511628211ULL;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
add(0, r.drilling_energy);
|
||||||
|
for (std::size_t i = 0; i < 24; ++i) {
|
||||||
|
add(1, r.drilling_force[i]);
|
||||||
|
add(4, r.force_directional_second[i]);
|
||||||
|
for (std::size_t j = 0; j < 24; ++j) {
|
||||||
|
add(2, r.drilling_tangent(i, j));
|
||||||
|
add(3, r.drilling_metric(i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (std::size_t n = 0; n < 4; ++n) {
|
||||||
|
add(5, r.drilling_constraint[n]);
|
||||||
|
for (std::size_t i = 0; i < 24; ++i) {
|
||||||
|
add(5, r.drilling_constraint_gradient(n, i));
|
||||||
|
add(7, r.drilling_constraint_third_directional_gradient[n][i]);
|
||||||
|
for (std::size_t j = 0; j < 24; ++j)
|
||||||
|
add(6, r.drilling_constraint_hessian[n](i, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Captured before implementation from HEAD
|
||||||
|
// 52e321e15c70377d4318aace3f3bc64b3ea1a65c, native MSVC x64 Debug. Entire
|
||||||
|
// energy/force/tangent/metric/directional/psi inventories are pinned
|
||||||
|
// independently, including the historical polar1 constraint inconsistency. Do
|
||||||
|
// not regenerate these from the selected mode.
|
||||||
|
constexpr std::array<std::uint64_t, 8> frozen{
|
||||||
|
16395275265220000061ULL, 12405324468590059764ULL, 79034762627498281ULL,
|
||||||
|
7305417493946306304ULL, 7191018337857616990ULL, 4845391414006668707ULL,
|
||||||
|
11613163243225266145ULL, 3872161451216566265ULL};
|
||||||
|
EXPECT_EQ(hashes, frozen);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|||||||
Reference in New Issue
Block a user