diff --git a/propulsion_design/propulsion_design_conf.xml b/propulsion_design/propulsion_design_conf.xml index ba8b56252deeeff7710a2cde17f5a2566fc980a0..fc424df112bf80b42022104c0f7717b39dea9cc6 100644 --- a/propulsion_design/propulsion_design_conf.xml +++ b/propulsion_design/propulsion_design_conf.xml @@ -11,7 +11,7 @@ <value>3</value> </own_tool_level> <console_output description="Selector to specify the console output. Selector: mode_0 (Off) / mode_1 (only out/err/warn) / mode_2 (1 + info) / mode_3 (2 + debug)"> - <value>mode_1</value> + <value>mode_3</value> </console_output> <log_file_output description="Selector to specify the log file output. Selector: mode_0 (Off) / mode_1 (only out/err/warn) / mode_2 (1 + info) / mode_3 (2 + debug)"> <value>mode_1</value> @@ -49,7 +49,7 @@ <program_settings description="Settings specific to engineSizing."> <method description="Choose the implementation method of each design domain."> <engine_designer description="Choose the engine designer. Selector: Empirical / Rubber / GasTurb"> - <value>Rubber</value> + <value>PropulsiveFuselage</value> </engine_designer> <nacelle_designer description="Select the nacelle designer. Selector: Default"> <value>Default</value> @@ -120,6 +120,9 @@ <value></value> </BPR> </Empirical> + <PropulsiveFuselage description="Settings for rubber engine designer. Selector: PW1127G-JM / V2527-A5"> + <engine_model><value>CROR</value></engine_model> + </PropulsiveFuselage> <Rubber description="Settings for rubber engine designer. Selector: PW1127G-JM / V2527-A5"> <engine_model><value>CROR</value></engine_model> </Rubber> diff --git a/propulsion_design/src/engine_design/propulsive_fuselage/propulsive_fuselage.cpp b/propulsion_design/src/engine_design/propulsive_fuselage/propulsive_fuselage.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ef0c425691d090d8a451aa9376f44e991d106eac --- /dev/null +++ b/propulsion_design/src/engine_design/propulsive_fuselage/propulsive_fuselage.cpp @@ -0,0 +1,97 @@ +/* + * UNICADO - UNIversity Conceptual Aircraft Design and Optimization + * + * Copyright (C) 2025 UNICADO consortium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * Description: + * This file is part of UNICADO. + */ + +/* === Includes === */ +#include "propulsive_fuselage_design.h" +#include <cmath> +#include <format> +#include "../../utility.h" +#include "../../io/engine_xml.h" + +/* === Design implementations === */ +namespace design +{ + void PropFuse::operator()(PropulsiveFuselage<EnergyCarrier::Kerosene> &engine) + { + /* Check whether the engine model is preselected */ + auto model_preselected = this->preselected_engine(engine.id()); + if (model_preselected.has_value()) + { + engine.set_model(*model_preselected); + } + else + { + /** @todo Should this selection always take place or should we stick to the selection + * in the following iteration loops even if there is a better fitting engine now? + */ + engine.set_model(this->select_engine(engine.required_thrust())); + } + + /* Get the engine data from the project or database directory */ + const EngineData engine_data = + io::load_engine_data(engine.model(), + {this->engine_directory(), this->engine_database()}); + + /* Scale the engine */ + + /* Load the unscaled engine */ + auto engine_unscaled = io::load_engine_scaled( + engine.model(), + 1, + {this->engine_directory(), + this->engine_database()}); + + /* Get the unscaled SLST value from the deck values as the highest N1 at SLS */ + engine_unscaled.calculate_N1_with_penalties(0 , 0, this->flight_condition().ambiance, 1.0, "takeoff", 0, 0); + + /* Scale the engine */ + engine.set_scale( + engine.required_thrust() > 0.0 ? engine.required_thrust() / engine_unscaled.get_thrust() : 1.0); // engine_unscaled.get_thrust() + + /* Set and sale the parameters according to Ray12 p. 285 */ + engine.set_dimension({engine_data.dimensions().height * std::pow(engine.scale(), 0.5), + engine_data.dimensions().width * std::pow(engine.scale(), 0.5), + engine_data.dimensions().length * std::pow(engine.scale(), 0.4)}); + engine.set_fan({engine_data.dimensions().diameter * std::pow(engine.scale(), 0.5)}); + + /* Set the engine mass */ + PointMass engine_mass; + engine_mass.mass = std::pow(engine.scale(), 1.1) * engine_data.dry_mass(); // Ray12 p. 285 + engine_mass.mass *= this->technology_factor("engine_mass"); // Apply the technology factor + engine.set_pointmass(Component::Engine, engine_mass); + + /* Set the bucket point of the engine */ + engine.set_bucket_point( + this->calculate_bucket_point(engine) + ); + + /* At this point the engine was successfully sized */ + this->add_designed_engine(engine.model()); + + /* Print information */ + utility::print(std::format( + "Designed engine: {} with thrust = {:6.2f} kN, mass = {:5.1f} kg, scale = {:1.2f}", + engine.model(), engine.required_thrust() / 1000, engine_mass.mass, engine.scale()), Level::Out); + } + + +} // namespace design diff --git a/propulsion_design/src/engine_design/propulsive_fuselage/propulsive_fuselage_design.cpp b/propulsion_design/src/engine_design/propulsive_fuselage/propulsive_fuselage_design.cpp new file mode 100644 index 0000000000000000000000000000000000000000..56e384fb4c290400ef00d9ded5f3acdcfa7999fe --- /dev/null +++ b/propulsion_design/src/engine_design/propulsive_fuselage/propulsive_fuselage_design.cpp @@ -0,0 +1,275 @@ +/* + * UNICADO - UNIversity Conceptual Aircraft Design and Optimization + * + * Copyright (C) 2025 UNICADO consortium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * Description: + * This file is part of UNICADO. + */ + +/* === Includes === */ +#include "propulsive_fuselage_design.h" +#include <algorithm> +#include <filesystem> +#include <format> +#include <list> +#include <ranges> +#include "../../io/engine_xml.h" +#include "../../utility.h" + +/* === Design implementations === */ +namespace design +{ + void PropFuse::initialize() + { + /* Initialize the EngineDesigner */ + EngineDesigner::initialize(); + + std::cout << "Initializing the propulsive fuselage engine designer." << std::endl; + + /* Check whether the engine efficiency technology factor is 1.0 */ + const auto efficiency_factor = static_cast<float>( + this->configuration()->at( + "module_configuration_file/program_settings/technology_factors/engine_efficiency/value")); + + std::filesystem::path path_to_csv = EngineDesigner::engine_database(); + std::string engine_name = this->configuration()->at("module_configuration_file/program_settings/propulsion/engine/PropulsiveFuselage/engine_model/value"); + std::string added_path = (std::filesystem::path(path_to_csv) / engine_name / engine_name).string(); + DeckData deck_data = DeckData::from_csv(added_path.append("_WF.csv")); + + DeckData deck_data_scaled = EngineDesigner::scale_deck_data_values(deck_data, EngineDesigner::technology_factor("engine_efficiency")); + EngineDesigner::write_deck_value(deck_data_scaled, engine_name); + if (std::abs(efficiency_factor - 1.0) > 1e-6) + { + /* Print information only when the deck was manipulated */ + utility::print(std::format( + "Engine efficiency manipulated using the efficiency factor = {:1.2f} ", + efficiency_factor), Level::Out); + } + + /* Loop through all propulsion settings */ + for (const auto &propulsion : this->configuration()->getVector("module_configuration_file/program_settings/propulsion")) + { + /* Get the preselected engine */ + node *engine_model = propulsion->find("engine/PropulsiveFuselage/engine_model/value"); + + /* Check whether the engine is preselected */ + if (engine_model != nullptr) + { + /* Get the engine id */ + const std::string id = propulsion->getStringAttrib("ID"); + const int engine_id = id == "Default" ? -1 : std::stoi(id); + + /* Get the engine model */ + const std::string model(*engine_model); + + /* Store the preselected engine */ + this->preselected_engines_[engine_id] = model; + } + } + } + + void PropFuse::save() + { + /* Copy all engines which do not exist in the engine directory */ + utility::print("Saving the propulsive fuselage engine results", Level::Info); + auto is_in_project = [this](const std::string &engine) + { + return std::filesystem::exists(this->engine_directory() / engine); + }; + std::list<std::string> engines_to_copy; + std::ranges::copy_if( + this->designed_engines(), + std::back_inserter(engines_to_copy), + std::not_fn(is_in_project)); + + /* Copy the engines */ + for (const auto &engine : engines_to_copy) + { + utility::print("Copying engine: " + engine, Level::Debug); + std::filesystem::copy(this->engine_database() / engine, this->engine_directory() / engine); + } + } + + template <EnergyCarrier carrier> + auto PropFuse::calculate_bucket_point(const Propulsion<carrier> &engine) const -> BucketPoint + { + /* Set the step size when looking for the minimum of the bucket curve */ + constexpr double step_size = 1e-2; + constexpr auto maxiter = static_cast<std::size_t>(1.0 / step_size); + + /* Load the scaled engine */ + auto engine_scaled = io::load_engine_scaled( + engine.model(), + engine.scale(), + {this->engine_directory(), + this->engine_database()}); + + /* Try to calculate the N1 */ + try + { + /* Get the maximum N1 */ + engine_scaled.calculate_N1_with_penalties( + this->flight_condition().altitude, + this->flight_condition().mach, + this->flight_condition().ambiance, + 1.0, + "idle", + engine.offtakes().bleed_air, + engine.offtakes().shaft_power / 1000); + } catch (const std::exception &error) { + utility::print("Calculating the N1 failed:" + std::string(error.what()) + "Bucket point will not be set!", Level::Warning); + return {0.0, 0.0}; + } + const double N1_min = engine_scaled.get_operating_point().N; + + /* Initialize the search point an lowest N1 */ + engine_scaled.calculate_N1_with_penalties( + this->flight_condition().altitude, + this->flight_condition().mach, + this->flight_condition().ambiance, + 1.0, + "takeoff", + engine.offtakes().bleed_air, + engine.offtakes().shaft_power / 1000); + const double N1_max = engine_scaled.get_operating_point().N; + + /* Print the bucket curve range */ + utility::print( + std::format( + "Calculating bucket point for engine {} => N1_min = {:.2f}, N1_max = {:.2f}", + engine.model(), + N1_min, + N1_max), + Level::Debug); + + /* Set the engine operating point to the lower starting point of N1 */ + auto op = engine_scaled.get_operating_point(); + op.N = N1_min; + engine_scaled.set_operating_point(op); + + /* Initialize the bucket N1 and TSFC */ + double N1_previous = N1_min; + double tsfc_previous = engine_scaled.get_aircraft_fuelflow() / engine_scaled.get_thrust_aircraft(); + + /* Loop until the fuel flow gradient becomes positive */ + for (std::size_t i = 0; i < maxiter; ++i) + { + /* Update the operating point of the engine */ + const double N1 = N1_previous + step_size; + op.N = N1; + engine_scaled.set_operating_point(op); + + /* Calculate the fuel flow at the current N1 */ + const double tsfc = engine_scaled.get_aircraft_fuelflow() / engine_scaled.get_thrust_aircraft(); + + /* Check whether the gradient is positive */ + if (tsfc > tsfc_previous) + { + break; + } else { + /* Update the tsfc and N1 */ + tsfc_previous = tsfc; + N1_previous = N1; + } + + /* Check whether N1 is still valid */ + if (N1 > N1_max) + { + utility::print("Maximum N1 reached in bucket point calculation!", Level::Warning); + break; + } + + /* Warn if iter reached maxiter */ + if (i == maxiter - 1) + { + utility::print("Maximum number of iterations reached in bucket point calculation!", Level::Warning); + } + } + + /* Calculate the found bucket point */ + op.N = N1_previous; + engine_scaled.set_operating_point(op); + const double thrust_bucket = engine_scaled.get_thrust_aircraft(); + const double fuel_flow_bucket = engine_scaled.get_aircraft_fuelflow(); + utility::print( + std::format( + "Found bucket point for engine {} at N1 = {:.2f} => thrust = {:3.1f} kN, TSFC = {:1.3e} kg/Ns", + engine.model(), N1_previous, thrust_bucket / 1000, fuel_flow_bucket / thrust_bucket), + Level::Info); + + /* Return the bucket point */ + return { + thrust_bucket, + fuel_flow_bucket / thrust_bucket}; + }; + template auto PropFuse::calculate_bucket_point(const Propulsion<EnergyCarrier::Kerosene> &) const -> BucketPoint; + + auto PropFuse::preselected_engine(const int engine_id) -> std::optional<std::string_view> + { + /* Check whether the engine id has a pre-selected model */ + if (this->preselected_engines_.contains(engine_id)) + { + return this->preselected_engines_.at(engine_id); + } + + /* Check whether there is a default preselection */ + if (this->preselected_engines_.contains(-1)) + { + return this->preselected_engines_.at(-1); + } + + /* No preselection available */ + return std::nullopt; + } + + auto PropFuse::select_engine(const double thrust_required) const -> std::string + { + /* Define the criteria for a valid engine entry */ + /** @todo Make the valid engine criteria more precise */ + const auto is_valid_engine = [](const auto &entry) -> bool + { + return entry.is_directory(); + }; + + /* Get a list with all valid folder names in the database path */ + const auto database = this->engine_database(); + std::list<std::string> engine_names; + std::ranges::copy( + std::filesystem::directory_iterator{database} | std::views::filter(is_valid_engine) | std::views::transform([](const auto &entry) + { return entry.path().filename().string(); }), + std::back_inserter(engine_names)); + + /* Find the minimum deviation to the required thrust of the engines */ + const auto best_engine = std::ranges::min_element( + engine_names, + [&database, &thrust_required](const auto &lhs, const auto &rhs) + { + const auto lhs_thrust = io::load_engine_data(lhs, {database}).SLST(); + const auto rhs_thrust = io::load_engine_data(rhs, {database}).SLST(); + return std::abs(lhs_thrust - thrust_required) < std::abs(rhs_thrust - thrust_required); }); + + /* Check whether there is a result */ + if (best_engine == engine_names.end()) + { + throw std::runtime_error("No engine found in the database"); + } + + /* Return the name of the best engine */ + return *best_engine; + } + +} // namespace design diff --git a/propulsion_design/src/engine_design/propulsive_fuselage/propulsive_fuselage_design.h b/propulsion_design/src/engine_design/propulsive_fuselage/propulsive_fuselage_design.h new file mode 100644 index 0000000000000000000000000000000000000000..03790834295498b2a69012043d904ff012591dc9 --- /dev/null +++ b/propulsion_design/src/engine_design/propulsive_fuselage/propulsive_fuselage_design.h @@ -0,0 +1,121 @@ +/* + * UNICADO - UNIversity Conceptual Aircraft Design and Optimization + * + * Copyright (C) 2025 UNICADO consortium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * Description: + * This file is part of UNICADO. + */ + +#ifndef SRC_ENGINE_DESIGN_PropFuse_PropFuse_DESIGN_H_ +#define SRC_ENGINE_DESIGN_PropFuse_PropFuse_DESIGN_H_ + +/* === Includes === */ +#include "../engine_design.h" +#include <filesystem> +#include <memory> +#include <optional> +#include <unordered_map> +#include <unordered_set> +#include <string> +#include <string_view> + +namespace design +{ + /* === Classes === */ + /** + * @class PropFuse + * @brief The propulsive fuselage engine designer + */ + class PropFuse : public EngineDesigner + { + public: + /* === Constructors === */ + /** + * @brief Construct a new propulsive fuselage engine designer. + * @param configuration The module configuration provided by the user. + * @param engine_directory The directory where the engine results are stored. + * @param design_condition The design flight condition of the aircraft. + */ + explicit PropFuse( + const std::shared_ptr<node> &configuration, + const std::filesystem::path &engine_directory, + const FlightCondition &design_condition) + : EngineDesigner(configuration, engine_directory, design_condition) {} + + /* === Methods === */ + /** + * @brief Finalize the initialization of the engine designer. + */ + void initialize() final; + + /** + * @brief Finalize the saving of the engine results. + */ + void save() final; + + /** + * @brief Design a propulsive fuselage engine with kerosene as fuel. + * @note The engine WILL be modified! + * + * @param engine The engine to design. + */ + + void operator()(PropulsiveFuselage<EnergyCarrier::Kerosene> &engine); // NOLINT runtime/references + + private: + /* === Methods === */ + /** + * @brief Calculate the bucket point of the engine. + * The design flight condition and the current engine offtakes + * are taken as the boundary conditions for the bucket point. + * + * @tparam carrier The energy carrier of the engine. + * @param engine The engine to calculate the bucket point for. + * @return BucketPoint [N, (kg/s)/N] The bucket point (thrust, tsfc) of the engine + * at the current operating conditions. + */ + template <EnergyCarrier carrier> + [[nodiscard]] auto calculate_bucket_point(const Propulsion<carrier> &engine) const -> BucketPoint; + + /** + * @brief Get the preselected engine for the given engine id. + * When the specific id does not have a preselected engine, + * the default preselected engine is returned. If there is no + * default preselected engine, an empty optional is returned. + * + * @param engine_id The id of the engine. + * @return std::optional<std::string_view> The preselected engine if available. + */ + [[nodiscard]] auto preselected_engine(int engine_id) -> std::optional<std::string_view>; + + /** + * @brief Selects the engine with the thrust closest to the required thrust. + * It searches the database directory for the engine with the thrust closest + * to the required thrust. + * + * @param thrust_required [N] The required thrust. + * @return std::string The name of the selected engine. + * @throws std::runtime_error if no engine is found in the database. + */ + [[nodiscard]] auto select_engine(const double thrust_required) const -> std::string; + + /* === Properties === */ + std::unordered_map<int, std::string> preselected_engines_; /**< The manual selected engines. */ + }; +}; // namespace design + +#endif // SRC_ENGINE_DESIGN_PropFuse_PropFuse_DESIGN_H_ diff --git a/propulsion_design/src/integration/default/default_integration.h b/propulsion_design/src/integration/default/default_integration.h index 5bb570bd954942ef96a2e8bc819d45c3992e7f09..fb929f387998a897ea14dae89e2bf4c4ce49ba83 100644 --- a/propulsion_design/src/integration/default/default_integration.h +++ b/propulsion_design/src/integration/default/default_integration.h @@ -78,6 +78,8 @@ namespace design void operator()(Openrotorfan<EnergyCarrier::Kerosene> &engine); // NOLINT runtime/references + void operator()(PropulsiveFuselage<EnergyCarrier::Kerosene> &engine); // NOLINT runtime/references + private: /* === Methods === */ /** @@ -122,6 +124,22 @@ namespace design template <EnergyCarrier EC> void openrotorfan_method(Openrotorfan<EC>& engine); // NOLINT runtime/references + + template <EnergyCarrier EC> + void integrate_into_wing(PropulsiveFuselage<EC> &engine); // NOLINT runtime/references + + template <EnergyCarrier EC> + void integrate_into_fuselage(PropulsiveFuselage<EC> &engine); // NOLINT runtime/references + + template <EnergyCarrier EC> + void integrate_into_empennage(PropulsiveFuselage<EC> &engine); // NOLINT runtime/references + + template <EnergyCarrier EC> + void guess_position(PropulsiveFuselage<EC> &engine); // NOLINT runtime/references + + template <EnergyCarrier EC> + void propulsive_fuselage_method(PropulsiveFuselage<EC>& engine); // NOLINT runtime/references + /** * @brief Calculate the engine span location on the wing * according to the procedure described in \cite Ata10. @@ -140,7 +158,7 @@ namespace design const double wing_span, const bool is_outer) const -> double; - /** + /** * @brief Calculate the engine span location on the wing * according to the procedure described in \cite Ata10. * @attention This always returns positive values! You have to adjust @@ -158,6 +176,24 @@ namespace design const double wing_span, const bool is_outer) const -> double; + /** + * @brief Calculate the engine span location on the wing + * according to the procedure described in \cite Ata10. + * @attention This always returns positive values! You have to adjust + * the sign according to the wing extrusion direction. + * + * @param width_fuselage [m] The maximum width of the fuselage. + * @param diameter_engine_max [m] The maximum diameter of the engine. + * @param wing_span [m] The absolute span of the wing. + * @param is_outer [-] Whether the position should be calculated for the outer engine. + * @return double [m] The absolute span position of the engine. + */ + [[nodiscard]] auto calculate_span_position_propulsive_fuselage( + const double width_fuselage, + const double diameter_engine_max, + const double wing_span, + const bool is_outer) const -> double; + /** * @brief Select the fuselage from the aircraft geometry. * diff --git a/propulsion_design/src/integration/default/propulsive_fuselage.cpp b/propulsion_design/src/integration/default/propulsive_fuselage.cpp new file mode 100644 index 0000000000000000000000000000000000000000..12cb2c4697ccb2017cdffecb88aa88e8983b37e7 --- /dev/null +++ b/propulsion_design/src/integration/default/propulsive_fuselage.cpp @@ -0,0 +1,331 @@ +/* + * UNICADO - UNIversity Conceptual Aircraft Design and Optimization + * + * Copyright (C) 2025 UNICADO consortium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * Description: + * This file is part of UNICADO. + */ + +/* === Includes === */ +#include "default_integration.h" +#include <aircraftGeometry2/processing/measure.h> +#include <aircraftGeometry2/processing/transform.h> +#include <cmath> +#include "../../utility.h" + +/* === Design implementations === */ +namespace design +{ + namespace integration + { + namespace detail + { + /** + * @brief Set of supported parent components for the engine integration. + */ + const std::set<ParentComponent> supported_parents = { + {Parent::Wing, Lateral::Right, Longitudinal::Front, Vertical::Under}, + {Parent::Wing, Lateral::Left, Longitudinal::Front, Vertical::Under}, + {Parent::Wing, Lateral::Right, Longitudinal::Front, Vertical::Over}, + {Parent::Wing, Lateral::Left, Longitudinal::Front, Vertical::Over}, + {Parent::Fuselage, Lateral::Right, Longitudinal::Rear, Vertical::Mid}, + {Parent::Fuselage, Lateral::Left, Longitudinal::Rear, Vertical::Mid}, + {Parent::Empennage, Lateral::Mid, Longitudinal::Front, Vertical::In}}; + } // namespace detail + + template <EnergyCarrier EC> + void Default::propulsive_fuselage_method(PropulsiveFuselage<EC> &engine) + { + /* Check whether the parent component is supported */ + if (!detail::supported_parents.contains(engine.parent_component())) + { + throw std::runtime_error("[design::integrator::Default] The parent component or location of the engine is not supported."); + } + + /* Check whether valid aircraft geometry was provided */ + if (this->aircraft()) + { + /* Check which parent component this engine has */ + switch (std::get<Parent>(engine.parent_component())) + { + case Parent::Wing: + /* Integrate the engine into the wing */ + integrate_into_wing(engine); + break; + case Parent::Fuselage: + /* Integrate the engine into the fuselage */ + integrate_into_fuselage(engine); + break; + case Parent::Empennage: + /* Integrate the engine into the empennage */ + integrate_into_empennage(engine); + break; + default: + throw std::runtime_error("[design::integrator::Default] The parent component of the engine is not supported."); + } + } + else + { + /* When no aircraft geometry exists we need to guess the initial engine position */ + this->guess_position(engine); + } + + /* At this point we can assume the engine was placed successfully */ + this->parents_placed.insert(engine.parent_component()); + } + + template <EnergyCarrier EC> + void Default::integrate_into_wing(PropulsiveFuselage<EC> &engine) + { + /* + * => This method follows the procedure described in \cite Ata10 + * It assumes one main wing which is extruded in global Y direction + */ + /** @fixed Add a warning when the aircraft contains more than one wing or fuselage */ + const auto &wing = this->select_wing(); + const auto &fuselage = this->select_fuselage(); + const double sign_wing_extrusion = std::signbit(wing.sections.back().origin.z()) ? -1.0 : 1.0; + if (wing.normal != geom2::Direction_3{0, sign_wing_extrusion * 1, 0}) + { + throw std::runtime_error("[design::integrator::Default] The wing is not oriented in y-direction. This method is not applicable."); + } + if (std::get<Vertical>(engine.parent_component()) != Vertical::Under) + { + throw std::runtime_error("[design::integrator::Default] The engine is not under the wing. This method is not applicable."); + } + + /* When the current parent position is already placed, we assume this is the outer engine */ + bool is_outer = this->parents_placed.contains(engine.parent_component()); + + /* Check whether there is still space for the engine to be integrated */ + switch (this->n_engines_.wing) + { + case 4: + /* Check whether the outer position is already placed */ + if (is_outer && this->engines_done.wing % 2) + { + break; + } + [[fallthrough]]; + case 2: + /* At this point there does not exist an outer engine position */ + if (is_outer) + { + throw std::runtime_error("[design::integrator::Default] No space on the wing for the engine to be integrated."); + } + break; + default: + throw std::runtime_error("[design::integrator::Default] The number of engines on the wing is not supported."); + } + + /* !!! + * The following assumes the wing local coordinate system: + * - X axis: chord direction + * - Y axis: height direction + * - Z axis: span direction (should be negative coordinates) + * !!! + */ + /* Get the relevant measurements from the aircraft */ + const double span = geom2::measure::span(wing); + const double width_fuselage = geom2::measure::width_max(fuselage); + const double diameter_max = 1.21 * engine.fan().diameter; // \cite Ata10 p. 45 + + /* Wing local z position of engine */ + const double span_engine = sign_wing_extrusion * this->calculate_span_position_propulsive_fuselage( + width_fuselage, diameter_max, span, is_outer); + + /* Get the wing quarter coordinate at the engine span position */ + const double wing_chord = geom2::measure::chord(wing, span_engine); + const auto wing_quarter = + geom2::measure::offset_LE(wing, span_engine) + + geom2::Vector_3{0.25 * wing_chord, 0, 0}; + + /* Wing local x position of engine */ + const double chord_engine = -0.9 * wing_chord + geom2::measure::offset_LE(wing, span_engine).x(); // \cite Ata10 p. 48f + + /* Wing local y position of engine */ + const double height_engine = wing_quarter.y() - + 0.5 * geom2::measure::height_max(engine.nacelle()) - + 0.1 * wing_chord; // \cite Ata10 p. 48f adapted to geometry_lib + + /* Adjust whether the engine is left or right */ + auto position = geom2::Point_3{chord_engine, height_engine, span_engine}; + + /* Include user input in there to shift engine in every direction acc to config */ + position = {position.x() + usershift_x, position.y() + usershift_z, position.z() - usershift_y}; + + if (std::get<Lateral>(engine.parent_component()) == Lateral::Left) + { + position = {position.x(), position.y(), -position.z()}; + } + + /* Set the position of the engine in the GLOBAL (!) reference frame */ + engine.set_position(geom2::transform::to_parent(wing, position)); + ++this->engines_done.wing; + } + + template <EnergyCarrier EC> + void Default::integrate_into_fuselage(PropulsiveFuselage<EC> &engine) + { + /* + * => This method follows the procedure described in \cite Ata10 + * It assumes one main fuselage which is extruded in global X direction + */ + /** @fixed Add a warning when there are more than one fuselages */ + const auto &fuselage = this->select_fuselage(); + const double sign_extrusion = std::signbit(fuselage.sections.back().origin.z()) ? -1.0 : 1.0; + if (fuselage.normal != geom2::Direction_3{sign_extrusion * 1, 0, 0}) + { + throw std::runtime_error("[design::integrator::Default] The fuselage is not oriented in x-direction. This method is not applicable."); + } + + /* Only two engines on the fuselage are supported right now */ + if (this->n_engines_.fuselage != 2) + { + throw std::runtime_error("[design::integrator::Default] The number of engines on the fuselage is not supported."); + } + + /* Check whether there is still space for the engine to be integrated */ + if (this->parents_placed.contains(engine.parent_component())) + { + throw std::runtime_error("[design::integrator::Default] No space left on the fuselage for the engine to be integrated."); + } + + /* !!! + * The following assumes the fuselage local coordinate system: + * - X axis: width direction + * - Y axis: height direction + * - Z axis: length direction + * !!! + */ + /* Get the engine position in local z direction */ + const double z_engine = sign_extrusion * 0.8 * geom2::measure::length(fuselage); + + /* Get the engine position in local y direction */ + const double y_engine = 0.25 * geom2::measure::height(fuselage, z_engine); + + /* Get the engine position in local x direction */ + const double x_engine = 0.5 * geom2::measure::width(fuselage, z_engine) + + 1.25 * 0.5 * geom2::measure::width_max(engine.nacelle()); + + /* Adjust whether engine is left or right */ + auto position = geom2::Point_3{x_engine, y_engine, z_engine}; + if (std::get<Lateral>(engine.parent_component()) == Lateral::Left) + { + position = {-position.x(), position.y(), position.z()}; + } + + /* Set the position of the engine in the GLOBAL (!) reference frame */ + engine.set_position(geom2::transform::to_parent(fuselage,position)); + ++this->engines_done.fuselage; + } + + template <EnergyCarrier EC> + void Default::integrate_into_empennage(PropulsiveFuselage<EC> &engine) + { + /* Get the first empennage surface */ + /** @fixed Make the selection which empennage surface to integrate the engine into a bit smarter. */ + const auto &empennage = this->select_vertical_tail(); + const auto &fuselage = this->select_fuselage(); + + /* Check whether there is still space for the engine to be integrated */ + if (this->parents_placed.contains(engine.parent_component())) + { + throw std::runtime_error("[design::integrator::Default] No space left on the empennage for the engine to be integrated."); + } + + /* Check whether the empennage surface is in line with the fuselage center */ + if (std::abs(fuselage.origin.y() - empennage.origin.y()) > 1e-4) + { + throw std::runtime_error( + "[design::integrator::Default] The empennage is not in line with the fuselage center. " + "This method is not applicable!"); + } + + /* Get quarter line point at the mac position of the empennage surface */ + const auto mac_position = geom2::measure::mean_aerodynamic_chord_position(empennage); + const auto chord = geom2::measure::chord(empennage, mac_position); + const auto p_25_local = geom2::measure::offset_LE(empennage, mac_position) + + geom2::Vector_3{0.25 * chord, 0, 0}; + const auto p_25_global = geom2::transform::to_parent(empennage, p_25_local); + const auto p_25_fuselage = geom2::transform::to_local(fuselage, p_25_global); + + /* Calculate the engine position */ + const auto position = geom2::Point_3{ + p_25_global.x() - 0.25 * engine.dimension().length, // Place engine 25% in front of quarter line + fuselage.origin.y(), // Place engine in the middle of the fuselage + 0.5 * geom2::measure::height(fuselage, p_25_fuselage.z()) + 1.25 * 0.5 * engine.dimension().width}; + + /* Set the position of the engine */ + engine.set_position(position); + ++this->engines_done.empennage; + } + + template <EnergyCarrier EC> + void Default::guess_position(PropulsiveFuselage<EC> &engine) + { + /* Guess the position of the engine based on their parent component */ + switch (std::get<Parent>(engine.parent_component())) + { + /* For now place everything at the origin */ + case Parent::Wing: + case Parent::Fuselage: + case Parent::Empennage: + default: + /* Fall back to ORIGIN as the default position */ + engine.set_position(CGAL::ORIGIN); + break; + } + } + + auto Default::calculate_span_position_propulsive_fuselage( + const double width_fuselage, + const double diameter_engine_max, + const double wing_span, + const bool is_outer) const -> double + { + /* How many engines on the wing */ + if (this->n_engines_.wing == 2) + { + return 0.95 * (width_fuselage / 2.0 + + 1.3 * diameter_engine_max + + 0.5 * diameter_engine_max); // \cite Ata10 p. 52, 0.95 additional factor + } + else if (this->n_engines_.wing == 4) + { + /* Return the position whether the engine is on the outer + * or inner side of the wing. + */ + if (is_outer) + { + return 0.64 * wing_span / 2.0; // \cite Ata10 p. 83 + } + return (0.9 / 4.0) * (0.64 * wing_span + + width_fuselage - + diameter_engine_max); // \cite Ata10 p. 94, 0.9 additional factor + } + /* Not a valid engine on the wing count */ + throw std::runtime_error("[design::integrator::Default] The number of engines on the wing is not valid."); + } + + void Default::operator()(PropulsiveFuselage<EnergyCarrier::Kerosene>& engine) + { + propulsive_fuselage_method(engine); + }; + + } // namespace integration +} // namespace design diff --git a/propulsion_design/src/io/aircraft_xml.cpp b/propulsion_design/src/io/aircraft_xml.cpp index 961872c7cefbe5cff35818daf4317b9eaa4442fb..fa35c0d4b05bdc361e24c1bc19fc12a3dfc339bf 100644 --- a/propulsion_design/src/io/aircraft_xml.cpp +++ b/propulsion_design/src/io/aircraft_xml.cpp @@ -173,6 +173,16 @@ namespace io this->insert_propulsion<EnergyCarrier::Kerosene>(engine); } + /** + * @brief Insert the data of a turbofan engine. + * + * @param engine The engine to insert into the xml. + */ + void insert(const PropulsiveFuselage<EnergyCarrier::Kerosene> &engine) override + { + this->insert_propulsion<EnergyCarrier::Kerosene>(engine); + } + private: /* === Methods === */ /** diff --git a/propulsion_design/src/io/aircraft_xml.h b/propulsion_design/src/io/aircraft_xml.h index 2f0ddce3b6ae5a6a7f252409cef73a7f725e0c36..694381b971caeeaf77443f6d046338b1729ad3f4 100644 --- a/propulsion_design/src/io/aircraft_xml.h +++ b/propulsion_design/src/io/aircraft_xml.h @@ -48,6 +48,7 @@ namespace io virtual void insert(const Turboprop<EnergyCarrier::Kerosene> &engine) = 0; virtual void insert(const Turboprop<EnergyCarrier::Liquid_Hydrogen> &engine) = 0; virtual void insert(const Openrotorfan<EnergyCarrier::Kerosene> &engine) = 0; + virtual void insert(const PropulsiveFuselage<EnergyCarrier::Kerosene> &engine) = 0; }; }; // namespace detail @@ -127,6 +128,19 @@ namespace io this->xml_interface->insert(engine); } + /** + * @brief Insert the data of a turboprop engine + * into the aircraft xml. + * + * @tparam carrier The energy carrier of the engine. + * @param engine The engine to insert into the xml. + */ + template <EnergyCarrier carrier> + void insert(const PropulsiveFuselage<carrier> &engine) + { + this->xml_interface->insert(engine); + } + private: std::unique_ptr<detail::AircraftXMLInterface> xml_interface; /** [-] The pointer to the xml interface. */ }; diff --git a/propulsion_design/src/mass/default/default_mass.h b/propulsion_design/src/mass/default/default_mass.h index caa0fe4dea838ed273ec92a204388cefac9d80a4..e6ebdc36cc78527e35d56a07dcbd5854d052c65e 100644 --- a/propulsion_design/src/mass/default/default_mass.h +++ b/propulsion_design/src/mass/default/default_mass.h @@ -62,6 +62,17 @@ public: template <EnergyCarrier EC> void openrotorfan_method(Openrotorfan<EC>& engine); + /* === Methods === */ + /** + * @brief Analyze the mass of a openrotorfan engine with kerosene as fuel. + * + * @param engine The engine to analyze the mass for. + */ + template <EnergyCarrier EC> + void propulsive_fuselage_method(PropulsiveFuselage<EC>& engine); + + void operator() (PropulsiveFuselage<EnergyCarrier::Kerosene>& engine); + void operator() (Openrotorfan<EnergyCarrier::Kerosene>& engine); void operator() (Turbofan<EnergyCarrier::Kerosene>& engine); diff --git a/propulsion_design/src/mass/default/propulsive_fuselage.cpp b/propulsion_design/src/mass/default/propulsive_fuselage.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a637c4d853babb576e35fcbcaa790fc583750d71 --- /dev/null +++ b/propulsion_design/src/mass/default/propulsive_fuselage.cpp @@ -0,0 +1,189 @@ +/* + * UNICADO - UNIversity Conceptual Aircraft Design and Optimization + * + * Copyright (C) 2025 UNICADO consortium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * Description: + * This file is part of UNICADO. + */ + +/* === Includes === */ +#include "default_mass.h" +#include <aircraftGeometry2/processing/measure.h> +#include <aircraftGeometry2/processing/transform.h> + +/* === Design implementations === */ +namespace mass +{ +/** + * @brief Method to calculate the engine mass based on a regression line of the LTH-data. + * @cite Pet13 + */ +auto calculate_engine_mass_propulsive_fuselage(const double engine_technology_factor , const double thrust) -> double +{ + return engine_technology_factor * (0.0159 * thrust + 306.57); +} + +/** + * @brief Method to calculate the engine CG assuming a circular cylinder + */ +auto calculate_engine_CG_propulsive_fuselage(double length) -> Eigen::Vector3d +{ + /* Calculate local center of gravity position (local KOS @ circle center of fan/propeller) */ + double cg_x_local = 0.5 * length; + Eigen::Vector3d engine_local_cg(cg_x_local, 0, 0); + Eigen::Vector3d engine_cg = engine_local_cg; + return engine_cg; +} + +/** + * @brief Method to calculate the engine inertia with respect to the center of gravity assuming a solid cylinder + */ +auto calculate_engine_inertia_propulsive_fuselage(double height, double width, double length, double mass) -> Eigen::Matrix3d +{ + Eigen::Matrix3d inertia_tensor = Eigen::Matrix3d::Zero(); + /* Assume average of height and width as engine diameter, respective radius */ + double engine_radius_average = (height + width)/ 4; + inertia_tensor(0, 0) = mass / 2 * pow(engine_radius_average, 2); // in length (x) axis + inertia_tensor(1, 1) = mass / 12 * (3 * pow(engine_radius_average, 2) + pow(length, 2)); // in y axis + inertia_tensor(2, 2) = mass / 12 * (3 * pow(engine_radius_average, 2) + pow(length, 2)); // in z axis - assumed symmetrical + return inertia_tensor; +} + +/** + * @brief Method to calculate the nacelle mass based on a regression line of the LTH-data. + * @cite Pet13 + */ +auto calculate_nacelle_mass_propulsive_fuselage(const double nacelle_technology_factor, const double thrust)-> double +{ + return nacelle_technology_factor * (thrust * 0.0039 + 595.42); +} + +/** + * @brief Method to calculate the nacelle CG with the aircraftGeometry2 lib + */ +auto calculate_nacelle_CG_propulsive_fuselage(const geom2::MultisectionSurface<geom2::PolygonSection> &nacelle_surface) -> Eigen::Vector3d +{ + /* + * Get the CG in the engine LOCAL coordinate frame + * => We always assume the CG to be in the LOCAL coordinate frame of the engine + */ + geom2::Point_3 nacelle_geom2_cg_local = geom2::transform::to_parent( + nacelle_surface, + geom2::measure::centroid(nacelle_surface)); + + /* Convert Point_3 in Vector3d */ + Eigen::Vector3d nacelle_cg; + nacelle_cg << nacelle_geom2_cg_local.x(), nacelle_geom2_cg_local.y(), nacelle_geom2_cg_local.z(); + return nacelle_cg; +} + +/** + * @brief Method to calculate the nacelle inertia with respect to the center of gravity with the aircraftGeometry2 lib + */ +auto calculate_nacelle_inertia_propulsive_fuselage(const geom2::MultisectionSurface<geom2::PolygonSection> &nacelle_surface) -> Eigen::Matrix3d +{ + /* Use geom2 lib to calculate the inertia tensor */ + CGAL::Eigen_matrix inertia_tensor_cgal = geom2::measure::inertia(nacelle_surface); + /* Convert CGAL Eigen_matrix to Eigen Matrix3d */ + Eigen::Matrix3d inertia_tensor = inertia_tensor_cgal.eigen_object(); + return inertia_tensor; +} + +/** + * @brief Method to calculate the pylon mass based on a regression line of the LTH-data. + * @cite Pet13 + */ +auto calculate_pylon_mass_propulsive_fuselage(const double pylon_technology_factor, const double thrust)-> double +{ + return pylon_technology_factor * (thrust * 0.0024 + 233.29); +} + +/** + * @brief Method to calculate the pylon CG with the aircraftGeometry2 lib + * @return Eigen::Vector3d The center of gravity of the pylon in the engine LOCAL coordinate system. + */ +auto calculate_pylon_CG_propulsive_fuselage(const geom2::MultisectionSurface<geom2::AirfoilSection> & pylon_surface)-> Eigen::Vector3d +{ + /* Calculate centroid with aircraft geometry lib */ + geom2::Point_3 pylon_geom2_cg_local = geom2::transform::to_parent( + pylon_surface, + geom2::measure::centroid(pylon_surface)); + + /* Convert Point_3 in Vector3d */ + Eigen::Vector3d pylon_cg; + pylon_cg << pylon_geom2_cg_local.x(), pylon_geom2_cg_local.y(), pylon_geom2_cg_local.z(); + return pylon_cg; +} + +/** + * @brief Method to calculate the pylon inertia with respect to the center of gravity with the aircraftGeometry2 lib + */ +auto calculate_pylon_inertia_propulsive_fuselage(const geom2::MultisectionSurface<geom2::AirfoilSection> &pylon_surface) -> Eigen::Matrix3d +{ + /* Use geom2 lib to calculate the inertia tensor */ + CGAL::Eigen_matrix inertia_tensor_cgal = geom2::measure::inertia(pylon_surface); + /* Convert CGAL Eigen_matrix to Eigen Matrix3d */ + Eigen::Matrix3d inertia_tensor = inertia_tensor_cgal.eigen_object(); + return inertia_tensor; +} + +template <EnergyCarrier EC> +void Default::propulsive_fuselage_method(PropulsiveFuselage<EC>& engine) +{ + const double thrust = engine.required_thrust(); + Dimension_3 dimension_engine = engine.dimension(); + + /* Mass, CG and inertia of engine (mass only if not calculated already) */ + Eigen::Vector3d CG_engine = calculate_engine_CG_propulsive_fuselage(dimension_engine.length); + double mass_engine = engine.pointmass(Component::Engine).mass; + if (mass_engine == 0.0) + { + /* Calculate engine mass with massAnalyzer function */ + const double tf_engine = this->technology_factor("engine_mass"); + mass_engine = calculate_engine_mass_propulsive_fuselage(tf_engine, thrust); + } + Eigen::Matrix3d inertia_engine = calculate_engine_inertia_propulsive_fuselage(dimension_engine.height, + dimension_engine.width, dimension_engine.length, mass_engine); + engine.set_pointmass(Component::Engine, {CG_engine, inertia_engine, mass_engine}); + + + /* Mass, CG and inertia of nacelle */ + const double tf_nacelle = this->technology_factor("nacelle_mass"); + double mass_nacelle = calculate_nacelle_mass_propulsive_fuselage(tf_nacelle, thrust); + Eigen::Vector3d CG_nacelle = calculate_nacelle_CG_propulsive_fuselage(engine.nacelle()); + Eigen::Matrix3d inertia_nacelle = calculate_nacelle_inertia_propulsive_fuselage(engine.nacelle()); + engine.set_pointmass(Component::Nacelle, {CG_nacelle, inertia_nacelle, mass_nacelle}); + + /* Mass, CG and inertia of pylon (if geometry not empty)*/ + if (!engine.pylon().sections.empty()) + { + const double tf_pylon = this->technology_factor("pylon_mass"); + double mass_pylon = calculate_pylon_mass_propulsive_fuselage(tf_pylon, thrust); + Eigen::Vector3d CG_pylon = calculate_pylon_CG_propulsive_fuselage(engine.pylon()); + Eigen::Matrix3d inertia_pylon = calculate_pylon_inertia_propulsive_fuselage(engine.pylon()); + engine.set_pointmass(Component::Pylon, {CG_pylon, inertia_pylon, mass_pylon}); + } + +} + +void Default::operator()(PropulsiveFuselage<EnergyCarrier::Kerosene>& engine) +{ + propulsive_fuselage_method(engine); +} + + +} //namespace mass diff --git a/propulsion_design/src/nacelle/default/default_nacelle.h b/propulsion_design/src/nacelle/default/default_nacelle.h index 13a05e5b9636ba81331d994422f7ea4f1c27e1a3..938ecbfb4553af8803d6747dd10c23657214dfd9 100644 --- a/propulsion_design/src/nacelle/default/default_nacelle.h +++ b/propulsion_design/src/nacelle/default/default_nacelle.h @@ -65,13 +65,22 @@ namespace geometry void operator()(Turbofan<EnergyCarrier::Kerosene> &engine); void operator()(Turbofan<EnergyCarrier::Liquid_Hydrogen>& engine); void operator()(Openrotorfan<EnergyCarrier::Kerosene> &engine); + void operator()(PropulsiveFuselage<EnergyCarrier::Kerosene> &engine); + /* === Methods === */ + /** + * @brief Design the geometry of a turbofan nacelle + * with kerosene as fuel. + * + * @param engine The engine to design the geometry for. + */ + template <EnergyCarrier EC> void turbofan_method(Turbofan<EC>& engine); /* === Methods === */ /** - * @brief Design the geometry of a turbofan nacelle + * @brief Design the geometry of a openrotor nacelle * with kerosene as fuel. * * @param engine The engine to design the geometry for. @@ -80,6 +89,17 @@ namespace geometry template <EnergyCarrier EC> void openrotor_method(Openrotorfan<EC>& engine); + /* === Methods === */ + /** + * @brief Design the geometry of a propulsive fuselage nacelle + * with kerosene as fuel. + * + * @param engine The engine to design the geometry for. + */ + + template <EnergyCarrier EC> + void propulsive_fuselage_method(PropulsiveFuselage<EC>& engine); + private: /* === Methods === */ /** diff --git a/propulsion_design/src/nacelle/default/propulsive_fuselage.cpp b/propulsion_design/src/nacelle/default/propulsive_fuselage.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8ad3e0abc7677686edd4d8427219a25d5b0e0cd0 --- /dev/null +++ b/propulsion_design/src/nacelle/default/propulsive_fuselage.cpp @@ -0,0 +1,85 @@ +/* === Includes === */ +#include "default_nacelle.h" +#include <algorithm> +#include <aircraftGeometry2/geometry/builder.h> + +/* === Design implementations === */ +namespace geometry +{ + namespace nacelle + { + template <EnergyCarrier EC> + void Default::propulsive_fuselage_method(PropulsiveFuselage<EC> &engine) + { + /* Initialize the surface and its builder */ + geom2::MultisectionSurface<geom2::PolygonSection> surface{}; + geom2::MultisectionSurface<geom2::PolygonSection> surface_engine{}; + geom2::SectionBuilder<geom2::PolygonSection> builder{}; + geom2::SectionBuilder<geom2::PolygonSection> builder_engine{}; + + /* Get the maximum diameter the nacelle has to fit */ + const double diameter_inner = std::max( + { engine.dimension().width, engine.dimension().height, engine.fan().diameter }); + + /* Create the cross section shape */ + geom2::PolygonSection shape = this->get_section_shape(engine.id()); + shape.set_width(diameter_inner); + shape.set_height(diameter_inner); + + /* Create the cross section shape */ + geom2::PolygonSection shape_engine = this->get_section_shape(engine.id()); + shape_engine.set_width(diameter_inner); + shape_engine.set_height(diameter_inner); + + /* + * Transform the scaled polygon coordinates to + * absolute values, so that the section can be + * scaled using the scale factor. + */ + shape.set_contour(shape.get_contour(true)); + shape_engine.set_contour(shape_engine.get_contour(true)); + + /* Section 0 */ + shape_engine.set_scale(0.0); + builder_engine.insert_back(shape_engine, {0.0, 0.0, - engine.dimension().length * 0.1}); + + /* Section 0 */ + shape_engine.set_scale(0.15); + builder_engine.insert_back(shape_engine, {0.0, 0.0, engine.dimension().length * 0.1}); + + /* Section 0 */ + shape.set_scale(1.0); + builder.insert_back(shape, {0.0, 0.0, 0.0}); + + /* Section 1 */ + shape.set_scale(1.21); // \cite Ata10 p. 45 + builder.insert_back(shape, {0.0, 0.0, engine.dimension().length * 0.25}); + + /* Section 2 */ + shape.set_scale(1.21); // \cite Ata10 p. 45 + builder.insert_back(shape, {0.0, 0.0, engine.dimension().length * 0.50}); + + /* Section 3 */ + shape.set_scale(1.0); + builder.insert_back(shape, {0.0, 0.0, engine.dimension().length * 0.25}); + + /* Build the surface and insert it into the engine */ + surface.name = "nacelle_" + std::to_string(engine.id()); + surface.normal = {1.0, 0.0, 0.0}; // Orient the nacelle along the global X direction + surface.sections = builder.get_result(); + engine.set_nacelle(surface); + + surface_engine.name = "engine_" + std::to_string(engine.id()); + surface_engine.normal = {1.0, 0.0, 0.0}; // Orient the nacelle along the global X direction + surface_engine.sections = builder_engine.get_result(); + engine.set_enginegeometry(surface_engine); + + } + + void Default::operator()(PropulsiveFuselage<EnergyCarrier::Kerosene>& engine) + { + propulsive_fuselage_method(engine); + }; + + } +} \ No newline at end of file diff --git a/propulsion_design/src/propulsion.h b/propulsion_design/src/propulsion.h index 161832e5ef706b65dcf672c2717b650b9992a00b..b86ece6afc2a11f70498569eb13d85fb5b1216da 100644 --- a/propulsion_design/src/propulsion.h +++ b/propulsion_design/src/propulsion.h @@ -671,6 +671,41 @@ class Openrotorfan : public Propulsion<carrier> double bypass_ratio_{0.0}; /** [-] The bypass ratio of the propulsion. */ }; +/** + * @class PropulsiveFuselage + * @brief Propulsion specialization for propulsive fuselage engines. + * + * @tparam carrier The energy carrier of the propulsion. + */ +template <EnergyCarrier carrier> +class PropulsiveFuselage : public Propulsion<carrier> +{ + public: + PropulsiveFuselage() = default; + explicit PropulsiveFuselage(const int id) : Propulsion<carrier>(id) {} + PropulsiveFuselage(const int id, const ParentComponent parent) : Propulsion<carrier>(id, parent) {} + PropulsiveFuselage(const int id, const ParentComponent parent, const double required_thrust) : Propulsion<carrier>(id, parent, required_thrust) {} + PropulsiveFuselage(const int id, const ParentComponent parent, const double required_thrust, const Offtakes &required_offtakes) : Propulsion<carrier>(id, parent, required_thrust, required_offtakes) {} + PropulsiveFuselage(const PropulsiveFuselage &other) = delete; + PropulsiveFuselage(PropulsiveFuselage &&other) = default; + auto operator=(const PropulsiveFuselage &other) -> PropulsiveFuselage & = delete; + auto operator=(PropulsiveFuselage &&other) -> PropulsiveFuselage & = default; + ~PropulsiveFuselage() override = default; + + /* === Getters === */ + [[nodiscard]] auto bypass_ratio() const -> double { return this->bypass_ratio_; } + [[nodiscard]] auto fan() const -> Fan { return this->fan_; } + + /* === Setters === */ + void set_bypass_ratio(const double bypass_ratio) noexcept { this->bypass_ratio_ = bypass_ratio; } + void set_fan(const Fan fan) noexcept { this->fan_ = fan; } + + private: + /* === Properties === */ + Fan fan_{}; /** [-] The fan of the propulsion. */ + double bypass_ratio_{0.0}; /** [-] The bypass ratio of the propulsion. */ +}; + /* === Type Aliases === */ /** * @brief The type alias for the different propulsion types used @@ -680,6 +715,7 @@ using PropulsionType = std::variant< Turbofan<EnergyCarrier::Kerosene>, Turbofan<EnergyCarrier::Liquid_Hydrogen>, Openrotorfan<EnergyCarrier::Kerosene>, + PropulsiveFuselage<EnergyCarrier::Kerosene>, Turboprop<EnergyCarrier::Kerosene>, Turboprop<EnergyCarrier::Liquid_Hydrogen>>; diff --git a/propulsion_design/src/propulsion_design.cpp b/propulsion_design/src/propulsion_design.cpp index b57d84c44284b6b6e7c87a8d65c6ce7c30ef8281..66dc041838836f969bbcd71764c460dbacaa3d87 100644 --- a/propulsion_design/src/propulsion_design.cpp +++ b/propulsion_design/src/propulsion_design.cpp @@ -23,6 +23,7 @@ /* === Includes === */ #include "propulsion_design.h" #include "engine_design/rubber/rubber_design.h" +#include "engine_design/propulsive_fuselage/propulsive_fuselage_design.h" #include "integration/default/default_integration.h" #include "io/aircraft_xml.h" #include "mass/default/default_mass.h" @@ -246,6 +247,12 @@ void PropulsionDesign::select_engine_designer() this->configuration_xml, this->get_RuntimeIO()->getEngineDataDir(), flight_condition); + else if (method == "PropulsiveFuselage") + this->engine_designer = + std::make_unique<design::PropFuse>( + this->configuration_xml, + this->get_RuntimeIO()->getEngineDataDir(), + flight_condition); else throw std::runtime_error("[PropulsionDesign] The engine designer method '" + method + "' is not supported."); diff --git a/propulsion_design/src/propulsion_strategy.h b/propulsion_design/src/propulsion_strategy.h index 289da145f7300f184d079970a2c1861842d0c358..b7979f1d7e8bfb0fc0d639f173949921a918f77e 100644 --- a/propulsion_design/src/propulsion_strategy.h +++ b/propulsion_design/src/propulsion_strategy.h @@ -76,6 +76,10 @@ class PropulsionStrategy : public Strategy { throw std::invalid_argument("The strategy does not yet implement a kerosene openrotorfan engine."); }; + virtual void operator()(PropulsiveFuselage<EnergyCarrier::Kerosene> &engine) // NOLINT runtime/reference + { + throw std::invalid_argument("The strategy does not yet implement a kerosene propulsive fuselage engine."); + }; /* === Methods === */ /** diff --git a/propulsion_design/src/pylon/default/default_pylon.h b/propulsion_design/src/pylon/default/default_pylon.h index e9b249628d8efa5099b49ff54c55c527fab9b908..8eb9e82d788ceeee426957595c9321fded3da50c 100644 --- a/propulsion_design/src/pylon/default/default_pylon.h +++ b/propulsion_design/src/pylon/default/default_pylon.h @@ -65,6 +65,7 @@ namespace geometry void operator()(Turbofan<EnergyCarrier::Kerosene> &engine); void operator()(Turbofan<EnergyCarrier::Liquid_Hydrogen> &engine); void operator()(Openrotorfan<EnergyCarrier::Kerosene> &engine); + void operator()(PropulsiveFuselage<EnergyCarrier::Kerosene> &engine); template <EnergyCarrier EC> void turbofan_method(Turbofan<EC> &engine); @@ -72,6 +73,9 @@ namespace geometry template <EnergyCarrier EC> void openrotorfan_method(Openrotorfan<EC> &engine); + template <EnergyCarrier EC> + void propulsive_fuselage_method(PropulsiveFuselage<EC> &engine); + /** * @brief Initialize the default pylon geometry designer. */ diff --git a/propulsion_design/src/pylon/default/propulsive_fuselage.cpp b/propulsion_design/src/pylon/default/propulsive_fuselage.cpp new file mode 100644 index 0000000000000000000000000000000000000000..15d92ba3485c2a9bf69c3b82fa7030238cf66825 --- /dev/null +++ b/propulsion_design/src/pylon/default/propulsive_fuselage.cpp @@ -0,0 +1,95 @@ +/* + * UNICADO - UNIversity Conceptual Aircraft Design and Optimization + * + * Copyright (C) 2025 UNICADO consortium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * Description: + * This file is part of UNICADO. + */ + +/* === Includes === */ +#include "default_pylon.h" +#include <aircraftGeometry2/io/dat.h> +#include <aircraftGeometry2/processing/measure.h> +#include <aircraftGeometry2/processing/transform.h> +#include <cmath> +#include <set> +#include <utility> + +/* === Design implementations === */ +namespace geometry +{ + namespace pylon + { + template <EnergyCarrier EC> + void Default::propulsive_fuselage_method(PropulsiveFuselage<EC> &engine) + { + /* Check whether a pylon can be designed */ + if ( + std::get<Vertical>(engine.parent_component()) == Vertical::In || + !this->aircraft() + ) + { + return; + } + + /* Get the start and end points of pylon */ + const auto attachment = this->get_attachment_points( + engine.position(), engine.nacelle(), engine.parent_component()); + + /* Get profile of each section */ + auto profile = this->get_section_profile(engine.id()); + + /* Set the global location and orientation of the pylon */ + geometry::Pylon pylon{}; + pylon.origin = CGAL::ORIGIN; + pylon.normal = (engine.position() - attachment.first).direction(); + + /* Set the pylon name */ + pylon.name = "pylon_" + std::to_string(engine.id()); + + /* + * The offset of the engine to the origin since + * the pylon origin relative to the position of + * the second segment of the nacelle. Therefore, + * the pylon is also elevated to the second of + * the nacelle height. + */ + const auto offset_start = engine.position() - CGAL::ORIGIN - geom2::Vector_3( + engine.dimension().length * 0.25, 0, (engine.dimension().height)*0.21/2); + const auto offset_end = engine.position() - CGAL::ORIGIN; + + /* Section at nacelle attachment */ + pylon.sections.emplace_back(profile); + pylon.sections.back().origin = geom2::transform::to_local(pylon, attachment.first - offset_start); + pylon.sections.back().set_chord_length(engine.dimension().length); + + /* Section attaching to the parent component */ + pylon.sections.emplace_back(profile); + pylon.sections.back().origin = geom2::transform::to_local(pylon, attachment.second -offset_end); + pylon.sections.back().set_chord_length(engine.dimension().length); + + /* Add the pylon to the engine */ + engine.set_pylon(pylon); + } + + void Default::operator()(PropulsiveFuselage<EnergyCarrier::Kerosene> &engine) + { + propulsive_fuselage_method(engine); + }; + + } // namespace pylon +} // namespace geometry diff --git a/propulsion_design/src/utility.cpp b/propulsion_design/src/utility.cpp index d6be3d77ef20b30176726d32405dec5668bdd06f..8ed659403712191548bbe6acf6faf89b4be710bf 100644 --- a/propulsion_design/src/utility.cpp +++ b/propulsion_design/src/utility.cpp @@ -243,6 +243,16 @@ namespace utility throw std::runtime_error("The energy carrier is not supported for a openrotor engine!"); } } + else if ((power_type + thrust_type) == "PropulsiveFuselage") + { + switch (energy_carrier) + { + case EnergyCarrier::Kerosene: + return PropulsiveFuselage<EnergyCarrier::Kerosene>{id, position, design_thrust, engine_offtakes}; + default: + throw std::runtime_error("The energy carrier is not supported for a propulsive fuselage engine!"); + } + } else { throw std::runtime_error("The powertrain '" + power_type + "' + '" + thrust_type + "' is not supported.");