NLP seems to be doing pretty well

This commit is contained in:
Connor
2021-10-10 20:53:14 -06:00
parent f83d73449f
commit b29afbce04
13 changed files with 524 additions and 198 deletions

View File

@@ -12,8 +12,13 @@ unit-test-job:
- julia --project=julia/Project.toml -E 'using Pkg; Pkg.test()' - julia --project=julia/Project.toml -E 'using Pkg; Pkg.test()'
artifacts: artifacts:
paths: paths:
- julia/plots/plot_test_earth.html - julia/plots/nlp_test_1_phase.html
- julia/plots/plot_test_sun.html - julia/plots/nlp_test_2_phase.html
- julia/plots/nlp_test_earth.html - julia/plots/plot_test_mission.html
- julia/plots/nlp_test_sun.html - julia/plots/plot_test_mission_guess.html
- julia/plots/plot_test_path.html
- julia/plots/plot_test_paths.html
- julia/plots/plot_test_planet_move.html
- julia/plots/plot_test_planet_no_move.html
- julia/plots/plot_test_planet_solar_system.html
expire_in: 10 weeks expire_in: 10 weeks

View File

@@ -19,14 +19,14 @@ module Thesis
include("./bodies.jl") include("./bodies.jl")
include("./constants.jl") include("./constants.jl")
include("./spacecraft.jl") include("./spacecraft.jl")
include("./mission.jl")
include("./conversions.jl")
include("./lamberts.jl")
include("./plotting.jl")
include("./inner_loop/laguerre-conway.jl") include("./inner_loop/laguerre-conway.jl")
include("./inner_loop/propagator.jl") include("./inner_loop/propagator.jl")
include("./mission.jl")
include("./conversions.jl")
include("./plotting.jl")
include("./inner_loop/nlp_solver.jl") include("./inner_loop/nlp_solver.jl")
# include("./inner_loop/monotonic_basin_hopping.jl") # include("./inner_loop/monotonic_basin_hopping.jl")
# include("./outer_loop.jl") # include("./outer_loop.jl")
include("./lamberts.jl")
end end

View File

@@ -1,17 +1,22 @@
export state export state, period
struct Body struct Body
μ::Float64 μ::Float64
r::Float64 # radius r::Float64 # radius
color::String color::String
id::Int # SPICE id id::Int # SPICE id
a::Float64 # semi-major axis
line_color::AbstractString
name::AbstractString
end end
function state(p::Body, t::DateTime, add_v∞::Vector{Float64}=[0., 0., 0.], add_mass::Float64=1e10) period(b::Body) = 2π * (b.a^3 / Sun.μ)
function state(p::Body, t::DateTime, add_v∞::Vector{T}=[0., 0., 0.], add_mass::Float64=1e10) where T <: Real
time = utc2et(Dates.format(t,"yyyy-mm-ddTHH:MM:SS")) time = utc2et(Dates.format(t,"yyyy-mm-ddTHH:MM:SS"))
[ spkssb(p.id, time, "ECLIPJ2000"); 0.0 ] + [ zeros(3); add_v∞; add_mass ] [ spkssb(p.id, time, "ECLIPJ2000"); 0.0 ] + [ zeros(3); add_v∞; add_mass ]
end end
function state(p::Body, t::Float64, add_v∞::Vector{Float64}=[0., 0., 0.], add_mass::Float64=1e10) function state(p::Body, t::S, add_v∞::Vector{T}=[0., 0., 0.], add_mass::Float64=1e10) where {T,S <: Real}
[ spkssb(p.id, t, "ECLIPJ2000"); 0.0 ] + [ zeros(3); add_v∞; add_mass ] [ spkssb(p.id, t, "ECLIPJ2000"); 0.0 ] + [ zeros(3); add_v∞; add_mass ]
end end

View File

@@ -7,17 +7,16 @@ export Jupiter, Saturn, Uranus, Neptune, Pluto
export G, AU, init_STM, hour, day, year, second export G, AU, init_STM, hour, day, year, second
export Pathlist export Pathlist
const Sun = Body(1.32712440018e11, 696000., "Electric", 10) const Sun = Body(1.32712440018e11, 696000., "YlOrRd", 10, 0., "", "Sun")
const Mercury = Body(2.2032e4, 2439., "heat", 1) const Mercury = Body(2.2032e4, 2439., "YlOrRd", 1, 57909083., "#FF2299", "Mercury")
const Venus = Body(3.257e5, 6052., "turbid", 2) const Venus = Body(3.257e5, 6052., "Portland", 2, 108208601., "#FFCC00", "Venus")
const Earth = Body(3.986004415e5, 6378.1363, "Blues", 399) const Earth = Body(3.986004415e5, 6378.1363, "Earth", 399, 149598023., "#0055FF", "Earth")
const Moon = Body(4.902799e3, 1738., "Greys", 301) const Mars = Body(4.305e4, 3397.2, "Reds", 4, 227939186., "#FF0000", "Mars")
const Mars = Body(4.305e4, 3397.2, "Reds", 4) const Jupiter = Body(1.266865361e8, 71492., "YlOrRd", 5, 778298361., "#FF7700", "Jupiter")
const Jupiter = Body(1.266865361e8, 71492., "solar", 5) const Saturn = Body(3.794e7, 60268., "Portland", 6, 1429394133., "#FFFF00", "Saturn")
const Saturn = Body(3.794e7, 60268., "turbid", 6) const Uranus = Body(5.794e6, 25559., "YlGnBu", 7, 2875038615., "#0000FF", "Uranus")
const Uranus = Body(5.794e6, 25559., "haline", 7) const Neptune = Body(6.809e6, 24764., "Blues", 8, 4504449769., "#6666FF", "Neptune")
const Neptune = Body(6.809e6, 24764., "ice", 8) const Pluto = Body(9e2, 1151., "Picnic", 9, 5915799000., "#FFCCCC", "Pluto")
const Pluto = Body(9e2, 1151., "matter", 9)
const G = 6.67430e-20 #universal gravity parameter const G = 6.67430e-20 #universal gravity parameter
const AU = 149597870.691 #km const AU = 149597870.691 #km

View File

@@ -15,7 +15,10 @@ struct V∞_Error <: Exception
v∞_in::AbstractVector v∞_in::AbstractVector
v∞_out::AbstractVector v∞_out::AbstractVector
end end
Base.showerror(io::IO, e::V∞_Error) = print(io, "v∞s weren't the same: ", e.v∞_in, " and ", e.v∞_out) Base.showerror(io::IO, e::V∞_Error) = print(io, """v∞s weren't the same:
v1 = $(e.v∞_in) and
v2 = $(e.v∞_out)
difference: $(norm(e.v∞_in) - norm(e.v∞_out))""")
struct PropOne_Error <: Exception struct PropOne_Error <: Exception
ΔV_unit::AbstractVector ΔV_unit::AbstractVector
@@ -31,3 +34,13 @@ struct Mission_Creation_Error{T} <: Exception where T <: Real
num_entries::T num_entries::T
end end
Base.showerror(io::IO, e::Mission_Creation_Error) = print(io, "Tried to create a mission with $(e.num_entries) entries") Base.showerror(io::IO, e::Mission_Creation_Error) = print(io, "Tried to create a mission with $(e.num_entries) entries")
struct Planet_Match_Error{T} <: Exception where T <: Real
state::Vector{T}
planet::Vector{T}
end
Base.showerror(io::IO, e::Planet_Match_Error) = print(io, """"
Spacecraft didn't make it to planet
spacecraft_pos = $(e.state)
planet_pos = $(e.planet)
difference = $(abs(norm(e.state - e.planet)))""")

View File

@@ -5,19 +5,36 @@ export solve_mission
struct Result struct Result
converged::Bool converged::Bool
info::Symbol info::Symbol
sol::Matrix{Float64} sol::Mission_Guess
end end
function constraint_bounds(guess::Mission_Guess)
low_constraint = Vector{Float64}()
high_constraint = Vector{Float64}()
for phase in guess.phases
if phase != guess.phases[end]
push!(low_constraint, -100., -100., -100., -0.005, -0.005, -0.005, -30., 100.)
push!(high_constraint, 100., 100., 100., 0.005, 0.005, 0.005, 30., 100_000.)
else
push!(low_constraint, -100., -100., -100., 0.)
push!(high_constraint, 100., 100., 100., guess.start_mass - guess.sc.dry_mass)
end
end
return low_constraint, high_constraint
end
""" """
This function will take an initial guess to a mission profile and leverage Ipopt to solve the mission This function will take an initial guess to a mission profile and leverage Ipopt to solve the mission
NOTE: This function will output a proper mission if it succeeded and just re-return the original
guess otherwise
""" """
function solve_mission( guess::Mission_Guess; function solve_mission( guess::Mission_Guess,
tol=1e-8, launch_window::Vector{DateTime},
latest_arrival::DateTime;
tol=1e-10,
verbose::Bool=false ) verbose::Bool=false )
# First we need to define a function that propagates the mission all the way through
# The function takes in a constraints vector (g) and an input vector (x) and spits out
# the objective function. In my case, the objective will be constant.
#
# First we define our starting point # First we define our starting point
x0 = Vector(guess) x0 = Vector(guess)
@@ -52,59 +69,39 @@ function solve_mission( guess::Mission_Guess;
goal = state(current_planet, time, v∞_in) goal = state(current_planet, time, v∞_in)
final = prop(reshape(thrusts, (n,3)), start, guess.sc, tof)[2] final = prop(reshape(thrusts, (n,3)), start, guess.sc, tof)[2]
if flyby != flybys[end] if flyby != flybys[end]
g[1+8i:6+8i] .= final[1:6] .- goal[1:6] g[1+8i:6+8i] .= (final[1:6] .- goal[1:6]) .* [1., 1., 1., 10_000., 10_000., 10_000.]
g[7+8i] = norm(v∞_out) - norm(v∞_in) g[7+8i] = (norm(v∞_out) - norm(v∞_in)) * 10_000.
δ = acos( ( v∞_in v∞_out ) / ( norm(v∞_in) * norm(v∞_out) ) ) δ = acos( ( v∞_in v∞_out ) / ( norm(v∞_in) * norm(v∞_out) ) )
g[8+8i] = (current_planet.μ/(v∞_in v∞_in)) * ( 1/sin(δ/2) - 1 ) g[8+8i] = (current_planet.μ/(v∞_in v∞_in)) * ( 1/sin(δ/2) - 1 ) - current_planet.r
else else
g[8length(flybys)+1:8length(flybys)+3] .= final[1:3] .- goal[1:3] g[8*(length(flybys)-1)+1:8*(length(flybys)-1)+3] .= final[1:3] .- goal[1:3]
g[8length(flybys)+4] = final[7] g[8*(length(flybys)-1)+4] = final[7] - guess.sc.dry_mass
end end
start = state(current_planet, time, v∞_out, final[7]) start = state(current_planet, time, v∞_out, final[7])
i += 1 i += 1
end
return 1.0 return 1.0
end end
max_time = Dates.datetime2unix(latest_arrival) - Dates.datetime2unix(launch_window[1])
lower_x = lowest_mission_vector(launch_window, length(guess.phases), n)
upper_x = highest_mission_vector(launch_window, max_time, length(guess.phases), n)
num_constraints = 8*(length(guess.phases)-1) + 4
g_low, g_high = constraint_bounds(guess)
ipopt_options = Dict("constr_viol_tol" => tol,
"acceptable_constr_viol_tol" => 100tol,
"max_iter" => 10_000,
"max_cpu_time" => 300.,
"print_level" => 5)
options = Options(solver=IPOPT(ipopt_options), derivatives=ForwardFD())
x, _, info = minimize(optimizer!, x0, num_constraints, lower_x, upper_x, g_low, g_high, options)
if verbose println(info) end
if info in [:Solve_Succeeded, :Solved_To_Acceptable_Level]
return Mission(x, guess.sc, guess.start_mass, flybys)
else
return guess
end end
g = zeros(8length(flybys)+4)
optimizer!(g,x0)
return g
# n = size(x0)[1]
# function f!(g,x)
# try
# g[1:6] .= prop(reshape(x,(n,3)), start, craft, tof, primary)[2][1:6]
# return 1.
# catch e
# if isa(e,LaGuerreConway_Error) || isa(e, Mass_Error) || isa(e, PropOne_Error)
# return 10_000.
# else
# rethrow
# end
# end
# end
# lower_x = -1 * ones(3n)
# upper_x = ones(3n)
# num_constraints = 6
# filename = "ipopt_"*string(rand(UInt))
# ipopt_options = Dict("constr_viol_tol" => tol,
# "acceptable_constr_viol_tol" => 1e-4,
# "max_iter" => 10_000,
# "max_cpu_time" => 30.,
# "print_level" => 0,
# "output_file" => filename)
# options = Options(solver=IPOPT(ipopt_options),
# derivatives=ForwardAD())
# x, _, info = minimize(f!, vec(x0), num_constraints, lower_x, upper_x, final[1:6], final[1:6], options)
# rm(filename)
# if info in [:Solve_Succeeded, :Solved_To_Acceptable_Level]
# return Result(true, info, reshape(x,(n,3)))
# else
# if verbose println(info) end
# return Result(false, info, x0)
# end
end end

View File

@@ -56,7 +56,20 @@ function prop(ΔVs::Matrix{T},
for k in 1:7 push!(states[k], interpolated_state[k]) end for k in 1:7 push!(states[k], interpolated_state[k]) end
end end
end end
try
state = prop_one(ΔVs[i,:], state, craft, time/n, primary) state = prop_one(ΔVs[i,:], state, craft, time/n, primary)
catch e
if isa(e,PropOne_Error)
for val in e.ΔV_unit
# If this isn't true, then we just let it slide
if abs(val) > 1.0001
rethrow()
end
end
else
rethrow()
end
end
for j in 1:7 push!(states[j], state[j]) end for j in 1:7 push!(states[j], state[j]) end
state[7] >= craft.dry_mass || throw(Mass_Error(state[7])) state[7] >= craft.dry_mass || throw(Mass_Error(state[7]))
end end
@@ -68,4 +81,4 @@ end
""" """
Convenience function for propagating a state with no thrust Convenience function for propagating a state with no thrust
""" """
prop(x::Vector{Float64}, t::Float64, p::Body=Sun) = prop(zeros(1000,3), [x;1e10], no_thrust, t, p)[1] prop(x::Vector{Float64}, t::Float64, p::Body=Sun) = prop(zeros(1000,3), [x;1.], no_thrust, t, p)[1]

View File

@@ -1,6 +1,6 @@
export Phase, Mission_Guess, Mission, Bad_Mission export Phase, Mission_Guess, Mission, Bad_Mission
export test_phase1, test_phase2 export test_phase1, test_phase2
export test_mg, test_mission export test_mg
export Vector export Vector
struct Phase struct Phase
@@ -11,7 +11,7 @@ struct Phase
thrust_profile::Matrix{Float64} thrust_profile::Matrix{Float64}
end end
const test_phase1 = Phase(Venus, [9., 10., 0.], [10., 9, 0.], 1.30464e7, zeros(20,3)) const test_phase1 = Phase(Venus, [20., -80., 0.], [50., -50., 0.], 1.30464e7, zeros(20,3))
const test_phase2 = Phase(Jupiter, [0.3, 7.1, 0.2], [0.3, 7.1, 0.2], 3.9year, zeros(20,3)) const test_phase2 = Phase(Jupiter, [0.3, 7.1, 0.2], [0.3, 7.1, 0.2], 3.9year, zeros(20,3))
const test_phases = [test_phase1, test_phase2] const test_phases = [test_phase1, test_phase2]
@@ -46,12 +46,6 @@ function Mission_Guess(sc::Sc, mass::Float64, date::DateTime, v∞::Vector{Float
mass_used += mass_consumption(sc, phase) mass_used += mass_consumption(sc, phase)
mass - mass_used > sc.dry_mass || throw(Mass_Error(mass - mass_used)) mass - mass_used > sc.dry_mass || throw(Mass_Error(mass - mass_used))
v∞_in, v∞_out = phase.v∞_in, phase.v∞_out v∞_in, v∞_out = phase.v∞_in, phase.v∞_out
if phase != phases[end]
norm(v∞_in) norm(v∞_out) || throw(V∞_Error(v∞_in, v∞_out))
δ = acos( ( v∞_in v∞_out ) / ( norm(v∞_in) * norm(v∞_out) ) )
periapsis = (phase.planet.μ/(v∞_in v∞_in)) * ( 1/sin(δ/2) - 1 )
periapsis > 1.1phase.planet.r || throw(HitPlanet_Error())
end
end end
Mission_Guess(sc, mass, date, v∞, phases, false) Mission_Guess(sc, mass, date, v∞, phases, false)
end end
@@ -101,6 +95,32 @@ function Base.Vector(g::Mission_Guess)
return result return result
end end
function lowest_mission_vector(launch_window::Vector{DateTime}, num_phases::Int, n::Int)
result = Vector{Float64}()
push!(result, Dates.datetime2unix(launch_window[1]))
push!(result, -10*ones(3)...)
for i in 1:num_phases
push!(result, -10*ones(3)...)
push!(result, -10*ones(3)...)
push!(result, 86_400.0)
push!(result, -1 * ones(n,3)...)
end
return result
end
function highest_mission_vector(launch_window::Vector{DateTime}, mission_length::Float64, num_phases::Int, n::Int)
result = Vector{Float64}()
push!(result, Dates.datetime2unix(launch_window[2]))
push!(result, 10*ones(3)...)
for i in 1:num_phases
push!(result, 10*ones(3)...)
push!(result, 10*ones(3)...)
push!(result, mission_length)
push!(result, ones(n,3)...)
end
return result
end
const test_mg = Mission_Guess(bepi, 12_000., DateTime(1992,11,19), [-3.4,1.2,0.1], test_phases) const test_mg = Mission_Guess(bepi, 12_000., DateTime(1992,11,19), [-3.4,1.2,0.1], test_phases)
struct Mission struct Mission
@@ -113,26 +133,46 @@ struct Mission
end end
""" """
Constructor for a mission. Generally mission guesses are converged Constructor for a mission. Generally mission guesses are converged, so we check everything
""" """
function Mission(sc::Sc, mass::Float64, date::DateTime, v∞::Vector{Float64}, phases::Vector{Phase}) function Mission(sc::Sc, mass::Float64, date::DateTime, v∞::Vector{Float64}, phases::Vector{Phase};
force=false)
# First do some checks to make sure that it's valid # First do some checks to make sure that it's valid
mass_used = 0 if !force
time = date
current_planet = Earth
start = state(current_planet, time, v∞, mass)
for phase in phases for phase in phases
mass_used += mass_consumption(sc, phase) final = prop(phase.thrust_profile, start, sc, phase.tof)[2]
mass - mass_used > sc.dry_mass || throw(Mass_Error(mass - mass_used)) mass = final[7]
mass > sc.dry_mass || throw(Mass_Error(mass - mass_used))
current_planet = phase.planet
time += Dates.Second(floor(phase.tof))
p_pos = state(current_planet, time)[1:3]
abs(norm(final[1:3] - p_pos)) < 30_000. || throw(Planet_Match_Error(final[1:3], p_pos))
v∞_in, v∞_out = phase.v∞_in, phase.v∞_out v∞_in, v∞_out = phase.v∞_in, phase.v∞_out
if phase != phases[end] if phase != phases[end]
norm(v∞_in) norm(v∞_out) || throw(V∞_Error(v∞_in, v∞_out)) abs(norm(v∞_in) - norm(v∞_out)) < 0.005 || throw(V∞_Error(v∞_in, v∞_out))
δ = acos( ( v∞_in v∞_out ) / ( norm(v∞_in) * norm(v∞_out) ) ) δ = acos( ( v∞_in v∞_out ) / ( norm(v∞_in) * norm(v∞_out) ) )
periapsis = (phase.planet.μ/(v∞_in v∞_in)) * ( 1/sin(δ/2) - 1 ) periapsis = (phase.planet.μ/(v∞_in v∞_in)) * ( 1/sin(δ/2) - 1 )
periapsis > 1.1phase.planet.r || throw(HitPlanet_Error()) periapsis > 1.1phase.planet.r || throw(HitPlanet_Error())
end end
end end
end
Mission(sc, mass, date, v∞, phases, true) Mission(sc, mass, date, v∞, phases, true)
end end
const test_mission = Mission(bepi, 12_000., DateTime(1992,11,19), [-3.4,1.2,0.1], test_phases) """
BE CAREFUL!! This just makes a guess converged, whether true or not
"""
function Mission(g::Mission_Guess)
return Mission(g.sc, g.start_mass, g.launch_date, g.launch_v∞, g.phases, force=true)
end
function Mission(x::Vector{Float64}, sc::Sc, mass::Float64, flybys::Vector{Body})
guess = Mission_Guess(x, sc, mass, flybys::Vector{Body})
return Mission(guess)
end
struct Bad_Mission struct Bad_Mission
message::String message::String

View File

@@ -3,7 +3,7 @@
using Random, PlotlyJS, Base.Iterators using Random, PlotlyJS, Base.Iterators
export plot_orbits export plot
function random_color() function random_color()
num1 = rand(0:255) num1 = rand(0:255)
@@ -12,51 +12,41 @@ function random_color()
return "#"*string(num1, base=16, pad=2)*string(num2, base=16, pad=2)*string(num3, base=16, pad=2) return "#"*string(num1, base=16, pad=2)*string(num2, base=16, pad=2)*string(num3, base=16, pad=2)
end end
function get_true_max(mat::Vector{Array{Float64,2}}) # Some convenience functions for maximums
return maximum(abs.(flatten(mat))) """
Gets the maximum absolute value of a path
"""
function Base.maximum(path::Vector{Vector{Float64}})
return maximum( abs.( reduce( vcat,path ) ) )
end end
function plot_orbits(paths::Vector{Vector{Vector{Float64}}}, """
primary::Body=Sun; Gets the maximum absolute value of a list of paths
labels::Vector{String}=Vector{String}(), """
title::String="Spacecraft Position", function Base.maximum(paths::Vector{Vector{Vector{Float64}}})
colors::Vector{String}=Vector{String}()) return maximum( abs.( reduce( vcat,reduce( vcat,paths ) ) ) )
N = 32
θ = collect(range(0,length=N,stop=2π))
ϕ = collect(range(0,length=N,stop=π))
x = cos.(θ) * sin.(ϕ)'
y = sin.(θ) * sin.(ϕ)'
z = repeat(cos.(ϕ)',outer=[N, 1])
ps = primary.r .* (x,y,z)
x_p,y_p,z_p = ps
t1 = []
for i = 1:length(paths)
path = paths[i]
label = labels != [] ? labels[i] : "orbit"
color = colors != [] ? colors[i] : random_color()
push!(t1, scatter3d(;x=(path[1]),y=(path[2]),z=(path[3]),
mode="lines", name=label, line_color=color, line_width=3))
push!(t1, scatter3d(;x=([path[1][1]]),y=([path[2][1]]),z=([path[3][1]]),
mode="markers", showlegend=false,
marker=attr(color=color, size=3, symbol="circle")))
push!(t1, scatter3d(;x=([path[1][end]]),y=([path[2][end]]),z=([path[3][end]]),
mode="markers", showlegend=false,
marker=attr(color=color, size=3, symbol="diamond")))
end end
limit = max(maximum(abs.(flatten(flatten(paths)))),
maximum(abs.(flatten(ps)))) * 1.1
t2 = surface(;x=(x_p), """
y=(y_p), Gets the maximum absolute value of a surface
z=(z_p), """
showscale=false, function Base.maximum(xs::Matrix{Float64},ys::Matrix{Float64},zs::Matrix{Float64})
colorscale = primary.color) return maximum([ vec(xs), vec(ys), vec(zs) ])
end
layout = Layout(title=title, """
width=1000, Basically rewriting Julia at this point
height=600, """
Base.getindex(x::Nothing, i::Int) = nothing
"""
Generates a layout that works for most plots. Requires a title and a limit.
"""
function standard_layout(limit::Float64, title::AbstractString)
limit *= 1.1
Layout(title=title,
# width=1400,
# height=800,
paper_bgcolor="rgba(5,10,40,1.0)", paper_bgcolor="rgba(5,10,40,1.0)",
plot_bgcolor="rgba(100,100,100,0.01)", plot_bgcolor="rgba(100,100,100,0.01)",
scene = attr(xaxis = attr(autorange = false,range=[-limit,limit]), scene = attr(xaxis = attr(autorange = false,range=[-limit,limit]),
@@ -64,8 +54,239 @@ function plot_orbits(paths::Vector{Vector{Vector{Float64}}},
zaxis = attr(autorange = false,range=[-limit,limit]), zaxis = attr(autorange = false,range=[-limit,limit]),
aspectratio=attr(x=1,y=1,z=1), aspectratio=attr(x=1,y=1,z=1),
aspectmode="manual")) aspectmode="manual"))
end
p = Plot([t1...,t2],layout) """
plot(p) A fundamental function to generate a plot for a single orbit path
"""
function gen_plot(path::Vector{Vector{Float64}};
label::Union{AbstractString, Nothing} = nothing,
color::AbstractString = random_color(),
markers=true)
# Plot the orbit
if label === nothing
path_trace = scatter3d(;x=(path[1]),y=(path[2]),z=(path[3]),
mode="lines", showlegend=false, line_color=color, line_width=3)
else
path_trace = scatter3d(;x=(path[1]),y=(path[2]),z=(path[3]),
mode="lines", name=label, line_color=color, line_width=3)
end
traces = [ path_trace ]
# Plot the markers
if markers
push!(traces, scatter3d(;x=([path[1][1]]),y=([path[2][1]]),z=([path[3][1]]),
mode="markers", showlegend=false,
marker=attr(color=color, size=3, symbol="circle")))
push!(traces, scatter3d(;x=([path[1][end]]),y=([path[2][end]]),z=([path[3][end]]),
mode="markers", showlegend=false,
marker=attr(color=color, size=3, symbol="diamond")))
end
return traces, maximum(path[1:6])
end
"""
A fundamental function to plot a single orbit path
"""
function plot(path::Vector{Vector{Float64}};
color::AbstractString = random_color(),
title::AbstractString = "Single Orbit Path",
markers=true)
# Generate the plot
p, limit = gen_plot(path, color=color, markers=markers)
# Now use the standard layout
layout = standard_layout(limit, title)
# Generate the plot
PlotlyJS.plot( PlotlyJS.Plot( p, layout ) )
end
"""
This will generate the plot of a planetary body in it's correct colors and position in a
sun-centered frame at a certain time
If the body is the sun, no time is necessary
An optional prop time is allowed, which will also provide an orbit
"""
function gen_plot(b::Body,
time::Union{DateTime, Nothing}=nothing,
prop_time::Union{Float64, Nothing}=nothing;
scale::Int=100)
# Gotta be a little weird with Sun Scale
if b == Sun && scale > 25 scale = 25 end
# This is how many points to poll to generate the sphere
N = 32
θ = collect(range(0,length=N,stop=2π))
ϕ = collect(range(0,length=N,stop=π))
# Convert polar -> cartesian
unscaled_xs = cos.(θ) * sin.(ϕ)'
unscaled_ys = sin.(θ) * sin.(ϕ)'
unscaled_zs = repeat(cos.(ϕ)',outer=[N, 1])
# Scale by the size of the body (and a scaling factor for visualization)
xs,ys,zs = scale * b.r .* (unscaled_xs,unscaled_ys,unscaled_zs)
# Add an offset if we're not plotting the sun
body_state = zeros(6)
if b != Sun body_state = state(b, time) end
max_value = maximum(xs .+ body_state[1], ys .+ body_state[2], zs .+ body_state[3])
# Generate the surface trace
body_surface = surface(;x = xs .+ body_state[1],
y = ys .+ body_state[2],
z = zs .+ body_state[3],
showscale = false,
colorscale = b.color)
outputs = [ body_surface ]
if prop_time !== nothing
p, maybe_max = gen_plot(prop(body_state, prop_time), label=b.name, color=b.line_color, markers=false)
push!(outputs, p...)
if maybe_max > max_value max_value = maybe_max end
end
return outputs, max_value
end
"""
This will plot a planetary body in it's correct colors and position in a
sun-centered frame at a certain time
If the body is the sun, no time is necessary
An optional prop time is allowed, which will also provide an orbit
"""
function plot(b::Body,
time::Union{DateTime, Nothing}=nothing,
prop_time::Union{Float64, Nothing}=nothing;
title::AbstractString="Single Planet Plot",
scale::Int=100)
# Generate the plot
p, limit = gen_plot(b,time,prop_time,scale=scale)
# Now use the standard layout
layout = standard_layout(limit, title)
# Generate the plot
PlotlyJS.plot( PlotlyJS.Plot( p, layout ) )
end
"""
This will plot a vector of planetary bodies at a certain time
The Sun is plotted automatically
An optional orbits bool is allowed, which shows the orbits
"""
function plot(bodies::Vector{Body},
time::Union{DateTime, Nothing};
orbits::Bool=true,
title::AbstractString="Multiple Planet Plot",
scale::Int=100)
# Generate the plots
trace, limit = gen_plot(Sun, scale=scale)
traces = [ trace... ]
for b in bodies
if orbits
trace, new_limit = gen_plot(b,time,period(b),scale=scale)
else
trace, new_limit = gen_plot(b,time,scale=scale)
end
push!(traces, trace...)
limit = max(limit, new_limit)
end
# Now use the standard layout
layout = standard_layout(limit, title)
# Generate the plot
PlotlyJS.plot( PlotlyJS.Plot( traces, layout ) )
end
"""
Generic plotting function for a bunch of paths. Useful for debugging.
"""
function plot(paths::Vector{Vector{Vector{Float64}}};
labels::Union{Vector{String},Nothing}=nothing,
colors::Union{Vector{String},Nothing}=nothing,
markers::Bool=true,
title::String="Spacecraft Position")
# Generate the path traces
color = colors === nothing ? random_color() : colors[1]
trace, limit = gen_plot(paths[1],label=labels[1],color=color,markers=markers)
traces = [ trace... ]
for i = 2:length(paths)
color = colors === nothing ? random_color() : colors[i]
trace, new_limit = gen_plot(paths[i],label=labels[i],color=color,markers=markers)
push!(traces, trace...)
limit = max(limit, new_limit)
end
# Generate the sun trace
push!(traces, gen_plot(Sun)[1]...)
# Determine layout details
layout = standard_layout(limit, title)
# Plot
PlotlyJS.plot( PlotlyJS.Plot( traces, layout ) )
end
function plot(m::Union{Mission, Mission_Guess}; title::String="Mision Plot")
# First plot the earth
# Then plot phase plus planet phase
time = m.launch_date
mass = m.start_mass
current_planet = Earth
earth_trace, limit = gen_plot(current_planet, time, year)
start = state(current_planet, time, m.launch_v∞, mass)
traces = [ earth_trace... ]
i = 1
for phase in m.phases
# First do the path
path, final = prop(phase.thrust_profile, start, m.sc, phase.tof, interpolate=true)
mass = final[7]
time += Dates.Second(floor(phase.tof))
current_planet = phase.planet
start = state(current_planet, time, phase.v∞_out, mass)
path_trace, new_limit = gen_plot(path, label="Phase "*string(i))
push!(traces, path_trace...)
limit = max(limit, new_limit)
# Then the planet
planet_trace, new_limit = gen_plot(current_planet, time, -phase.tof)
push!(traces, planet_trace...)
limit = max(limit, new_limit)
i += 1
end
# Generate the sun trace
push!(traces, gen_plot(Sun)[1]...)
# Determine layout details
layout = standard_layout(limit, title)
# Plot
PlotlyJS.plot( PlotlyJS.Plot( traces, layout ) )
end end

View File

@@ -10,4 +10,4 @@ end
const test_sc = Sc(8000., 0.00025/(2000*0.00981), 0.00025, 50, 0.9) const test_sc = Sc(8000., 0.00025/(2000*0.00981), 0.00025, 50, 0.9)
const bepi = Sc(8000., 2*0.00025/(2000*0.00981), 0.00025, 2, 0.9) const bepi = Sc(8000., 2*0.00025/(2000*0.00981), 0.00025, 2, 0.9)
const no_thrust = Sc(8000., 0.01, 0., 0, 0.) const no_thrust = Sc(0., 0.01, 0., 0, 0.)

View File

@@ -1,36 +1,48 @@
@testset "Phase" begin @testset "Phase" begin
using PlotlyJS: savefig
println("Testing NLP solver") println("Testing NLP solver")
# We'll start by testing the mission_guess -> vector function # Test the optimizer for a one-phase mission
vec = Vector(test_mg) # The lambert's solver said this should be pretty valid
@test typeof(vec) == Vector{Float64} launch_window = [DateTime(1992,11,1), DateTime(1992,12,1)]
latest_arrival = DateTime(1993,6,1)
# Now we go in the other direction
flybys = [ p.planet for p in test_mg.phases ]
guess = Mission_Guess(vec, test_mg.sc, test_mg.start_mass, flybys)
@test typeof(guess) == Mission_Guess
@test guess.sc == test_mg.sc
@test guess.start_mass == test_mg.start_mass
@test guess.launch_date == test_mg.launch_date
@test guess.launch_v∞ == test_mg.launch_v∞
@test guess.converged == test_mg.converged
for i in 1:length(guess.phases)
@test guess.phases[i].planet == test_mg.phases[i].planet
@test guess.phases[i].v∞_in == test_mg.phases[i].v∞_in
@test guess.phases[i].v∞_out == test_mg.phases[i].v∞_out
@test guess.phases[i].tof == test_mg.phases[i].tof
@test guess.phases[i].thrust_profile == test_mg.phases[i].thrust_profile
end
# Now we test an example run of the basic "inner function"
leave, arrive = DateTime(1992,11,19), DateTime(1993,4,1) leave, arrive = DateTime(1992,11,19), DateTime(1993,4,1)
test_leave = DateTime(1992,11,12)
earth_state = state(Earth, leave) earth_state = state(Earth, leave)
venus_state = state(Venus, arrive) venus_state = state(Venus, arrive)
v∞_out, v∞_in, tof = Thesis.lamberts(Earth, Venus, leave, arrive) v∞_out, v∞_in, tof = Thesis.lamberts(Earth, Venus, leave, arrive)
phase = Phase(Venus, v∞_in, v∞_in, tof, zeros(20,3)) # We can get the thrust profile and tof pretty wrong and still be ok
guess = Mission_Guess(bepi, 12_000., leave, v∞_out, [phase]) phase = Phase(Venus, 1.1v∞_in, v∞_in, 0.9*tof, 0.1*ones(20,3))
g = solve_mission(guess) guess = Mission_Guess(bepi, 12_000., test_leave, 0.9*v∞_out, [phase])
println(g) m = solve_mission(guess, launch_window, latest_arrival, verbose=true)
@test typeof(m) == Mission
@test m.converged == true
# Now we can plot the results to check visually
p = plot(m, title="NLP Test Solution")
savefig(p,"../plots/nlp_test_1_phase.html")
# Now we can look at a more complicated trajectory
flybys = [Earth, Venus, Mars]
launch_window = [DateTime(2021,10,1), DateTime(2021,12,1)]
latest_arrival = DateTime(2023,1,1)
dates = [DateTime(2021,11,1), DateTime(2022,3,27), DateTime(2022,8,28)]
phases = Vector{Phase}()
launch_v∞, _, tof1 = Thesis.lamberts(flybys[1], flybys[2], dates[1], dates[2])
println(launch_v∞)
for i in 1:length(dates)-2
v∞_out1, v∞_in1, tof1 = Thesis.lamberts(flybys[i], flybys[i+1], dates[i], dates[i+1])
v∞_out2, v∞_in2, tof2 = Thesis.lamberts(flybys[i+1], flybys[i+2], dates[i+1], dates[i+2])
push!(phases, Phase(flybys[i+1], v∞_in1, v∞_out2, tof1, 0.01*ones(20,3)))
end
v∞_out, v∞_in, tof = Thesis.lamberts(flybys[end-1], flybys[end], dates[end-1], dates[end])
push!(phases, Phase(flybys[end], v∞_in, v∞_in, tof, 0.01*ones(20,3)))
# This isn't quite right. V∞ discrepancy is too big
guess = Mission_Guess(bepi, 12_000., dates[1], launch_v∞, phases)
m = solve_mission(guess, launch_window, latest_arrival, verbose=true)
p = plot(m, title="NLP Test Solution (2 Phases)")
savefig(p,"../plots/nlp_test_2_phase.html")
end end

View File

@@ -2,34 +2,55 @@
println("Testing plotting features") println("Testing plotting features")
using PlotlyJS using PlotlyJS: savefig, SyncPlot
# Plot an earth plot # Generate a couple of useful paths
T = rand(2hour : 1 : 4hour)
revs = 30
n = revs*100
start = gen_orbit(T, 12_000., Earth)
thrust = spiral(0.9, n, start, test_sc, revs*T, Earth)
path = prop(thrust, start, test_sc, revs*T, Earth)[1]
p = plot_orbits([path], Earth)
savefig(p,"../plots/plot_test_earth.html")
@test typeof(p) == PlotlyJS.SyncPlot
# Now change a little bit and plot around the Sun
# This also checks that the spacecraft are configured right:
# They really shouldn't run out of fuel in 4 years
T = rand(year : hour : 4year) T = rand(year : hour : 4year)
tof = 4year tof = 4year
n = 4_000
start = gen_orbit(T, 12_000.) start = gen_orbit(T, 12_000.)
thrust = spiral(0.9, n, start, bepi, tof) thrust = spiral(0.9, n, start, bepi, tof)
sun_paths = Vector{Vector{Vector{Float64}}}() spiral_path = prop(thrust, start, bepi, tof)[1]
push!(sun_paths, prop(zeros(100,3), start, bepi, tof)[1]) no_thrust_path = prop(zeros(n,3), start, bepi, tof)[1]
push!(sun_paths, prop(thrust, start, bepi, tof)[1]) paths = [ spiral_path, no_thrust_path ]
p = plot_orbits(sun_paths)
savefig(p,"../plots/plot_test_sun.html")
@test typeof(p) == PlotlyJS.SyncPlot
# First let's plot a single basic path
p = plot(spiral_path)
savefig(p,"../plots/plot_test_path.html")
@test typeof(p) == SyncPlot
# Next I just want to plot a planet both not moving...
p = plot(Jupiter, Dates.now(), title="Jupiter")
savefig(p,"../plots/plot_test_planet_nomove.html")
@test typeof(p) == SyncPlot
# ...and moving
p = plot(Venus, Dates.now(), 0.95period(Venus), title="Venus")
savefig(p,"../plots/plot_test_planet_move.html")
@test typeof(p) == SyncPlot
# Next I want to plot the entire solar system
p = plot([Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune, Pluto],
Dates.now(),
title="The Solar System")
savefig(p,"../plots/plot_test_solar_system.html")
@test typeof(p) == SyncPlot
# Next I want to plot a vector of states
p = plot(paths,
labels = ["No Thrust Orbit","Thrust Spiral"],
title = "Vector of Paths Plot Test")
savefig(p,"../plots/plot_test_paths.html")
@test typeof(p) == SyncPlot
# And finally, I'd like to plot a mission guess...
p = plot(test_mg)
savefig(p,"../plots/plot_test_mission_guess.html")
@test typeof(p) == SyncPlot
# ...and a mission
p = plot(Mission(test_mg))
savefig(p,"../plots/plot_test_mission.html")
@test typeof(p) == SyncPlot
end end

View File

@@ -14,9 +14,9 @@ catch
end end
@testset "All Tests" begin @testset "All Tests" begin
# include("plotting.jl") include("plotting.jl")
# include("inner_loop/laguerre-conway.jl") include("inner_loop/laguerre-conway.jl")
# include("inner_loop/propagator.jl") include("inner_loop/propagator.jl")
include("inner_loop/nlp_solver.jl") include("inner_loop/nlp_solver.jl")
# include("inner_loop/monotonic_basin_hopping.jl") # include("inner_loop/monotonic_basin_hopping.jl")
end end