#include "fesa/model/domain.h" #include #include #include #include #include #include #include #include #include #include #include "../elements/thermal_shell_geometry.h" namespace fesa { namespace { Status InvalidThermalDefinition(const SourceLocation& location, const std::string& identity, const std::string& message) { return Status::Failure( FailureCategory::kModel, {{Severity::kError, "invalid-thermal-definition", location, "THERMAL", identity, message}}); } bool IsThermalDof(const int dof) { return dof == 11 || dof == 15; } Status ValidateThermalTargets( const std::vector& boundaries, const std::vector& active_nodes, const char* identity) { std::map, double> values; for (const auto& boundary : boundaries) { if (boundary.node_index >= active_nodes.size() || !active_nodes[boundary.node_index] || !IsThermalDof(boundary.dof) || !std::isfinite(boundary.value)) { return InvalidThermalDefinition(boundary.location, identity, "Thermal target is invalid or inactive."); } const auto inserted = values.emplace( std::make_pair(boundary.node_index, boundary.dof), boundary.value); if (!inserted.second && inserted.first->second != boundary.value) { return InvalidThermalDefinition( boundary.location, identity, "Conflicting thermal boundary values target one node and DOF."); } } return Status::Ok(); } Status ValidateThermalDefinition(const ModelDefinition& definition) { const bool has_structural = !definition.elements.empty() || !definition.shell_elements.empty() || !definition.solid_elements.empty(); const bool has_thermal_data = !definition.thermal_materials.empty() || !definition.thermal_sections.empty() || definition.thermal_step.has_value() || !definition.thermal_initial_conditions.empty() || !definition.thermal_model_boundaries.empty(); if (has_structural && !definition.thermal_elements.empty()) { return InvalidThermalDefinition({}, "DS4", "Structural and DS4 elements cannot mix."); } if (definition.thermal_elements.empty()) { if (has_thermal_data) { return InvalidThermalDefinition({}, "DS4", "Thermal definitions require a DS4 element."); } return Status::Ok(); } if (!definition.thermal_step) { return InvalidThermalDefinition({}, "DS4", "A DS4 model requires one thermal step."); } std::map material_identities; for (const auto& material : definition.thermal_materials) { const std::string identity = material.source_id.instance_name + ":" + material.source_id.source_label_text; if (!material_identities.emplace(identity, true).second) { return InvalidThermalDefinition(material.location, identity, "Thermal material identity is duplicated."); } const auto candidate = IsotropicThermalMaterial::Create( material.source_id, material.name, material.conductivity, material.density, material.specific_heat, material.location); if (!candidate.HasValue()) return candidate.GetStatus(); } std::map section_identities; for (const auto& section : definition.thermal_sections) { const std::string identity = section.source_id.instance_name + ":" + section.source_id.source_label_text; if (section.source_id.source_label_text.empty() || section.name.empty() || !std::isfinite(section.thickness) || !(section.thickness > 0.0) || section.material_index >= definition.thermal_materials.size() || !section_identities.emplace(identity, true).second) { return InvalidThermalDefinition(section.location, section.name, "Thermal section is invalid."); } } std::vector active_nodes(definition.nodes.size(), false); std::map identities; for (const auto& element : definition.thermal_elements) { const std::string identity = element.source_id.instance_name + ":" + element.source_id.source_label_text; if (element.source_id.source_label_text.empty() || !identities.emplace(identity, true).second || element.section_index >= definition.thermal_sections.size()) { return InvalidThermalDefinition(element.location, identity, "DS4 section assignment is invalid."); } for (const EntityIndex node : element.node_indices) { if (node >= active_nodes.size()) { return InvalidThermalDefinition(element.location, identity, "DS4 node target is invalid."); } active_nodes[node] = true; } } const Status geometry_status = ValidateThermalShellMesh(definition); if (!geometry_status.IsOk()) return geometry_status; std::map> initial_values; for (const auto& initial : definition.thermal_initial_conditions) { if (initial.node_index >= active_nodes.size() || !active_nodes[initial.node_index]) { return InvalidThermalDefinition(initial.location, "INITIAL CONDITIONS", "Thermal initial target is inactive."); } for (const double value : initial.station_values) { if (!std::isfinite(value) || value != initial.station_values[0U]) { return InvalidThermalDefinition( initial.location, "INITIAL CONDITIONS", "Initial temperatures must be finite and equal at five stations."); } } const auto inserted = initial_values.emplace(initial.node_index, initial.station_values); if (!inserted.second && inserted.first->second != initial.station_values) { return InvalidThermalDefinition(initial.location, "INITIAL CONDITIONS", "Conflicting initial temperatures target " "one node."); } } Status model_status = ValidateThermalTargets( definition.thermal_model_boundaries, active_nodes, "BOUNDARY"); if (!model_status.IsOk()) return model_status; const auto& step = *definition.thermal_step; if (!std::isfinite(step.nominal_increment) || !(step.nominal_increment > 0.0) || !std::isfinite(step.time_period) || !(step.time_period > 0.0) || step.maximum_increments == 0U || step.nlgeom) { return InvalidThermalDefinition(step.location, step.name, "Thermal step controls are invalid."); } Status step_status = ValidateThermalTargets(step.boundaries, active_nodes, "STEP BOUNDARY"); if (!step_status.IsOk()) return step_status; for (const auto& load : step.loads) { if (load.node_index >= active_nodes.size() || !active_nodes[load.node_index] || !IsThermalDof(load.dof) || !std::isfinite(load.power)) { return InvalidThermalDefinition(load.location, "CFLUX", "Thermal flux target is invalid or inactive."); } } return Status::Ok(); } /// @brief Checks Solid semantic candidates before immutable ownership starts. Status ValidateSolidDefinition(ModelDefinition& definition) { const auto fail = [](const SourceLocation& location, const std::string& identity, const std::string& message) { return Status::Failure(FailureCategory::kModel, {{Severity::kError, "invalid-solid-definition", location, "SOLID", identity, message}}); }; if (definition.solid_elements.empty()) { if (!definition.solid_sections.empty() || !definition.amplitudes.empty()) return fail({}, "C3D8", "Solid definitions require a C3D8 model."); return Status::Ok(); } if (!definition.elements.empty() || !definition.shell_elements.empty() || !definition.thermal_elements.empty() || definition.thermal_step || !definition.sections.empty() || !definition.shell_sections.empty() || !definition.element_surfaces.empty()) { return fail({}, "C3D8", "Solid models cannot mix element or section families."); } if (definition.steps.size() + (definition.frequency_step ? 1U : 0U) + (definition.dynamic_step ? 1U : 0U) != 1U) { return fail({}, "STEP", "Solid models require exactly one procedure."); } std::vector active(definition.nodes.size(), false); std::set> node_ids; for (const auto& node : definition.nodes) { if (!node_ids .emplace(node.source_id.instance_name, node.source_id.source_label) .second) return fail(node.location, node.source_id.source_label_text, "Node identity is duplicated."); for (const double coordinate : node.coordinates) if (!std::isfinite(coordinate)) return fail(node.location, node.source_id.source_label_text, "Solid coordinates must be finite."); } std::set> element_ids; std::set active_materials; for (const auto& element : definition.solid_elements) { if (!element_ids .emplace(element.source_id.instance_name, element.source_id.source_label) .second || element.section_index >= definition.solid_sections.size() || element.material_index >= definition.materials.size()) { return fail( element.location, element.source_id.source_label_text, "Solid identity, section, and material must resolve uniquely."); } if (definition.solid_sections[element.section_index].MaterialIndex() != element.material_index) return fail(element.location, element.source_id.source_label_text, "Solid material assignment conflicts."); active_materials.insert(element.material_index); std::set distinct; for (const auto node : element.node_indices) { if (node >= active.size() || !distinct.insert(node).second) return fail(element.location, element.source_id.source_label_text, "Solid connectivity requires eight distinct active nodes."); active[node] = true; } } for (std::size_t node = 0U; node < active.size(); ++node) if (!active[node]) return fail(definition.nodes[node].location, definition.nodes[node].source_id.source_label_text, "Solid nodes must be connected."); for (const auto& section : definition.solid_sections) if (section.MaterialIndex() >= definition.materials.size() || section.Name().empty()) return fail(section.Location(), section.Name(), "Solid material assignment is invalid."); for (const auto index : active_materials) { const auto& material = definition.materials[index]; const double young = material.YoungsModulus(); const double nu = material.PoissonsRatio(); const double mu = young / (2.0 * (1.0 + nu)); const double lambda = young * nu / ((1.0 + nu) * (1.0 - 2.0 * nu)); if (!std::isfinite(young) || !(young > 0.0) || !std::isfinite(nu) || !(nu > -1.0 && nu < 0.5) || !std::isfinite(mu) || !std::isfinite(lambda)) return fail(material.Location(), material.Name(), "Solid isotropic constants are invalid."); } std::set density_materials; for (const auto& density : definition.densities) { if (density.material_index >= definition.materials.size() || !std::isfinite(density.rho) || !(density.rho > 0.0) || !density_materials.insert(density.material_index).second) return fail(density.location, "DENSITY", "Solid density must be finite positive and unique."); } if (definition.dynamic_step || definition.frequency_step) for (const auto material : active_materials) if (density_materials.count(material) == 0U) return fail({}, "DENSITY", "Solid frequency and dynamic materials require density."); std::set amplitude_names; for (const auto& amplitude : definition.amplitudes) { if (!definition.dynamic_step || amplitude.name_raw.empty() || amplitude.name_canonical.empty() || !amplitude_names.insert(amplitude.name_canonical).second || amplitude.points.empty()) return fail(amplitude.location, amplitude.name_raw, "Solid amplitude identity is invalid."); double previous = -1.0; for (const auto& point : amplitude.points) { if (!std::isfinite(point[0]) || !std::isfinite(point[1]) || point[0] < 0.0 || !(point[0] > previous)) return fail(amplitude.location, amplitude.name_raw, "Solid amplitude pairs are invalid."); previous = point[0]; } } const auto validate_boundaries = [&fail](const auto& boundaries, bool homogeneous) { for (const auto& boundary : boundaries) { if (boundary.first_dof < 1 || boundary.last_dof < boundary.first_dof || boundary.last_dof > 3 || !std::isfinite(boundary.value) || (homogeneous && boundary.value != 0.0)) return fail(boundary.location, boundary.target, "Solid physical boundaries require DOF1..3."); } return Status::Ok(); }; const auto validate_loads = [&fail, &definition](const auto& loads, bool dynamic) { for (const auto& load : loads) { if (load.dof < 1 || load.dof > 3 || !std::isfinite(load.magnitude) || (load.amplitude_index && (!dynamic || *load.amplitude_index >= definition.amplitudes.size()))) return fail(load.location, load.target, "Solid load component or amplitude is invalid."); } return Status::Ok(); }; if (definition.frequency_step) return validate_boundaries(definition.frequency_step->boundaries, true); if (definition.dynamic_step) { const auto& dynamic = *definition.dynamic_step; if (!std::isfinite(dynamic.initial_increment) || !(dynamic.initial_increment > 0.0) || !std::isfinite(dynamic.time_period) || !(dynamic.time_period > 0.0) || dynamic.increment_count == 0U || dynamic.increment_count > dynamic.maximum_increments || dynamic.initial_increment * static_cast(dynamic.increment_count) != dynamic.time_period || dynamic.alpha != -0.05 || dynamic.beta != 0.275625 || dynamic.gamma != 0.55) return fail(dynamic.data_location, dynamic.source_step_name_raw, "Solid dynamic controls are invalid."); const auto status = validate_boundaries(dynamic.boundaries, true); if (!status.IsOk()) return status; return validate_loads(dynamic.loads, true); } const auto& step = definition.steps[0]; if (step.direct && (!std::isfinite(step.initial_increment) || !(step.initial_increment > 0.0) || !std::isfinite(step.time_period) || !(step.time_period > 0.0) || step.increment_count == 0U || step.increment_count > step.maximum_increments || step.initial_increment * static_cast(step.increment_count) != step.time_period)) return fail(step.location, step.name, "Solid static fixed schedule is invalid."); const auto status = validate_boundaries(step.boundaries, false); if (!status.IsOk()) return status; return validate_loads(step.loads, false); } } // namespace StepDefinition::StepDefinition(StaticStepDefinition definition) : name_{std::move(definition.name)}, procedure_kind_{definition.procedure_kind}, nlgeom_{definition.nlgeom}, initial_increment_{definition.initial_increment}, time_period_{definition.time_period}, minimum_increment_{definition.minimum_increment}, maximum_increment_{definition.maximum_increment}, location_{std::move(definition.location)} { for (auto& boundary : definition.boundaries) { for (int source_dof = boundary.first_dof; source_dof <= boundary.last_dof; ++source_dof) { const std::size_t source_order = boundary_conditions_.size(); auto owned = std::make_unique( SourceTargetQuery{SourceEntityKind::kNode, "", boundary.target}, static_cast(source_dof - 1), boundary.value, source_order, boundary.location); boundary_conditions_view_.push_back(std::cref(*owned)); prescribed_displacements_view_.Add(*owned); boundary_conditions_.push_back(std::move(owned)); } } loads_.reserve(definition.loads.size()); loads_view_.reserve(definition.loads.size()); for (std::size_t source_order = 0U; source_order < definition.loads.size(); ++source_order) { auto& load = definition.loads[source_order]; auto owned = std::make_unique( SourceTargetQuery{SourceEntityKind::kNode, "", load.target}, load.dof, load.magnitude, source_order, std::move(load.location), load.amplitude_index, std::move(load.amplitude_name_raw), std::move(load.amplitude_location)); loads_view_.push_back(std::cref(*owned)); concentrated_loads_view_.Add(*owned); loads_.push_back(std::move(owned)); } surface_pressure_loads_ = std::move(definition.surface_pressure_loads); direct_static_ = definition.direct; increment_count_ = definition.increment_count; maximum_increments_ = definition.maximum_increments; } StepDefinition::StepDefinition(FrequencyStepDefinition definition) : name_{definition.source_step_name_raw}, procedure_kind_{StaticProcedureKind::kLinearSmallDisplacement}, nlgeom_{false}, initial_increment_{0.0}, time_period_{0.0}, minimum_increment_{0.0}, maximum_increment_{0.0}, location_{definition.step_location}, frequency_{std::move(definition)} { for (const auto& boundary : frequency_->boundaries) { for (int source_dof = boundary.first_dof; source_dof <= boundary.last_dof; ++source_dof) { const std::size_t source_order = boundary_conditions_.size(); auto owned = std::make_unique( SourceTargetQuery{SourceEntityKind::kNode, "", boundary.target}, static_cast(source_dof - 1), boundary.value, source_order, boundary.location); boundary_conditions_view_.push_back(std::cref(*owned)); prescribed_displacements_view_.Add(*owned); boundary_conditions_.push_back(std::move(owned)); } } } StepDefinition::StepDefinition(DynamicStepDefinition definition) : name_{definition.source_step_name_raw}, procedure_kind_{definition.nlgeom ? StaticProcedureKind::kNonlinearFullNewton : StaticProcedureKind::kLinearSmallDisplacement}, nlgeom_{definition.nlgeom}, initial_increment_{definition.initial_increment}, time_period_{definition.time_period}, minimum_increment_{definition.initial_increment}, maximum_increment_{definition.initial_increment}, location_{definition.step_location}, dynamic_{std::move(definition)} { for (const auto& boundary : dynamic_->boundaries) { for (int source_dof = boundary.first_dof; source_dof <= boundary.last_dof; ++source_dof) { const std::size_t source_order = boundary_conditions_.size(); auto owned = std::make_unique( SourceTargetQuery{SourceEntityKind::kNode, "", boundary.target}, static_cast(source_dof - 1), boundary.value, source_order, boundary.location); boundary_conditions_view_.push_back(std::cref(*owned)); prescribed_displacements_view_.Add(*owned); boundary_conditions_.push_back(std::move(owned)); } } loads_.reserve(dynamic_->loads.size()); loads_view_.reserve(dynamic_->loads.size()); for (std::size_t source_order = 0U; source_order < dynamic_->loads.size(); ++source_order) { auto& load = dynamic_->loads[source_order]; auto owned = std::make_unique( SourceTargetQuery{SourceEntityKind::kNode, "", load.target}, load.dof, load.magnitude, source_order, std::move(load.location), load.amplitude_index, std::move(load.amplitude_name_raw), std::move(load.amplitude_location)); loads_view_.push_back(std::cref(*owned)); concentrated_loads_view_.Add(*owned); loads_.push_back(std::move(owned)); } } const std::string& StepDefinition::Name() const noexcept { return name_; } const BoundaryConditionView& StepDefinition::BoundaryConditions() const noexcept { return boundary_conditions_view_; } const DomainCollectionView& StepDefinition::PrescribedDisplacements() const noexcept { return prescribed_displacements_view_; } const LoadView& StepDefinition::Loads() const noexcept { return loads_view_; } const DomainCollectionView& StepDefinition::ConcentratedLoads() const noexcept { return concentrated_loads_view_; } const std::vector& StepDefinition::SurfacePressureLoads() const noexcept { return surface_pressure_loads_; } StaticProcedureKind StepDefinition::ProcedureKind() const noexcept { return procedure_kind_; } bool StepDefinition::IsNonlinearStatic() const noexcept { return !dynamic_ && procedure_kind_ == StaticProcedureKind::kNonlinearFullNewton; } bool StepDefinition::IsFrequency() const noexcept { return frequency_.has_value(); } bool StepDefinition::IsDynamic() const noexcept { return dynamic_.has_value(); } const FrequencyStepDefinition* StepDefinition::Frequency() const noexcept { return frequency_ ? &*frequency_ : nullptr; } const DynamicStepDefinition* StepDefinition::Dynamic() const noexcept { return dynamic_ ? &*dynamic_ : nullptr; } bool StepDefinition::UsesNonlinearGeometry() const noexcept { return nlgeom_; } double StepDefinition::InitialIncrement() const noexcept { return initial_increment_; } double StepDefinition::TimePeriod() const noexcept { return time_period_; } double StepDefinition::MinimumIncrement() const noexcept { return minimum_increment_; } double StepDefinition::MaximumIncrement() const noexcept { return maximum_increment_; } bool StepDefinition::IsDirectStatic() const noexcept { return direct_static_; } std::uint32_t StepDefinition::IncrementCount() const noexcept { return increment_count_; } std::uint32_t StepDefinition::MaximumIncrements() const noexcept { return maximum_increments_; } const SourceLocation& StepDefinition::Location() const noexcept { return location_; } Result Domain::Create(ModelDefinition definition) { const Status solid_status = ValidateSolidDefinition(definition); if (!solid_status.IsOk()) return Result::Failure(solid_status); const Status thermal_status = ValidateThermalDefinition(definition); if (!thermal_status.IsOk()) return Result::Failure(thermal_status); return Result::Success(Domain{std::move(definition)}); } const std::vector& Domain::Nodes() const noexcept { return definition_.nodes; } const DomainCollectionView& Domain::Elements() const noexcept { return elements_view_; } const DomainCollectionView& Domain::BeamElements() const noexcept { return beam_elements_view_; } const DomainCollectionView& Domain::ShellElements() const noexcept { return shell_elements_view_; } const DomainCollectionView& Domain::SolidElements() const noexcept { return solid_elements_view_; } const DomainCollectionView& Domain::SolidSections() const noexcept { return solid_sections_view_; } bool Domain::IsSolid() const noexcept { return !solid_elements_view_.Empty() && solid_elements_view_.Size() == elements_view_.Size(); } const std::vector& Domain::Amplitudes() const noexcept { return definition_.amplitudes; } const DomainCollectionView& Domain::Materials() const noexcept { return materials_view_; } const DomainCollectionView& Domain::LinearElasticMaterials() const noexcept { return linear_materials_view_; } const DomainCollectionView& Domain::ThermalMaterials() const noexcept { return thermal_materials_view_; } const DomainCollectionView& Domain::ThermalElements() const noexcept { return thermal_elements_view_; } const std::vector& Domain::ThermalSections() const noexcept { return definition_.thermal_sections; } const std::vector& Domain::ThermalInitialConditions() const noexcept { return definition_.thermal_initial_conditions; } const std::vector& Domain::ThermalModelBoundaries() const noexcept { return definition_.thermal_model_boundaries; } const ThermalStepDefinition* Domain::ThermalStep() const noexcept { return definition_.thermal_step ? &*definition_.thermal_step : nullptr; } const DomainCollectionView& Domain::Properties() const noexcept { return properties_view_; } const DomainCollectionView& Domain::Sections() const noexcept { return sections_view_; } const DomainCollectionView& Domain::ShellSections() const noexcept { return shell_sections_view_; } const std::vector& Domain::ShellNodeInitialFrames() const noexcept { return definition_.shell_node_initial_frames; } const std::vector& Domain::NodeSets() const noexcept { return definition_.node_sets; } const std::vector& Domain::ElementSets() const noexcept { return definition_.element_sets; } const std::vector& Domain::ElementSurfaces() const noexcept { return definition_.element_surfaces; } const DomainCollectionView& Domain::Steps() const noexcept { return steps_view_; } const std::vector& Domain::Densities() const noexcept { return definition_.densities; } const FrequencyStepDefinition* Domain::Frequency() const noexcept { return steps_view_.Size() == 1U ? steps_view_[0U].Frequency() : nullptr; } const DynamicStepDefinition* Domain::Dynamic() const noexcept { return steps_view_.Size() == 1U ? steps_view_[0U].Dynamic() : nullptr; } const std::vector& Domain::Warnings() const noexcept { return definition_.warnings; } const std::filesystem::path& Domain::SourcePath() const noexcept { return definition_.source_path; } const std::string& Domain::SourceContentIdentity() const noexcept { return definition_.source_content_identity; } Domain::Domain(ModelDefinition definition) : definition_{std::move(definition)} { materials_.reserve(definition_.materials.size()); for (auto& material : definition_.materials) { auto owned = std::make_unique(std::move(material)); materials_view_.Add(*owned); linear_materials_view_.Add(*owned); materials_.push_back(std::move(owned)); } definition_.materials.clear(); const std::size_t structural_material_count = materials_.size(); materials_.reserve(materials_.size() + definition_.thermal_materials.size()); for (const auto& material : definition_.thermal_materials) { const auto candidate = IsotropicThermalMaterial::Create( material.source_id, material.name, material.conductivity, material.density, material.specific_heat, material.location); auto owned = std::make_unique( std::move(candidate.Value())); materials_view_.Add(*owned); thermal_materials_view_.Add(*owned); materials_.push_back(std::move(owned)); } definition_.thermal_materials.clear(); element_properties_.reserve(definition_.sections.size() + definition_.shell_sections.size()); for (auto& section : definition_.sections) { auto owned = std::make_unique(std::move(section)); properties_view_.Add(*owned); sections_view_.Add(*owned); element_properties_.push_back(std::move(owned)); } const std::size_t beam_property_count = element_properties_.size(); definition_.sections.clear(); for (auto& section : definition_.shell_sections) { auto owned = std::make_unique(std::move(section)); properties_view_.Add(*owned); shell_sections_view_.Add(*owned); element_properties_.push_back(std::move(owned)); } definition_.shell_sections.clear(); const std::size_t solid_property_offset = element_properties_.size(); for (auto& section : definition_.solid_sections) { auto owned = std::make_unique(std::move(section)); properties_view_.Add(*owned); solid_sections_view_.Add(*owned); element_properties_.push_back(std::move(owned)); } definition_.solid_sections.clear(); element_definitions_.reserve(definition_.elements.size() + definition_.shell_elements.size()); for (auto& element : definition_.elements) { element.SynchronizeNodeIndices(); auto owned = std::make_unique(std::move(element)); elements_view_.Add(*owned); beam_elements_view_.Add(*owned); element_definitions_.push_back(std::move(owned)); } definition_.elements.clear(); for (auto& element : definition_.shell_elements) { element.SynchronizeNodeIndices(); element.SetPropertyIndex( static_cast(beam_property_count + element.section_index)); auto owned = std::make_unique(std::move(element)); elements_view_.Add(*owned); shell_elements_view_.Add(*owned); element_definitions_.push_back(std::move(owned)); } definition_.shell_elements.clear(); for (auto& element : definition_.solid_elements) { element.SynchronizeNodeIndices(); element.SetPropertyIndex(static_cast(solid_property_offset + element.section_index)); auto owned = std::make_unique(std::move(element)); elements_view_.Add(*owned); solid_elements_view_.Add(*owned); element_definitions_.push_back(std::move(owned)); } definition_.solid_elements.clear(); for (auto& element : definition_.thermal_elements) { const EntityIndex material_index = static_cast( structural_material_count + definition_.thermal_sections[element.section_index].material_index); auto owned = std::make_unique( std::move(element.source_id), element.node_indices, element.section_index, std::move(element.location)); owned->BindMaterialIndex(material_index); elements_view_.Add(*owned); thermal_elements_view_.Add(*owned); element_definitions_.push_back(std::move(owned)); } definition_.thermal_elements.clear(); if (definition_.thermal_step) { for (std::size_t index = 0U; index < definition_.thermal_step->loads.size(); ++index) { definition_.thermal_step->loads[index].source_order = index; } } step_definitions_.reserve(definition_.steps.size() + (definition_.frequency_step ? 1U : 0U) + (definition_.dynamic_step ? 1U : 0U)); for (auto& step : definition_.steps) { auto owned = std::unique_ptr(new StepDefinition(std::move(step))); steps_view_.Add(*owned); step_definitions_.push_back(std::move(owned)); } definition_.steps.clear(); if (definition_.frequency_step) { auto owned = std::unique_ptr( new StepDefinition(std::move(*definition_.frequency_step))); steps_view_.Add(*owned); step_definitions_.push_back(std::move(owned)); } if (definition_.dynamic_step) { auto owned = std::unique_ptr( new StepDefinition(std::move(*definition_.dynamic_step))); steps_view_.Add(*owned); step_definitions_.push_back(std::move(owned)); } } } // namespace fesa