Files
FESADev/src/fesa/elements/thermal_shell.cpp
T

237 lines
10 KiB
C++

#include "fesa/elements/thermal_shell.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <utility>
#include "thermal_shell_geometry.h"
namespace fesa {
namespace {
using thermal_internal::Interval;
/// @brief Measures the full endpoint deviation from a computed coefficient.
double ErrorBound(Interval interval, double value) {
return thermal_internal::Up(std::max(std::abs(interval.lower - value),
std::abs(interval.upper - value)));
}
} // namespace
ThermalShell::ThermalShell(SourceEntityId source,
std::array<EntityIndex, 4> node_indices,
SourceLocation location, double conductivity)
: source_{std::move(source)},
node_indices_{node_indices},
location_{std::move(location)},
conductivity_value_{conductivity} {}
Result<ThermalShell> ThermalShell::Create(
SourceEntityId source, std::array<EntityIndex, 4> node_indices,
const std::array<Vector3, 4>& coordinates, double conductivity,
double density, double specific_heat, double thickness,
SourceLocation location) {
using namespace thermal_internal;
for (const double property :
{conductivity, density, specific_heat, thickness}) {
if (!(property > 0) || !std::isfinite(property)) {
return Result<ThermalShell>::Failure(
GeometryFailure(source, location, "invalid-thermal-shell-material",
"Thermal properties must be finite and positive."));
}
}
auto sorted = node_indices;
std::sort(sorted.begin(), sorted.end());
if (std::adjacent_find(sorted.begin(), sorted.end()) != sorted.end()) {
return Result<ThermalShell>::Failure(
GeometryFailure(source, location, "invalid-thermal-shell-geometry",
"Patch contains repeated source nodes."));
}
const auto geometry = BuildGeometry(coordinates, source, location);
if (!geometry.HasValue())
return Result<ThermalShell>::Failure(geometry.GetStatus());
ThermalShell kernel{source, node_indices, location, conductivity};
Matrix mass{4, 4};
Matrix surface{4, 4};
std::array<std::array<Interval, 4>, 4> mass_interval{};
std::array<std::array<Interval, 4>, 4> surface_interval{};
const double gauss = std::sqrt(3. / 5.);
const Interval gauss_interval = Sqrt(Interval{3} / Interval{5});
const std::array<double, 3> points{{-gauss, 0., gauss}};
const std::array<Interval, 3> point_intervals{
{Interval{0} - gauss_interval, Interval{0}, gauss_interval}};
const std::array<double, 3> weights{{5. / 9., 8. / 9., 5. / 9.}};
const std::array<Interval, 3> weight_intervals{{Interval{5} / Interval{9},
Interval{8} / Interval{9},
Interval{5} / Interval{9}}};
for (std::size_t x = 0; x < 3; ++x) {
for (std::size_t y = 0; y < 3; ++y) {
const auto result = EvaluateSurface(geometry.Value(), points[x],
points[y], point_intervals[x],
point_intervals[y], source, location);
if (!result.HasValue())
return Result<ThermalShell>::Failure(result.GetStatus());
const auto& point = result.Value();
const double weight = weights[x] * weights[y] * point.jacobian;
const Interval weight_interval =
weight_intervals[x] * weight_intervals[y] * point.jacobian_interval;
for (std::size_t i = 0; i < 4; ++i) {
for (std::size_t j = 0; j < 4; ++j) {
mass(i, j) += point.shape[i] * point.shape[j] * weight;
surface(i, j) += point.gradient[i].Dot(point.gradient[j]) * weight;
mass_interval[i][j] = mass_interval[i][j] +
point.shape_interval[i] *
point.shape_interval[j] * weight_interval;
surface_interval[i][j] =
surface_interval[i][j] +
Dot(point.gradient_interval[i], point.gradient_interval[j]) *
weight_interval;
}
}
}
}
Matrix z0{5, 5};
Matrix z1{5, 5};
std::array<std::array<Interval, 5>, 5> z0_interval{};
std::array<std::array<Interval, 5>, 5> z1_interval{};
constexpr double kZ0[3][3] = {{4, 2, -1}, {2, 16, 2}, {-1, 2, 4}};
constexpr double kZ1[3][3] = {{7, -8, 1}, {-8, 16, -8}, {1, -8, 7}};
for (const std::size_t first : {0U, 2U}) {
for (std::size_t a = 0; a < 3; ++a) {
for (std::size_t b = 0; b < 3; ++b) {
z0(first + a, first + b) += kZ0[a][b] / 30.;
z1(first + a, first + b) += kZ1[a][b] / 3.;
z0_interval[first + a][first + b] = z0_interval[first + a][first + b] +
Interval{kZ0[a][b]} / Interval{30};
z1_interval[first + a][first + b] = z1_interval[first + a][first + b] +
Interval{kZ1[a][b]} / Interval{3};
}
}
}
for (std::size_t i = 0; i < 4; ++i) {
for (std::size_t j = 0; j < 4; ++j) {
for (std::size_t a = 0; a < 5; ++a) {
for (std::size_t b = 0; b < 5; ++b) {
const std::size_t p = 5 * i + a;
const std::size_t q = 5 * j + b;
kernel.capacity_(p, q) =
density * specific_heat * (thickness / 2) * mass(i, j) * z0(a, b);
kernel.conductivity_(p, q) =
conductivity * ((thickness / 2) * surface(i, j) * z0(a, b) +
(2 / thickness) * mass(i, j) * z1(a, b));
const Interval capacity = Interval{density} *
Interval{specific_heat} *
(Interval{thickness} / Interval{2}) *
mass_interval[i][j] * z0_interval[a][b];
const Interval conduction =
Interval{conductivity} *
((Interval{thickness} / Interval{2}) * surface_interval[i][j] *
z0_interval[a][b] +
(Interval{2} / Interval{thickness}) * mass_interval[i][j] *
z1_interval[a][b]);
kernel.capacity_error_(p, q) =
ErrorBound(capacity, kernel.capacity_(p, q));
kernel.conductivity_error_(p, q) =
ErrorBound(conduction, kernel.conductivity_(p, q));
if (!capacity.IsFinite() || !conduction.IsFinite() ||
!std::isfinite(kernel.capacity_(p, q)) ||
!std::isfinite(kernel.conductivity_(p, q)) ||
!std::isfinite(kernel.capacity_error_(p, q)) ||
!std::isfinite(kernel.conductivity_error_(p, q)) ||
(p == q && (!(kernel.capacity_(p, q) > 0) ||
!(kernel.conductivity_(p, q) > 0)))) {
return Result<ThermalShell>::Failure(GeometryFailure(
source, location, "nonfinite-thermal-shell-operator",
"Local matrix/enclosure is not finite or a diagonal lost "
"positivity."));
}
}
}
}
}
const double recovery = 1 / std::sqrt(3.);
const Interval recovery_interval = Interval{1} / Sqrt(Interval{3});
constexpr std::array<double, 4> kXi{{-1, 1, 1, -1}};
constexpr std::array<double, 4> kEta{{-1, -1, 1, 1}};
constexpr std::array<double, 6> kLocal{{-1, 0, 1, -1, 0, 1}};
for (std::size_t ip = 0; ip < 4; ++ip) {
const auto result = EvaluateSurface(
geometry.Value(), kXi[ip] * recovery, kEta[ip] * recovery,
Interval{kXi[ip]} * recovery_interval,
Interval{kEta[ip]} * recovery_interval, source, location);
if (!result.HasValue())
return Result<ThermalShell>::Failure(result.GetStatus());
const auto& point = result.Value();
kernel.frames_[ip] = point.axes;
for (std::size_t trace = 0; trace < 6; ++trace) {
const double s = kLocal[trace];
const std::array<double, 3> shape{
{.5 * s * (s - 1), 1 - s * s, .5 * s * (s + 1)}};
const std::array<double, 3> derivative{{2 * s - 1, -4 * s, 2 * s + 1}};
const std::size_t first = trace < 3 ? 0 : 2;
for (std::size_t i = 0; i < 4; ++i) {
for (std::size_t a = 0; a < 3; ++a) {
const Vector3 gradient =
shape[a] * point.gradient[i] +
(point.shape[i] * (2 / thickness) * derivative[a]) *
point.axes[2];
if (!gradient.IsFinite()) {
return Result<ThermalShell>::Failure(GeometryFailure(
source, location, "nonfinite-thermal-shell-recovery",
"Flux gradient is not finite."));
}
kernel.gradients_[6 * ip + trace][5 * i + first + a] = gradient;
}
}
}
}
return Result<ThermalShell>::Success(std::move(kernel));
}
const SourceEntityId& ThermalShell::SourceId() const noexcept {
return source_;
}
const std::array<EntityIndex, 4>& ThermalShell::NodeIndices() const noexcept {
return node_indices_;
}
const Matrix& ThermalShell::Conductivity() const noexcept {
return conductivity_;
}
const Matrix& ThermalShell::Capacity() const noexcept { return capacity_; }
const Matrix& ThermalShell::ConductivityError() const noexcept {
return conductivity_error_;
}
const Matrix& ThermalShell::CapacityError() const noexcept {
return capacity_error_;
}
Result<ThermalShellRecovery> ThermalShell::Recover(
const std::array<double, 20>& temperature) const {
for (const double value : temperature) {
if (!std::isfinite(value)) {
return Result<ThermalShellRecovery>::Failure(
thermal_internal::GeometryFailure(
source_, location_, "nonfinite-thermal-shell-temperature",
"Recovery temperature is not finite."));
}
}
ThermalShellRecovery recovery{};
recovery.frames = frames_;
for (std::size_t row = 0; row < 24; ++row) {
Vector3 gradient;
for (std::size_t p = 0; p < 20; ++p)
gradient = gradient + temperature[p] * gradients_[row][p];
recovery.global_flux[row] = -conductivity_value_ * gradient;
if (!recovery.global_flux[row].IsFinite()) {
return Result<ThermalShellRecovery>::Failure(
thermal_internal::GeometryFailure(
source_, location_, "nonfinite-thermal-shell-flux",
"Recovered heat flux is not finite."));
}
}
return Result<ThermalShellRecovery>::Success(recovery);
}
} // namespace fesa