Fully working mbh for two phase missions!
This commit is contained in:
@@ -5,41 +5,22 @@ Generates pareto-distributed random numbers
|
||||
|
||||
This is usually around one to two percent, but sometimes up to ten or so (fat tails)
|
||||
"""
|
||||
function pareto(shape::Tuple{Int, Int}, α::Float64=1.01)
|
||||
s = rand((-1,1), shape)
|
||||
r = rand(Float64, shape)
|
||||
function pareto(shape::Union{Tuple{Int, Int}, Int, Nothing}=nothing; α::Float64=1.01)
|
||||
if shape !== nothing
|
||||
s = rand((-1,1), shape)
|
||||
r = rand(Float64, shape)
|
||||
else
|
||||
s = rand((-1,1))
|
||||
r = rand(Float64)
|
||||
end
|
||||
ϵ = 1e-10
|
||||
return 1 .+ (s./ϵ) * (α - 1.0) ./ (ϵ ./ (ϵ .+ r)).^(-α)
|
||||
end
|
||||
|
||||
function pareto(shape::Int, α::Float64=1.01)
|
||||
s = rand((-1,1), shape)
|
||||
r = rand(Float64, shape)
|
||||
ϵ = 1e-10
|
||||
return 1 .+ (s./ϵ) * (α - 1.0) ./ (ϵ ./ (ϵ .+ r)).^(-α)
|
||||
end
|
||||
|
||||
function pareto(α::Float64=1.01)
|
||||
s = rand((-1,1))
|
||||
r = rand(Float64)
|
||||
ϵ = 1e-10
|
||||
return 1 + (s./ϵ) * (α - 1.0) ./ (ϵ ./ (ϵ .+ r)).^(-α)
|
||||
end
|
||||
|
||||
function pareto_add(α::Float64=1.01)
|
||||
s = rand((-1,1))
|
||||
r = rand(Float64)
|
||||
ϵ = 1e-10
|
||||
return (s./ϵ) * (α - 1.0) ./ (ϵ ./ (ϵ .+ r)).^(-α)
|
||||
end
|
||||
|
||||
"""
|
||||
Returns a random date between two dates
|
||||
"""
|
||||
function gen_date(date_range::Tuple{DateTime, DateTime})
|
||||
l0, lf = date_range
|
||||
l0 + Millisecond(floor(rand()*(lf-l0).value))
|
||||
end
|
||||
Base.rand(l0::DateTime,lf::DateTime) = l0 + Millisecond(floor(rand()*(lf-l0).value))
|
||||
|
||||
"""
|
||||
Perturbs a valid mission with pareto-distributed variables, generating a mission guess
|
||||
@@ -47,7 +28,7 @@ Perturbs a valid mission with pareto-distributed variables, generating a mission
|
||||
function perturb(mission::Mission)
|
||||
mission_guess = Bad_Mission("Starting point")
|
||||
while typeof(mission_guess) == Bad_Mission
|
||||
new_launch_date = mission.launch_date + Dates.Second(floor(7day * pareto_add()))
|
||||
new_launch_date = mission.launch_date + Dates.Second(floor(7day * (pareto()-1)))
|
||||
new_launch_v∞ = mission.launch_v∞ .* pareto(3)
|
||||
new_phases = Vector{Phase}()
|
||||
for phase in mission.phases
|
||||
@@ -58,16 +39,7 @@ function perturb(mission::Mission)
|
||||
new_thrust_profile = phase.thrust_profile .* pareto(size(phase.thrust_profile))
|
||||
push!(new_phases, Phase(phase.planet, new_v∞_in, new_v∞_out, new_tof, new_thrust_profile))
|
||||
end
|
||||
# TODO: Mission_Guess.validate()
|
||||
try
|
||||
mission_guess = Mission_Guess(mission.sc, mission.start_mass, new_launch_date, new_launch_v∞, new_phases)
|
||||
catch e
|
||||
if isa(e, Mass_Error) ||isa(e,V∞_Error) || isa(e,HitPlanet_Error)
|
||||
continue
|
||||
else
|
||||
rethrow()
|
||||
end
|
||||
end
|
||||
mission_guess = Mission_Guess(mission.sc, mission.start_mass, new_launch_date, new_launch_v∞, new_phases)
|
||||
end
|
||||
return mission_guess
|
||||
end
|
||||
@@ -89,7 +61,7 @@ function mission_guess( flybys::Vector{Body},
|
||||
n = 20
|
||||
|
||||
# Determine the launch conditions
|
||||
launch_date = gen_date(launch_window)
|
||||
launch_date = rand(launch_window...)
|
||||
launch_v∞_normalized = rand(-1:0.0001:1, 3)
|
||||
launch_v∞ = rand(0:0.0001:√max_C3_out) * launch_v∞_normalized/norm(launch_v∞_normalized)
|
||||
|
||||
@@ -142,52 +114,6 @@ function mission_guess( flybys::Vector{Body},
|
||||
return mission_guess
|
||||
end
|
||||
|
||||
"""
|
||||
Sequentially calls the NLP solver to attempt to solve based on the initial guess
|
||||
"""
|
||||
function inner_loop_solve(guess::Mission_Guess)
|
||||
v∞_out = guess.launch_v∞
|
||||
mass = guess.start_mass
|
||||
current_planet = Earth
|
||||
time = utc2et(Dates.format(guess.launch_date,"yyyy-mm-ddTHH:MM:SS"))
|
||||
start = state(current_planet, time, v∞_out, mass)
|
||||
|
||||
corrected_phases = Vector{Phase}()
|
||||
for i in 1:length(guess.phases)
|
||||
phase = guess.phases[i]
|
||||
current_planet = phase.planet
|
||||
time += phase.tof
|
||||
goal = state(current_planet, time, phase.v∞_in)
|
||||
result = solve_phase( start, goal, guess.sc, phase.tof, phase.thrust_profile)
|
||||
result.converged || return Bad_Mission(result.info) # Drop if it's not working
|
||||
corrected_phase = Phase(phase.planet, phase.v∞_in, phase.v∞_out, phase.tof, result.sol)
|
||||
push!(corrected_phases, corrected_phase)
|
||||
mass_used = mass_consumption(guess.sc, corrected_phase)
|
||||
mass -= mass_used
|
||||
if i != length(guess.phases)
|
||||
v∞_out = phase.v∞_out
|
||||
start = state(current_planet, time, v∞_out, mass)
|
||||
end
|
||||
end
|
||||
|
||||
return Mission(guess.sc, guess.start_mass, guess.launch_date, guess.launch_v∞, corrected_phases)
|
||||
end
|
||||
|
||||
"""
|
||||
The cost function for the mission
|
||||
TODO: This will probably move and eventually be passed as an argument
|
||||
"""
|
||||
function cost(mission::Mission, max_C3::Float64, max_v∞::Float64)
|
||||
mass_used = 0.0
|
||||
for phase in mission.phases mass_used += mass_used(sc, phase) end
|
||||
mass_percent = mass_used/mission.sc.dry_mass
|
||||
C3_percent = ( mission.launch_v∞ ⋅ mission.launch_v∞ ) / max_C3
|
||||
v∞_percent = norm(mission.phases[end].v∞_in) / max_v∞
|
||||
return 3mass_percent + C3_percent + v∞_percent
|
||||
end
|
||||
|
||||
cost(_::Bad_Mission) = Inf
|
||||
|
||||
"""
|
||||
This is the main monotonic basin hopping function. There's a lot going on here, but the general idea
|
||||
is that hopefully you can provide mission parameters and a list of flybys and get the optimal
|
||||
@@ -200,44 +126,50 @@ function mbh( flybys::Vector{Body},
|
||||
max_C3::Float64,
|
||||
max_v∞::Float64,
|
||||
latest_arrival::DateTime,
|
||||
cost_fn::Function,
|
||||
primary::Body=Sun;
|
||||
search_patience::Int=10_000,
|
||||
drill_patience::Int=50,
|
||||
verbose::Bool=false)
|
||||
|
||||
# A convenience function
|
||||
# Convenience Functions
|
||||
random_guess() = mission_guess(flybys,sc,start_mass,launch_window,max_C3,max_v∞,latest_arrival,primary)
|
||||
cost(m::Mission) = cost(m, max_C3, max_v∞)
|
||||
status(s,d,l) = print("\r\t", "search: ", s, " drill: ", d, " archive length: ", l, " ")
|
||||
solve(g::Mission_Guess) = solve_mission(g, launch_window, latest_arrival)
|
||||
cost(m::Mission) = cost_fn(m, max_C3, max_v∞)
|
||||
cost(_::Nothing) = Inf
|
||||
|
||||
# Initialize stuff
|
||||
search_count = 0
|
||||
x_current = Bad_Mission("Starting point")
|
||||
drill_count = 0
|
||||
archive = Vector{Mission}()
|
||||
function log()
|
||||
print("\r ")
|
||||
print("\rsearch: $(search_count) drill: $(drill_count) archive: $(length(archive))\t")
|
||||
end
|
||||
|
||||
# The main loop
|
||||
x_current = nothing
|
||||
if verbose println("Starting Monotonic Basin Hopper") end
|
||||
while search_count < search_patience
|
||||
|
||||
# Intialize an x_star, if it doesn't converge, hop on to the next basin
|
||||
search_count += 1
|
||||
drill_count = 0
|
||||
if verbose status(search_count, drill_count, length(archive)) end
|
||||
x_star = inner_loop_solve(random_guess())
|
||||
verbose && log()
|
||||
x_star = solve(random_guess())
|
||||
x_star.converged || continue
|
||||
x_basin = x_star
|
||||
|
||||
|
||||
# If it does, though, we check to see if it's better than the current
|
||||
if cost(x_star) < cost(x_current) x_current = x_star end
|
||||
|
||||
# Either way, we need to drill into this particular basin, since it's valid
|
||||
while drill_count < drill_patience
|
||||
|
||||
if verbose status(search_count, drill_count, length(archive)) end
|
||||
verbose && log()
|
||||
|
||||
#Perturb to generate a slightly different x_star
|
||||
x_star = inner_loop_solve(perturb(x_basin))
|
||||
x_star = solve(perturb(x_basin))
|
||||
|
||||
# If better than the best, then keep it as current
|
||||
if x_star.converged && cost(x_star) < cost(x_current)
|
||||
@@ -258,6 +190,8 @@ function mbh( flybys::Vector{Body},
|
||||
|
||||
end
|
||||
|
||||
if verbose println() end
|
||||
|
||||
return x_current, archive
|
||||
|
||||
end
|
||||
|
||||
@@ -31,7 +31,7 @@ NOTE: This function will output a proper mission if it succeeded and just re-ret
|
||||
guess otherwise
|
||||
"""
|
||||
function solve_mission( guess::Mission_Guess,
|
||||
launch_window::Vector{DateTime},
|
||||
launch_window::Tuple{DateTime,DateTime},
|
||||
latest_arrival::DateTime;
|
||||
tol=1e-10,
|
||||
verbose::Bool=false,
|
||||
@@ -114,7 +114,7 @@ function solve_mission( guess::Mission_Guess,
|
||||
else
|
||||
g = zeros(num_constraints)
|
||||
optimizer!(g,x)
|
||||
println(g)
|
||||
if verbose println(g) end
|
||||
return guess
|
||||
end
|
||||
|
||||
|
||||
@@ -21,9 +21,6 @@ function prop_one(ΔV_unit::Vector{<:Real},
|
||||
time::Float64,
|
||||
primary::Body=Sun)
|
||||
|
||||
for direction in ΔV_unit
|
||||
abs(direction) <= 1.0 || throw(PropOne_Error(ΔV_unit))
|
||||
end
|
||||
ΔV = max_ΔV(craft.duty_cycle, craft.num_thrusters, craft.max_thrust, time, 0., state[7]) * ΔV_unit
|
||||
halfway = laguerre_conway(state, time/2, primary) + [zeros(3); ΔV]
|
||||
final = laguerre_conway(halfway, time/2, primary)
|
||||
@@ -81,3 +78,24 @@ end
|
||||
Convenience function for propagating a state with no thrust
|
||||
"""
|
||||
prop(x::Vector{Float64}, t::Float64, p::Body=Sun) = prop(zeros(1000,3), [x;1.], no_thrust, t, p)[1]
|
||||
|
||||
"""
|
||||
This is solely for the purposes of getting the final state of a mission or guess
|
||||
"""
|
||||
function prop(m::Mission)
|
||||
time = m.launch_date
|
||||
current_planet = Earth
|
||||
start = state(current_planet, time, m.launch_v∞, m.start_mass)
|
||||
|
||||
final = zeros(7)
|
||||
for phase in m.phases
|
||||
final = prop(phase.thrust_profile, start, m.sc, phase.tof)[2]
|
||||
mass = final[7]
|
||||
current_planet = phase.planet
|
||||
time += Dates.Second(floor(phase.tof))
|
||||
start = state(current_planet, time, phase.v∞_out, mass)
|
||||
end
|
||||
|
||||
return final
|
||||
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user