From 43ef4e0580015a4bc625230940764ea446e32f39 Mon Sep 17 00:00:00 2001 From: Racheal Erhard Date: Sat, 28 Jan 2023 22:32:35 -0800 Subject: [PATCH 1/5] Creating linear acceleration, constant pitchrate, constant altitude transition segment --- ...on_Constant_Pitchrate_Constant_Altitude.py | 151 ++++++++++++++++++ .../Mission/Segments/Transition/__init__.py | 1 + ...on_Constant_Pitchrate_Constant_Altitude.py | 141 ++++++++++++++++ .../Missions/Segments/Transition/__init__.py | 1 + 4 files changed, 294 insertions(+) create mode 100644 trunk/SUAVE/Analyses/Mission/Segments/Transition/Linear_Acceleration_Constant_Pitchrate_Constant_Altitude.py create mode 100644 trunk/SUAVE/Methods/Missions/Segments/Transition/Linear_Acceleration_Constant_Pitchrate_Constant_Altitude.py diff --git a/trunk/SUAVE/Analyses/Mission/Segments/Transition/Linear_Acceleration_Constant_Pitchrate_Constant_Altitude.py b/trunk/SUAVE/Analyses/Mission/Segments/Transition/Linear_Acceleration_Constant_Pitchrate_Constant_Altitude.py new file mode 100644 index 0000000000..3765c44287 --- /dev/null +++ b/trunk/SUAVE/Analyses/Mission/Segments/Transition/Linear_Acceleration_Constant_Pitchrate_Constant_Altitude.py @@ -0,0 +1,151 @@ +## @ingroup Analyses-Mission-Segments-Transition +# Linear_Acceleration_Constant_Pitchrate_Constant_Altitude.py +# +# Created: Jan 2023, R. Erhard +# Modified: + +# ---------------------------------------------------------------------- +# Imports +# ---------------------------------------------------------------------- + +# SUAVE imports +from SUAVE.Analyses.Mission.Segments import Aerodynamic +from SUAVE.Analyses.Mission.Segments import Conditions + +from SUAVE.Methods.Missions import Segments as Methods +from SUAVE.Methods.skip import skip + +from SUAVE.Analyses import Process + +# Units +from SUAVE.Core import Units + + +# ---------------------------------------------------------------------- +# Segment +# ---------------------------------------------------------------------- + +## @ingroup Analyses-Mission-Segments-Transition +class Linear_Acceleration_Constant_Pitchrate_Constant_Altitude(Aerodynamic): + """ Vehicle accelerates at a constant rate between two airspeeds. + + Assumptions: + None + + Source: + None + """ + + def __defaults__(self): + """ This sets the default solver flow. Anything in here can be modified after initializing a segment. + + Assumptions: + None + + Source: + N/A + + Inputs: + None + + Outputs: + None + + Properties Used: + None + """ + + # -------------------------------------------------------------- + # User inputs + # -------------------------------------------------------------- + self.altitude = None + self.acceleration_initial = 1. * Units['m/s/s'] + self.acceleration_final = 1. * Units['m/s/s'] + self.air_speed_start = 0.0 * Units['m/s'] + self.air_speed_end = 1.0 * Units['m/s'] + self.pitch_initial = None + self.pitch_final = 0.0 * Units['rad'] + self.true_course = 0.0 * Units.degrees + + + # -------------------------------------------------------------- + # State + # -------------------------------------------------------------- + + # conditions + self.state.conditions.update( Conditions.Aerodynamics() ) + + # initials and unknowns + ones_row = self.state.ones_row + self.state.residuals.forces = ones_row(2) * 0.0 + + + # -------------------------------------------------------------- + # The Solving Process + # -------------------------------------------------------------- + + # -------------------------------------------------------------- + # Initialize - before iteration + # -------------------------------------------------------------- + initialize = self.process.initialize + + initialize.expand_state = Methods.expand_state + initialize.differentials = Methods.Common.Numerics.initialize_differentials_dimensionless + initialize.conditions = Methods.Transition.Linear_Acceleration_Constant_Pitchrate_Constant_Altitude.initialize_conditions + + # -------------------------------------------------------------- + # Converge - starts iteration + # -------------------------------------------------------------- + converge = self.process.converge + + converge.converge_root = Methods.converge_root + + # -------------------------------------------------------------- + # Iterate - this is iterated + # -------------------------------------------------------------- + iterate = self.process.iterate + + # Update Initials + iterate.initials = Process() + iterate.initials.time = Methods.Common.Frames.initialize_time + iterate.initials.weights = Methods.Common.Weights.initialize_weights + iterate.initials.inertial_position = Methods.Common.Frames.initialize_inertial_position + iterate.initials.planet_position = Methods.Common.Frames.initialize_planet_position + + # Unpack Unknowns + iterate.unknowns = Process() + iterate.unknowns.mission = Methods.Cruise.Common.unpack_unknowns + + # Update Conditions + iterate.conditions = Process() + iterate.conditions.differentials = Methods.Common.Numerics.update_differentials_time + iterate.conditions.altitude = Methods.Common.Aerodynamics.update_altitude + iterate.conditions.atmosphere = Methods.Common.Aerodynamics.update_atmosphere + iterate.conditions.gravity = Methods.Common.Weights.update_gravity + iterate.conditions.freestream = Methods.Common.Aerodynamics.update_freestream + iterate.conditions.orientations = Methods.Common.Frames.update_orientations + iterate.conditions.propulsion = Methods.Common.Energy.update_thrust + iterate.conditions.aerodynamics = Methods.Common.Aerodynamics.update_aerodynamics + iterate.conditions.stability = Methods.Common.Aerodynamics.update_stability + iterate.conditions.weights = Methods.Common.Weights.update_weights + iterate.conditions.forces = Methods.Common.Frames.update_forces + iterate.conditions.planet_position = Methods.Common.Frames.update_planet_position + + # Solve Residuals + iterate.residuals = Process() + iterate.residuals.total_forces = Methods.Transition.Linear_Acceleration_Constant_Pitchrate_Constant_Altitude.residual_total_forces + + # -------------------------------------------------------------- + # Finalize - after iteration + # -------------------------------------------------------------- + finalize = self.process.finalize + + # Post Processing + finalize.post_process = Process() + finalize.post_process.inertial_position = Methods.Common.Frames.integrate_inertial_horizontal_position + finalize.post_process.stability = Methods.Common.Aerodynamics.update_stability + finalize.post_process.aero_derivatives = skip + finalize.post_process.noise = Methods.Common.Noise.compute_noise + + return + diff --git a/trunk/SUAVE/Analyses/Mission/Segments/Transition/__init__.py b/trunk/SUAVE/Analyses/Mission/Segments/Transition/__init__.py index 717b289691..14b24fe862 100644 --- a/trunk/SUAVE/Analyses/Mission/Segments/Transition/__init__.py +++ b/trunk/SUAVE/Analyses/Mission/Segments/Transition/__init__.py @@ -3,4 +3,5 @@ # @ingroup Analyses-Mission-Segments from .Constant_Acceleration_Constant_Pitchrate_Constant_Altitude import Constant_Acceleration_Constant_Pitchrate_Constant_Altitude +from .Linear_Acceleration_Constant_Pitchrate_Constant_Altitude import Linear_Acceleration_Constant_Pitchrate_Constant_Altitude from .Constant_Acceleration_Constant_Angle_Linear_Climb import Constant_Acceleration_Constant_Angle_Linear_Climb diff --git a/trunk/SUAVE/Methods/Missions/Segments/Transition/Linear_Acceleration_Constant_Pitchrate_Constant_Altitude.py b/trunk/SUAVE/Methods/Missions/Segments/Transition/Linear_Acceleration_Constant_Pitchrate_Constant_Altitude.py new file mode 100644 index 0000000000..1e93587a73 --- /dev/null +++ b/trunk/SUAVE/Methods/Missions/Segments/Transition/Linear_Acceleration_Constant_Pitchrate_Constant_Altitude.py @@ -0,0 +1,141 @@ +## @ingroup Methods-Missions-Segments-Transition +# Linear_Acceleration_Constant_Pitchrate_Constant_Altitude.py +# +# Created: Jan 2023, R. Erhard +# Modified: + +# ---------------------------------------------------------------------- +# Initialize Conditions +# ---------------------------------------------------------------------- +import numpy as np +## @ingroup Methods-Missions-Segments-Transition +def initialize_conditions(segment): + """Sets the specified conditions which are given for the segment type. + + Assumptions: + Linear acceleration, constant pitch rate, and constant altitude + + Source: + N/A + + Inputs: + segment.altitude [meters] + segment.air_speed_start [meters/second] + segment.air_speed_end [meters/second] + segment.acceleration [meters/second^2] + conditions.frames.inertial.time [seconds] + + Outputs: + conditions.frames.inertial.velocity_vector [meters/second] + conditions.frames.inertial.position_vector [meters] + conditions.freestream.altitude [meters] + conditions.frames.inertial.time [seconds] + + Properties Used: + N/A + """ + + # unpack + alt = segment.altitude + v0 = segment.air_speed_start + vf = segment.air_speed_end + ax0 = segment.acceleration_initial + axf = segment.acceleration_final + T0 = segment.pitch_initial + Tf = segment.pitch_final + + # check for initial altitude + if alt is None: + if not segment.state.initials: raise AttributeError('altitude not set') + alt = -1.0 * segment.state.initials.conditions.frames.inertial.position_vector[-1,2] + segment.altitude = alt + + # check for initial pitch + if T0 is None: + T0 = segment.state.initials.conditions.frames.body.inertial_rotations[-1,1] + segment.pitch_initial = T0 + + # compute control point accelerations + t_nondim = segment.state.numerics.dimensionless.control_points + n_cp = len(t_nondim) + ax_t = ax0 + (t_nondim * (axf-ax0)) # linear acceleration + + # set up A matrix for solving control point delta_t and velocities + Amat = np.identity(n_cp-1) + i,j = np.indices(Amat.shape) + Amat[i==j+1] = -1 * np.ones(n_cp-2) + Amat[j==n_cp-2] = -0.5 * np.ravel((ax_t[1:]-ax_t[0:-1])) - np.ravel(ax_t[0:-1]) + + # setup the b solution vector + b = np.zeros(n_cp-1) + b[0] = v0 + b[-1] = -vf + + # solve for x + x = np.linalg.solve(Amat, b) + vx_t = np.atleast_2d(np.hstack((v0, x[0:-1], vf))).T + dt = x[-1] + + # dimensionalize time + t_initial = segment.state.conditions.frames.inertial.time[0,0] + t_final = t_initial + (n_cp - 1) * dt + time = t_nondim * (t_final-t_initial) + t_initial + + # Figure out x + x0 = segment.state.conditions.frames.inertial.position_vector[:,0] + xpos = x0 + (vx_t[:,0] * time[:,0]) + + # set the body angle + body_angle = T0 + time*(Tf-T0)/(t_final-t_initial) + segment.state.conditions.frames.body.inertial_rotations[:,1] = body_angle[:,0] + + # pack + segment.state.conditions.freestream.altitude[:,0] = alt + segment.state.conditions.frames.inertial.position_vector[:,2] = -alt # z points down + segment.state.conditions.frames.inertial.position_vector[:,0] = xpos # z points down + segment.state.conditions.frames.inertial.velocity_vector[:,0] = vx_t[:,0] + segment.state.conditions.frames.inertial.time[:,0] = time[:,0] + + +# ---------------------------------------------------------------------- +# Residual Total Forces +# ---------------------------------------------------------------------- + +## @ingroup Methods-Missions-Segments-Cruise +def residual_total_forces(segment): + """ Calculates a residual based on forces + + Assumptions: + The vehicle is not accelerating, doesn't use gravity + + Inputs: + segment.acceleration [meters/second^2] + segment.state.ones_row [vector] + state.conditions: + frames.inertial.total_force_vector [Newtons] + weights.total_mass [kg] + + Outputs: + state.conditions: + state.residuals.forces [meters/second^2] + + Properties Used: + N/A + + """ + + # Unpack + FT = segment.state.conditions.frames.inertial.total_force_vector + ax0 = segment.acceleration_initial + axf = segment.acceleration_final + m = segment.state.conditions.weights.total_mass + t_nondim = segment.state.numerics.dimensionless.control_points + + a_x = ax0 + (t_nondim * (axf-ax0)) # linear acceleration + + # horizontal + segment.state.residuals.forces[:,0] = FT[:,0]/m[:,0] - a_x[:,0] + # vertical + segment.state.residuals.forces[:,1] = FT[:,2]/m[:,0] + + return diff --git a/trunk/SUAVE/Methods/Missions/Segments/Transition/__init__.py b/trunk/SUAVE/Methods/Missions/Segments/Transition/__init__.py index 97bbbcdaaa..d5305dcf99 100644 --- a/trunk/SUAVE/Methods/Missions/Segments/Transition/__init__.py +++ b/trunk/SUAVE/Methods/Missions/Segments/Transition/__init__.py @@ -3,4 +3,5 @@ # @ingroup Methods-Missions-Segments from . import Constant_Acceleration_Constant_Pitchrate_Constant_Altitude +from . import Linear_Acceleration_Constant_Pitchrate_Constant_Altitude from . import Constant_Acceleration_Constant_Angle_Linear_Climb \ No newline at end of file From 78737c90e50981d17dce683b9029748ae70806d8 Mon Sep 17 00:00:00 2001 From: Racheal Erhard Date: Sun, 29 Jan 2023 13:49:50 -0800 Subject: [PATCH 2/5] adding linear acceleration transition segment to regression test --- .../segments/transition_segment_test.py | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/regression/scripts/segments/transition_segment_test.py b/regression/scripts/segments/transition_segment_test.py index 488ab990bf..d2b8c11343 100644 --- a/regression/scripts/segments/transition_segment_test.py +++ b/regression/scripts/segments/transition_segment_test.py @@ -62,7 +62,7 @@ def main(): # Truth values departure_throttle_truth = 0.6516875478807475 transition_1_throttle_truth = 0.6013997974737667 - cruise_throttle_truth = 0.46492807449474316 + cruise_throttle_truth = 0.46508811557252966 # Store errors error = Data() @@ -236,6 +236,28 @@ def mission_setup(analyses,vehicle): # add to misison mission.append_segment(segment) + # -------------------------------------------------------------------------- + # Segment 1b: Transition Segment: Linear Acceleration, Constant Climb Rate + # -------------------------------------------------------------------------- + # Use original transition segment, converge on rotor y-axis rotation and throttle + segment = Segments.Transition.Linear_Acceleration_Constant_Pitchrate_Constant_Altitude(base_segment) + segment.tag = "Transition_1b" + segment.analyses.extend( analyses.transition_1 ) + segment.altitude = 40.0 * Units.ft + segment.acceleration_initial = 2.3 * Units['m/s/s'] + segment.acceleration_final = 2.35 * Units['m/s/s'] + segment.air_speed_start = 1.2 * V_stall + segment.air_speed_end = 1.3 * V_stall + segment.pitch_initial = 0.0 * Units.degrees + segment.pitch_final = 3.6 * Units.degrees + segment.state.unknowns.throttle = 0.95 * ones_row(1) + segment.process.iterate.conditions.stability = SUAVE.Methods.skip + segment.process.finalize.post_process.stability = SUAVE.Methods.skip + segment = vehicle.networks.battery_propeller.add_tiltrotor_transition_unknowns_and_residuals_to_segment(segment, + initial_power_coefficient = 0.03) + # add to misison + mission.append_segment(segment) + # -------------------------------------------------------------------------- # Segment 2a: Transition Segment: Linear Speed, Linear Climb # -------------------------------------------------------------------------- From d089ed7346b64b9131b5b11f6a34a586d46cc818 Mon Sep 17 00:00:00 2001 From: Racheal Erhard Date: Sun, 29 Jan 2023 13:55:21 -0800 Subject: [PATCH 3/5] updating comments for linear acceleration segment --- ...ar_Acceleration_Constant_Pitchrate_Constant_Altitude.py | 2 +- ...ar_Acceleration_Constant_Pitchrate_Constant_Altitude.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/trunk/SUAVE/Analyses/Mission/Segments/Transition/Linear_Acceleration_Constant_Pitchrate_Constant_Altitude.py b/trunk/SUAVE/Analyses/Mission/Segments/Transition/Linear_Acceleration_Constant_Pitchrate_Constant_Altitude.py index 3765c44287..8ef50fc2a6 100644 --- a/trunk/SUAVE/Analyses/Mission/Segments/Transition/Linear_Acceleration_Constant_Pitchrate_Constant_Altitude.py +++ b/trunk/SUAVE/Analyses/Mission/Segments/Transition/Linear_Acceleration_Constant_Pitchrate_Constant_Altitude.py @@ -27,7 +27,7 @@ ## @ingroup Analyses-Mission-Segments-Transition class Linear_Acceleration_Constant_Pitchrate_Constant_Altitude(Aerodynamic): - """ Vehicle accelerates at a constant rate between two airspeeds. + """ Vehicle accelerates linearly between two airspeeds. Assumptions: None diff --git a/trunk/SUAVE/Methods/Missions/Segments/Transition/Linear_Acceleration_Constant_Pitchrate_Constant_Altitude.py b/trunk/SUAVE/Methods/Missions/Segments/Transition/Linear_Acceleration_Constant_Pitchrate_Constant_Altitude.py index 1e93587a73..a264da730d 100644 --- a/trunk/SUAVE/Methods/Missions/Segments/Transition/Linear_Acceleration_Constant_Pitchrate_Constant_Altitude.py +++ b/trunk/SUAVE/Methods/Missions/Segments/Transition/Linear_Acceleration_Constant_Pitchrate_Constant_Altitude.py @@ -22,7 +22,10 @@ def initialize_conditions(segment): segment.altitude [meters] segment.air_speed_start [meters/second] segment.air_speed_end [meters/second] - segment.acceleration [meters/second^2] + segment.acceleration_initial [meters/second^2] + segment.acceleration_final [meters/second^2] + segment.pitch_initial [meters/second^2] + segment.pitch_final [meters/second^2] conditions.frames.inertial.time [seconds] Outputs: @@ -71,7 +74,7 @@ def initialize_conditions(segment): b[0] = v0 b[-1] = -vf - # solve for x + # solve for control point velocities and delta time step between them x = np.linalg.solve(Amat, b) vx_t = np.atleast_2d(np.hstack((v0, x[0:-1], vf))).T dt = x[-1] From 25ae4b35f27e1b350bb36039f46d4c0aeb8643f8 Mon Sep 17 00:00:00 2001 From: Racheal Erhard Date: Mon, 30 Jan 2023 09:18:00 -0800 Subject: [PATCH 4/5] adding error flag to check for linear numerics on linear acceleration segment --- ...ar_Acceleration_Constant_Pitchrate_Constant_Altitude.py | 3 ++- ...ar_Acceleration_Constant_Pitchrate_Constant_Altitude.py | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/trunk/SUAVE/Analyses/Mission/Segments/Transition/Linear_Acceleration_Constant_Pitchrate_Constant_Altitude.py b/trunk/SUAVE/Analyses/Mission/Segments/Transition/Linear_Acceleration_Constant_Pitchrate_Constant_Altitude.py index 8ef50fc2a6..dd2e1c7067 100644 --- a/trunk/SUAVE/Analyses/Mission/Segments/Transition/Linear_Acceleration_Constant_Pitchrate_Constant_Altitude.py +++ b/trunk/SUAVE/Analyses/Mission/Segments/Transition/Linear_Acceleration_Constant_Pitchrate_Constant_Altitude.py @@ -19,6 +19,7 @@ # Units from SUAVE.Core import Units +import SUAVE # ---------------------------------------------------------------------- @@ -67,7 +68,6 @@ def __defaults__(self): self.pitch_final = 0.0 * Units['rad'] self.true_course = 0.0 * Units.degrees - # -------------------------------------------------------------- # State # -------------------------------------------------------------- @@ -78,6 +78,7 @@ def __defaults__(self): # initials and unknowns ones_row = self.state.ones_row self.state.residuals.forces = ones_row(2) * 0.0 + self.state.numerics.discretization_method = SUAVE.Methods.Utilities.Chebyshev.linear_data # -------------------------------------------------------------- diff --git a/trunk/SUAVE/Methods/Missions/Segments/Transition/Linear_Acceleration_Constant_Pitchrate_Constant_Altitude.py b/trunk/SUAVE/Methods/Missions/Segments/Transition/Linear_Acceleration_Constant_Pitchrate_Constant_Altitude.py index a264da730d..8a485e2ddc 100644 --- a/trunk/SUAVE/Methods/Missions/Segments/Transition/Linear_Acceleration_Constant_Pitchrate_Constant_Altitude.py +++ b/trunk/SUAVE/Methods/Missions/Segments/Transition/Linear_Acceleration_Constant_Pitchrate_Constant_Altitude.py @@ -14,6 +14,7 @@ def initialize_conditions(segment): Assumptions: Linear acceleration, constant pitch rate, and constant altitude + Requires linearly spaced control points Source: N/A @@ -58,8 +59,12 @@ def initialize_conditions(segment): T0 = segment.state.initials.conditions.frames.body.inertial_rotations[-1,1] segment.pitch_initial = T0 - # compute control point accelerations + # check for linearity of control points (required for this method) t_nondim = segment.state.numerics.dimensionless.control_points + if len(np.unique(np.round(np.diff(t_nondim.T),5)))!=1: raise AttributeError('Linear numerics required for linear acceleration segment!') + + + # compute control point accelerations n_cp = len(t_nondim) ax_t = ax0 + (t_nondim * (axf-ax0)) # linear acceleration From 76ba05d3265027c53b3240688e4bacb1127d5712 Mon Sep 17 00:00:00 2001 From: Racheal Erhard Date: Mon, 30 Jan 2023 09:23:06 -0800 Subject: [PATCH 5/5] correcting regression for linear numerics on linear acceleration transition --- regression/scripts/segments/transition_segment_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/regression/scripts/segments/transition_segment_test.py b/regression/scripts/segments/transition_segment_test.py index d2b8c11343..774bc4222f 100644 --- a/regression/scripts/segments/transition_segment_test.py +++ b/regression/scripts/segments/transition_segment_test.py @@ -255,6 +255,7 @@ def mission_setup(analyses,vehicle): segment.process.finalize.post_process.stability = SUAVE.Methods.skip segment = vehicle.networks.battery_propeller.add_tiltrotor_transition_unknowns_and_residuals_to_segment(segment, initial_power_coefficient = 0.03) + segment.state.numerics.discretization_method = SUAVE.Methods.Utilities.Chebyshev.linear_data # add to misison mission.append_segment(segment)