use std::ops::Range;
use stormath::{
type_aliases::Float,
consts::PI,
spatial_vector::SpatialVector,
spatial_vector::transformations::RotationType,
statistics::mean,
interpolation::linear_interpolation,
rigid_body_motion::RigidBodyMotion
};
pub mod builder;
pub mod data_access;
pub mod data_update;
pub mod force_calculations;
pub mod value_mapping;
pub mod span_line;
pub mod input_power;
pub mod corrections;
pub mod prelude;
#[cfg(test)]
mod tests;
use crate::common_utils::prelude::*;
use crate::section_models::SectionModel;
use crate::controller::output::ControllerOutput;
use corrections::{
circulation::CirculationCorrection,
angle_of_attack::AngleOfAttackCorrection,
};
use builder::single_wing::SingleWing;
use span_line::*;
use force_calculations::ForceCalculationSettings;
use input_power::InputPowerModel;
#[derive(Clone, Debug)]
pub struct LineForceModel {
pub span_lines_local: Vec<SpanLine>,
pub chord_vectors_local_not_rotated: Vec<SpatialVector>,
pub chord_lengths: Vec<Float>,
pub line_segment_is_virtual: Vec<bool>,
pub section_models: Vec<SectionModel>,
pub wing_indices: Vec<Range<usize>>,
pub non_zero_circulation_at_ends: Vec<[bool; 2]>,
pub density: Float,
pub circulation_correction: CirculationCorrection,
pub angle_of_attack_correction: AngleOfAttackCorrection,
pub output_coordinate_system: CoordinateSystem,
pub rigid_body_motion: RigidBodyMotion,
pub local_wing_angles: Vec<Float>,
pub chord_vectors_local: Vec<SpatialVector>,
pub chord_vectors_global: Vec<SpatialVector>,
pub chord_vectors_global_at_span_points: Vec<SpatialVector>,
pub span_lines_global: Vec<SpanLine>,
pub span_points_global: Vec<SpatialVector>,
pub ctrl_points_global: Vec<SpatialVector>,
pub ctrl_point_spanwise_distance: Vec<Float>,
pub ctrl_point_spanwise_distance_non_dimensional: Vec<Float>,
pub ctrl_point_spanwise_distance_circulation_model: Vec<Float>,
pub input_power_models: Vec<InputPowerModel>,
pub force_calculation_settings: ForceCalculationSettings,
}
impl Default for LineForceModel {
fn default() -> Self {
Self::new(Self::default_density())
}
}
impl LineForceModel {
pub fn default_density() -> Float {
1.225
}
pub fn new(density: Float) -> LineForceModel {
Self {
span_lines_local: Vec::new(),
chord_vectors_local_not_rotated: Vec::new(),
chord_lengths: Vec::new(),
line_segment_is_virtual: Vec::new(),
section_models: Vec::new(),
wing_indices: Vec::new(),
rigid_body_motion: RigidBodyMotion::default(),
local_wing_angles: Vec::new(),
non_zero_circulation_at_ends: Vec::new(),
density,
circulation_correction: Default::default(),
angle_of_attack_correction: Default::default(),
output_coordinate_system: CoordinateSystem::Global,
chord_vectors_local: Vec::new(),
chord_vectors_global: Vec::new(),
chord_vectors_global_at_span_points: Vec::new(),
span_lines_global: Vec::new(),
span_points_global: Vec::new(),
ctrl_points_global: Vec::new(),
ctrl_point_spanwise_distance: Vec::new(),
ctrl_point_spanwise_distance_non_dimensional: Vec::new(),
ctrl_point_spanwise_distance_circulation_model: Vec::new(),
input_power_models: Vec::new(),
force_calculation_settings: ForceCalculationSettings::default()
}
}
pub fn add_wing(&mut self, wing: &SingleWing) {
let start_index = if self.span_lines_local.is_empty() {
0
} else {
self.wing_indices.last().unwrap().end
};
let end_index = start_index + wing.span_lines_local.len();
self.wing_indices.push(Range {
start: start_index,
end: end_index,
});
for line in &wing.span_lines_local {
self.span_lines_local.push(line.clone());
self.span_lines_global.push(line.clone());
}
for chord_vector in &wing.chord_vectors_local {
self.chord_vectors_local_not_rotated.push(*chord_vector);
self.chord_vectors_local.push(*chord_vector);
self.chord_vectors_global.push(*chord_vector);
}
for chord_length in &wing.chord_lengths {
self.chord_lengths.push(*chord_length);
}
for is_virtual in &wing.line_segment_is_virtual {
self.line_segment_is_virtual.push(*is_virtual);
}
self.section_models.push(wing.section_model.clone());
self.local_wing_angles.push(0.0);
self.non_zero_circulation_at_ends.push(wing.non_zero_circulation_at_ends);
self.input_power_models.push(wing.input_power_model.clone());
self.update_global_data_representations();
}
pub fn wing_index_from_global(&self, global_index: usize) -> usize {
for (wing_index, wing_indices) in self.wing_indices.iter().enumerate() {
if wing_indices.contains(&global_index) {
return wing_index;
}
}
panic!("Wing index not found. The global index is not part of any wing")
}
pub fn local_index_from_global(&self, global_index: usize) -> usize {
for wing_indices in &self.wing_indices {
if wing_indices.contains(&global_index) {
return global_index - wing_indices.start;
}
}
panic!("Local index not found. The global index is not part of any wing")
}
pub fn remove_span_velocity(
&self,
velocity: &[SpatialVector],
input_coordinate_system: CoordinateSystem,
) -> Vec<SpatialVector> {
let span_lines = match input_coordinate_system {
CoordinateSystem::Global => &self.span_lines_global,
CoordinateSystem::Body => &self.span_lines_local,
};
velocity
.iter()
.zip(span_lines.iter())
.map(|(vel, line)| {
let span_velocity = vel.project(line.relative_vector());
*vel - span_velocity
})
.collect()
}
pub fn wake_angles(&self, velocity: &[SpatialVector]) -> Vec<Float> {
(0..self.nr_span_lines())
.map(|index| {
let wing_index = self.wing_index_from_global(index);
match &self.section_models[wing_index] {
SectionModel::Foil(_) => 0.0,
SectionModel::VaryingFoil(_) => 0.0,
SectionModel::RotatingCylinder(cylinder) => cylinder.wake_angle(
self.chord_lengths[index],
velocity[index].length(),
),
SectionModel::EffectiveWindSensor => 0.0,
}
})
.collect()
}
pub fn total_force_factor(&self, freestream_velocity: Float) -> Float {
0.5 * self.density * freestream_velocity.powi(2) * self.total_projected_area()
}
}