Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion regression/scripts/segments/transition_segment_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -236,6 +236,29 @@ 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)
segment.state.numerics.discretization_method = SUAVE.Methods.Utilities.Chebyshev.linear_data
# add to misison
mission.append_segment(segment)

# --------------------------------------------------------------------------
# Segment 2a: Transition Segment: Linear Speed, Linear Climb
# --------------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
## @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
import SUAVE


# ----------------------------------------------------------------------
# Segment
# ----------------------------------------------------------------------

## @ingroup Analyses-Mission-Segments-Transition
class Linear_Acceleration_Constant_Pitchrate_Constant_Altitude(Aerodynamic):
""" Vehicle accelerates linearly 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
self.state.numerics.discretization_method = SUAVE.Methods.Utilities.Chebyshev.linear_data


# --------------------------------------------------------------
# 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

Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
## @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
Requires linearly spaced control points

Source:
N/A

Inputs:
segment.altitude [meters]
segment.air_speed_start [meters/second]
segment.air_speed_end [meters/second]
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:
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

# 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

# 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 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]

# 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
Original file line number Diff line number Diff line change
Expand Up @@ -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