#include #include #include #include #include #include #include "serif/discretization/blocks/fields.hpp" #include "serif/model/state.hpp" namespace { namespace blocks = serif::discretization::blocks; namespace dependencies = serif::utils::dependencies; using Density = blocks::density::mass; using Shape = blocks::surface_deformation::coefficients; using ShapeRow = blocks::surface_deformation::shape_equilibrium; using Constant = blocks::barotropic_constant::mass_normalization; using Layout = blocks::SystemLayout, dependencies::KeyList>; using State = serif::model::StellarState; } TEST_CASE("A stellar state owns its layout and exposes fields in physical rather than residual order", "[model][state][unit]") { Layout layout{.state_sizes = {2, 3, 1}, .residual_sizes = {3, 1, 2}}; State state(layout, {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}); layout.state_sizes[0] = 9; layout.residual_sizes[0] = 8; CHECK(state.layout().state_sizes == std::array{2, 3, 1}); CHECK(state.layout().residual_sizes == std::array{3, 1, 2}); CHECK(state.layout().offset() == 4); CHECK(state.get().data() == state.values().data()); CHECK(state.get().data() == state.values().data() + 2); CHECK(state.get().data() == state.values().data() + 5); REQUIRE(state.get().size() == 3); state.get()[1] = 7.0; state.get()[0] = 8.0; CHECK(state.values()[3] == 7.0); CHECK(state.values()[5] == 8.0); const State& read_only = state; CHECK(read_only.get()[1] == 7.0); CHECK(read_only.values().data() == state.values().data()); } TEST_CASE("Copied and moved stellar candidates retain independent coefficients and valid typed access", "[model][state][ownership]") { const Layout layout{.state_sizes = {2, 3, 1}, .residual_sizes = {3, 1, 2}}; State original(layout, {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}); State candidate = original; CHECK(candidate.values().data() != original.values().data()); candidate.get()[0] = 9.0; candidate.get()[2] = 10.0; CHECK(original.get()[0] == 1.0); CHECK(original.get()[2] == 5.0); State moved(std::move(candidate)); CHECK(moved.layout() == layout); CHECK(moved.get()[0] == 9.0); CHECK(moved.get()[2] == 10.0); CHECK(moved.get().data() == moved.values().data() + 2); moved.get()[0] = 11.0; CHECK(original.get()[0] == 6.0); State assigned(Layout{.state_sizes = {1, 1, 1}, .residual_sizes = {1, 1, 1}}); assigned = std::move(moved); CHECK(assigned.layout() == layout); CHECK(assigned.get()[0] == 11.0); CHECK(assigned.get().data() == assigned.values().data() + 5); assigned = original; assigned.get()[1] = 12.0; CHECK(original.get()[1] == 2.0); } TEST_CASE("Stellar states reject inconsistent storage and overflowing sizes while supporting empty coordinates", "[model][state][validation]") { const Layout layout{.state_sizes = {2, 3, 1}, .residual_sizes = {3, 1, 2}}; const State zero(layout); REQUIRE(zero.values().size() == 6); for (double value : zero.values()) { CHECK(value == 0.0); } CHECK_THROWS_WITH(State(layout, std::vector(5)), Catch::Matchers::ContainsSubstring("INVALID_DOF_COUNT")); CHECK_THROWS_WITH(State(layout, std::vector(7)), Catch::Matchers::ContainsSubstring("INVALID_DOF_COUNT")); const Layout overflow{.state_sizes = {std::numeric_limits::max(), 1, 0}, .residual_sizes = {0, 1, 0}}; CHECK_THROWS_WITH(State(overflow), Catch::Matchers::ContainsSubstring("INVALID_DOF_COUNT")); const State partial(Layout{.state_sizes = {0, 2, 0}, .residual_sizes = {2, 0, 0}}); CHECK(partial.get().empty()); CHECK(partial.get().size() == 2); CHECK(partial.get().empty()); const blocks::SystemLayout, dependencies::KeyList<>> empty_layout{}; const serif::model::StellarState empty(empty_layout); CHECK(empty.values().empty()); }