diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 90df6ae..d6e513a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,6 +2,7 @@ stages: - test unit-test-job: + timeout: 2h stage: test script: - apt-get update @@ -15,4 +16,8 @@ unit-test-job: - julia/plots/find_closest_test.html - julia/plots/mbh_nominal.html - julia/plots/mbh_best.html + - julia/plots/mbh_sun_initial.html + - julia/plots/mbh_sun_solved.html + - julia/plots/inner_loop_before.html + - julia/plots/inner_loop_after.html expire_in: 1 week diff --git a/julia/src/constants.jl b/julia/src/constants.jl index a973363..1d54d7c 100644 --- a/julia/src/constants.jl +++ b/julia/src/constants.jl @@ -2,7 +2,7 @@ # DEFINING CONSTANTS # ----------------------------------------------------------------------------- -export μs, G, GMs, μ, rs, as, es, AU +export μs, G, GMs, μ, rs, as, es, AU, ids # Gravitational Constants μs = Dict( @@ -84,17 +84,17 @@ export μs, G, GMs, μ, rs, as, es, AU # These are just the colors for plots const p_colors = Dict( - "Sun" => :Electric, - "Mercury" => :heat, - "Venus" => :turbid, - "Earth" => :Blues, - "Moon" => :Greys, - "Mars" => :Reds, - "Jupiter" => :solar, - "Saturn" => :turbid, - "Uranus" => :haline, - "Neptune" => :ice, - "Pluto" => :matter) + "Sun" => "Electric", + "Mercury" => "heat", + "Venus" => "turbid", + "Earth" => "Blues", + "Moon" => "Greys", + "Mars" => "Reds", + "Jupiter" => "solar", + "Saturn" => "turbid", + "Uranus" => "haline", + "Neptune" => "ice", + "Pluto" => "matter") const ids = Dict( "Sun" => 10, diff --git a/julia/src/conversions.jl b/julia/src/conversions.jl index cb66a64..115e799 100644 --- a/julia/src/conversions.jl +++ b/julia/src/conversions.jl @@ -76,7 +76,6 @@ function conv_T(Tm::Vector{Float64}, Ta::Vector{Float64}, Tb::Vector{Float64}, init_state::Vector{Float64}, - m::Float64, craft::Sc, time::Float64, μ::Float64) @@ -109,7 +108,7 @@ function conv_T(Tm::Vector{Float64}, si*sθ si*cθ ci ] Tx, Ty, Tz = DCM*thrust_rθh - state = prop_one([Tx, Ty, Tz], state, craft, μ, time/n)[1] + state = prop_one([Tx, Ty, Tz], state, copy(craft), μ, time/n) push!(Txs, Tx) push!(Tys, Ty) push!(Tzs, Tz) diff --git a/julia/src/inner_loop/find_closest.jl b/julia/src/inner_loop/find_closest.jl index 0fd9f98..06b447b 100644 --- a/julia/src/inner_loop/find_closest.jl +++ b/julia/src/inner_loop/find_closest.jl @@ -5,16 +5,13 @@ export nlp_solve, mass_est function mass_est(T) ans = 0 n = Int(length(T)/3) - for i in 1:n - ans += norm(T[i,:]) - end + for i in 1:n ans += norm(T[i,:]) end return ans/n end -converged(x) = NLsolve.converged(x) - -function converged(_::String) - return false +struct Result + converged::Bool + zero::Matrix{Float64} end function nlp_solve(start::Vector{Float64}, @@ -28,10 +25,21 @@ function nlp_solve(start::Vector{Float64}, num_iters=1_000) function f!(F,x) - F .= 0.0 - F[1:6, 1] .= prop_nlsolve(tanh.(x), start, craft, μ, tf-t0) .- final + try + F .= 0.0 + F[1:6, 1] .= prop(tanh.(x), start, copy(craft), μ, tf-t0)[2][1:6] .- final[1:6] + catch e + F .= 10000000.0 + end end - return nlsolve(f!, atanh.(x0), ftol=tol, autodiff=:forward, iterations=num_iters) + result = Result(false, zeros(size(x0))) + try + nl_results = nlsolve(f!, atanh.(x0), ftol=tol, autodiff=:forward, iterations=num_iters) + result = Result(converged(nl_results), tanh.(nl_results.zero)) + catch e + end -end \ No newline at end of file + return result + +end diff --git a/julia/src/inner_loop/inner_loop.jl b/julia/src/inner_loop/inner_loop.jl index 43212a8..21d4d3c 100644 --- a/julia/src/inner_loop/inner_loop.jl +++ b/julia/src/inner_loop/inner_loop.jl @@ -8,6 +8,7 @@ there's only the outer loop left to do. And that's pretty easy. """ function inner_loop(launch_date::DateTime, craft::Sc, + start_mass::Float64, phases::Vector{Phase}; min_flyby::Float64=1000., mbh_specs=nothing, @@ -38,36 +39,32 @@ function inner_loop(launch_date::DateTime, δ = acos((phases[i].v∞_outgoing ⋅ phases[i-1].v∞_incoming)/v∞^2) flyby = μs[phases[i].from_planet]/v∞^2 * (1/sin(δ/2) - 1) true_min = rs[phases[i].from_planet] + min_flyby - if flyby <= true_min - error("Flyby was too low between phase $(i-1) and $(i): $(flyby) / $(true_min)") - end + flyby <= true_min || error("Flyby too low from phase $(i-1) to $(i): $(flyby) / $(true_min)") end end time = utc2et(Dates.format(launch_date,"yyyy-mm-ddTHH:MM:SS")) thrust_profiles = [] - try - for phase in phases - planet1_state = spkssb(ids[phase.from_planet], time, "J2000") - time += phase.time_of_flight - planet2_state = spkssb(ids[phase.to_planet], time, "J2000") - # TODO: Come up with improved method of calculating "n" - start = planet1_state + [0., 0., 0., phase.v∞_outgoing...] - final = planet2_state + [0., 0., 0., phase.v∞_incoming...] - if mbh_specs === nothing - best = mbh(start, final, craft, μs["Sun"], 0.0, phase.time_of_flight, 10, - verbose=verbose)[1] - else - num_iters, sil, dil = mbh_specs - best = mbh(start, final, craft, μs["Sun"], 0.0, phase.time_of_flight, 10, - verbose=verbose, num_iters=num_iters, search_patience_lim=sil, - drill_patience_lim=dil) - end - push!(thrust_profiles, best) - craft.mass = prop(tanh.(best.zero), planet1_state, sc, μs["Sun"], prop_time)[2][end] + + for phase in phases + planet1_state = [spkssb(ids[phase.from_planet], time, "J2000"); 0.0] + time += phase.time_of_flight + planet2_state = [spkssb(ids[phase.to_planet], time, "J2000"); 0.0] + start = planet1_state + [0., 0., 0., phase.v∞_outgoing..., start_mass] + final = planet2_state + [0., 0., 0., phase.v∞_incoming..., start_mass] + println(start) + println(final) + # TODO: Come up with improved method of calculating "n" + if mbh_specs === nothing + best = mbh(start, final, craft, μs["Sun"], 0.0, phase.time_of_flight, 20, + verbose=verbose)[1] + else + sil, dil = mbh_specs + best = mbh(start, final, craft, μs["Sun"], 0.0, phase.time_of_flight, 20, + verbose=verbose, search_patience_lim=sil, drill_patience_lim=dil)[1] end - return craft.mass, thrust_profiles - catch - return "One path did not converge" + push!(thrust_profiles, best.zero) end + return thrust_profiles + end diff --git a/julia/src/inner_loop/laguerre-conway.jl b/julia/src/inner_loop/laguerre-conway.jl index 5f332c8..0220bf8 100644 --- a/julia/src/inner_loop/laguerre-conway.jl +++ b/julia/src/inner_loop/laguerre-conway.jl @@ -1,4 +1,4 @@ -function laguerre_conway(state::Vector{T}, μ::Float64, time::Float64) where T +function laguerre_conway(state::Vector{<:Real}, μ::Float64, time::Float64) n = 5 # Choose LaGuerre-Conway "n" i = 0 diff --git a/julia/src/inner_loop/monotonic_basin_hopping.jl b/julia/src/inner_loop/monotonic_basin_hopping.jl index 12c4b8e..926dfbe 100644 --- a/julia/src/inner_loop/monotonic_basin_hopping.jl +++ b/julia/src/inner_loop/monotonic_basin_hopping.jl @@ -26,42 +26,38 @@ function mbh(start::AbstractVector, t0::AbstractFloat, tf::AbstractFloat, n::Int; - num_iters=25, - search_patience_lim::Int=200, - drill_patience_lim::Int=200, + search_patience_lim::Int=2000, + drill_patience_lim::Int=40, tol=1e-6, verbose=false) archive = [] i = 0 - if verbose println("Current Iteration") end - while true + x_current = Result(false, 1e8*ones(n,3)) + while i < search_patience_lim i += 1 - if verbose print("\r",i) end - search_impatience = 0 drill_impatience = 0 - x_star = nlp_solve(start, final, craft, μ, t0, tf, new_x(n), tol=tol, num_iters=100) - while converged(x_star) == false && search_impatience < search_patience_lim - search_impatience += 1 - x_star = nlp_solve(start, final, craft, μ, t0, tf, new_x(n), tol=tol, num_iters=100) - end - if drill_impatience > drill_patience_lim break end - drill_impatience = 0 - if converged(x_star) + if verbose print("\r\t", "search: ", i, " drill: ", drill_impatience, " ") end + x_star = nlp_solve(start, final, craft, μ, t0, tf, new_x(n), tol=tol) + # If x_star is converged and better, set new x_current + if x_star.converged && mass_est(x_star.zero) < mass_est(x_current.zero) x_current = x_star + end + # If x_star is converged, drill down, otherwise, start over + if x_star.converged while drill_impatience < drill_patience_lim - x_star = nlp_solve(start, final, craft, μ, t0, tf, perturb(tanh.(x_current.zero),n), tol=tol) - if converged(x_star) && mass_est(tanh.(x_star.zero)) < mass_est(tanh.(x_current.zero)) + x_star = nlp_solve(start, final, craft, μ, t0, tf, perturb(x_current.zero,n), tol=tol) + if x_star.converged && mass_est(x_star.zero) < mass_est(x_current.zero) x_current = x_star drill_impatience = 0 else + if verbose print("\r\t", "search: ", i, " drill: ", drill_impatience, " ") end drill_impatience += 1 end end push!(archive, x_current) end - if i >= num_iters break end end if verbose println() end @@ -70,8 +66,8 @@ function mbh(start::AbstractVector, current_best_mass = 1e8 best = archive[1] for candidate in archive - if mass_est(tanh.(candidate.zero)) < current_best_mass - current_best_mass = mass_est(tanh.(candidate.zero)) + if mass_est(candidate.zero) < current_best_mass + current_best_mass = mass_est(candidate.zero) best = candidate end end diff --git a/julia/src/inner_loop/propagator.jl b/julia/src/inner_loop/propagator.jl index 9d9eb0b..278ebe5 100644 --- a/julia/src/inner_loop/propagator.jl +++ b/julia/src/inner_loop/propagator.jl @@ -3,234 +3,80 @@ export prop """ Maximum ΔV that a spacecraft can impulse for a given single time step """ -function max_ΔV(duty_cycle::T, +function max_ΔV(duty_cycle::Float64, num_thrusters::Int, - max_thrust::T, - tf::T, - t0::T, - mass::S) where {T <: Real, S <: Real} + max_thrust::Float64, + tf::Float64, + t0::Float64, + mass::T) where T <: Real return duty_cycle*num_thrusters*max_thrust*(tf-t0)/mass end -""" -This function propagates the spacecraft forward in time 1 Sim-Flanagan step (of variable length of time), -applying a thrust in the center. -""" -function prop_one(thrust_unit::Vector{<:Real}, - state::Vector{<:Real}, - duty_cycle::Float64, - num_thrusters::Int, - max_thrust::Float64, - mass::T, - mass_flow_rate::Float64, - μ::Float64, - time::Float64) where T<:Real - - ΔV = max_ΔV(duty_cycle, num_thrusters, max_thrust, time, 0., mass) * thrust_unit - halfway = laguerre_conway(state, μ, time/2) + [0., 0., 0., ΔV...] - return laguerre_conway(halfway, μ, time/2), mass - mass_flow_rate*norm(thrust_unit)*time - -end - """ A convenience function for using spacecraft. Note that this function outputs a sc instead of a mass """ -function prop_one(ΔV_unit::Vector{T}, - state::Vector{S}, +function prop_one(ΔV_unit::Vector{<:Real}, + state::Vector{<:Real}, craft::Sc, μ::Float64, - time::Float64) where {T <: Real,S <: Real} - state, mass = prop_one(ΔV_unit, state, craft.duty_cycle, craft.num_thrusters, craft.max_thrust, - craft.mass, craft.mass_flow_rate, μ, time) - return state, Sc(mass, craft.mass_flow_rate, craft.max_thrust, craft.num_thrusters, craft.duty_cycle) + time::Float64) + + for direction in ΔV_unit + if abs(direction) > 1.0 + println(direction) + error("ΔV is impossibly high") + end + 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) + [zeros(3); ΔV] + final = laguerre_conway(halfway, μ, time/2) + return [final; state[7] - craft.mass_flow_rate*norm(ΔV_unit)*time] + end - """ -This propagates over a given time period, with a certain number of intermediate steps +The propagator function """ function prop(ΔVs::Matrix{T}, state::Vector{Float64}, - duty_cycle::Float64, - num_thrusters::Int, - max_thrust::Float64, - mass::Float64, - mass_flow_rate::Float64, + craft::Sc, μ::Float64, time::Float64) where T <: Real - if size(ΔVs)[2] != 3 throw(ErrorException("ΔV input is wrong size")) end - n = size(ΔVs)[i] - - for i in 1:n - state, mass = prop_one(ΔVs[i,:], state, duty_cycle, num_thrusters, max_thrust, mass, - mass_flow_rate, μ, time/n) - end - - return state, mass - -end - -""" -The same function, using Scs -""" -function prop(ΔVs::Matrix{T}, - state::Vector{S}, - craft::Sc, - μ::Float64, - time::Float64) where {T <: Real, S <: Real} - if size(ΔVs)[2] != 3 throw(ErrorException("ΔV input is wrong size")) end n = size(ΔVs)[1] - x_states = [state[1]] - y_states = [state[2]] - z_states = [state[3]] - dx_states = [state[4]] - dy_states = [state[5]] - dz_states = [state[6]] - masses = [craft.mass] + x_states = Vector{T}() + y_states = Vector{T}() + z_states = Vector{T}() + dx_states = Vector{T}() + dy_states = Vector{T}() + dz_states = Vector{T}() + masses = Vector{T}() + + push!(x_states, state[1]) + push!(y_states, state[2]) + push!(z_states, state[3]) + push!(dx_states, state[4]) + push!(dy_states, state[5]) + push!(dz_states, state[6]) + push!(masses, state[7]) for i in 1:n - state, craft = prop_one(ΔVs[i,:], state, craft, μ, time/n) + state = prop_one(ΔVs[i,:], state, craft, μ, time/n) push!(x_states, state[1]) push!(y_states, state[2]) push!(z_states, state[3]) push!(dx_states, state[4]) push!(dy_states, state[5]) push!(dz_states, state[6]) - push!(masses, craft.mass) + push!(masses, state[7]) + if state[7] < craft.dry_mass + println(state[7]) + error("Mass is too low") + end end - return [x_states, y_states, z_states, dx_states, dy_states, dz_states], masses, state + return [x_states, y_states, z_states, dx_states, dy_states, dz_states, masses], state end - -function prop_nlsolve(ΔVs::Matrix{T}, - state::Vector{S}, - craft::Sc, - μ::Float64, - time::Float64) where {T <: Real, S <: Real} - - n = size(ΔVs)[1] - try - for i in 1:n - state, craft = prop_one(ΔVs[i,:], state, craft, μ, time/n) - end - return state - catch - return [0., 0., 0., 0., 0., 0.] - end - - -end - -function prop_simple(ΔVs::AbstractMatrix, - state::AbstractVector, - craft::Sc, - μ::Float64, - time::Float64) - - if size(ΔVs)[2] != 3 throw(ErrorException("ΔV input is wrong size")) end - n = size(ΔVs)[1] - - for i in 1:n - state, craft = prop_one(ΔVs[i,:], state, craft, μ, time/n) - end - - return state - -end - -function prop_one_simple(Tx, Ty, Tz, x, y, z, dx, dy, dz, t, μ) - - # perform laguerre_conway once - r0_mag = √(x^2 + y^2 + z^2) - v0_mag = √(dx^2 + dy^2 + dz^2) - σ0 = ([x, y, z] ⋅ [dx, dy, dz])/√(μ) - a = 1 / ( 2/r0_mag - v0_mag^2/μ ) - coeff = 1 - r0_mag/a - - if a > 0 # Elliptical - ΔM = ΔE_new = √(μ) / sqrt(a^3) * t/2 - ΔE = 1000 - while abs(ΔE - ΔE_new) > 1e-10 - ΔE = ΔE_new - F = ΔE - ΔM + σ0 / √(a) * (1-cos(ΔE)) - coeff * sin(ΔE) - dF = 1 + σ0 / √(a) * sin(ΔE) - coeff * cos(ΔE) - d2F = σ0 / √(a) * cos(ΔE) + coeff * sin(ΔE) - sign = dF >= 0 ? 1 : -1 - ΔE_new = ΔE - 5*F / ( dF + sign * √(abs((5-1)^2*dF^2 - 5*(5-1)*F*d2F ))) - end - F = 1 - a/r0_mag * (1-cos(ΔE)) - G = a * σ0/ √(μ) * (1-cos(ΔE)) + r0_mag * √(a) / √(μ) * sin(ΔE) - r = a + (r0_mag - a) * cos(ΔE) + σ0 * √(a) * sin(ΔE) - Ft = -√(a)*√(μ) / (r*r0_mag) * sin(ΔE) - Gt = 1 - a/r * (1-cos(ΔE)) - else # Hyperbolic or Parabolic - ΔN = √(μ) / sqrt(-a^3) * t/2 - ΔH = 0 - ΔH_new = t/2 < 0 ? -1 : 1 - while abs(ΔH - ΔH_new) > 1e-10 - ΔH = ΔH_new - F = -ΔN - ΔH + σ0 / √(-a) * (cos(ΔH)-1) + coeff * sin(ΔH) - dF = -1 + σ0 / √(-a) * sin(ΔH) + coeff * cos(ΔH) - d2F = σ0 / √(-a) * cos(ΔH) + coeff * sin(ΔH) - sign = dF >= 0 ? 1 : -1 - ΔH_new = ΔH - 5*F / ( dF + sign * √(abs((5-1)^2*dF^2 - 5*(5-1)*F*d2F ))) - end - F = 1 - a/r0_mag * (1-cos(ΔH)) - G = a * σ0/ √(μ) * (1-cos(ΔH)) + r0_mag * √(-a) / √(μ) * sin(ΔH) - r = a + (r0_mag - a) * cos(ΔH) + σ0 * √(-a) * sin(ΔH) - Ft = -√(-a)*√(μ) / (r*r0_mag) * sin(ΔH) - Gt = 1 - a/r * (1-cos(ΔH)) - end - - # add the thrust vector - x,y,z,dx,dy,dz = [F*[x,y,z] + G*[dx,dy,dz]; Ft*[x,y,z] + Gt*[dx,dy,dz] + [Tx, Ty, Tz]] - - #perform again - r0_mag = √(x^2 + y^2 + z^2) - v0_mag = √(dx^2 + dy^2 + dz^2) - σ0 = ([x, y, z] ⋅ [dx, dy, dz])/√(μ) - a = 1 / ( 2/r0_mag - v0_mag^2/μ ) - coeff = 1 - r0_mag/a - - if a > 0 # Elliptical - ΔM = ΔE_new = √(μ) / sqrt(a^3) * t/2 - ΔE = 1000 - while abs(ΔE - ΔE_new) > 1e-10 - ΔE = ΔE_new - F = ΔE - ΔM + σ0 / √(a) * (1-cos(ΔE)) - coeff * sin(ΔE) - dF = 1 + σ0 / √(a) * sin(ΔE) - coeff * cos(ΔE) - d2F = σ0 / √(a) * cos(ΔE) + coeff * sin(ΔE) - sign = dF >= 0 ? 1 : -1 - ΔE_new = ΔE - 5*F / ( dF + sign * √(abs((5-1)^2*dF^2 - 5*(5-1)*F*d2F ))) - end - F = 1 - a/r0_mag * (1-cos(ΔE)) - G = a * σ0/ √(μ) * (1-cos(ΔE)) + r0_mag * √(a) / √(μ) * sin(ΔE) - r = a + (r0_mag - a) * cos(ΔE) + σ0 * √(a) * sin(ΔE) - Ft = -√(a)*√(μ) / (r*r0_mag) * sin(ΔE) - Gt = 1 - a/r * (1-cos(ΔE)) - else # Hyperbolic or Parabolic - ΔN = √(μ) / sqrt(-a^3) * t/2 - ΔH = 0 - ΔH_new = t/2 < 0 ? -1 : 1 - while abs(ΔH - ΔH_new) > 1e-10 - ΔH = ΔH_new - F = -ΔN - ΔH + σ0 / √(-a) * (cos(ΔH)-1) + coeff * sin(ΔH) - dF = -1 + σ0 / √(-a) * sin(ΔH) + coeff * cos(ΔH) - d2F = σ0 / √(-a) * cos(ΔH) + coeff * sin(ΔH) - sign = dF >= 0 ? 1 : -1 - ΔH_new = ΔH - 5*F / ( dF + sign * √(abs((5-1)^2*dF^2 - 5*(5-1)*F*d2F ))) - end - F = 1 - a/r0_mag * (1-cos(ΔH)) - G = a * σ0/ √(μ) * (1-cos(ΔH)) + r0_mag * √(-a) / √(μ) * sin(ΔH) - r = a + (r0_mag - a) * cos(ΔH) + σ0 * √(-a) * sin(ΔH) - Ft = -√(-a)*√(μ) / (r*r0_mag) * sin(ΔH) - Gt = 1 - a/r * (1-cos(ΔH)) - end - - return [F*[x,y,z] + G*[dx,dy,dz]; Ft*[x,y,z] + Gt*[dx,dy,dz]] - -end \ No newline at end of file diff --git a/julia/src/plotting.jl b/julia/src/plotting.jl index 129fdf0..fea4c5c 100644 --- a/julia/src/plotting.jl +++ b/julia/src/plotting.jl @@ -38,6 +38,12 @@ function plot_orbits(paths::Vector{Vector{Vector{Float64}}}; 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 limit = max(maximum(abs.(flatten(flatten(paths)))), maximum(abs.(flatten(ps)))) * 1.1 @@ -48,15 +54,16 @@ function plot_orbits(paths::Vector{Vector{Vector{Float64}}}; showscale=false, colorscale = p_colors[primary]) - layout = Layout(;title=title, - width=1000, - height=600, - paper_bgcolor="#222529", - scene = attr(xaxis = attr(autorange = false,range=[-limit,limit]), - yaxis = attr(autorange = false,range=[-limit,limit]), - zaxis = attr(autorange = false,range=[-limit,limit]), - aspectratio=attr(x=1,y=1,z=1), - aspectmode="manual")) + layout = Layout(title=title, + width=1000, + height=600, + paper_bgcolor="rgba(5,10,40,1.0)", + plot_bgcolor="rgba(100,100,100,0.01)", + scene = attr(xaxis = attr(autorange = false,range=[-limit,limit]), + yaxis = attr(autorange = false,range=[-limit,limit]), + zaxis = attr(autorange = false,range=[-limit,limit]), + aspectratio=attr(x=1,y=1,z=1), + aspectmode="manual")) p = Plot([t1...,t2],layout) plot(p) diff --git a/julia/src/spacecraft.jl b/julia/src/spacecraft.jl index 711da0f..fbffbca 100644 --- a/julia/src/spacecraft.jl +++ b/julia/src/spacecraft.jl @@ -1,6 +1,6 @@ export Sc -struct Sc{T <: Real} - mass::T +mutable struct Sc + dry_mass::Float64 mass_flow_rate::Float64 max_thrust::Float64 num_thrusters::Int @@ -8,11 +8,17 @@ struct Sc{T <: Real} end function Sc(name::String) + # This has extra thrusters to make plots more visible (and most don't use fuel) if name == "test" - return Sc(10000., 0.01, 0.05, 2, 1.) + return Sc(9000., 0.00025/(2000*0.00981), 0.00025, 50, 0.9) + # This is the normal one + elseif name == "bepi" + return Sc(9000., 2*0.00025/(2000*0.00981), 0.00025, 2, 0.9) elseif name == "no_thrust" - return Sc(10000., 0.01, 0., 0, 0.) + return Sc(9000., 0.01, 0., 0, 0.) else throw(ErrorException("Bad sc name")) end end + +Base.copy(s::Sc) = Sc(s.dry_mass, s.mass_flow_rate, s.max_thrust, s.num_thrusters, s.duty_cycle) diff --git a/julia/test/inner_loop/find_closest.jl b/julia/test/inner_loop/find_closest.jl index 24fafd7..199377e 100644 --- a/julia/test/inner_loop/find_closest.jl +++ b/julia/test/inner_loop/find_closest.jl @@ -6,45 +6,45 @@ # Initial Setup sc = Sc("test") + fresh_sc = copy(sc) a = rand(25000:1.:40000) e = rand(0.01:0.01:0.05) i = rand(0.01:0.01:π/6) T = 2π*√(a^3/μs["Earth"]) - prop_time = T - n = 10 + prop_time = 5T + n = 200 # A simple orbit raising - start = oe_to_xyz([ a, e, i, 0., 0., 0. ], μs["Earth"]) - Tx, Ty, Tz = conv_T(repeat([0.6], n), repeat([0.], n), repeat([0.], n), + start_mass = 10_000. + start = [ oe_to_xyz([ a, e, i, 0., 0., 0. ], μs["Earth"]); start_mass ] + Tx, Ty, Tz = conv_T(repeat([0.9], n), repeat([0.], n), repeat([0.], n), start, - sc.mass, sc, prop_time, μs["Earth"]) - final = prop(hcat(Tx, Ty, Tz), start, sc, μs["Earth"], prop_time)[3] + final = prop(hcat(Tx, Ty, Tz), start, copy(sc), μs["Earth"], prop_time)[2] new_T = 2π*√(xyz_to_oe(final, μs["Earth"])[1]^3/μs["Earth"]) - # This should be close enough to 0.6 for convergence - Tx, Ty, Tz = conv_T(repeat([0.59], n), repeat([0.01], n), repeat([0.], n), + # This should be close enough to converge + Tx, Ty, Tz = conv_T(repeat([0.89], n), repeat([0.], n), repeat([0.], n), start, - sc.mass, sc, prop_time, μs["Earth"]) result = nlp_solve(start, final, sc, μs["Earth"], 0.0, prop_time, hcat(Tx, Ty, Tz)) # Test and plot - @test converged(result) + @test result.converged path1 = prop(zeros((100,3)), start, sc, μs["Earth"], T)[1] - path2, mass, calc_final = prop(tanh.(result.zero), start, sc, μs["Earth"], prop_time) + path2, calc_final = prop(result.zero, start, sc, μs["Earth"], prop_time) path3 = prop(zeros((100,3)), calc_final, sc, μs["Earth"], new_T)[1] - path4 = prop(zeros((100,3)), final, sc, μs["Earth"], new_T)[1] + path4 = prop(zeros((100,3)), final, fresh_sc, μs["Earth"], new_T)[1] savefig(plot_orbits([path1, path2, path3, path4], labels=["initial", "transit", "after transit", "final"], colors=["#FFFFFF","#FF4444","#44FF44","#4444FF"]), "../plots/find_closest_test.html") - if converged(result) - @test norm(calc_final - final) < 1e-4 + if result.converged + @test norm(calc_final[1:6] - final[1:6]) < 1e-4 end end diff --git a/julia/test/inner_loop/inner_loop.jl b/julia/test/inner_loop/inner_loop.jl index 7913d59..8cfc023 100644 --- a/julia/test/inner_loop/inner_loop.jl +++ b/julia/test/inner_loop/inner_loop.jl @@ -2,28 +2,73 @@ println("Testing Inner Loop") - using Dates + using Dates, SPICE, PlotlyJS sc = Sc("test") - phase1 = Phase("Earth", - "Mars", - 3600*24*365*1.85, - [1., 0.3, 0.], - [3., 3., 0.]) + launch_date = DateTime(2016,3,28) + launch_j2000 = utc2et(Dates.format(launch_date,"yyyy-mm-ddTHH:MM:SS")) + leg1_tof = 3600*24*30*10.75 + leg2_tof = 3600*24*365*5.5 + v∞s = [ [0.34251772594197877, -2.7344726708862477, -1.1854707164631975], + [-1.1, -3., -2.6], + [3.5, 3.5, √(5^2 - 2*3.5^2)], + [0.3, 1., 0.] ] + p1 = "Mars" + p2 = "Saturn" + start_mass = 10_000. - phase2 = Phase("Mars", - "Jupiter", - 3600*24*365*3.5, - [2., 3.7416573867739413, 0.], - [0.3, 1., 0.]) + phase1 = Phase("Earth", p1, leg1_tof, v∞s[1], v∞s[2]) + phase2 = Phase(p1, p2, leg2_tof, v∞s[3], v∞s[4]) - result = inner_loop(DateTime(2024,3,5), - sc, - [phase1, phase2], - verbose=true, - mbh_specs=(5,50,100)) + # For finding the best trajectories + earth_state = [spkssb(Thesis.ids["Earth"], launch_j2000, "J2000"); start_mass] + p1_state = [spkssb(Thesis.ids[p1], launch_j2000+leg1_tof, "J2000"); start_mass] + p2_state = [spkssb(Thesis.ids[p2], launch_j2000+leg1_tof+leg2_tof, "J2000"); start_mass] + earth = prop(zeros(100,3), earth_state, sc, μs["Sun"], 3600*24*365.)[1] + p1_path = prop(zeros(100,3), p1_state, sc, μs["Sun"], 3600*24*365*2.)[1] + p2_path = prop(zeros(100,3), p2_state, sc, μs["Sun"], 3600*24*365*8.)[1] + leg1, leg1_final = prop(zeros(400,3), + earth_state+[zeros(3);v∞s[1];start_mass], + copy(sc), + μs["Sun"], + leg1_tof) + leg2, leg2_final = prop(zeros(400,3), + p1_state+[zeros(3);v∞s[3];start_mass], + copy(sc), + μs["Sun"], + leg2_tof) + savefig(plot_orbits( [earth, p1_path, p2_path, leg1, leg2], + primary="Sun", + labels=["Earth", p1, p2, "Leg 1", "Leg 2"], + title="Inner Loop without thrusting", + colors=["#0000FF", "#FF0000", "#FFFF00", "#FF00FF", "#00FFFF"] ), + "../plots/inner_loop_before.html") - @test result == "One path did not converge" + # The first leg should be valid + fresh_sc = copy(sc) + mass, thrusts = inner_loop( launch_date, + sc, + start_mass, + [phase1], + verbose=true, + mbh_specs=(25,50) ) + @test sc.mass > sc.dry_mass + path, final = prop(thrusts[1], earth_state+[zeros(3);v∞s[1]], fresh_sc, μs["Sun"], leg1_tof) + @test final ≈ p1_state + [zeros(3); v∞s[2]] + savefig(plot_orbits( [earth, p1_path, path], + primary="Sun", + labels=["Earth", p1, "Leg 1"], + title="Inner Loop with thrusting", + colors=["#0000FF", "#FF0000", "#FF00FF"] ), + "../plots/inner_loop_after.html") + + # The second one was too hard to figure out on my own, so I'm letting it fail + @test_throws ErrorException inner_loop( launch_date, + sc, + start_mass, + [phase1, phase2], + verbose=true, + mbh_specs=(10,10) ) end diff --git a/julia/test/inner_loop/monotonic_basin_hopping.jl b/julia/test/inner_loop/monotonic_basin_hopping.jl index d53233e..bb330fc 100644 --- a/julia/test/inner_loop/monotonic_basin_hopping.jl +++ b/julia/test/inner_loop/monotonic_basin_hopping.jl @@ -1,28 +1,27 @@ @testset "Monotonic Basin Hopping" begin - using PlotlyJS, NLsolve + using PlotlyJS, NLsolve, Dates println("Testing Monotonic Basin Hopper") # Initial Setup sc = Sc("test") - a = rand(15000:1.:40000) + a = rand(50_000:1.:100_000) e = rand(0.01:0.01:0.5) i = rand(0.01:0.01:π/6) T = 2π*√(a^3/μs["Earth"]) - prop_time = 0.75T - n = 10 + prop_time = 0.5T + n = 20 + start_mass = 10_000. # A simple orbit raising - start = oe_to_xyz([ a, e, i, 0., 0., 0. ], μs["Earth"]) -# T_craft = hcat(repeat([0.6], n), repeat([0.], n), repeat([0.], n)) + start = [oe_to_xyz([ a, e, i, 0., 0., 0. ], μs["Earth"]); start_mass] Tx, Ty, Tz = conv_T(repeat([0.8], n), repeat([0.], n), repeat([0.], n), start, - sc.mass, sc, prop_time, μs["Earth"]) - nominal_path, normal_mass, final = prop(hcat(Tx, Ty, Tz), start, sc, μs["Earth"], prop_time) + nominal_path, final = prop(hcat(Tx, Ty, Tz), start, sc, μs["Earth"], prop_time) new_T = 2π*√(xyz_to_oe(final, μs["Earth"])[1]^3/μs["Earth"]) # Find the best solution @@ -33,14 +32,13 @@ 0.0, prop_time, n, - num_iters=5, - search_patience_lim=50, - drill_patience_lim=100, + search_patience_lim=25, + drill_patience_lim=50, verbose=true) # Test and plot - @test converged(best) - transit, best_masses, calc_final = prop(tanh.(best.zero), start, sc, μs["Earth"], prop_time) + @test best.converged + transit, calc_final = prop(best.zero, start, sc, μs["Earth"], prop_time) initial_path = prop(zeros((100,3)), start, sc, μs["Earth"], T)[1] after_transit = prop(zeros((100,3)), calc_final, sc, μs["Earth"], new_T)[1] final_path = prop(zeros((100,3)), final, sc, μs["Earth"], new_T)[1] @@ -53,21 +51,62 @@ colors=["#FFFFFF", "#FF4444","#44FF44","#4444FF"]), "../plots/mbh_best.html") i = 0 - best_mass = best_masses[end] - nominal_mass = normal_mass[end] + best_mass = calc_final[end] + nominal_mass = final[end] masses = [] for candidate in archive - @test converged(candidate) - path2, cand_ms, calc_final = prop(tanh.(candidate.zero), start, sc, μs["Earth"], prop_time) - push!(masses, cand_ms[end]) - @test norm(calc_final - final) < 1e-4 + @test candidate.converged + path2, calc_final = prop(candidate.zero, start, sc, μs["Earth"], prop_time) + push!(masses, calc_final[end]) + @test norm(calc_final[1:6] - final[1:6]) < 1e-4 end @test best_mass == maximum(masses) # This won't always work since the test is reduced in fidelity, # but hopefully will usually work: - @test (sc.mass - best_mass) < 1.1 * (sc.mass - nominal_mass) - @show best_mass - @show nominal_mass + @test (start_mass - best_mass) < 1.1 * (start_mass - nominal_mass) + + # Now let's test a sun case. This should be pretty close to begin with + launch_date = DateTime(2016,3,28) + launch_j2000 = utc2et(Dates.format(launch_date,"yyyy-mm-ddTHH:MM:SS")) + earth_start = [spkssb(ids["Earth"], launch_j2000, "J2000"); 1e5] + earth_speed = earth_start[4:6] + v∞ = 3.0*earth_speed/norm(earth_speed) + start = earth_start + [zeros(3); v∞; 0.0] + final = [1.62914115303947e8, 1.33709639408102e8, 5.690490452749867e7, -16.298522963602757, 15.193294491415365, 6.154820267250081, 1.0001e8] + tof = 3600*24*30*10.75 + a = xyz_to_oe(final, μs["Sun"])[1] + T = 2π*√(a^3/μs["Sun"]) + n = 20 + + # But we'll plot to see + beginning_path = prop(zeros(100,3), start, Sc("test"), μs["Sun"], tof)[1] + ending_path = prop(zeros(100,3), final, Sc("test"), μs["Sun"], T)[1] + savefig(plot_orbits([beginning_path, ending_path], + labels=["initial", "ending"], + colors=["#F2F", "#2F2"]), + "../plots/mbh_sun_initial.html") + + # Now we solve and plot the new case + best, archive = mbh(start, + final, + Sc("test"), + μs["Sun"], + 0.0, + tof, + n, + search_patience_lim=25, + drill_patience_lim=50, + verbose=true) + solved_path, solved_state = prop(best.zero, start, Sc("test"), μs["Sun"], tof) + ending_path = prop(zeros(100,3), final, Sc("test"), μs["Sun"], T)[1] + savefig(plot_orbits([solved_path, ending_path], + labels=["best", "ending"], + colors=["#C2F", "#2F2"]), + "../plots/mbh_sun_solved.html") + + # We'll just make sure that this at least converged correctly + @test norm(solved_state[1:6] - final[1:6]) < 1e-4 + end diff --git a/julia/test/inner_loop/propagator.jl b/julia/test/inner_loop/propagator.jl index d66f0c1..595ad68 100644 --- a/julia/test/inner_loop/propagator.jl +++ b/julia/test/inner_loop/propagator.jl @@ -5,37 +5,27 @@ using Thesis: prop_one # Set up - start = oe_to_xyz([ (μs["Earth"]*(rand(3600*1.5:0.01:3600*4)/(2π))^2)^(1/3), - rand(0.01:0.01:0.5), - rand(0.01:0.01:0.45π), - 0., - 0., - 1. ], μs["Earth"]) + start_mass = 10_000. + start = [oe_to_xyz([ (μs["Earth"]*(rand(3600*1.5:0.01:3600*4)/(2π))^2)^(1/3), + rand(0.01:0.01:0.5), + rand(0.01:0.01:0.45π), + 0., + 0., + 1. ], μs["Earth"]); start_mass] stepsize = rand(100.0:0.01:500.0) - # Test that Laguerre-Conway is the default propagator - propped = prop_one([0., 0., 0.], start, 0., 0, 0., 1000., 0.1, μs["Earth"], stepsize) - @test laguerre_conway(start, μs["Earth"], stepsize) ≈ propped[1] - # Test that Laguerre-Conway is the default propagator for spacecrafts craft = Sc("no_thrust") - start_mass = craft.mass - state, craft = prop_one([0., 0., 0.], start, craft, μs["Earth"], stepsize) - @test laguerre_conway(start, μs["Earth"], stepsize) ≈ state - @test craft.mass == start_mass + state = prop_one([0., 0., 0.], start, craft, μs["Earth"], stepsize) + @test laguerre_conway(start, μs["Earth"], stepsize) ≈ state[1:6] + @test state[7] == start_mass # Test that mass is reduced properly craft = Sc("test") - start_mass = craft.mass - state, craft = prop_one([1., 0., 0.], start, craft, μs["Earth"], stepsize) - @test craft.mass == start_mass - craft.mass_flow_rate*stepsize + state = prop_one([1., 0., 0.], start, craft, μs["Earth"], stepsize) + @test state[7] == start_mass - craft.mass_flow_rate*stepsize # Test that a bad ΔV throws an error - # craft = Sc("test") - # start_mass = craft.mass - # @test_throws ErrorException prop_one([1.5, 0., 0.], start, craft, μs["Earth"], stepsize) - - # Test that a full propagation doesn't take too long - + @test_throws ErrorException prop_one([1.5, 0., 0.], start, craft, μs["Earth"], stepsize) end diff --git a/julia/test/plotting.jl b/julia/test/plotting.jl index e369885..46b93f6 100644 --- a/julia/test/plotting.jl +++ b/julia/test/plotting.jl @@ -7,15 +7,16 @@ # First some setup sc = Sc("test") T = rand(3600*2:0.01:3600*4) - start = oe_to_xyz([ (μs["Earth"]*(T/(2π))^2)^(1/3), - 0.1, - π/4, - 0., - 0., - 1. ], μs["Earth"]) - n = 100 - ΔVs = repeat([0.5, 0., 0.]', outer=(n,1)) - path = prop(ΔVs, start, sc, μs["Earth"], 3T)[1] + start = [oe_to_xyz([ (μs["Earth"]*(T/(2π))^2)^(1/3), + 0.1, + π/4, + 0., + 0., + 1. ], μs["Earth"]); 10_000.] + revs = 30 + n = revs*100 + ΔVs = repeat([0.9, 0., 0.]', outer=(n,1)) + path = prop(ΔVs, start, copy(sc), μs["Earth"], revs*T)[1] p = plot_orbits([path]) savefig(p,"../plots/plot_test.html") @test typeof(p) == PlotlyJS.SyncPlot diff --git a/julia/test/spacecraft.jl b/julia/test/spacecraft.jl index 236d8d0..274b764 100644 --- a/julia/test/spacecraft.jl +++ b/julia/test/spacecraft.jl @@ -4,17 +4,25 @@ # Test that the standard spacecraft can be created craft = Sc("test") - @test craft.mass == 10000. - @test craft.mass_flow_rate == 0.01 - @test craft.max_thrust == 0.05 - @test craft.num_thrusters == 2 - @test craft.duty_cycle == 1. + @test craft.dry_mass == 9000. + @test craft.mass_flow_rate == craft.max_thrust/(0.00981*2000) + @test craft.max_thrust == 0.00025 + @test craft.num_thrusters == 50 + @test craft.duty_cycle == 0.9 craft = Sc("no_thrust") - @test craft.mass == 10000. + @test craft.dry_mass == 9000. @test craft.mass_flow_rate == 0.01 @test craft.max_thrust == 0. @test craft.num_thrusters == 0 @test craft.duty_cycle == 0. + + # Test that the standard spacecraft can be copied + new_craft = copy(craft) + @test new_craft.dry_mass == craft.dry_mass + @test new_craft.mass_flow_rate == craft.mass_flow_rate + @test new_craft.max_thrust == craft.max_thrust + @test new_craft.num_thrusters == craft.num_thrusters + @test new_craft.duty_cycle == craft.duty_cycle end