use crate::core::constants::{
ASTRONAUMICAL_UNIT, MINIMUM_PRECISION, NUMBER_THERMAL_SKIN_DEPTH, NUMERICAL_STABILITY,
SOLAR_CONSTANT, TAU,
};
use crate::core::object_3d::Object3D;
use crate::toolbox::matrix::{clip, dot_vectors, linspace};
use crate::toolbox::physics::{
compute_conductivity, compute_diffusivity, compute_ground_step_from_time_and_stability,
};
use crate::{M3, V3, V3X, VX};
use itertools::zip;
use na::{DMatrix, Dynamic, MatrixSliceMutXx1, MatrixSliceXx1, Rotation3, Unit, U1, U3};
use std::path::Path;
const EXPECT_TIME_STEP_NOT_SET: &str = "The time step of the simulation has not been shared yet with the properties of the celestial body. Once the World is initialized, the time step is shared and you can call this variable.";
pub struct Body {
pub name: String,
pub position: V3<f64>,
pub object: Object3D,
pub properties: Properties,
_ground_temperatures: Option<DMatrix<f64>>,
_surface_heat_flux: VX<f64>,
_ground_limit_heat_flux: VX<f64>,
_is_fixed: bool,
_daily_surface_temperatures: Option<VX<f64>>,
_index_current_daily: usize,
_index_daily_surface_temperature: usize,
_fixed_surface_temperatures: bool,
_fixed_ground_limit_temperatures: bool,
_self_view_factor: Option<DMatrix<f64>>,
}
impl Body {
pub fn new<P: AsRef<Path>>(
name: &str,
position: V3<f64>,
path: P,
properties: Properties,
) -> Self {
let object = Object3D::new(path);
let number_faces_raw = object.number_faces_raw();
let mut body = Self {
name: String::from(name),
position,
object,
properties,
_ground_temperatures: None,
_surface_heat_flux: VX::zeros(number_faces_raw),
_ground_limit_heat_flux: VX::zeros(number_faces_raw),
_is_fixed: false,
_daily_surface_temperatures: None,
_index_current_daily: 0,
_index_daily_surface_temperature: 0,
_fixed_surface_temperatures: false,
_fixed_ground_limit_temperatures: false,
_self_view_factor: None,
};
body.initiate_obliquity();
body.recompute();
body
}
pub fn empty(name: &str, position: V3<f64>, properties: Properties) -> Self {
let mut body = Self {
name: String::from(name),
position,
object: Object3D::empty(),
properties,
_ground_temperatures: None,
_surface_heat_flux: VX::from_element(0, f64::NAN),
_ground_limit_heat_flux: VX::from_element(0, f64::NAN),
_is_fixed: false,
_daily_surface_temperatures: None,
_index_current_daily: 0,
_index_daily_surface_temperature: 0,
_fixed_surface_temperatures: false,
_fixed_ground_limit_temperatures: false,
_self_view_factor: None,
};
body.initiate_obliquity();
body.recompute();
body
}
pub fn share_time_step(&mut self, time_step: f64) {
let modified = self.properties.set_time_step(time_step);
if modified {
self.properties.recompute_ground_vector();
self.recompute_ground_temperatures(None);
}
}
pub fn new_ground_temperatures(&self, value: f64) -> DMatrix<f64> {
DMatrix::<f64>::from_element(
self.object.number_faces(),
self.properties.ground_vector().len(),
value,
)
}
pub fn recompute_ground_temperatures(&mut self, temperature: Option<f64>) {
let (ground_temperatures_is_some, ground_size_match) =
match self._ground_temperatures.as_mut() {
Some(ground_temperatures) => (
true,
ground_temperatures.ncols() == self.properties.ground_vector().len(),
),
None => (false, false),
};
if temperature.is_none() && ground_temperatures_is_some && ground_size_match {
let old_ground_temperatures = self._ground_temperatures.as_mut().unwrap().clone();
let mut ground_temperatures = self.new_ground_temperatures(0.0);
let mask = self.object.faces_mask();
for (mut ground_temperature, (_, old_ground_temperature)) in zip(
ground_temperatures.row_iter_mut(),
old_ground_temperatures
.row_iter()
.enumerate()
.filter(|&(i, _)| mask[i]),
) {
ground_temperature.copy_from(&old_ground_temperature);
}
self._ground_temperatures = Some(ground_temperatures);
} else {
let initial_value = match temperature {
Some(value) => value,
None => 0.0,
};
self._ground_temperatures = Some(self.new_ground_temperatures(initial_value));
}
}
pub fn ground_temperatures(&self) -> &DMatrix<f64> {
self._ground_temperatures.as_ref().unwrap()
}
pub fn ground_temperatures_mut(&mut self) -> &mut DMatrix<f64> {
self._ground_temperatures.as_mut().unwrap()
}
pub fn set_ground_temperatures(&mut self, ground_temperatures: DMatrix<f64>) {
self._ground_temperatures = Some(ground_temperatures);
}
pub fn surface_temperatures(&self) -> MatrixSliceXx1<f64, U1, Dynamic> {
self._ground_temperatures.as_ref().unwrap().column(0)
}
pub fn surface_temperatures_mut(&mut self) -> MatrixSliceMutXx1<f64, U1, Dynamic> {
self._ground_temperatures.as_mut().unwrap().column_mut(0)
}
pub fn set_surface_temperatures(&mut self, surface_temperatures: VX<f64>) {
self._ground_temperatures
.as_mut()
.unwrap()
.column_mut(0)
.copy_from(&surface_temperatures);
}
pub fn surface_temperatures_as_vec(&self) -> VX<f64> {
VX::from_iterator(
self.object.number_faces(),
self._ground_temperatures
.as_ref()
.unwrap()
.column(0)
.iter()
.cloned(),
)
}
pub fn deepest_temperatures(&self) -> MatrixSliceXx1<f64, U1, Dynamic> {
let ground_size = self.properties.ground_vector().len();
self._ground_temperatures
.as_ref()
.unwrap()
.column(ground_size - 1)
}
pub fn surface_heat_flux(&self) -> &VX<f64> {
&self._surface_heat_flux
}
pub fn set_surface_heat_flux(&mut self, heat_flux: VX<f64>) {
self._surface_heat_flux = heat_flux;
}
pub fn set_index_daily_surface_temperature(&mut self, index: usize) {
self._index_daily_surface_temperature = index;
}
pub fn has_last_revolution_started(&self) -> bool {
self._index_current_daily > 0
}
pub fn daily_surface_temperatures(&self) -> &VX<f64> {
self._daily_surface_temperatures.as_ref().unwrap()
}
pub fn daily_surface_temperatures_mut(&mut self) -> &mut VX<f64> {
self._daily_surface_temperatures.as_mut().unwrap()
}
pub fn initiate_daily_record(&mut self, size: usize) {
self._daily_surface_temperatures = Some(VX::zeros(size));
self.set_daily_surface_temperatures();
}
pub fn set_daily_surface_temperatures(&mut self) {
let surface_temperatures = self.surface_temperatures_as_vec();
if let Some(daily_surface_temperatures) = &mut self._daily_surface_temperatures {
daily_surface_temperatures[self._index_current_daily] =
surface_temperatures[self._index_daily_surface_temperature];
self._index_current_daily += 1;
}
}
pub fn revolution_iteration(&mut self) {
if self._is_fixed {
return;
}
let new_normals = self.properties.rotation_matrix() * self.object.normals();
self.object.normals_mut().copy_from(&new_normals);
}
pub fn direct_solar_flux(&self, position_sun: &V3<f64>) -> VX<f64> {
clip(
&(SOLAR_CONSTANT
* (1. - self.properties.albedo())
* self.illumination_cosine(position_sun)
/ (self.distance_to(position_sun) / ASTRONAUMICAL_UNIT).powi(2)),
Some(0.),
None,
)
}
pub fn illumination_cosine(&self, position_sun: &V3<f64>) -> VX<f64> {
let facets_directions_to_sun = self.facets_directions_to_sun(position_sun);
dot_vectors(
&-&facets_directions_to_sun,
&self.object.normals().into_owned(),
)
}
pub fn facets_directions_to_sun(&self, position_sun: &V3<f64>) -> V3X<f64> {
let mut directions = self.object.centers().into_owned();
for mut face in directions.column_iter_mut() {
face.copy_from(&(self.position + &face - position_sun).normalize());
}
directions
}
pub fn distance_to(&self, other_position: &V3<f64>) -> f64 {
(self.position - other_position).norm()
}
pub fn set_faces_mask_equator(&mut self) {
self.object.set_equator_mask(None);
self.recompute_ground_temperatures(None);
}
fn initiate_obliquity(&mut self) {
let old_vertices = self.object.vertices().clone();
self.object
.set_vertices(self.properties.obliquity_initiation_rotation_matrix() * old_vertices);
}
pub fn new_face(&mut self, vertices: M3<f64>) {
self.object.new_face(vertices);
self.recompute();
}
pub fn recompute(&mut self) {
self.object.recompute_faces();
self.recompute_ground_temperatures(None);
}
pub fn fixed(&mut self, is_fixed: bool) {
self._is_fixed = is_fixed;
}
pub fn fixed_surface_temperatures(&self) -> bool {
self._fixed_surface_temperatures
}
pub fn set_fixed_surface_temperatures(&mut self, is_fixed: bool) {
self._fixed_surface_temperatures = is_fixed;
}
pub fn fixed_ground_limit_temperatures(&self) -> bool {
self._fixed_ground_limit_temperatures
}
pub fn set_fixed_ground_limit_temperatures(&mut self, is_fixed: bool) {
self._fixed_ground_limit_temperatures = is_fixed;
}
pub fn compute_self_view_factor(&mut self) {
let mut view_factor =
DMatrix::<f64>::zeros(self.object.number_faces(), self.object.number_faces());
for ((face_target, face_other), view) in izip!(
iproduct!(self.object.face_iter_all(), self.object.face_iter_all()),
view_factor.iter_mut()
) {
let normal_target = face_target.fixed_rows::<U3>(3);
let normal_other = face_other.fixed_rows::<U3>(3);
let center_target = face_target.fixed_rows::<U3>(0);
let center_other = face_other.fixed_rows::<U3>(0);
if center_target == center_other {
continue;
}
let area_other = face_other[9];
let relative_position_faces = center_other - center_target;
let direction_faces = relative_position_faces.normalize();
let distance_faces = relative_position_faces.norm();
if distance_faces < area_other.sqrt() {
continue;
}
let angle_target = normal_target.angle(&direction_faces);
let angle_other = normal_other.angle(&-direction_faces);
if angle_target > TAU / 4.0 || angle_other > TAU / 4.0 {
continue;
}
*view = 2. * area_other * angle_target.cos() * angle_other.cos()
/ (TAU * distance_faces.powi(2));
}
self._self_view_factor = Some(view_factor);
}
pub fn self_view_factor(&mut self) -> &DMatrix<f64> {
self._self_view_factor.as_ref().unwrap()
}
}
#[derive(Clone)]
pub struct Properties {
_rotation_period: f64,
_revolution_period: f64,
_obliquity: f64,
_thermal_inertia: f64,
_density: f64,
_heat_capacity: f64,
_albedo: f64,
_emissivity: f64,
_number_thermal_skin_depth: usize,
_time_step: Option<f64>,
_conductivity: Option<f64>,
_diffusivity: Option<f64>,
_thermal_skin_depth: Option<f64>,
_ground_depth: Option<f64>,
_ground_step: Option<f64>,
_ground_vector: Option<VX<f64>>,
_diffusivity_field: Option<f64>,
_rotation_axis: Option<Unit<V3<f64>>>,
_rotation_angle: Option<f64>,
_rotation_matrix: Option<Rotation3<f64>>,
_obliquity_initiation_axis: Option<Unit<V3<f64>>>,
_obliquity_initiation_rotation_matrix: Option<Rotation3<f64>>,
}
impl Properties {
pub fn new(
rotation_period: f64,
revolution_period: f64,
obliquity: f64,
thermal_inertia: f64,
density: f64,
heat_capacity: f64,
albedo: f64,
emissivity: f64,
) -> Self {
let mut prop = Self {
_rotation_period: rotation_period,
_revolution_period: revolution_period,
_obliquity: obliquity,
_thermal_inertia: thermal_inertia,
_density: density,
_heat_capacity: heat_capacity,
_albedo: albedo,
_emissivity: emissivity,
_number_thermal_skin_depth: NUMBER_THERMAL_SKIN_DEPTH,
_time_step: None,
_conductivity: None,
_diffusivity: None,
_thermal_skin_depth: None,
_ground_depth: None,
_ground_step: None,
_ground_vector: None,
_diffusivity_field: None,
_rotation_axis: None,
_rotation_angle: None,
_rotation_matrix: None,
_obliquity_initiation_axis: None,
_obliquity_initiation_rotation_matrix: None,
};
prop.compute_obliquity_initiation_axis();
prop.compute_obliquity_initiation_rotation_matrix();
prop.compute_ground_vector();
prop
}
pub fn rotation_period(&self) -> f64 {
self._rotation_period
}
pub fn revolution_period(&self) -> f64 {
self._revolution_period
}
pub fn obliquity(&self) -> f64 {
self._obliquity
}
pub fn thermal_inertia(&self) -> f64 {
self._thermal_inertia
}
pub fn density(&self) -> f64 {
self._density
}
pub fn heat_capacity(&self) -> f64 {
self._heat_capacity
}
pub fn albedo(&self) -> f64 {
self._albedo
}
pub fn emissivity(&self) -> f64 {
self._emissivity
}
pub fn number_thermal_skin_depth(&self) -> usize {
self._number_thermal_skin_depth
}
pub fn set_number_thermal_skin_depth(&mut self, number_thermal_skin_depth: usize) {
self._number_thermal_skin_depth = number_thermal_skin_depth;
self._ground_depth = None;
self._ground_vector = None;
self.compute_properties();
}
pub fn time_step(&self) -> f64 {
self._time_step.expect(EXPECT_TIME_STEP_NOT_SET)
}
pub fn set_time_step(&mut self, time_step: f64) -> bool {
if self._time_step.is_some() && (self.time_step() - time_step).abs() < MINIMUM_PRECISION {
return false;
}
self._time_step = Some(time_step);
self._diffusivity_field = None;
self._rotation_angle = None;
self.compute_properties();
true
}
pub fn compute_properties(&mut self) {
self.compute_conductivity();
self.compute_diffusivity();
self.compute_thermal_skin_depth();
self.compute_ground_depth();
self.compute_ground_step();
self.compute_ground_vector();
self.compute_diffusivity_field();
self.compute_rotation_axis();
self.compute_rotation_angle();
self.compute_rotation_matrix();
}
pub fn conductivity(&self) -> f64 {
self._conductivity.unwrap()
}
pub fn compute_conductivity(&mut self) {
if self._conductivity.is_none() {
self._conductivity = Some(compute_conductivity(
self.thermal_inertia(),
self.density(),
self.heat_capacity(),
));
}
}
pub fn diffusivity(&self) -> f64 {
self._diffusivity.expect(EXPECT_TIME_STEP_NOT_SET)
}
pub fn compute_diffusivity(&mut self) {
if self._diffusivity.is_none() {
self._diffusivity = Some(compute_diffusivity(
self.conductivity(),
self.density(),
self.heat_capacity(),
));
}
}
pub fn thermal_skin_depth(&self) -> f64 {
self._thermal_skin_depth.expect(EXPECT_TIME_STEP_NOT_SET)
}
pub fn compute_thermal_skin_depth(&mut self) {
if self._thermal_skin_depth.is_none() {
self._thermal_skin_depth =
Some((self.diffusivity() * TAU / 2. * self.revolution_period()).sqrt());
}
}
pub fn ground_depth(&self) -> f64 {
self._ground_depth.expect(EXPECT_TIME_STEP_NOT_SET)
}
pub fn set_ground_depth(&mut self, ground_depth: f64) {
self._ground_depth = Some(ground_depth);
self._ground_vector = None;
self.compute_ground_vector();
}
pub fn compute_ground_depth(&mut self) {
if self._ground_depth.is_none() {
self._ground_depth =
Some(self.thermal_skin_depth() * self.number_thermal_skin_depth() as f64);
}
}
pub fn ground_step(&self) -> f64 {
self._ground_step.expect(EXPECT_TIME_STEP_NOT_SET)
}
pub fn compute_ground_step(&mut self) {
if self._ground_step.is_none() {
self._ground_step = Some(compute_ground_step_from_time_and_stability(
self.time_step(),
self.diffusivity(),
NUMERICAL_STABILITY,
));
}
}
pub fn set_ground_step(&mut self, ground_step: f64) {
self._ground_step = Some(ground_step);
}
pub fn ground_vector(&self) -> &VX<f64> {
self._ground_vector
.as_ref()
.expect(EXPECT_TIME_STEP_NOT_SET)
}
pub fn recompute_ground_vector(&mut self) {
self._ground_vector = None;
self.compute_ground_vector();
}
pub fn compute_ground_vector(&mut self) {
if self._ground_vector.is_none() {
if self._ground_depth.is_some() && self._ground_step.is_some() {
self._ground_vector = Some(linspace(0., self.ground_depth(), self.ground_step()));
}
else {
self._ground_vector = Some(VX::zeros(1));
}
}
}
pub fn diffusivity_field(&self) -> &f64 {
self._diffusivity_field
.as_ref()
.expect(EXPECT_TIME_STEP_NOT_SET)
}
pub fn compute_diffusivity_field(&mut self) {
if self._diffusivity_field.is_none() {
self._diffusivity_field =
Some(self.diffusivity() * self.time_step() / self.ground_step().powi(2));
}
}
pub fn rotation_axis(&self) -> &Unit<V3<f64>> {
self._rotation_axis
.as_ref()
.expect(EXPECT_TIME_STEP_NOT_SET)
}
pub fn compute_rotation_axis(&mut self) {
if self._rotation_axis.is_none() {
self._rotation_axis = Some(Unit::new_normalize(V3::new(
0.,
-self.obliquity().sin(),
self.obliquity().cos(),
)));
self._rotation_matrix = None;
}
}
pub fn rotation_angle(&self) -> f64 {
self._rotation_angle.expect(EXPECT_TIME_STEP_NOT_SET)
}
pub fn compute_rotation_angle(&mut self) {
if self._rotation_angle.is_none() {
if self.revolution_period() > 0. {
self._rotation_angle = Some(TAU * self.time_step() / self.revolution_period());
} else {
self._rotation_angle = Some(0.);
}
self._rotation_matrix = None;
}
}
pub fn rotation_matrix(&self) -> &Rotation3<f64> {
self._rotation_matrix
.as_ref()
.expect(EXPECT_TIME_STEP_NOT_SET)
}
pub fn compute_rotation_matrix(&mut self) {
if self._rotation_matrix.is_none() {
self._rotation_matrix = Some(Rotation3::from_axis_angle(
self.rotation_axis(),
self.rotation_angle(),
));
}
}
pub fn obliquity_initiation_axis(&self) -> &Unit<V3<f64>> {
self._obliquity_initiation_axis
.as_ref()
.expect("Obliquity initiation axis not set.")
}
pub fn compute_obliquity_initiation_axis(&mut self) {
if self._obliquity_initiation_axis.is_none() {
self._obliquity_initiation_axis = Some(Unit::new_normalize(V3::new(1., 0., 0.)));
}
}
pub fn obliquity_initiation_rotation_matrix(&self) -> &Rotation3<f64> {
self._obliquity_initiation_rotation_matrix
.as_ref()
.expect("Obliquity initiation rotation matrix no set.")
}
pub fn compute_obliquity_initiation_rotation_matrix(&mut self) {
if self._obliquity_initiation_rotation_matrix.is_none() {
self._obliquity_initiation_rotation_matrix = Some(Rotation3::from_axis_angle(
self.obliquity_initiation_axis(),
self.obliquity(),
));
}
}
}