feat(solid): add Hex8 input and three-DOF semantics

This commit is contained in:
KOKO\Mimi
2026-10-09 01:12:30 +09:00
parent df0388ae2f
commit 9acf18db4b
16 changed files with 1468 additions and 98 deletions
@@ -0,0 +1,449 @@
#include <gtest/gtest.h>
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <functional>
#include <limits>
#include <stdexcept>
#include <string>
#include <vector>
#include "fesa/analysis/analysis_model.h"
#include "fesa/assembly/load_assembler.h"
#include "fesa/assembly/parallel_for.h"
#include "fesa/assembly/sparse_assembler.h"
#include "fesa/fem/dof_manager.h"
#include "fesa/io/abaqus/domain_mapper.h"
#include "fesa/io/abaqus/input_reader.h"
namespace {
std::string Deck(const std::string& procedure = "*Static\n1,1,1,1\n",
const std::string& boundary = "10,1,3\n",
const std::string& extra = "") {
return "*Part,name=P\n*Node\n10,0,0,0\n20,1,0,0\n30,1,1,0\n40,0,1,0\n"
"50,0,0,1\n60,1,0,1\n70,1,1,1\n80,0,1,1\n"
"*Element,type=C3D8,elset=E\n99,10,20,30,40,50,60,70,80\n"
"*Solid Section,elset=E,material=M\n,\n*End Part\n"
"*Assembly,name=A\n*Instance,name=I,part=P\n*End Instance\n"
"*End Assembly\n*Material,name=M\n*Elastic\n1000,.3\n" +
extra + "*Step,name=Solid,inc=100\n" + procedure +
(boundary.empty() ? "" : "*Boundary\n" + boundary) + "*End Step\n";
}
fesa::Result<fesa::Domain> Map(const std::string& text) {
const auto path =
std::filesystem::temp_directory_path() / "fesa-hex8-semantics.inp";
{
std::ofstream out{path};
out << text;
}
auto syntax = fesa::AbaqusInputReader{}.Read(path);
std::filesystem::remove(path);
if (!syntax.HasValue())
return fesa::Result<fesa::Domain>::Failure(syntax.GetStatus());
return fesa::AbaqusDomainMapper{}.Map(syntax.Value());
}
TEST(Hex8Semantics, MapsEightSourceNodesAndExactlyThreePhysicalDofs) {
auto domain = Map(Deck());
ASSERT_TRUE(domain.HasValue())
<< (domain.GetStatus().Diagnostics().empty()
? ""
: domain.GetStatus().Diagnostics()[0].message);
ASSERT_EQ(domain.Value().Elements().Size(), 1U);
EXPECT_EQ(domain.Value().Elements()[0].SourceElementType(), "C3D8");
EXPECT_EQ(domain.Value().Elements()[0].SourceId().source_label, 99);
EXPECT_EQ(domain.Value().Elements()[0].NodeIndices().size(), 8U);
auto model = fesa::AnalysisModel::Create(domain.Value());
ASSERT_TRUE(model.HasValue());
auto dofs = fesa::DofManager::Create(model.Value());
ASSERT_TRUE(dofs.HasValue());
EXPECT_EQ(dofs.Value().FullDofCount(), 24U);
EXPECT_EQ(dofs.Value().ConstrainedDofCount(), 3U);
}
TEST(Hex8Semantics, InactiveZeroRotationsWarnAndDoNotCreateConstraints) {
auto domain = Map(Deck("*Static\n1,1,1,1\n", "10,1,6\n"));
ASSERT_TRUE(domain.HasValue())
<< (domain.GetStatus().Diagnostics().empty()
? ""
: domain.GetStatus().Diagnostics()[0].message);
EXPECT_EQ(domain.Value().Steps()[0].BoundaryConditions().size(), 3U);
ASSERT_FALSE(domain.Value().Warnings().empty());
EXPECT_EQ(domain.Value().Warnings().back().keyword, "BOUNDARY");
EXPECT_EQ(domain.Value().Warnings().back().entity_identity, "10");
EXPECT_GT(domain.Value().Warnings().back().location.line, 0U);
}
TEST(Hex8Semantics, RejectsNonzeroRotationsAndAllRotationalLoads) {
EXPECT_FALSE(Map(Deck("*Static\n1,1,1,1\n", "10,4,4,1\n")).HasValue());
EXPECT_FALSE(
Map(Deck("*Static\n1,1,1,1\n", "10,1,3\n*Cload\n20,4,0\n")).HasValue());
}
std::string Replace(std::string input, const std::string& from,
const std::string& to) {
const auto position = input.find(from);
if (position == std::string::npos)
throw std::logic_error{"Missing mutation source"};
input.replace(position, from.size(), to);
return input;
}
TEST(Hex8Semantics, RotationNoOpPreservesPhysicalMapAndLoads) {
auto physical =
Map(Deck("*Static\n1,1,1,1\n", "10,1,3,0\n*Cload\n20,2,17\n"));
auto extra =
Map(Deck("*Static\n1,1,1,1\n", "10,1,6,0\n20,4,6,-0\n*Cload\n20,2,17\n"));
ASSERT_TRUE(physical.HasValue());
ASSERT_TRUE(extra.HasValue());
auto pm = fesa::AnalysisModel::Create(physical.Value());
auto em = fesa::AnalysisModel::Create(extra.Value());
auto pd = fesa::DofManager::Create(pm.Value());
auto ed = fesa::DofManager::Create(em.Value());
ASSERT_TRUE(pd.HasValue());
ASSERT_TRUE(ed.HasValue());
EXPECT_EQ(pd.Value().FreeDofs(), ed.Value().FreeDofs());
EXPECT_EQ(pd.Value().ConstrainedDofs(), ed.Value().ConstrainedDofs());
EXPECT_EQ(pd.Value().GetSparsePattern().column_indices,
ed.Value().GetSparsePattern().column_indices);
EXPECT_EQ(ed.Value().Profile(), fesa::DofProfile::kSolidStructural);
EXPECT_EQ(ed.Value().ComponentsPerNode(), 3U);
EXPECT_THROW(ed.Value().FullDof(0, fesa::DofComponent::kUrx),
std::out_of_range);
const auto& element = extra.Value().Elements()[0];
auto scatter = ed.Value().ElementScatter(
{element.SourceId(),
element.NodeIndices(),
{fesa::DofComponent::kUx, fesa::DofComponent::kUy,
fesa::DofComponent::kUz}});
ASSERT_TRUE(scatter.HasValue());
EXPECT_EQ(scatter.Value().size(), 24U);
EXPECT_EQ(scatter.Value().front(), 0U);
EXPECT_EQ(scatter.Value().back(), 23U);
auto pf = fesa::LoadAssembler::AssembleFullNodalLoad(pm.Value(), pd.Value());
auto ef = fesa::LoadAssembler::AssembleFullNodalLoad(em.Value(), ed.Value());
ASSERT_TRUE(pf.HasValue());
ASSERT_TRUE(ef.HasValue());
for (std::size_t i = 0; i < 24U; ++i) EXPECT_EQ(pf.Value()[i], ef.Value()[i]);
}
TEST(Hex8Semantics, RejectsTinyRotationBadTargetsOptionsAndDofRanges) {
for (const std::string row :
{"10,4,6,1e-100\n", "Missing,4,6,0\n", "10,0,3\n", "10,1,7\n"})
EXPECT_FALSE(Map(Deck("*Static\n1,1,1,1\n", row)).HasValue()) << row;
for (const std::string option : {"type=VELOCITY", "amplitude=A", "op=NEW"})
EXPECT_FALSE(
Map(Replace(Deck(), "*Boundary", "*Boundary," + option)).HasValue());
for (const std::string row : {"10,4,6\n", "10,4,6,0\n", "10,4,6,\n"}) {
auto result = Map(Deck("*Static\n1,1,1,1\n", row));
ASSERT_TRUE(result.HasValue()) << row;
EXPECT_TRUE(result.Value().Steps()[0].BoundaryConditions().empty());
ASSERT_EQ(result.Value().Warnings().size(), 1U);
EXPECT_EQ(result.Value().Warnings()[0].code,
"inactive-solid-rotation-boundary");
EXPECT_EQ(result.Value().Warnings()[0].severity, fesa::Severity::kWarning);
}
}
TEST(Hex8Semantics, AllowsEmptyFreeAndNoBoundaryModels) {
std::string all;
for (int node = 10; node <= 80; node += 10)
all += std::to_string(node) + ",1,6\n";
for (const auto& boundaries : {all, std::string{}}) {
auto domain = Map(Deck("*Static\n1,1,1,1\n", boundaries));
ASSERT_TRUE(domain.HasValue());
auto model = fesa::AnalysisModel::Create(domain.Value());
auto dofs = fesa::DofManager::Create(model.Value());
ASSERT_TRUE(dofs.HasValue());
EXPECT_EQ(dofs.Value().FullDofCount(), 24U);
EXPECT_EQ(dofs.Value().FreeDofCount(), boundaries.empty() ? 24U : 0U);
}
}
TEST(Hex8Semantics, RetainsStaticDensityTargetsAndDirectSchedule) {
auto domain =
Map(Deck("*Static,direct\n.1,1,\n", "10,1,3,.25\n", "*Density\n2\n"));
ASSERT_TRUE(domain.HasValue())
<< (domain.GetStatus().Diagnostics().empty()
? ""
: domain.GetStatus().Diagnostics()[0].message);
EXPECT_TRUE(domain.Value().IsSolid());
ASSERT_EQ(domain.Value().SolidSections().Size(), 1U);
EXPECT_EQ(domain.Value().SolidSections()[0].MaterialIndex(), 0U);
EXPECT_EQ(domain.Value().Densities()[0].rho, 2.0);
const auto& step = domain.Value().Steps()[0];
EXPECT_EQ(step.Name(), "Solid");
EXPECT_TRUE(step.IsDirectStatic());
EXPECT_EQ(step.IncrementCount(), 10U);
EXPECT_EQ(step.MaximumIncrements(), 100U);
auto model = fesa::AnalysisModel::Create(domain.Value());
auto dofs = fesa::DofManager::Create(model.Value());
EXPECT_EQ(dofs.Value().PrescribedValues()[0], .25);
for (const std::string values : {".3,1\n", "0,1\n", ".1,11\n"})
EXPECT_FALSE(Map(Deck("*Static,direct\n" + values)).HasValue());
EXPECT_FALSE(Map(Replace(Deck(), "1000,.3", "1000,.5")).HasValue());
EXPECT_FALSE(Map(Replace(Deck(), "1000,.3", "1000,-1")).HasValue());
}
TEST(Hex8Semantics, SamplesIndependentAmplitudeLoadsAtActualEndpoints) {
const std::string amplitudes =
"*Density\n2\n*Amplitude,name=A\n0,0,.1,1\n10,1\n"
"*Amplitude,name=B,definition=TABULAR,time=STEP "
"TIME,value=RELATIVE\n0,2,1,4\n";
const auto text = Deck("*Dynamic,direct\n.1,10,\n",
"10,1,6\n*Cload,amplitude=a\n20,1,10\n"
"*Cload,amplitude=B\n20,1,3\n*Cload\n20,1,5\n",
amplitudes);
auto domain = Map(text);
ASSERT_TRUE(domain.HasValue())
<< (domain.GetStatus().Diagnostics().empty()
? ""
: domain.GetStatus().Diagnostics()[0].message);
ASSERT_EQ(domain.Value().Amplitudes().size(), 2U);
const auto& amplitude = domain.Value().Amplitudes()[0];
EXPECT_EQ(amplitude.name_raw, "A");
EXPECT_EQ(amplitude.Evaluate(0), 0);
EXPECT_EQ(amplitude.Evaluate(.05), .5);
EXPECT_EQ(amplitude.Evaluate(.1), 1);
EXPECT_EQ(amplitude.Evaluate(20), 1);
EXPECT_FALSE(domain.Value().Dynamic()->nlgeom);
EXPECT_EQ(domain.Value().Dynamic()->increment_count, 100U);
const auto& loads = domain.Value().Steps()[0].ConcentratedLoads();
EXPECT_EQ(loads[0].AmplitudeIndex(), std::optional<fesa::EntityIndex>{0U});
EXPECT_EQ(loads[1].AmplitudeIndex(), std::optional<fesa::EntityIndex>{1U});
EXPECT_EQ(loads[0].AmplitudeName(), "a");
EXPECT_GT(loads[0].AmplitudeLocation().line, 0U);
auto model = fesa::AnalysisModel::Create(domain.Value());
auto dofs = fesa::DofManager::Create(model.Value());
ASSERT_TRUE(dofs.HasValue());
for (double time : {0., .05, .1, 1., 10.}) {
auto load = fesa::LoadAssembler::AssembleFullNodalLoad(model.Value(),
dofs.Value(), time);
ASSERT_TRUE(load.HasValue());
EXPECT_DOUBLE_EQ(load.Value()[3],
amplitude.Evaluate(time) * 10 +
domain.Value().Amplitudes()[1].Evaluate(time) * 3 + 5);
}
EXPECT_FALSE(Map(Replace(text, "10,1,6", "10,1,3,1e-100")).HasValue());
EXPECT_FALSE(
Map(Replace(text, "amplitude=a", "amplitude=missing")).HasValue());
EXPECT_TRUE(
Map(Replace(text, "name=Solid,inc=100", "name=Solid,nlgeom=YES,inc=100"))
.HasValue());
}
TEST(Hex8Semantics, RejectsAmplitudeOptionsDataAndCrossFamilyUse) {
const std::string prefix = "*Density\n2\n";
for (const std::string amplitude :
{"*Amplitude,name=A,time=TOTAL TIME\n0,0\n",
"*Amplitude,name=A,definition=SMOOTH STEP\n0,0\n",
"*Amplitude,name=A,value=ABSOLUTE\n0,0\n",
"*Amplitude,name=A,smooth=0\n0,0\n", "*Amplitude,name=A\n0,0,1\n",
"*Amplitude,name=A\n0,0,0,1\n", "*Amplitude,name=A\n1,0,0,1\n",
"*Amplitude,name=A\n-1,0\n", "*Amplitude,name=A\n0,nan\n",
"*Amplitude,name=A\n",
"*Amplitude,name=A\n0,0\n*Amplitude,name=a\n0,1\n"})
EXPECT_FALSE(
Map(Deck("*Dynamic,direct\n.1,1\n", "10,1,3\n", prefix + amplitude))
.HasValue())
<< amplitude;
EXPECT_FALSE(
Map(Deck("*Static\n1,1,1,1\n", "10,1,3\n", "*Amplitude,name=A\n0,0\n"))
.HasValue());
EXPECT_FALSE(Map(Deck("*Dynamic,direct\n.1,1\n", "10,1,3\n")).HasValue());
}
TEST(Hex8Semantics, RejectsAliasesConnectivityThicknessAndMixedModels) {
for (const std::string type : {"C3D8R", "C3D8I", "C3D8H", "C3D20"})
EXPECT_FALSE(Map(Replace(Deck(), "type=C3D8", "type=" + type)).HasValue());
EXPECT_FALSE(Map(Replace(Deck(), "99,10,20,30,40,50,60,70,80",
"99,10,20,30,40,50,60,70,70"))
.HasValue());
EXPECT_FALSE(Map(Replace(Deck(), "99,10,20,30,40,50,60,70,80",
"99,10,20,30,40,50,60,70,999"))
.HasValue());
EXPECT_FALSE(
Map(Replace(Deck(), "material=M\n,", "material=M\n2")).HasValue());
EXPECT_FALSE(Map(Replace(Deck(), "*Solid Section",
"*Element,type=S4\n100,10,20,30,40\n*Solid Section"))
.HasValue());
}
TEST(Hex8Semantics, PreservesShellSixComponentProfile) {
auto text = Replace(Deck(), "50,0,0,1\n60,1,0,1\n70,1,1,1\n80,0,1,1\n", "");
text = Replace(text, "*Element,type=C3D8,elset=E\n99,10,20,30,40,50,60,70,80",
"*Element,type=S4\n99,10,20,30,40\n*Elset,elset=E\n99");
text = Replace(text, "*Solid Section,elset=E,material=M\n,",
"*Shell Section,elset=E,material=M\n.1");
auto domain = Map(text);
ASSERT_TRUE(domain.HasValue());
auto model = fesa::AnalysisModel::Create(domain.Value());
auto dofs = fesa::DofManager::Create(model.Value());
ASSERT_TRUE(dofs.HasValue());
EXPECT_EQ(dofs.Value().Profile(), fesa::DofProfile::kStructural);
EXPECT_EQ(dofs.Value().ComponentsPerNode(), 6U);
EXPECT_EQ(dofs.Value().FullDofCount(), 24U);
EXPECT_EQ(dofs.Value().FullDof(1, fesa::DofComponent::kUrx), 9U);
}
TEST(Hex8Semantics, MapsAllFiveUnmodifiedRequiredSourceDecks) {
auto root = std::filesystem::path{__FILE__}.parent_path();
for (int i = 0; i < 4; ++i) root = root.parent_path();
for (const std::string name :
{"hexa_linearstatic.inp", "hexa_nonlinearstatic.inp", "hexa_freq.inp",
"hexa_dynlinear.inp", "hexa_dynnonlinear.inp"}) {
auto syntax =
fesa::AbaqusInputReader{}.Read(root / "reference/hexa" / name);
ASSERT_TRUE(syntax.HasValue()) << name;
auto domain = fesa::AbaqusDomainMapper{}.Map(syntax.Value());
ASSERT_TRUE(domain.HasValue())
<< name << ": "
<< (domain.GetStatus().Diagnostics().empty()
? ""
: domain.GetStatus().Diagnostics()[0].message);
EXPECT_EQ(domain.Value().SolidElements().Size(), 300U);
EXPECT_EQ(domain.Value().Nodes().size(), 442U);
EXPECT_EQ(std::count_if(domain.Value().Warnings().begin(),
domain.Value().Warnings().end(),
[](const auto& warning) {
return warning.code ==
"inactive-solid-rotation-boundary";
}),
3);
auto model = fesa::AnalysisModel::Create(domain.Value());
auto dofs = fesa::DofManager::Create(model.Value());
ASSERT_TRUE(dofs.HasValue()) << name;
EXPECT_EQ(dofs.Value().FullDofCount(), 1326U);
EXPECT_EQ(dofs.Value().ConstrainedDofCount(), 51U);
}
}
class ConstantMatrixElement final : public fesa::Element {
public:
ConstantMatrixElement(const fesa::ElementDefinition& definition, double entry)
: layout_{definition.SourceId(),
definition.NodeIndices(),
{fesa::DofComponent::kUx, fesa::DofComponent::kUy,
fesa::DofComponent::kUz}},
entry_{entry} {}
const fesa::ElementDofLayout& DofLayout() const noexcept override {
return layout_;
}
fesa::Result<fesa::ElementStiffnessContribution> ComputeStiffness()
const override {
fesa::Matrix values{24U, 24U};
for (std::size_t row = 0U; row < 24U; ++row)
for (std::size_t column = 0U; column < 24U; ++column)
values(row, column) = entry_;
return fesa::Result<fesa::ElementStiffnessContribution>::Success(
{layout_, std::move(values)});
}
fesa::Result<fesa::ElementResultBundle> Recover(
const fesa::Vector&) const override {
return fesa::Result<fesa::ElementResultBundle>::Failure(
fesa::Status::Failure(fesa::FailureCategory::kModel,
{{fesa::Severity::kError,
"test-no-recovery",
{},
"",
"",
"Assembly fixture has no recovery."}}));
}
private:
fesa::ElementDofLayout layout_;
double entry_;
};
TEST(Hex8Semantics, AssemblesFullSymmetricSolidMatricesInStableSourceOrder) {
const auto text =
Replace(Deck(), "99,10,20,30,40,50,60,70,80\n",
"99,10,20,30,40,50,60,70,80\n100,10,20,30,40,50,60,70,80\n"
"101,10,20,30,40,50,60,70,80\n");
auto domain = Map(text);
ASSERT_TRUE(domain.HasValue());
auto model = fesa::AnalysisModel::Create(domain.Value());
ConstantMatrixElement first{domain.Value().Elements()[0], 1e16};
ConstantMatrixElement second{domain.Value().Elements()[1], -1e16};
ConstantMatrixElement third{domain.Value().Elements()[2], 1};
const fesa::ElementView elements{std::cref(first), std::cref(second),
std::cref(third)};
fesa::DofManager dofs;
ASSERT_TRUE(dofs.Build(model.Value(), elements).IsOk());
auto serial = fesa::SparseAssembler::Assemble(elements, dofs,
fesa::SerialParallelFor{});
auto parallel =
fesa::SparseAssembler::Assemble(elements, dofs, fesa::TbbParallelFor{});
ASSERT_TRUE(serial.HasValue());
ASSERT_TRUE(parallel.HasValue());
EXPECT_EQ(serial.Value().Rows(), 24U);
EXPECT_EQ(serial.Value().Columns(), 24U);
EXPECT_EQ(serial.Value().Values().size(), 576U);
for (double value : serial.Value().Values()) EXPECT_EQ(value, 1.0);
EXPECT_EQ(serial.Value().RowOffsets(), parallel.Value().RowOffsets());
EXPECT_EQ(serial.Value().ColumnIndices(), parallel.Value().ColumnIndices());
EXPECT_EQ(serial.Value().Values(), parallel.Value().Values());
}
TEST(Hex8Semantics, ValidatesOutputNoOpsAndRejectsUnapprovedDynamicChildren) {
for (const std::string output :
{"*Output,field,variable=PRESELECT\n*Node Output\nU,RF\n*Element "
"Output,directions=YES\nS,E\n",
"*Output,history,variable=PRESELECT\n"})
EXPECT_TRUE(
Map(Replace(Deck(), "*End Step", output + "*End Step")).HasValue());
EXPECT_FALSE(Map(Replace(Deck(), "*End Step",
"*Output,field,variable=OTHER\n*End Step"))
.HasValue());
EXPECT_FALSE(
Map(Replace(
Deck(), "*End Step",
"*Output,field\n*Element Output,position=NODES\nS\n*End Step"))
.HasValue());
auto dynamic = Deck("*Dynamic,direct\n.1,1\n", "10,1,3\n", "*Density\n2\n");
EXPECT_FALSE(
Map(Replace(
dynamic, "*End Step",
"*Output,field,variable=PRESELECT\n*Node Output\nU\n*End Step"))
.HasValue());
EXPECT_FALSE(Map(Replace(dynamic, "*End Step",
"*Restart,write,frequency=1\n*End Step"))
.HasValue());
}
fesa::ModelDefinition SolidDefinition() {
fesa::ModelDefinition definition;
const std::array<std::array<double, 3>, 8> coordinates{{{0, 0, 0},
{1, 0, 0},
{1, 1, 0},
{0, 1, 0},
{0, 0, 1},
{1, 0, 1},
{1, 1, 1},
{0, 1, 1}}};
for (std::size_t i = 0; i < coordinates.size(); ++i)
definition.nodes.push_back({{"I", static_cast<std::int64_t>(10 * (i + 1)),
std::to_string(10 * (i + 1))},
coordinates[i],
{"solid.inp", i + 1}});
definition.materials.emplace_back("M", 1000, .3,
fesa::SourceLocation{"solid.inp", 20});
definition.solid_sections.emplace_back(fesa::SourceEntityId{"P", 1, "E"}, "E",
0,
fesa::SourceLocation{"solid.inp", 21});
definition.solid_elements.emplace_back(
fesa::SourceEntityId{"I", 99, "99"},
std::array<fesa::EntityIndex, 8>{{0, 1, 2, 3, 4, 5, 6, 7}}, 0, 0,
fesa::SourceLocation{"solid.inp", 22});
definition.steps.emplace_back(
"Solid", std::vector<fesa::PrescribedDisplacementDefinition>{},
std::vector<fesa::NodalLoad>{}, 1., 1., 1., 1.,
fesa::SourceLocation{"solid.inp", 23});
return definition;
}
TEST(Hex8Semantics, RejectsInvalidProgrammaticSolidCandidates) {
EXPECT_TRUE(fesa::Domain::Create(SolidDefinition()).HasValue());
auto repeated = SolidDefinition();
repeated.solid_elements[0].node_indices[7] = 0;
EXPECT_FALSE(fesa::Domain::Create(std::move(repeated)).HasValue());
auto material = SolidDefinition();
material.materials[0].poisson_ratio = .5;
EXPECT_FALSE(fesa::Domain::Create(std::move(material)).HasValue());
auto section = SolidDefinition();
section.solid_elements[0].section_index = 99;
EXPECT_FALSE(fesa::Domain::Create(std::move(section)).HasValue());
auto rotational = SolidDefinition();
rotational.steps[0].loads.push_back({"10", 4, 0, {"solid.inp", 24}});
EXPECT_FALSE(fesa::Domain::Create(std::move(rotational)).HasValue());
auto unused = SolidDefinition();
unused.nodes.push_back({{"I", 90, "90"}, {2, 2, 2}, {"solid.inp", 25}});
EXPECT_FALSE(fesa::Domain::Create(std::move(unused)).HasValue());
}
} // namespace