Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions include/numsim-materials/default_materials.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,20 @@
#include <numsim-core/object_registry.h>
#include "numsim-materials/core/material_base.h"
#include "numsim-materials/solvers/backward_euler.h"
#include "numsim-materials/solvers/local_newton.h"
#include "numsim-materials/solvers/vector_newton.h"
#include "numsim-materials/materials/scalar_stepper.h"
#include "numsim-materials/materials/constant_scalar.h"
#include "numsim-materials/materials/props_scalar.h"
#include "numsim-materials/materials/j2_plasticity.h"
#include "numsim-materials/materials/j2_rk_plasticity.h"
#include "numsim-materials/materials/drucker_prager_plasticity.h"
#include "numsim-materials/materials/linear_isotropic_hardening.h"
#include "numsim-materials/materials/exponential_isotropic_hardening.h"
#include "numsim-materials/materials/linear_damage_law.h"
#include "numsim-materials/materials/curing_rate.h"
#include "numsim-materials/materials/strain_energy_state_function.h"
#include "numsim-materials/materials/vector_strain_state_function.h"
#include "numsim-materials/materials/isotropic_tangent.h"
#include "numsim-materials/materials/linear_elasticity.h"
#include "numsim-materials/materials/linear_stress.h"
Expand Down Expand Up @@ -75,10 +85,42 @@ void register_default_materials() {
factory.template register_type<linear_elasticity<Traits>>("linear_elasticity");
factory.template register_type<constant_scalar<Traits>>("constant_scalar");
factory.template register_type<props_scalar<Traits>>("props_scalar");

// Plasticity. Each model is its own class -- j2_plasticity,
// j2_rk_plasticity and drucker_prager_plasticity -- rather than a template
// over the yield-function type, so the registrable names are the classes.
factory.template register_type<j2_plasticity<Traits>>("j2_plasticity");
factory.template register_type<j2_rk_plasticity<Traits>>("j2_rk_plasticity");

// Drucker-Prager was held back while its cone parameters arrived inside a
// C++ "yield_function" object the JSON reader could not convert: a document
// naming it would have got a DEFAULT-constructed one -- eta = beta = k = 0 --
// which builds, runs, never yields, and is indistinguishable from
// elasticity. #43 made eta, beta and K_bulk plain required scalars, so a
// deck now either supplies them or gets a named missing-parameter error.
// That was the stated condition for registering it (closes #33).
factory.template register_type<drucker_prager_plasticity<Traits>>(
"drucker_prager_plasticity");

factory.template register_type<linear_isotropic_hardening<Traits>>(
"linear_isotropic_hardening");
factory.template register_type<exponential_isotropic_hardening<Traits>>(
"exponential_isotropic_hardening");
factory.template register_type<linear_damage_law<Traits>>("linear_damage_law");
factory.template register_type<curing_rate<Traits>>("curing_rate");
factory.template register_type<strain_energy_state_function<Traits>>(
"strain_energy_state_function");
factory.template register_type<vector_strain_state_function<Traits>>(
"vector_strain_state_function");
factory.template register_type<isotropic_tangent<Traits>>("isotropic_tangent");
factory.template register_type<linear_stress<Traits>>("linear_stress");
factory.template register_type<autocatalytic_reaction<Traits>>("autocatalytic_reaction");
factory.template register_type<backward_euler<Traits>>("backward_euler");
// local_newton is the solver every return map names through "solver_source".
// Registering the plasticity classes without it leaves them unreachable from
// a document anyway: the deck can name j2_plasticity but not the solver it
// requires.
factory.template register_type<local_newton<Traits>>("local_newton");
factory.template register_type<tensor_component_stepper<1, Traits>>("tensor_component_stepper_rank1");
factory.template register_type<tensor_component_stepper<2, Traits>>("tensor_component_stepper_rank2");
factory.template register_type<scalar_identity_weight<Traits>>("scalar_identity_weight");
Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ add_numsim_test(test_umat_interface test_umat_interface.cpp)
add_numsim_test(test_tangent_generator test_tangent_generator.cpp)
add_numsim_test(test_json_model test_json_model.cpp)
add_numsim_test(test_props_scalar test_props_scalar.cpp)
add_numsim_test(test_material_registry test_material_registry.cpp)
add_numsim_test(test_uncovered_materials test_uncovered_materials.cpp)
add_numsim_test(test_weighted_sum test_weighted_sum.cpp)
target_link_libraries(test_umat_interface PRIVATE Threads::Threads)
Expand Down
131 changes: 131 additions & 0 deletions tests/test_material_registry.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
#include <gtest/gtest.h>
#include <string>
#include <nlohmann/json.hpp>
#include "numsim-materials/default_materials.h"
#include "numsim-materials/io/json_material_factory.h"

namespace {

namespace nm = numsim::materials;
using policy = nm::material_policy_default;
using T = policy::value_type;
using factory_type = nm::object_store<policy>::factory_type;

struct Registration {
Registration() { nm::register_default_materials<policy>(); }
};
const Registration registration_{};

/// A material that is not registered cannot be named in a document, whatever
/// else works about it. These were all absent, so a JSON model could build
/// elasticity and damage and essentially nothing else.
TEST(MaterialRegistry, PlasticityAndFriendsAreReachableFromJson) {
auto& f = factory_type::instance();
for (const char* name : {"j2_plasticity", "drucker_prager_plasticity",
"j2_rk_plasticity", "local_newton",
"linear_isotropic_hardening",
"exponential_isotropic_hardening", "linear_damage_law",
"curing_rate", "strain_energy_state_function",
"vector_strain_state_function"})
EXPECT_TRUE(f.contains(name)) << name << " cannot be named in a document";
}

/// The one that matters: a J2 model built entirely from a document, driven to
/// yield. Registration alone would pass the check above while still failing
/// here if a parameter were not JSON-convertible.
TEST(MaterialRegistry, AJ2ModelRunsFromADocumentAlone) {
const char* doc = R"([
{"type":"tensor_component_stepper_rank2","name":"stepper",
"increment":0.01,"indices":[0,0]},
{"type":"linear_elasticity","name":"elastic",
"strain_producer_name":"stepper","K":166.67,"G":76.92},
{"type":"local_newton","name":"solver"},
{"type":"linear_isotropic_hardening","name":"hardening",
"source":"j2","K":1000.0},
{"type":"j2_plasticity","name":"j2",
"hardening_source":"hardening","strain_source":"stepper",
"solver_source":"solver","K":166.67,"G":76.92,"sigma_0":50.0}
])";

nm::material_context<policy> ctx;
ASSERT_NO_THROW({
for (const auto& m : nlohmann::json::parse(doc))
nm::create_from_json<policy>(ctx, m);
ctx.finalize();
});

for (int i = 0; i < 40; ++i) { ctx.update(); ctx.commit(); }
EXPECT_GT(ctx.get<T>("j2", "equivalent_plastic_strain"), 1e-6)
<< "the document built, but the model never yielded";
}

/// Drucker-Prager, from a document, driven to yield.
///
/// It was held back while eta, beta and K_bulk arrived inside a C++
/// "yield_function" object the JSON reader could not convert: a document naming
/// it would have got a default-constructed cone -- eta = beta = k = 0 -- which
/// builds, runs, never yields, and is indistinguishable from elasticity. #43
/// made them plain required scalars. This is the test that the condition is
/// actually met, rather than that the type merely appears in the factory.
TEST(MaterialRegistry, ADruckerPragerModelRunsFromADocumentAlone) {
const char* doc = R"([
{"type":"tensor_component_stepper_rank2","name":"stepper",
"increment":0.01,"indices":[0,0]},
{"type":"local_newton","name":"solver"},
{"type":"linear_isotropic_hardening","name":"hardening",
"source":"dp","K":500.0},
{"type":"drucker_prager_plasticity","name":"dp",
"hardening_source":"hardening","strain_source":"stepper",
"solver_source":"solver","G":76.92,"sigma_0":20.0,
"eta":0.1,"beta":0.05,"K_bulk":166.67}
])";

nm::material_context<policy> ctx;
ASSERT_NO_THROW({
for (const auto& m : nlohmann::json::parse(doc))
nm::create_from_json<policy>(ctx, m);
ctx.finalize();
});

for (int i = 0; i < 40; ++i) { ctx.update(); ctx.commit(); }
EXPECT_GT(ctx.get<T>("dp", "equivalent_plastic_strain"), 1e-6)
<< "the document built, but the cone never yielded -- the silently "
"elastic failure this material was held back for";
}

/// And the reason it is safe to register: an omitted cone parameter stops the
/// build. This is the guarantee that replaced "keep it out of the factory" --
/// previously a document naming drucker_prager_plasticity got a
/// default-constructed cone with eta = beta = k = 0 and ran as elasticity.
///
/// The exception carries only a COUNT -- "missing 1 required parameter(s)" --
/// not the name. numsim-core's input_parameter_controller prints the names to
/// stdout and throws the count separately, so a deck typo is loud but not
/// self-explanatory. That is a numsim-core change, tracked separately; what
/// matters here is that the cone cannot be built without its parameters.
TEST(MaterialRegistry, ADruckerPragerDocumentMissingEtaFailsLoudly) {
const char* doc = R"([
{"type":"tensor_component_stepper_rank2","name":"stepper",
"increment":0.01,"indices":[0,0]},
{"type":"local_newton","name":"solver"},
{"type":"linear_isotropic_hardening","name":"hardening",
"source":"dp","K":500.0},
{"type":"drucker_prager_plasticity","name":"dp",
"hardening_source":"hardening","strain_source":"stepper",
"solver_source":"solver","G":76.92,"sigma_0":20.0,
"beta":0.05,"K_bulk":166.67}
])";

nm::material_context<policy> ctx;
EXPECT_THROW(
{
for (const auto& m : nlohmann::json::parse(doc))
nm::create_from_json<policy>(ctx, m);
},
std::exception)
<< "a cone with no friction coefficient must not build silently";
EXPECT_EQ(ctx.find("dp"), nullptr)
<< "the failed material must not be left behind in the context";
}

} // namespace
Loading