diff --git a/aviary/docs/examples/additional_flight_phases.ipynb b/aviary/docs/examples/additional_flight_phases.ipynb index 293c11858..908f19636 100644 --- a/aviary/docs/examples/additional_flight_phases.ipynb +++ b/aviary/docs/examples/additional_flight_phases.ipynb @@ -72,6 +72,7 @@ " 'duration_bounds': ((25.5, 76.5), 'min'),\n", " },\n", " 'initial_guesses': {'time': ([0, 51], 'min')},\n", + " \"initial_guesses\": {\"times\": ([0, 51], \"min\")},\n", " },\n", " 'cruise_1': {\n", " 'subsystem_options': {'core_aerodynamics': {'method': 'computed'}},\n", @@ -96,6 +97,9 @@ " 'duration_bounds': ((23.5, 70.5), 'min'),\n", " },\n", " 'initial_guesses': {'time': ([51, 47], 'min')},\n", + + " \"initial_guesses\": {\"times\": ([51, 47], \"min\")},\n", + " },\n", " 'climb_2': {\n", " 'subsystem_options': {'core_aerodynamics': {'method': 'computed'}},\n", @@ -119,7 +123,11 @@ " 'initial_bounds': ((49.0, 147.0), 'min'),\n", " 'duration_bounds': ((5.0, 15.0), 'min'),\n", " },\n", + " 'initial_guesses': {'time': ([98, 10], 'min')},\n", + + " \"initial_guesses\": {\"times\": ([98, 10], \"min\")},\n", + " },\n", " 'cruise_2': {\n", " 'subsystem_options': {'core_aerodynamics': {'method': 'computed'}},\n", @@ -143,7 +151,11 @@ " 'initial_bounds': ((54.0, 162.0), 'min'),\n", " 'duration_bounds': ((24.0, 72.0), 'min'),\n", " },\n", + " 'initial_guesses': {'time': ([108, 48], 'min')},\n", + + " \"initial_guesses\": {\"times\": ([108, 48], \"min\")},\n", + " },\n", " 'climb_3': {\n", " 'subsystem_options': {'core_aerodynamics': {'method': 'computed'}},\n", @@ -167,7 +179,11 @@ " 'initial_bounds': ((78.0, 234.0), 'min'),\n", " 'duration_bounds': ((7.0, 21.0), 'min'),\n", " },\n", + " 'initial_guesses': {'time': ([156, 14], 'min')},\n", + + " \"initial_guesses\": {\"times\": ([156, 14], \"min\")},\n", + " },\n", " 'climb_4': {\n", " 'subsystem_options': {'core_aerodynamics': {'method': 'computed'}},\n", @@ -191,7 +207,11 @@ " 'initial_bounds': ((85.0, 255.0), 'min'),\n", " 'duration_bounds': ((43.0, 129.0), 'min'),\n", " },\n", + " 'initial_guesses': {'time': ([170, 86], 'min')},\n", + + " \"initial_guesses\": {\"times\": ([170, 86], \"min\")},\n", + " },\n", " 'descent_1': {\n", " 'subsystem_options': {'core_aerodynamics': {'method': 'computed'}},\n", @@ -215,7 +235,11 @@ " 'initial_bounds': ((128.0, 384.0), 'min'),\n", " 'duration_bounds': ((41.0, 123.0), 'min'),\n", " },\n", + " 'initial_guesses': {'time': ([256, 82], 'min')},\n", + + " \"initial_guesses\": {\"times\": ([256, 82], \"min\")},\n", + " },\n", " 'post_mission': {\n", " 'include_landing': False,\n", @@ -298,11 +322,18 @@ "\n", "Playing around with a model and seeing how different settings affect the optimization and resulting aircraft design is always an enlightening experience." ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { "kernelspec": { - "display_name": "base", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -316,9 +347,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.13" + "version": "3.12.2" } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/aviary/docs/examples/coupled_aircraft_mission_optimization.ipynb b/aviary/docs/examples/coupled_aircraft_mission_optimization.ipynb index 92c1d34be..daebd7818 100644 --- a/aviary/docs/examples/coupled_aircraft_mission_optimization.ipynb +++ b/aviary/docs/examples/coupled_aircraft_mission_optimization.ipynb @@ -68,7 +68,11 @@ " 'initial_bounds': ((0.0, 0.0), 'min'),\n", " 'duration_bounds': ((35.0, 105.0), 'min'),\n", " },\n", + " 'initial_guesses': {'time': ([0, 70], 'min')},\n", + + " \"initial_guesses\": {\"times\": ([0, 70], \"min\")},\n", + " },\n", " 'cruise': {\n", " 'subsystem_options': {'core_aerodynamics': {'method': 'computed'}},\n", @@ -92,7 +96,11 @@ " 'initial_bounds': ((35.0, 105.0), 'min'),\n", " 'duration_bounds': ((91.5, 274.5), 'min'),\n", " },\n", + " 'initial_guesses': {'time': ([70, 183], 'min')},\n", + + " \"initial_guesses\": {\"times\": ([70, 183], \"min\")},\n", + " },\n", " 'descent_1': {\n", " 'subsystem_options': {'core_aerodynamics': {'method': 'computed'}},\n", @@ -116,7 +124,11 @@ " 'initial_bounds': ((126.5, 379.5), 'min'),\n", " 'duration_bounds': ((25.0, 75.0), 'min'),\n", " },\n", + " 'initial_guesses': {'time': ([253, 50], 'min')},\n", + + " \"initial_guesses\": {\"times\": ([253, 50], \"min\")},\n", + " },\n", " 'post_mission': {\n", " 'include_landing': False,\n", @@ -588,7 +600,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.3" + "version": "3.12.2" } }, "nbformat": 4, diff --git a/aviary/docs/examples/more_advanced_example.ipynb b/aviary/docs/examples/more_advanced_example.ipynb index 1837a19fb..d366d42ed 100644 --- a/aviary/docs/examples/more_advanced_example.ipynb +++ b/aviary/docs/examples/more_advanced_example.ipynb @@ -67,7 +67,11 @@ " 'initial_bounds': ((0.0, 0.0), 'min'),\n", " 'duration_bounds': ((27.0, 81.0), 'min'),\n", " },\n", + " 'initial_guesses': {'time': ([0, 54], 'min')},\n", + + " \"initial_guesses\": {\"times\": ([0, 54], \"min\")},\n", + " },\n", " 'cruise': {\n", " 'subsystem_options': {'core_aerodynamics': {'method': 'computed'}},\n", @@ -91,7 +95,11 @@ " 'initial_bounds': ((27.0, 81.0), 'min'),\n", " 'duration_bounds': ((85.5, 256.5), 'min'),\n", " },\n", + " 'initial_guesses': {'time': ([54, 171], 'min')},\n", + + " \"initial_guesses\": {\"times\": ([54, 171], \"min\")},\n", + " },\n", " 'descent_1': {\n", " 'subsystem_options': {'core_aerodynamics': {'method': 'computed'}},\n", @@ -115,7 +123,11 @@ " 'initial_bounds': ((112.5, 337.5), 'min'),\n", " 'duration_bounds': ((26.5, 79.5), 'min'),\n", " },\n", + " 'initial_guesses': {'time': ([225, 53], 'min')},\n", + + " \"initial_guesses\": {\"times\": ([225, 53], \"min\")},\n", + " },\n", " 'post_mission': {\n", " 'include_landing': False,\n", @@ -468,7 +480,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.13" + "version": "3.8.19" } }, "nbformat": 4, diff --git a/aviary/docs/examples/simple_mission_example.ipynb b/aviary/docs/examples/simple_mission_example.ipynb index 7fb77f491..bdfe2b3c6 100644 --- a/aviary/docs/examples/simple_mission_example.ipynb +++ b/aviary/docs/examples/simple_mission_example.ipynb @@ -190,7 +190,11 @@ " 'initial_bounds': ((0.0, 0.0), 'min'),\n", " 'duration_bounds': ((27.0, 81.0), 'min'),\n", " },\n", + " 'initial_guesses': {'time': ([0, 54], 'min')},\n", + + " \"initial_guesses\": {\"times\": ([0, 54], \"min\")},\n", + " },\n", " 'cruise': {\n", " 'subsystem_options': {'core_aerodynamics': {'method': 'computed'}},\n", @@ -214,7 +218,11 @@ " 'initial_bounds': ((27.0, 81.0), 'min'),\n", " 'duration_bounds': ((85.5, 256.5), 'min'),\n", " },\n", + " 'initial_guesses': {'time': ([54, 171], 'min')},\n", + + " \"initial_guesses\": {\"times\": ([54, 171], \"min\")},\n", + " },\n", " 'descent_1': {\n", " 'subsystem_options': {'core_aerodynamics': {'method': 'computed'}},\n", @@ -238,7 +246,11 @@ " 'initial_bounds': ((112.5, 337.5), 'min'),\n", " 'duration_bounds': ((26.5, 79.5), 'min'),\n", " },\n", + " 'initial_guesses': {'time': ([225, 53], 'min')},\n", + + " \"initial_guesses\": {\"times\": ([225, 53], \"min\")},\n", + " },\n", " 'post_mission': {\n", " 'include_landing': False,\n", @@ -418,7 +430,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.13" + "version": "3.8.19" } }, "nbformat": 4, diff --git a/aviary/mission/sixdof/force_component_calc.py b/aviary/mission/sixdof/force_component_calc.py new file mode 100644 index 000000000..9fe759467 --- /dev/null +++ b/aviary/mission/sixdof/force_component_calc.py @@ -0,0 +1,490 @@ +import numpy as np +import openmdao.api as om +from aviary.variable_info.variables import Dynamic +from aviary.utils.functions import add_aviary_input + + +class ForceComponentResolver(om.ExplicitComponent): + """ + This class will resolve forces (thrust, drag, lift, etc.) into their + respective x,y,z components for the 6 DOF equations of motion. + + This class assumes that the total force is given and needs to be resolved + into the separate components. + + Assumptions: + - Thrust is entirely in -z direction (T = (0,0,-T_z)^T) w.r.t. body CS + - Assuming F_i is in body CS, and D, S, and L are in wind CS. Wind -> body rotation matrix + was applied for coordinate transformations + - Thrust is initially in NED CS. So, two rotations (NED -> wind and wind -> body) are applied + + """ + + def initialize(self): + self.options.declare('num_nodes', types=int) + + def setup(self): + nn = self.options['num_nodes'] + + # inputs + + self.add_input( + 'u', + val=np.zeros(nn), + units='m/s', + desc="axial velocity" + ) + + self.add_input( + 'v', + val=np.zeros(nn), + units='m/s', + desc="lateral velocity" + ) + + self.add_input( + 'w', + val=np.zeros(nn), + units='m/s', + desc="vertical velocity" + ) + + self.add_input( + 'drag', + val=np.zeros(nn), + units='N', + desc="Drag vector (unresolved)" + ) + + self.add_input( + 'thrust', + val=np.zeros(nn), + units='N', + desc="Thrust vector (unresolved)" + ) + + self.add_input( + 'lift', + val=np.zeros(nn), + units='N', + desc="Lift vector (unresolved)" + ) + + self.add_input( + 'side', + val=np.zeros(nn), + units='N', + desc="Side vector (unresolved)" + ) + + self.add_input( + 'heading_angle', + val=np.zeros(nn), + units='rad', + desc='Heading angle in body' + ) + + add_aviary_input(self, + Dynamic.Mission.FLIGHT_PATH_ANGLE, + units='rad', + desc="Flight path angle in body") + + self.add_input( + 'heading_angle_NED', + val=np.zeros(nn), + units='rad', + desc="Thrust heading angle in NED" + ) + + self.add_input( + 'fpa_NED', + val=np.zeros(nn), + units='rad', + desc="Thrust flight path angle in NED" + ) + + # self.add_input( + # 'true_air_speed', + # val=np.zeros(nn), + # units='m/s', + # desc="True air speed" + # ) # This is an aviary variable + + # outputs + + self.add_output( + 'Fx', + val=np.zeros(nn), + units='N', + desc="x-comp of final force" + ) + + self.add_output( + 'Fy', + val=np.zeros(nn), + units='N', + desc="y-comp of final force" + ) + + self.add_output( + 'Fz', + val=np.zeros(nn), + units='N', + desc="z-comp of final force" + ) + + ar = np.arange(nn) + + self.declare_partials(of='Fx', wrt='u', rows=ar, cols=ar) + self.declare_partials(of='Fx', wrt='v', rows=ar, cols=ar) + self.declare_partials(of='Fx', wrt='w', rows=ar, cols=ar) + self.declare_partials(of='Fx', wrt='drag', rows=ar, cols=ar) + self.declare_partials(of='Fx', wrt='lift', rows=ar, cols=ar) + self.declare_partials(of='Fx', wrt='side', rows=ar, cols=ar) + self.declare_partials(of='Fx', wrt='thrust', rows=ar, cols=ar) + self.declare_partials(of='Fx', wrt='heading_angle', rows=ar, cols=ar) + self.declare_partials(of='Fx', wrt=Dynamic.Mission.FLIGHT_PATH_ANGLE, rows=ar, cols=ar) + self.declare_partials(of='Fx', wrt='heading_angle_NED', rows=ar, cols=ar) + self.declare_partials(of='Fx', wrt='fpa_NED', rows=ar, cols=ar) + + self.declare_partials(of='Fy', wrt='u', rows=ar, cols=ar) + self.declare_partials(of='Fy', wrt='v', rows=ar, cols=ar) + self.declare_partials(of='Fy', wrt='w', rows=ar, cols=ar) + self.declare_partials(of='Fy', wrt='drag', rows=ar, cols=ar) + self.declare_partials(of='Fy', wrt='lift', rows=ar, cols=ar) + self.declare_partials(of='Fy', wrt='side', rows=ar, cols=ar) + self.declare_partials(of='Fy', wrt='thrust', rows=ar, cols=ar) + self.declare_partials(of='Fy', wrt='heading_angle', rows=ar, cols=ar) + self.declare_partials(of='Fy', wrt=Dynamic.Mission.FLIGHT_PATH_ANGLE, rows=ar, cols=ar) + self.declare_partials(of='Fy', wrt='heading_angle_NED', rows=ar, cols=ar) + self.declare_partials(of='Fy', wrt='fpa_NED', rows=ar, cols=ar) + + self.declare_partials(of='Fz', wrt='u', rows=ar, cols=ar) + self.declare_partials(of='Fz', wrt='v', rows=ar, cols=ar) + self.declare_partials(of='Fz', wrt='w', rows=ar, cols=ar) + self.declare_partials(of='Fz', wrt='drag', rows=ar, cols=ar) + self.declare_partials(of='Fz', wrt='lift', rows=ar, cols=ar) + self.declare_partials(of='Fz', wrt='side', rows=ar, cols=ar) + self.declare_partials(of='Fz', wrt='thrust', rows=ar, cols=ar) + self.declare_partials(of='Fz', wrt='heading_angle', rows=ar, cols=ar) + self.declare_partials(of='Fz', wrt=Dynamic.Mission.FLIGHT_PATH_ANGLE, rows=ar, cols=ar) + self.declare_partials(of='Fz', wrt='heading_angle_NED', rows=ar, cols=ar) + self.declare_partials(of='Fz', wrt='fpa_NED', rows=ar, cols=ar) + + def compute(self, inputs, outputs): + + u = inputs['u'] + v = inputs['v'] + w = inputs['w'] + D = inputs['drag'] + T = inputs['thrust'] + L = inputs['lift'] + S = inputs['side'] # side force -- assume 0 for now + chi = inputs['heading_angle'] + gamma = inputs[Dynamic.Mission.FLIGHT_PATH_ANGLE] + chi_T = inputs['heading_angle_NED'] + gamma_T = inputs['fpa_NED'] + + nn = self.options['num_nodes'] + + # true air speed + + V = np.sqrt(u**2 + v**2 + w**2) + + # angle of attack + + # divide by zero checks + if np.any(u == 0): + u[u == 0] = 1e-4 + alpha = np.arctan(w / u) + else: + alpha = np.arctan(w / u) + + # side slip angle + + # divide by zero checks + if ((np.any(u != 0) or np.any(w != 0))) : + beta = np.arctan(v / np.sqrt(u**2 + w**2)) + else: + u[u == 0] = 1.0e-4 + beta = np.arctan(v / np.sqrt(u**2 + w**2)) + + + + + # some trig needed + + cos_a = np.cos(alpha) + cos_b = np.cos(beta) + sin_a = np.sin(alpha) + sin_b = np.sin(beta) + cos_g = np.cos(gamma) + sin_g = np.sin(gamma) + cos_c = np.cos(chi) + sin_c = np.sin(chi) + + # Thrust direction in NED -- as \hat{t}_n + + t_hat_n = [ + np.cos(gamma_T) * np.cos(chi_T), + np.cos(gamma_T) * np.sin(chi_T), + -np.sin(gamma_T) + ] + + t_hat_n = np.array(t_hat_n) + t_hat_n = t_hat_n.reshape((3, 1)) + + # C_{b-=1.8.1", "hvplot", "importlib_resources", "matplotlib", "numpy<2", - "openmdao>=3.37.0", + "openmdao>=3.36.0", "pandas", "panel>=1.0.0", "parameterized", @@ -22,19 +21,25 @@ dependencies = [ ] [project.optional-dependencies] -docs = [ - "jupyter-book", - "itables" -] -dev = [ +all = [ + "ambiance", + "itables", + "myst-nb", + "openaerostruct", "pre-commit", + "sphinx_book_theme==1.1.0", "testflo", +] +examples = [ "ambiance", + "itables", "openaerostruct", ] -all = [ - "aviary[docs]", - "aviary[dev]", +test = [ + "myst-nb", + "pre-commit", + "sphinx_book_theme==1.1.0", + "testflo", ] [project.scripts] @@ -60,7 +65,7 @@ quote-style = "single" [tool.ruff.lint] # isort, pydocstyle extend-select = ["I", "D"] -# disabling these rules help current Aviary code pass a lint check +# disabling these rules will help current Aviary code pass a pre-commit lint check extend-ignore = [ "D100", "D101",