use super::*;
use serde::{Serialize, Deserialize};
use stormath::consts::MIN_POSITIVE;
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct ForceCalculationSettings {
#[serde(default)]
pub include_viscous_lift_in_the_circulation: bool
}
impl LineForceModel {
pub fn felt_ctrl_points_velocity(
&self,
ctrl_points_velocity_no_motion: &[SpatialVector]
) -> Vec<SpatialVector> {
let ctrl_points = &self.ctrl_points_global;
let mut ctrl_point_velocity = Vec::with_capacity(ctrl_points.len());
for i in 0..ctrl_points.len() {
let motion_velocity = self.rigid_body_motion.velocity_at_point(
ctrl_points[i]
);
ctrl_point_velocity.push(
ctrl_points_velocity_no_motion[i] - motion_velocity
);
}
ctrl_point_velocity
}
pub fn angles_of_attack(
&self,
velocity: &[SpatialVector],
input_coordinate_system: CoordinateSystem
) -> Vec<Float> {
let (span_lines, chord_vectors) = match input_coordinate_system {
CoordinateSystem::Global => (&self.span_lines_global, &self.chord_vectors_global),
CoordinateSystem::Body => (&self.span_lines_local, &self.chord_vectors_local),
};
let angles_of_attack: Vec<Float> = (0..velocity.len()).map(|index| {
if velocity[index].length() > MIN_POSITIVE {
chord_vectors[index].signed_angle_between(
velocity[index],
span_lines[index].direction()
)
} else {
0.0
}
}).collect();
match &self.angle_of_attack_correction {
AngleOfAttackCorrection::None => angles_of_attack,
AngleOfAttackCorrection::GaussianSmoothing => {
todo!()
}
}
}
pub fn lift_coefficients_total(
&self,
angles_of_attack: &[Float],
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(foil) =>
foil.lift_coefficient(angles_of_attack[index]),
SectionModel::VaryingFoil(foil) =>
foil.lift_coefficient(angles_of_attack[index]),
SectionModel::RotatingCylinder(cylinder) =>
cylinder.lift_coefficient(
self.chord_lengths[index], velocity[index].length()
),
SectionModel::EffectiveWindSensor => 0.0
}
}
).collect()
}
pub fn lift_coefficients_pre_stall_with_stall_drop_off(
&self,
angles_of_attack: &[Float],
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(foil) =>
foil.lift_coefficient_pre_stall_with_stall_drop_off(angles_of_attack[index]),
SectionModel::VaryingFoil(foil) =>
foil.lift_coefficient_pre_stall_with_stall_drop_off(angles_of_attack[index]),
SectionModel::RotatingCylinder(cylinder) =>
cylinder.lift_coefficient(
self.chord_lengths[index], velocity[index].length()
),
SectionModel::EffectiveWindSensor => 0.0
}
}
).collect()
}
pub fn lift_coefficients_post_stall_with_stall_weight(
&self,
angles_of_attack: &[Float]
) -> 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(foil) =>
foil.lift_coefficient_post_stall_with_stall_weight(angles_of_attack[index]),
SectionModel::VaryingFoil(foil) =>
foil.lift_coefficient_post_stall_with_stall_weight(angles_of_attack[index]),
SectionModel::RotatingCylinder(_) => 0.0,
SectionModel::EffectiveWindSensor => 0.0
}
}
).collect()
}
pub fn lift_coefficients_linear(
&self,
angles_of_attack: &[Float],
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(foil) =>
foil.lift_coefficient_linear(angles_of_attack[index]),
SectionModel::VaryingFoil(foil) =>
foil.lift_coefficient_linear(angles_of_attack[index]),
SectionModel::RotatingCylinder(cylinder) =>
cylinder.lift_coefficient(
self.chord_lengths[index], velocity[index].length()
),
SectionModel::EffectiveWindSensor => 0.0
}
}
).collect()
}
pub fn lift_coefficients_derivatives(&self) -> 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(foil) => foil.cl_initial_slope,
SectionModel::VaryingFoil(varying_foil) => {
let foil = varying_foil.get_foil();
foil.cl_initial_slope
},
SectionModel::RotatingCylinder(_) => 0.0,
SectionModel::EffectiveWindSensor => 0.0
}
}
).collect()
}
pub fn circulation_strength(
&self,
angles_of_attack: &[Float],
velocity: &[SpatialVector]
) -> Vec<Float> {
match &self.circulation_correction {
CirculationCorrection::None => self.circulation_strength_raw(
angles_of_attack,
velocity
),
CirculationCorrection::Prescribed(prescribed_circulation) =>
self.circulation_strength_prescribed(
angles_of_attack,
velocity,
prescribed_circulation,
),
CirculationCorrection::Smoothing(circulation_smoothing) => {
self.circulation_strength_smoothed(
angles_of_attack,
velocity,
circulation_smoothing
)
}
}
}
pub fn circulation_strength_raw(
&self,
angles_of_attack: &[Float],
velocity: &[SpatialVector],
) -> Vec<Float> {
let mut cl = self.lift_coefficients_pre_stall_with_stall_drop_off(
angles_of_attack,
velocity
);
if self.force_calculation_settings.include_viscous_lift_in_the_circulation {
let cl_viscous = self.lift_coefficients_post_stall_with_stall_weight(angles_of_attack);
for i in 0..cl.len() {
cl[i] += cl_viscous[i];
}
}
(0..velocity.len()).map(|index| {
-0.5 * self.chord_lengths[index] * velocity[index].length() * cl[index]
}).collect()
}
pub fn viscous_drag_coefficients(
&self,
angles_of_attack: &[Float],
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(foil) =>
foil.drag_coefficient(angles_of_attack[index]),
SectionModel::VaryingFoil(foil) =>
foil.drag_coefficient(angles_of_attack[index]),
SectionModel::RotatingCylinder(cylinder) =>
cylinder.drag_coefficient(self.chord_lengths[index], velocity[index].length()),
SectionModel::EffectiveWindSensor => 0.0
}
}
).collect()
}
pub fn sectional_force_input(
&self,
solver_result: &SolverResult,
ctrl_point_acceleration: &[SpatialVector]
) -> SectionalForcesInput {
let angles_of_attack = self.angles_of_attack(
&solver_result.output_ctrl_points_velocity,
CoordinateSystem::Global
);
let nr_span_lines = self.nr_span_lines();
let mut acceleration = ctrl_point_acceleration.to_vec();
let mut rotation_velocity = self.rigid_body_motion.velocity_angular;
let mut velocity = solver_result.output_ctrl_points_velocity.clone();
match self.output_coordinate_system {
CoordinateSystem::Body => {
for i in 0..nr_span_lines {
velocity[i] = self.rigid_body_motion.vector_in_body_fixed_coordinate_system(velocity[i]);
acceleration[i] = self.rigid_body_motion.vector_in_body_fixed_coordinate_system(acceleration[i]);
}
rotation_velocity = self.rigid_body_motion.vector_in_body_fixed_coordinate_system(rotation_velocity);
},
CoordinateSystem::Global => {}
}
SectionalForcesInput {
circulation_strength: solver_result.circulation_strength.clone(),
velocity,
angles_of_attack,
acceleration,
rotation_velocity,
coordinate_system: self.output_coordinate_system
}
}
pub fn sectional_forces(&self, input: &SectionalForcesInput) -> SectionalForces {
let mut sectional_forces = SectionalForces {
circulatory: self.sectional_circulatory_forces(
&input.circulation_strength,
&input.velocity
),
viscous_lift: self.viscous_lift_forces(&input.angles_of_attack, &input.velocity),
sectional_drag: self.sectional_drag_forces(&input.angles_of_attack, &input.velocity),
added_mass: self.sectional_added_mass_force(&input.acceleration),
gyroscopic: self.sectional_gyroscopic_force(input.rotation_velocity),
total: vec![SpatialVector::default(); self.nr_span_lines()],
coordinate_system: input.coordinate_system
};
sectional_forces.compute_total();
sectional_forces
}
pub fn sectional_circulatory_forces(
&self,
strength: &[Float],
velocity: &[SpatialVector]
) -> Vec<SpatialVector> {
let span_lines = match self.output_coordinate_system {
CoordinateSystem::Global => &self.span_lines_global,
CoordinateSystem::Body => &self.span_lines_local,
};
(0..self.nr_span_lines()).map(
|index| {
if velocity[index].length() == 0.0 {
SpatialVector::default()
} else {
strength[index] *
velocity[index].cross(span_lines[index].relative_vector()) *
self.density
}
}
).collect()
}
pub fn viscous_lift_forces(&self, angles_of_attack: &[Float], velocity: &[SpatialVector]) -> Vec<SpatialVector> {
if self.force_calculation_settings.include_viscous_lift_in_the_circulation {
vec![SpatialVector::default(); self.nr_span_lines()]
} else {
let cl_viscous = self.lift_coefficients_post_stall_with_stall_weight(
angles_of_attack
);
(0..self.nr_span_lines()).map(
|index| {
let lift_direction = self.span_lines_local[index].relative_vector().cross(velocity[index]).normalize();
let lift_area = self.chord_lengths[index] * self.span_lines_local[index].length();
let force_factor = 0.5 * lift_area * self.density * velocity[index].length_squared();
lift_direction * cl_viscous[index] * force_factor
}
).collect()
}
}
pub fn sectional_drag_forces(&self, angles_of_attack: &[Float], velocity: &[SpatialVector]) -> Vec<SpatialVector> {
let cd = self.viscous_drag_coefficients(angles_of_attack, velocity);
(0..self.nr_span_lines()).map(
|index| {
let drag_direction = velocity[index].normalize();
let drag_area = self.chord_lengths[index] * self.span_lines_local[index].length();
let force_factor = 0.5 * drag_area * self.density * velocity[index].length_squared();
drag_direction * cd[index] * force_factor
}
).collect()
}
pub fn sectional_added_mass_force(
&self,
acceleration: &[SpatialVector]
) -> Vec<SpatialVector> {
let (span_lines, chord_vectors) = match self.output_coordinate_system {
CoordinateSystem::Global => (&self.span_lines_global, &self.chord_vectors_global),
CoordinateSystem::Body => (&self.span_lines_local, &self.chord_vectors_local)
};
(0..self.nr_span_lines()).map(
|index| {
let wing_index = self.wing_index_from_global(index);
let strip_area = self.chord_lengths[index] * span_lines[index].length();
let mut relevant_acceleration = acceleration[index];
relevant_acceleration -= relevant_acceleration.project(span_lines[index].direction());
match self.section_models[wing_index] {
SectionModel::Foil(_) | SectionModel::VaryingFoil(_) => {
relevant_acceleration -= relevant_acceleration.project(chord_vectors[index]);
},
_ => {}
}
if relevant_acceleration.length() == 0.0 {
SpatialVector::default()
} else {
let added_mass_coefficient = match &self.section_models[wing_index] {
SectionModel::Foil(foil) => {
foil.added_mass_coefficient(relevant_acceleration.length())
},
SectionModel::VaryingFoil(foil) => {
foil.added_mass_coefficient(relevant_acceleration.length())
},
SectionModel::RotatingCylinder(cylinder) => {
cylinder.added_mass_coefficient(relevant_acceleration.length())
},
SectionModel::EffectiveWindSensor => 0.0
};
added_mass_coefficient * self.density * strip_area * relevant_acceleration.normalize()
}
}
).collect()
}
pub fn sectional_gyroscopic_force(
&self,
rotation_velocity: SpatialVector
) -> Vec<SpatialVector> {
let span_lines = match self.output_coordinate_system {
CoordinateSystem::Global => &self.span_lines_global,
CoordinateSystem::Body => &self.span_lines_local,
};
(0..self.nr_span_lines()).map(
|index| {
let wing_index = self.wing_index_from_global(index);
match &self.section_models[wing_index] {
SectionModel::Foil(_) | SectionModel::VaryingFoil(_) | SectionModel::EffectiveWindSensor => SpatialVector::default(),
SectionModel::RotatingCylinder(cylinder) => {
let i_zz = cylinder.moment_of_inertia_2d * span_lines[index].length();
let radial_velocity = 2.0 * PI * cylinder.revolutions_per_second;
let angular_momentum = i_zz * radial_velocity * span_lines[index].relative_vector();
angular_momentum.cross(rotation_velocity)
}
}
}
).collect()
}
pub fn lift_coefficient_from_circulation(
&self,
strength: &[Float],
velocity: &[SpatialVector]
) -> Vec<Float> {
let force = self.sectional_circulatory_forces(strength, velocity);
(0..self.nr_span_lines()).map(|i_span| {
let sign = -1.0 * strength[i_span].signum();
let lift_area = self.chord_lengths[i_span] * self.span_lines_local[i_span].length();
let force_factor = 0.5 * lift_area * self.density * velocity[i_span].length_squared();
if force_factor == 0.0 {
return 0.0;
}
force[i_span].length() * sign / force_factor
}).collect()
}
pub fn lift_from_coefficients(&self, angles_of_attack: &[Float], velocity: &[SpatialVector]) -> Vec<Float> {
let cl = self.lift_coefficients_pre_stall_with_stall_drop_off(angles_of_attack, velocity);
(0..self.nr_span_lines()).map(
|i| {
let lift_area = self.chord_lengths[i] * self.span_lines_local[i].length();
let force_factor = 0.5 * lift_area * self.density * velocity[i].length_squared();
cl[i] * force_factor
}
).collect()
}
pub fn residual(
&self,
strength: &[Float],
angles_of_attack: &[Float],
velocity: &[SpatialVector]
) -> Vec<Float> {
let cl_gamma = self.lift_coefficient_from_circulation(strength, velocity);
let cl_direct = self.lift_coefficients_pre_stall_with_stall_drop_off(
angles_of_attack,
velocity
);
(0..self.nr_span_lines()).map(|i_span| {
cl_gamma[i_span] - cl_direct[i_span]
}).collect()
}
pub fn residual_absolute(
&self,
strength: &[Float],
angles_of_attack: &[Float],
velocity: &[SpatialVector],
) -> Vec<Float> {
self.residual(strength, angles_of_attack, velocity).iter().map(|r| r.abs()).collect()
}
pub fn average_residual_absolute(
&self,
strength: &[Float],
angles_of_attack: &[Float],
velocity: &[SpatialVector]
) -> Float {
let residuals = self.residual_absolute(strength, angles_of_attack, velocity);
residuals.iter().sum::<Float>() / residuals.len() as Float
}
pub fn amount_of_flow_separation(&self, angles_of_attack: &[Float]) -> Vec<Float> {
(0..self.nr_span_lines()).map(
|i| {
let wing_index = self.wing_index_from_global(i);
self.section_models[wing_index].amount_of_flow_separation(angles_of_attack[i])
}
).collect()
}
pub fn input_power(&self, velocity: &[SpatialVector]) -> Vec<Float> {
let nr_wings = self.nr_wings();
let nr_span_lines = self.nr_span_lines();
let mut out = vec![0.0; nr_wings];
let internal_states = self.section_models_internal_state();
for i in 0..nr_span_lines {
let wing_index = self.wing_index_from_global(i);
let power_model = &self.input_power_models[wing_index];
out[wing_index] += power_model.input_power_for_strip(
internal_states[wing_index],
self.span_lines_local[i],
self.chord_lengths[i],
self.density,
velocity[i]
);
}
out
}
pub fn calculate_simulation_result(
&self,
solver_result: &SolverResult,
ctrl_point_acceleration: &[SpatialVector],
time: Float,
) -> SimulationResult {
let force_input = self.sectional_force_input(&solver_result, ctrl_point_acceleration);
let ctrl_points = self.ctrl_points_global.clone();
let sectional_forces = self.sectional_forces(&force_input);
let integrated_forces = sectional_forces.integrate_forces(&self);
let integrated_moments = sectional_forces.integrate_moments(&self);
let input_power = self.input_power(&solver_result.output_ctrl_points_velocity);
SimulationResult {
time,
ctrl_points,
solver_input_ctrl_points_velocity: solver_result.input_ctrl_points_velocity.clone(),
force_input,
sectional_forces,
integrated_forces,
integrated_moments,
input_power,
iterations: solver_result.iterations,
residual: solver_result.residual,
wing_indices: self.wing_indices.clone(),
rigid_body_motion: self.rigid_body_motion.clone()
}
}
}