|
| 1 | +using TestItems |
| 2 | + |
| 3 | +@testitem "Evaluator micro-benchmarks: bilinear N=51" begin |
| 4 | + using HarmoniqsBenchmarks, BenchmarkTools, DirectTrajOpt, NamedTrajectories |
| 5 | + using SparseArrays, ExponentialAction, MathOptInterface, Random, Dates, Printf |
| 6 | + const MOI = MathOptInterface |
| 7 | + |
| 8 | + Random.seed!(42) |
| 9 | + N = 51; |
| 10 | + Δt = 0.1; |
| 11 | + u_bound = 0.1; |
| 12 | + ω = 0.1 |
| 13 | + Gx = sparse(Float64[0 0 0 1; 0 0 1 0; 0 -1 0 0; -1 0 0 0]) |
| 14 | + Gy = sparse(Float64[0 -1 0 0; 1 0 0 0; 0 0 0 -1; 0 0 1 0]) |
| 15 | + Gz = sparse(Float64[0 0 1 0; 0 0 0 -1; -1 0 0 0; 0 1 0 0]) |
| 16 | + G(u) = ω * Gz + u[1] * Gx + u[2] * Gy |
| 17 | + |
| 18 | + traj = NamedTrajectory( |
| 19 | + ( |
| 20 | + x = 2rand(4, N) .- 1, |
| 21 | + u = u_bound*(2rand(2, N) .- 1), |
| 22 | + du = randn(2, N), |
| 23 | + ddu = randn(2, N), |
| 24 | + Δt = fill(Δt, N), |
| 25 | + ); |
| 26 | + controls = (:ddu, :Δt), |
| 27 | + timestep = :Δt, |
| 28 | + bounds = (u = u_bound, Δt = (0.01, 0.5)), |
| 29 | + initial = (x = [1.0, 0.0, 0.0, 0.0], u = zeros(2)), |
| 30 | + final = (u = zeros(2),), |
| 31 | + goal = (x = [0.0, 1.0, 0.0, 0.0],), |
| 32 | + ) |
| 33 | + integrators = [ |
| 34 | + BilinearIntegrator(G, :x, :u, traj), |
| 35 | + DerivativeIntegrator(:u, :du, traj), |
| 36 | + DerivativeIntegrator(:du, :ddu, traj), |
| 37 | + ] |
| 38 | + J = QuadraticRegularizer(:u, traj, 1.0) + QuadraticRegularizer(:du, traj, 1.0) |
| 39 | + prob = DirectTrajOptProblem(traj, J, integrators) |
| 40 | + |
| 41 | + evaluator, Z_vec = build_evaluator(prob) |
| 42 | + dims = evaluator_dims(evaluator) |
| 43 | + |
| 44 | + g = zeros(dims.n_constraints) |
| 45 | + grad = zeros(dims.n_variables) |
| 46 | + H = zeros(dims.n_hessian_entries) |
| 47 | + Jac = zeros(dims.n_jacobian_entries) |
| 48 | + sigma = 1.0 |
| 49 | + mu = ones(dims.n_constraints) |
| 50 | + |
| 51 | + benchmarks = Dict{Symbol,EvalBenchmark}( |
| 52 | + :eval_objective => |
| 53 | + trial_to_eval_benchmark(@benchmark(MOI.eval_objective($evaluator, $Z_vec))), |
| 54 | + :eval_gradient => trial_to_eval_benchmark( |
| 55 | + @benchmark(MOI.eval_objective_gradient($evaluator, $grad, $Z_vec)) |
| 56 | + ), |
| 57 | + :eval_constraint => trial_to_eval_benchmark( |
| 58 | + @benchmark(MOI.eval_constraint($evaluator, $g, $Z_vec)) |
| 59 | + ), |
| 60 | + :eval_jacobian => trial_to_eval_benchmark( |
| 61 | + @benchmark(MOI.eval_constraint_jacobian($evaluator, $Jac, $Z_vec)) |
| 62 | + ), |
| 63 | + :eval_hessian_lagrangian => trial_to_eval_benchmark( |
| 64 | + @benchmark(MOI.eval_hessian_lagrangian($evaluator, $H, $Z_vec, $sigma, $mu)) |
| 65 | + ), |
| 66 | + ) |
| 67 | + |
| 68 | + result = MicroBenchmarkResult( |
| 69 | + package = "DirectTrajOpt", |
| 70 | + package_version = "0.8.10", |
| 71 | + commit = ( |
| 72 | + try |
| 73 | + String(strip(read(`git rev-parse --short HEAD`, String))) |
| 74 | + catch |
| 75 | + ; "unknown" |
| 76 | + end |
| 77 | + ), |
| 78 | + benchmark_name = "evaluator_micro_bilinear_N51", |
| 79 | + N = N, |
| 80 | + state_dim = 4, |
| 81 | + control_dim = 2, |
| 82 | + eval_benchmarks = benchmarks, |
| 83 | + julia_version = string(VERSION), |
| 84 | + timestamp = Dates.now(), |
| 85 | + runner = get(ENV, "BENCHMARK_RUNNER", "local"), |
| 86 | + n_threads = Threads.nthreads(), |
| 87 | + ) |
| 88 | + |
| 89 | + println("\n=== Evaluator Micro-benchmarks (bilinear N=$N) ===") |
| 90 | + for (name, eb) in sort(collect(result.eval_benchmarks), by = first) |
| 91 | + @printf( |
| 92 | + " %-25s median: %8.1f ns allocs: %d memory: %d bytes\n", |
| 93 | + name, |
| 94 | + eb.median_ns, |
| 95 | + eb.allocs, |
| 96 | + eb.memory_bytes |
| 97 | + ) |
| 98 | + end |
| 99 | + |
| 100 | + results_dir = joinpath(@__DIR__, "results") |
| 101 | + save_micro_results(results_dir, result.benchmark_name, result) |
| 102 | + println(" Saved to $results_dir/") |
| 103 | +end |
| 104 | + |
| 105 | +@testitem "Ipopt vs MadNLP: bilinear N=51" begin |
| 106 | + using HarmoniqsBenchmarks, DirectTrajOpt, NamedTrajectories |
| 107 | + using SparseArrays, ExponentialAction, Random, Dates |
| 108 | + import MadNLP |
| 109 | + |
| 110 | + const MadNLPSolverExt = [ |
| 111 | + mod for mod in reverse(Base.loaded_modules_order) if Symbol(mod) == :MadNLPSolverExt |
| 112 | + ][1] |
| 113 | + |
| 114 | + function make_bilinear_problem(; seed = 42) |
| 115 | + Random.seed!(seed) |
| 116 | + N = 51; |
| 117 | + Δt = 0.1; |
| 118 | + u_bound = 0.1; |
| 119 | + ω = 0.1 |
| 120 | + Gx = sparse(Float64[0 0 0 1; 0 0 1 0; 0 -1 0 0; -1 0 0 0]) |
| 121 | + Gy = sparse(Float64[0 -1 0 0; 1 0 0 0; 0 0 0 -1; 0 0 1 0]) |
| 122 | + Gz = sparse(Float64[0 0 1 0; 0 0 0 -1; -1 0 0 0; 0 1 0 0]) |
| 123 | + G(u) = ω * Gz + u[1] * Gx + u[2] * Gy |
| 124 | + |
| 125 | + traj = NamedTrajectory( |
| 126 | + ( |
| 127 | + x = 2rand(4, N) .- 1, |
| 128 | + u = u_bound*(2rand(2, N) .- 1), |
| 129 | + du = randn(2, N), |
| 130 | + ddu = randn(2, N), |
| 131 | + Δt = fill(Δt, N), |
| 132 | + ); |
| 133 | + controls = (:ddu, :Δt), |
| 134 | + timestep = :Δt, |
| 135 | + bounds = (u = u_bound, Δt = (0.01, 0.5)), |
| 136 | + initial = (x = [1.0, 0.0, 0.0, 0.0], u = zeros(2)), |
| 137 | + final = (u = zeros(2),), |
| 138 | + goal = (x = [0.0, 1.0, 0.0, 0.0],), |
| 139 | + ) |
| 140 | + integrators = [ |
| 141 | + BilinearIntegrator(G, :x, :u, traj), |
| 142 | + DerivativeIntegrator(:u, :du, traj), |
| 143 | + DerivativeIntegrator(:du, :ddu, traj), |
| 144 | + ] |
| 145 | + J = QuadraticRegularizer(:u, traj, 1.0) + QuadraticRegularizer(:du, traj, 1.0) |
| 146 | + return DirectTrajOptProblem(traj, J, integrators) |
| 147 | + end |
| 148 | + |
| 149 | + prob_ipopt = make_bilinear_problem() |
| 150 | + result_ipopt = benchmark_solve!( |
| 151 | + prob_ipopt, |
| 152 | + IpoptOptions(max_iter = 200, print_level = 0); |
| 153 | + benchmark_name = "bilinear_N51_ipopt", |
| 154 | + ) |
| 155 | + |
| 156 | + prob_madnlp = make_bilinear_problem() |
| 157 | + result_madnlp = benchmark_solve!( |
| 158 | + prob_madnlp, |
| 159 | + MadNLPSolverExt.MadNLPOptions(max_iter = 200, print_level = 1); |
| 160 | + benchmark_name = "bilinear_N51_madnlp", |
| 161 | + ) |
| 162 | + |
| 163 | + println("\n=== Ipopt vs MadNLP: bilinear N=51 ===") |
| 164 | + println( |
| 165 | + " Ipopt: $(round(result_ipopt.wall_time_s, digits=3))s, $(result_ipopt.total_allocations_bytes ÷ 1024) KB alloc", |
| 166 | + ) |
| 167 | + println( |
| 168 | + " MadNLP: $(round(result_madnlp.wall_time_s, digits=3))s, $(result_madnlp.total_allocations_bytes ÷ 1024) KB alloc", |
| 169 | + ) |
| 170 | + |
| 171 | + results_dir = joinpath(@__DIR__, "results") |
| 172 | + save_results(results_dir, "ipopt_vs_madnlp_N51", [result_ipopt, result_madnlp]) |
| 173 | +end |
| 174 | + |
| 175 | +@testitem "Memory scaling: N and state_dim sweep" begin |
| 176 | + using HarmoniqsBenchmarks, DirectTrajOpt, NamedTrajectories |
| 177 | + using SparseArrays, ExponentialAction, Random, Dates, Printf |
| 178 | + import MadNLP |
| 179 | + |
| 180 | + const MadNLPSolverExt = [ |
| 181 | + mod for mod in reverse(Base.loaded_modules_order) if Symbol(mod) == :MadNLPSolverExt |
| 182 | + ][1] |
| 183 | + |
| 184 | + function make_scaled_problem(; N, state_dim, n_controls = 2, seed = 42) |
| 185 | + Random.seed!(seed) |
| 186 | + G_drift = sparse(randn(state_dim, state_dim)) |
| 187 | + G_drives = [sparse(randn(state_dim, state_dim)) for _ = 1:n_controls] |
| 188 | + G(u) = G_drift + sum(u[i] * G_drives[i] for i = 1:n_controls) |
| 189 | + |
| 190 | + x_init = zeros(state_dim); |
| 191 | + x_init[1] = 1.0 |
| 192 | + x_goal = zeros(state_dim); |
| 193 | + x_goal[min(2, state_dim)] = 1.0 |
| 194 | + |
| 195 | + traj = NamedTrajectory( |
| 196 | + ( |
| 197 | + x = randn(state_dim, N), |
| 198 | + u = 0.1*randn(n_controls, N), |
| 199 | + du = randn(n_controls, N), |
| 200 | + Δt = fill(0.1, N), |
| 201 | + ); |
| 202 | + controls = (:du, :Δt), |
| 203 | + timestep = :Δt, |
| 204 | + bounds = (u = 1.0, Δt = (0.01, 0.5)), |
| 205 | + initial = (x = x_init, u = zeros(n_controls)), |
| 206 | + final = (u = zeros(n_controls),), |
| 207 | + goal = (x = x_goal,), |
| 208 | + ) |
| 209 | + integrators = |
| 210 | + [BilinearIntegrator(G, :x, :u, traj), DerivativeIntegrator(:u, :du, traj)] |
| 211 | + J = QuadraticRegularizer(:u, traj, 1.0) |
| 212 | + return DirectTrajOptProblem(traj, J, integrators) |
| 213 | + end |
| 214 | + |
| 215 | + N_values = [25, 51, 101] |
| 216 | + dim_values = [4, 8, 16] |
| 217 | + results = BenchmarkResult[] |
| 218 | + |
| 219 | + println("\n=== Memory Scaling Study ===") |
| 220 | + @printf( |
| 221 | + " %5s | %5s | %12s | %12s | %12s | %12s\n", |
| 222 | + "N", |
| 223 | + "dim", |
| 224 | + "Ipopt (s)", |
| 225 | + "Ipopt (KB)", |
| 226 | + "MadNLP (s)", |
| 227 | + "MadNLP (KB)" |
| 228 | + ) |
| 229 | + @printf( |
| 230 | + " %5s-+-%5s-+-%12s-+-%12s-+-%12s-+-%12s\n", |
| 231 | + "-"^5, |
| 232 | + "-"^5, |
| 233 | + "-"^12, |
| 234 | + "-"^12, |
| 235 | + "-"^12, |
| 236 | + "-"^12 |
| 237 | + ) |
| 238 | + |
| 239 | + for N in N_values |
| 240 | + for dim in dim_values |
| 241 | + prob = make_scaled_problem(; N = N, state_dim = dim) |
| 242 | + r_ipopt = benchmark_solve!( |
| 243 | + prob, |
| 244 | + IpoptOptions(max_iter = 50, print_level = 0); |
| 245 | + benchmark_name = "scaling_N$(N)_d$(dim)_ipopt", |
| 246 | + ) |
| 247 | + push!(results, r_ipopt) |
| 248 | + |
| 249 | + prob = make_scaled_problem(; N = N, state_dim = dim) |
| 250 | + r_madnlp = benchmark_solve!( |
| 251 | + prob, |
| 252 | + MadNLPSolverExt.MadNLPOptions(max_iter = 50, print_level = 1); |
| 253 | + benchmark_name = "scaling_N$(N)_d$(dim)_madnlp", |
| 254 | + ) |
| 255 | + push!(results, r_madnlp) |
| 256 | + |
| 257 | + @printf( |
| 258 | + " %5d | %5d | %12.3f | %12d | %12.3f | %12d\n", |
| 259 | + N, |
| 260 | + dim, |
| 261 | + r_ipopt.wall_time_s, |
| 262 | + r_ipopt.total_allocations_bytes ÷ 1024, |
| 263 | + r_madnlp.wall_time_s, |
| 264 | + r_madnlp.total_allocations_bytes ÷ 1024 |
| 265 | + ) |
| 266 | + end |
| 267 | + end |
| 268 | + |
| 269 | + results_dir = joinpath(@__DIR__, "results") |
| 270 | + save_results(results_dir, "memory_scaling", results) |
| 271 | + println("\n Saved $(length(results)) results to $results_dir/") |
| 272 | +end |
0 commit comments