feat(thermal): certify shell geometry and local heat operators
This commit is contained in:
@@ -1,43 +1,236 @@
|
||||
#include "fesa/elements/thermal_shell.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
#include "thermal_shell_geometry.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
constexpr std::array<double, 3> kGauss{{-0.7745966692414834, 0.0, 0.7745966692414834}};
|
||||
constexpr std::array<double, 3> kWeight{{5.0 / 9.0, 8.0 / 9.0, 5.0 / 9.0}};
|
||||
constexpr std::array<double, 4> kXi{{-1., 1., 1., -1.}};
|
||||
constexpr std::array<double, 4> kEta{{-1., -1., 1., 1.}};
|
||||
void AddZ(Matrix& z0, Matrix& z1, std::size_t first) {
|
||||
const double a[3][3]={{4,2,-1},{2,16,2},{-1,2,4}};
|
||||
const double b[3][3]={{7,-8,1},{-8,16,-8},{1,-8,7}};
|
||||
for (std::size_t i=0;i<3;++i) for (std::size_t j=0;j<3;++j) { z0(first+i,first+j)+=a[i][j]/30.; z1(first+i,first+j)+=b[i][j]/3.; }
|
||||
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(std::array<std::size_t, 4> node_indices,
|
||||
std::array<Vector3, 4> coordinates,
|
||||
double conductivity, double density,
|
||||
double specific_heat, double thickness)
|
||||
: node_indices_{node_indices} {
|
||||
if (!(conductivity > 0.) || !(density > 0.) || !(specific_heat > 0.) ||
|
||||
!(thickness > 0.)) throw std::invalid_argument("invalid thermal shell material");
|
||||
Matrix m{4U,4U}; Matrix s{4U,4U}; Matrix z0{5U,5U}; Matrix z1{5U,5U};
|
||||
AddZ(z0,z1,0U); AddZ(z0,z1,2U);
|
||||
for (std::size_t x=0;x<3;++x) for (std::size_t y=0;y<3;++y) {
|
||||
const double xi=kGauss[x], eta=kGauss[y]; std::array<double,4> n{}, dx{}, dy{};
|
||||
Vector3 a1{},a2{};
|
||||
for(std::size_t i=0;i<4;++i) { n[i]=.25*(1+kXi[i]*xi)*(1+kEta[i]*eta); dx[i]=.25*kXi[i]*(1+kEta[i]*eta); dy[i]=.25*kEta[i]*(1+kXi[i]*xi); a1=a1+dx[i]*coordinates[i]; a2=a2+dy[i]*coordinates[i]; }
|
||||
const Vector3 w=a1.Cross(a2); const double jac=w.Norm(); const double g11=a1.Dot(a1),g12=a1.Dot(a2),g22=a2.Dot(a2), det=g11*g22-g12*g12;
|
||||
if (!(jac>0.) || !(det>0.) || !std::isfinite(jac)) throw std::invalid_argument("invalid thermal shell geometry");
|
||||
const double wt=kWeight[x]*kWeight[y]*jac;
|
||||
for(std::size_t i=0;i<4;++i) for(std::size_t j=0;j<4;++j) { m(i,j)+=n[i]*n[j]*wt; s(i,j)+=(dx[i]*(g22*dx[j]-g12*dy[j])+dy[i]*(g11*dy[j]-g12*dx[j]))/det*wt; }
|
||||
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."));
|
||||
}
|
||||
}
|
||||
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,q=5*j+b; capacity_(p,q)=density*specific_heat*thickness*.5*m(i,j)*z0(a,b); conductivity_(p,q)=conductivity*(thickness*.5*s(i,j)*z0(a,b)+2./thickness*m(i,j)*z1(a,b)); }
|
||||
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 std::array<std::size_t,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
|
||||
|
||||
Reference in New Issue
Block a user