From 859c634bbfaf287ea85ae98048a838b61881e029 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Fri, 3 Oct 2025 13:41:57 +0530 Subject: [PATCH 01/11] preallocated coordinate format vectors --- src/julia_interface.jl | 98 +++++++++++++++++++++++++++++--------- src/model.jl | 37 ++++++++++++++ test/test_comprehensive.jl | 76 +++++++++++++++++++++++++++++ 3 files changed, 188 insertions(+), 23 deletions(-) create mode 100644 test/test_comprehensive.jl diff --git a/src/julia_interface.jl b/src/julia_interface.jl index 3187ca0d..fefd1738 100644 --- a/src/julia_interface.jl +++ b/src/julia_interface.jl @@ -2,6 +2,42 @@ export cons_coord, cons_coord!, consjac using NLPModels, SparseArrays +# Type conversion helpers for Issue #392: Preallocate more vectors +""" + prepare_input!(workspace::Vector{T}, x::AbstractVector{S}) where {T, S} + +Prepare input vector `x` for use with CUTEst functions, using preallocated workspace +to avoid allocations when type conversion is needed. + +Returns the input vector directly if no conversion is needed, or the workspace vector +with converted values if type conversion is required. +""" +@inline function prepare_input!(workspace::Vector{T}, x::AbstractVector{S}) where {T, S} + if S === T && typeof(x) <: Vector{T} + return x # No conversion needed + else + resize!(workspace, length(x)) + workspace .= x + return workspace + end +end + +""" + prepare_output!(workspace::Vector{T}, target::AbstractVector{S}, source::AbstractVector{T}) where {T, S} + +Prepare output by copying from source to target, using workspace for type conversion if needed. +""" +@inline function prepare_output!(workspace::Vector{T}, target::AbstractVector{S}, source::AbstractVector{T}) where {T, S} + if S === T && typeof(target) <: Vector{T} + target .= source + else + resize!(workspace, length(source)) + workspace .= source + target .= workspace + end + return target +end + function NLPModels.obj(nlp::CUTEstModel{T}, x::StrideOneVector{T}) where {T} @lencheck nlp.meta.nvar x if nlp.meta.ncon > 0 @@ -40,10 +76,17 @@ end function NLPModels.grad!(nlp::CUTEstModel{T}, x::AbstractVector, g::AbstractVector) where {T} @lencheck nlp.meta.nvar x g - x_ = Vector{T}(x) - g_ = Vector{T}(g) - grad!(nlp, x_, g_) - g .= g_ + + # Use type conversion helpers to avoid allocations (Issue #392) + x_prepared = prepare_input!(nlp.input_workspace, x) + + if typeof(g) <: Vector{T} + grad!(nlp, x_prepared, g) + else + resize!(nlp.output_workspace, length(g)) + grad!(nlp, x_prepared, view(nlp.output_workspace, 1:length(g))) + g .= view(nlp.output_workspace, 1:length(g)) + end end function NLPModels.objcons!( @@ -105,13 +148,15 @@ end function NLPModels.objgrad!(nlp::CUTEstModel{T}, x::AbstractVector, g::StrideOneVector{T}) where {T} @lencheck nlp.meta.nvar x g - objgrad!(nlp, Vector{T}(x), g) + x_prepared = prepare_input!(nlp.input_workspace, x) + objgrad!(nlp, x_prepared, g) end function NLPModels.objgrad!(nlp::CUTEstModel{T}, x::AbstractVector, g::AbstractVector) where {T} @lencheck nlp.meta.nvar x g + x_prepared = prepare_input!(nlp.input_workspace, x) gc = nlp.workspace_nvar - f, _ = objgrad!(nlp, Vector{T}(x), gc) + f, _ = objgrad!(nlp, x_prepared, gc) g .= gc return f, g end @@ -179,15 +224,15 @@ function cons_coord!( @lencheck nlp.meta.nvar x @lencheck nlp.meta.ncon c @lencheck nlp.meta.nnzj rows cols vals - rows_ = Vector{Cint}(undef, nlp.meta.nnzj) - cols_ = Vector{Cint}(undef, nlp.meta.nnzj) - vals_ = Vector{T}(undef, nlp.meta.nnzj) - c_ = Vector{T}(undef, nlp.meta.ncon) - cons_coord!(nlp, Vector{T}(x), c_, rows_, cols_, vals_) - rows .= rows_ - cols .= cols_ - vals .= vals_ - c .= c_ + + # Use preallocated vectors instead of allocating new ones (Issue #392) + cons_coord!(nlp, Vector{T}(x), nlp.cons_vals, nlp.jac_coord_rows, nlp.jac_coord_cols, nlp.jac_coord_vals) + + # Copy results to output vectors + rows .= nlp.jac_coord_rows + cols .= nlp.jac_coord_cols + vals .= nlp.jac_coord_vals + c .= nlp.cons_vals return c, rows, cols, vals end @@ -209,11 +254,17 @@ Usage: """ function cons_coord(nlp::CUTEstModel{T}, x::StrideOneVector{T}) where {T} @lencheck nlp.meta.nvar x - c = Vector{T}(undef, nlp.meta.ncon) - rows = Vector{Cint}(undef, nlp.meta.nnzj) - cols = Vector{Cint}(undef, nlp.meta.nnzj) - vals = Vector{T}(undef, nlp.meta.nnzj) - cons_coord!(nlp, x, c, rows, cols, vals) + + # Use preallocated vectors to avoid allocations (Issue #392) + cons_coord!(nlp, x, nlp.cons_vals, nlp.jac_coord_rows, nlp.jac_coord_cols, nlp.jac_coord_vals) + + # Return copies of the results to maintain API compatibility + c = copy(nlp.cons_vals) + rows = copy(nlp.jac_coord_rows) + cols = copy(nlp.jac_coord_cols) + vals = copy(nlp.jac_coord_vals) + + return c, rows, cols, vals end function cons_coord(nlp::CUTEstModel{T}, x::AbstractVector) where {T} @@ -630,9 +681,10 @@ function NLPModels.hess_coord!( @lencheck nlp.meta.nvar x @lencheck nlp.meta.ncon y @lencheck nlp.meta.nnzh vals - vals_ = Vector{T}(undef, nlp.meta.nnzh) - NLPModels.hess_coord!(nlp, Vector{T}(x), convert(Vector{T}, y), vals_, obj_weight = obj_weight) - vals .= vals_ + + # Use preallocated vector instead of allocating (Issue #392) + NLPModels.hess_coord!(nlp, Vector{T}(x), convert(Vector{T}, y), nlp.hess_coord_vals, obj_weight = obj_weight) + vals .= nlp.hess_coord_vals return vals end diff --git a/src/model.jl b/src/model.jl index 02728eb9..9b7057ea 100644 --- a/src/model.jl +++ b/src/model.jl @@ -15,6 +15,20 @@ mutable struct CUTEstModel{T} <: AbstractNLPModel{T, Vector{T}} workspace_nvar::Vector{T} workspace_ncon::Vector{T} + # Preallocated coordinate format vectors + jac_coord_rows::Vector{Cint} # nnzj elements for Jacobian row indices + jac_coord_cols::Vector{Cint} # nnzj elements for Jacobian column indices + jac_coord_vals::Vector{T} # nnzj elements for Jacobian values + hess_coord_vals::Vector{T} # nnzh elements for Hessian values + + # Preallocated constraint evaluation vectors + cons_vals::Vector{T} # ncon elements for constraint values + cons_nln_vals::Vector{T} # nnln elements for nonlinear constraints subset + + # Type conversion workspace vectors + input_workspace::Vector{T} # nvar elements for input conversion + output_workspace::Vector{T} # max(nvar, ncon) elements for output conversion + Jval::Vector{T} Jvar::Vector{Cint} @@ -297,6 +311,21 @@ function CUTEstModel{T}( workspace_nvar = Vector{T}(undef, nvar) workspace_ncon = Vector{T}(undef, ncon) + # Preallocate new coordinate format vectors (Issue #392) + jac_coord_rows = Vector{Cint}(undef, nnzj) + jac_coord_cols = Vector{Cint}(undef, nnzj) + jac_coord_vals = Vector{T}(undef, nnzj) + hess_coord_vals = Vector{T}(undef, nnzh) + + # Preallocate constraint evaluation vectors + cons_vals = Vector{T}(undef, ncon) + nnln = count(.!linear) # Number of nonlinear constraints + cons_nln_vals = Vector{T}(undef, nnln) + + # Preallocate type conversion workspace vectors + input_workspace = Vector{T}(undef, nvar) + output_workspace = Vector{T}(undef, max(nvar, ncon)) + fclose(T, libsif, funit, status) cutest_error(status[]) @@ -330,6 +359,14 @@ function CUTEstModel{T}( clinvals, workspace_nvar, workspace_ncon, + jac_coord_rows, + jac_coord_cols, + jac_coord_vals, + hess_coord_vals, + cons_vals, + cons_nln_vals, + input_workspace, + output_workspace, Jval, Jvar, libsif, diff --git a/test/test_comprehensive.jl b/test/test_comprehensive.jl new file mode 100644 index 00000000..e7acb693 --- /dev/null +++ b/test/test_comprehensive.jl @@ -0,0 +1,76 @@ +using CUTEst +using BenchmarkTools +using LinearAlgebra +using NLPModels + +# Test with an unconstrained problem first +println("Testing with unconstrained problem (ROSENBR):") +nlp1 = CUTEstModel{Float64}("ROSENBR") +x0 = nlp1.meta.x0 + +# Test hess_coord allocations +println("✓ Problem loaded: $(nlp1.meta.name) ($(nlp1.meta.nvar) vars, $(nlp1.meta.ncon) cons)") +vals_h = Vector{Float64}(undef, nlp1.meta.nnzh) +hess_coord!(nlp1, x0, vals_h) + +b_hess = @benchmark hess_coord!($nlp1, $x0, $vals_h) samples=100 evals=5 +println("✓ hess_coord!: $(b_hess.allocs) allocations, $(BenchmarkTools.prettytime(median(b_hess).time))") + +finalize(nlp1) + +# Test with a constrained problem +println("\nTesting with constrained problem (HS6):") +nlp2 = CUTEstModel{Float64}("HS6") +x0 = nlp2.meta.x0 + +println("✓ Problem loaded: $(nlp2.meta.name) ($(nlp2.meta.nvar) vars, $(nlp2.meta.ncon) cons)") +println(" nnzj: $(nlp2.meta.nnzj), nnzh: $(nlp2.meta.nnzh)") + +# Test basic functionality +f = obj(nlp2, x0) +g = similar(x0) +grad!(nlp2, x0, g) +println("✓ obj/grad: f = $(round(f, digits=6)), ||g|| = $(round(norm(g), digits=6))") + +# Test constraint functions +c = Vector{Float64}(undef, nlp2.meta.ncon) +cons!(nlp2, x0, c) +println("✓ constraints: ||c|| = $(round(norm(c), digits=6))") + +# Test cons_coord - this should show major allocation improvements +println("\nTesting cons_coord allocation improvements:") +c1, rows1, cols1, vals1 = cons_coord(nlp2, x0) + +# Benchmark cons_coord +b_cons = @benchmark cons_coord($nlp2, $x0) samples=100 evals=5 +println("✓ cons_coord: $(b_cons.allocs) allocations, $(BenchmarkTools.prettytime(median(b_cons).time))") +println(" Memory: $(BenchmarkTools.prettymemory(median(b_cons).memory))") +println(" Returned $(length(vals1)) Jacobian elements") + +# Test cons_coord! +rows = Vector{Cint}(undef, nlp2.meta.nnzj) +cols = Vector{Cint}(undef, nlp2.meta.nnzj) +vals = Vector{Float64}(undef, nlp2.meta.nnzj) +c_out = Vector{Float64}(undef, nlp2.meta.ncon) + +b_cons_inplace = @benchmark cons_coord!($nlp2, $x0, $c_out, $rows, $cols, $vals) samples=100 evals=5 +println("✓ cons_coord!: $(b_cons_inplace.allocs) allocations, $(BenchmarkTools.prettytime(median(b_cons_inplace).time))") + +# Test type conversion +println("\nTesting type conversion improvements:") +x0_f32 = Float32.(x0) +g_f32 = Vector{Float32}(undef, nlp2.meta.nvar) + +b_grad_conv = @benchmark grad!($nlp2, $x0_f32, $g_f32) samples=100 evals=5 +println("✓ grad! with Float32->Float64 conversion: $(b_grad_conv.allocs) allocations") +println(" Time: $(BenchmarkTools.prettytime(median(b_grad_conv).time))") + +# Test hess_coord with constraints +vals_h2 = Vector{Float64}(undef, nlp2.meta.nnzh) +y = zeros(nlp2.meta.ncon) +hess_coord!(nlp2, x0, y, vals_h2) + +b_hess2 = @benchmark hess_coord!($nlp2, $x0, $y, $vals_h2) samples=100 evals=5 +println("✓ hess_coord! (constrained): $(b_hess2.allocs) allocations, $(BenchmarkTools.prettytime(median(b_hess2).time))") + +finalize(nlp2) From 050276c00ca9d12f19835f1b159424cbe4f8b8ad Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Fri, 3 Oct 2025 16:05:41 +0530 Subject: [PATCH 02/11] do --- src/julia_interface.jl | 62 ++++++++--- test/test_double_buffering.jl | 187 ++++++++++++++++++++++++++++++++++ 2 files changed, 234 insertions(+), 15 deletions(-) create mode 100644 test/test_double_buffering.jl diff --git a/src/julia_interface.jl b/src/julia_interface.jl index fefd1738..03abe35b 100644 --- a/src/julia_interface.jl +++ b/src/julia_interface.jl @@ -225,14 +225,27 @@ function cons_coord!( @lencheck nlp.meta.ncon c @lencheck nlp.meta.nnzj rows cols vals - # Use preallocated vectors instead of allocating new ones (Issue #392) - cons_coord!(nlp, Vector{T}(x), nlp.cons_vals, nlp.jac_coord_rows, nlp.jac_coord_cols, nlp.jac_coord_vals) + # Resize workspace vectors on demand if needed (Issue #392 - double buffering) + nnzj = nlp.meta.nnzj + if length(nlp.jac_coord_rows) < nnzj + resize!(nlp.jac_coord_rows, nnzj) + resize!(nlp.jac_coord_cols, nnzj) + resize!(nlp.jac_coord_vals, nnzj) + end + if length(nlp.cons_vals) < nlp.meta.ncon + resize!(nlp.cons_vals, nlp.meta.ncon) + end + + # Use preallocated vectors instead of allocating new ones + cons_coord!(nlp, Vector{T}(x), view(nlp.cons_vals, 1:nlp.meta.ncon), + view(nlp.jac_coord_rows, 1:nnzj), view(nlp.jac_coord_cols, 1:nnzj), + view(nlp.jac_coord_vals, 1:nnzj)) # Copy results to output vectors - rows .= nlp.jac_coord_rows - cols .= nlp.jac_coord_cols - vals .= nlp.jac_coord_vals - c .= nlp.cons_vals + rows .= view(nlp.jac_coord_rows, 1:nnzj) + cols .= view(nlp.jac_coord_cols, 1:nnzj) + vals .= view(nlp.jac_coord_vals, 1:nnzj) + c .= view(nlp.cons_vals, 1:nlp.meta.ncon) return c, rows, cols, vals end @@ -255,14 +268,27 @@ Usage: function cons_coord(nlp::CUTEstModel{T}, x::StrideOneVector{T}) where {T} @lencheck nlp.meta.nvar x - # Use preallocated vectors to avoid allocations (Issue #392) - cons_coord!(nlp, x, nlp.cons_vals, nlp.jac_coord_rows, nlp.jac_coord_cols, nlp.jac_coord_vals) + # Resize workspace vectors on demand if needed (Issue #392 - double buffering) + nnzj = nlp.meta.nnzj + if length(nlp.jac_coord_rows) < nnzj + resize!(nlp.jac_coord_rows, nnzj) + resize!(nlp.jac_coord_cols, nnzj) + resize!(nlp.jac_coord_vals, nnzj) + end + if length(nlp.cons_vals) < nlp.meta.ncon + resize!(nlp.cons_vals, nlp.meta.ncon) + end + + # Use preallocated vectors to avoid allocations + cons_coord!(nlp, x, view(nlp.cons_vals, 1:nlp.meta.ncon), + view(nlp.jac_coord_rows, 1:nnzj), view(nlp.jac_coord_cols, 1:nnzj), + view(nlp.jac_coord_vals, 1:nnzj)) # Return copies of the results to maintain API compatibility - c = copy(nlp.cons_vals) - rows = copy(nlp.jac_coord_rows) - cols = copy(nlp.jac_coord_cols) - vals = copy(nlp.jac_coord_vals) + c = copy(view(nlp.cons_vals, 1:nlp.meta.ncon)) + rows = copy(view(nlp.jac_coord_rows, 1:nnzj)) + cols = copy(view(nlp.jac_coord_cols, 1:nnzj)) + vals = copy(view(nlp.jac_coord_vals, 1:nnzj)) return c, rows, cols, vals end @@ -682,9 +708,15 @@ function NLPModels.hess_coord!( @lencheck nlp.meta.ncon y @lencheck nlp.meta.nnzh vals - # Use preallocated vector instead of allocating (Issue #392) - NLPModels.hess_coord!(nlp, Vector{T}(x), convert(Vector{T}, y), nlp.hess_coord_vals, obj_weight = obj_weight) - vals .= nlp.hess_coord_vals + # Resize workspace vector on demand if needed (Issue #392 - double buffering) + if length(nlp.hess_coord_vals) < nlp.meta.nnzh + resize!(nlp.hess_coord_vals, nlp.meta.nnzh) + end + + # Use preallocated vector instead of allocating + NLPModels.hess_coord!(nlp, Vector{T}(x), convert(Vector{T}, y), + view(nlp.hess_coord_vals, 1:nlp.meta.nnzh), obj_weight = obj_weight) + vals .= view(nlp.hess_coord_vals, 1:nlp.meta.nnzh) return vals end diff --git a/test/test_double_buffering.jl b/test/test_double_buffering.jl new file mode 100644 index 00000000..5485c406 --- /dev/null +++ b/test/test_double_buffering.jl @@ -0,0 +1,187 @@ +using Test +using CUTEst +using NLPModels + +@testset "Double Buffering Tests" begin + # Test with a simple problem that has constraints and Jacobian + nlp = CUTEstModel("HS9") + + try + # Test initial state - workspace vectors should start empty or be resized on demand + x = [1.0, 1.0] + + # Test cons_coord! function + c = zeros(nlp.meta.ncon) + rows = zeros(Int, nlp.meta.nnzj) + cols = zeros(Int, nlp.meta.nnzj) + vals = zeros(nlp.meta.nnzj) + + # This should trigger workspace allocation/resizing + @test_nowarn cons_coord!(nlp, x, c, rows, cols, vals) + + # Verify results are reasonable + @test length(c) == nlp.meta.ncon + @test length(rows) == nlp.meta.nnzj + @test length(cols) == nlp.meta.nnzj + @test length(vals) == nlp.meta.nnzj + + # Test cons_coord function (should reuse workspace) + c2, rows2, cols2, vals2 = cons_coord(nlp, x) + + @test c ≈ c2 + @test rows == rows2 + @test cols == cols2 + @test vals ≈ vals2 + + # Test with different vector types + x_view = view([1.0, 1.0, 0.0], 1:2) + c3, rows3, cols3, vals3 = cons_coord(nlp, x_view) + + @test c2 ≈ c3 + @test rows2 == rows3 + @test cols2 == cols3 + @test vals2 ≈ vals3 + + # Test hess_coord! function if problem has Hessian + if nlp.meta.nnzh > 0 + hvals = zeros(nlp.meta.nnzh) + y = zeros(nlp.meta.ncon) + + @test_nowarn NLPModels.hess_coord!(nlp, x, y, hvals) + @test length(hvals) == nlp.meta.nnzh + + # Test with different y vector - use larger multipliers to ensure difference + y2 = 10.0 * ones(nlp.meta.ncon) + hvals2 = zeros(nlp.meta.nnzh) + @test_nowarn NLPModels.hess_coord!(nlp, x, y2, hvals2) + + # Test with zero objective weight to isolate constraint Hessian + hvals3 = zeros(nlp.meta.nnzh) + @test_nowarn NLPModels.hess_coord!(nlp, x, y2, hvals3; obj_weight = 0.0) + + # At least one of these should be different unless all constraint Hessians are zero + different_found = !(hvals ≈ hvals2) || !(hvals ≈ hvals3) || !(hvals2 ≈ hvals3) + @test different_found || all(abs.(hvals3) .< 1e-12) # Either different or constraint Hessians are essentially zero + end + + finally + finalize(nlp) + end +end + +@testset "Memory Efficiency Tests" begin + # Test that workspace vectors are only allocated when needed + nlp = CUTEstModel("HS9") + + try + x = [1.0, 1.0] + + # First, check if workspace vectors exist + has_jac_workspace = hasfield(typeof(nlp), :jac_coord_rows) && + hasfield(typeof(nlp), :jac_coord_cols) && + hasfield(typeof(nlp), :jac_coord_vals) && + hasfield(typeof(nlp), :cons_vals) + + if has_jac_workspace + println("Model has jacobian workspace vectors") + else + println("Model does not have jacobian workspace vectors - workspace not implemented yet") + end + + # Check that multiple calls don't cause excessive allocations + initial_allocations = @allocated begin + c, rows, cols, vals = cons_coord(nlp, x) + end + + # Second call should have minimal allocations due to workspace reuse (if implemented) + second_allocations = @allocated begin + c2, rows2, cols2, vals2 = cons_coord(nlp, x) + end + + # Third call to verify consistency + third_allocations = @allocated begin + c3, rows3, cols3, vals3 = cons_coord(nlp, x) + end + + println("Initial allocations: $initial_allocations bytes") + println("Second allocations: $second_allocations bytes") + println("Third allocations: $third_allocations bytes") + + if has_jac_workspace + # The current implementation might still allocate for return values + # Test that allocations are reasonable and consistent + @test second_allocations <= initial_allocations # Should not increase + @test third_allocations == second_allocations # Should be consistent + + # Test that workspace reuse is working by checking if internal calls allocate less + # This tests the cons_coord! function directly which should use workspace + c_test = zeros(nlp.meta.ncon) + rows_test = zeros(Int, nlp.meta.nnzj) + cols_test = zeros(Int, nlp.meta.nnzj) + vals_test = zeros(nlp.meta.nnzj) + + # First call to cons_coord! to initialize workspace + cons_coord!(nlp, x, c_test, rows_test, cols_test, vals_test) + + # Second call should have minimal allocations + workspace_allocations = @allocated begin + cons_coord!(nlp, x, c_test, rows_test, cols_test, vals_test) + end + + println("Direct cons_coord! allocations (after warmup): $workspace_allocations bytes") + + # Direct workspace usage should have very low allocations + @test workspace_allocations < initial_allocations + + else + # If workspace not implemented yet, allocations will be similar + # Test that function still works correctly + @test second_allocations >= 0 # Just ensure it doesn't error + @test initial_allocations > 0 # Ensure some allocation happened + end + + finally + finalize(nlp) + end +end + +@testset "Workspace Vector Initialization" begin + # Test that the model has the required workspace vectors + nlp = CUTEstModel("HS9") + + try + # Print field names for debugging + field_names = fieldnames(typeof(nlp)) + println("CUTEstModel fields: ", field_names) + + # Check if workspace vectors exist + jac_workspace_fields = [:jac_coord_rows, :jac_coord_cols, :jac_coord_vals, :cons_vals] + hess_workspace_fields = [:hess_coord_vals] + + for field in jac_workspace_fields + if field in field_names + println("✓ Found workspace field: $field") + @test isdefined(nlp, field) + if isdefined(nlp, field) + workspace_vec = getfield(nlp, field) + @test isa(workspace_vec, Vector) + println(" - Type: $(typeof(workspace_vec)), Length: $(length(workspace_vec))") + end + else + println("✗ Missing workspace field: $field") + end + end + + for field in hess_workspace_fields + if field in field_names + println("✓ Found hessian workspace field: $field") + @test isdefined(nlp, field) + else + println("✗ Missing hessian workspace field: $field") + end + end + + finally + finalize(nlp) + end +end From db50341325f31f88b6bfe235658c7e2f8e05a4b3 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Fri, 3 Oct 2025 16:06:29 +0530 Subject: [PATCH 03/11] Delete test/test_double_buffering.jl --- test/test_double_buffering.jl | 187 ---------------------------------- 1 file changed, 187 deletions(-) delete mode 100644 test/test_double_buffering.jl diff --git a/test/test_double_buffering.jl b/test/test_double_buffering.jl deleted file mode 100644 index 5485c406..00000000 --- a/test/test_double_buffering.jl +++ /dev/null @@ -1,187 +0,0 @@ -using Test -using CUTEst -using NLPModels - -@testset "Double Buffering Tests" begin - # Test with a simple problem that has constraints and Jacobian - nlp = CUTEstModel("HS9") - - try - # Test initial state - workspace vectors should start empty or be resized on demand - x = [1.0, 1.0] - - # Test cons_coord! function - c = zeros(nlp.meta.ncon) - rows = zeros(Int, nlp.meta.nnzj) - cols = zeros(Int, nlp.meta.nnzj) - vals = zeros(nlp.meta.nnzj) - - # This should trigger workspace allocation/resizing - @test_nowarn cons_coord!(nlp, x, c, rows, cols, vals) - - # Verify results are reasonable - @test length(c) == nlp.meta.ncon - @test length(rows) == nlp.meta.nnzj - @test length(cols) == nlp.meta.nnzj - @test length(vals) == nlp.meta.nnzj - - # Test cons_coord function (should reuse workspace) - c2, rows2, cols2, vals2 = cons_coord(nlp, x) - - @test c ≈ c2 - @test rows == rows2 - @test cols == cols2 - @test vals ≈ vals2 - - # Test with different vector types - x_view = view([1.0, 1.0, 0.0], 1:2) - c3, rows3, cols3, vals3 = cons_coord(nlp, x_view) - - @test c2 ≈ c3 - @test rows2 == rows3 - @test cols2 == cols3 - @test vals2 ≈ vals3 - - # Test hess_coord! function if problem has Hessian - if nlp.meta.nnzh > 0 - hvals = zeros(nlp.meta.nnzh) - y = zeros(nlp.meta.ncon) - - @test_nowarn NLPModels.hess_coord!(nlp, x, y, hvals) - @test length(hvals) == nlp.meta.nnzh - - # Test with different y vector - use larger multipliers to ensure difference - y2 = 10.0 * ones(nlp.meta.ncon) - hvals2 = zeros(nlp.meta.nnzh) - @test_nowarn NLPModels.hess_coord!(nlp, x, y2, hvals2) - - # Test with zero objective weight to isolate constraint Hessian - hvals3 = zeros(nlp.meta.nnzh) - @test_nowarn NLPModels.hess_coord!(nlp, x, y2, hvals3; obj_weight = 0.0) - - # At least one of these should be different unless all constraint Hessians are zero - different_found = !(hvals ≈ hvals2) || !(hvals ≈ hvals3) || !(hvals2 ≈ hvals3) - @test different_found || all(abs.(hvals3) .< 1e-12) # Either different or constraint Hessians are essentially zero - end - - finally - finalize(nlp) - end -end - -@testset "Memory Efficiency Tests" begin - # Test that workspace vectors are only allocated when needed - nlp = CUTEstModel("HS9") - - try - x = [1.0, 1.0] - - # First, check if workspace vectors exist - has_jac_workspace = hasfield(typeof(nlp), :jac_coord_rows) && - hasfield(typeof(nlp), :jac_coord_cols) && - hasfield(typeof(nlp), :jac_coord_vals) && - hasfield(typeof(nlp), :cons_vals) - - if has_jac_workspace - println("Model has jacobian workspace vectors") - else - println("Model does not have jacobian workspace vectors - workspace not implemented yet") - end - - # Check that multiple calls don't cause excessive allocations - initial_allocations = @allocated begin - c, rows, cols, vals = cons_coord(nlp, x) - end - - # Second call should have minimal allocations due to workspace reuse (if implemented) - second_allocations = @allocated begin - c2, rows2, cols2, vals2 = cons_coord(nlp, x) - end - - # Third call to verify consistency - third_allocations = @allocated begin - c3, rows3, cols3, vals3 = cons_coord(nlp, x) - end - - println("Initial allocations: $initial_allocations bytes") - println("Second allocations: $second_allocations bytes") - println("Third allocations: $third_allocations bytes") - - if has_jac_workspace - # The current implementation might still allocate for return values - # Test that allocations are reasonable and consistent - @test second_allocations <= initial_allocations # Should not increase - @test third_allocations == second_allocations # Should be consistent - - # Test that workspace reuse is working by checking if internal calls allocate less - # This tests the cons_coord! function directly which should use workspace - c_test = zeros(nlp.meta.ncon) - rows_test = zeros(Int, nlp.meta.nnzj) - cols_test = zeros(Int, nlp.meta.nnzj) - vals_test = zeros(nlp.meta.nnzj) - - # First call to cons_coord! to initialize workspace - cons_coord!(nlp, x, c_test, rows_test, cols_test, vals_test) - - # Second call should have minimal allocations - workspace_allocations = @allocated begin - cons_coord!(nlp, x, c_test, rows_test, cols_test, vals_test) - end - - println("Direct cons_coord! allocations (after warmup): $workspace_allocations bytes") - - # Direct workspace usage should have very low allocations - @test workspace_allocations < initial_allocations - - else - # If workspace not implemented yet, allocations will be similar - # Test that function still works correctly - @test second_allocations >= 0 # Just ensure it doesn't error - @test initial_allocations > 0 # Ensure some allocation happened - end - - finally - finalize(nlp) - end -end - -@testset "Workspace Vector Initialization" begin - # Test that the model has the required workspace vectors - nlp = CUTEstModel("HS9") - - try - # Print field names for debugging - field_names = fieldnames(typeof(nlp)) - println("CUTEstModel fields: ", field_names) - - # Check if workspace vectors exist - jac_workspace_fields = [:jac_coord_rows, :jac_coord_cols, :jac_coord_vals, :cons_vals] - hess_workspace_fields = [:hess_coord_vals] - - for field in jac_workspace_fields - if field in field_names - println("✓ Found workspace field: $field") - @test isdefined(nlp, field) - if isdefined(nlp, field) - workspace_vec = getfield(nlp, field) - @test isa(workspace_vec, Vector) - println(" - Type: $(typeof(workspace_vec)), Length: $(length(workspace_vec))") - end - else - println("✗ Missing workspace field: $field") - end - end - - for field in hess_workspace_fields - if field in field_names - println("✓ Found hessian workspace field: $field") - @test isdefined(nlp, field) - else - println("✗ Missing hessian workspace field: $field") - end - end - - finally - finalize(nlp) - end -end From 696d17fb026459872cb687be65c7408b6862c49b Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Thu, 16 Oct 2025 03:07:22 +0530 Subject: [PATCH 04/11] api reference documentation --- docs/src/api.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 docs/src/api.md diff --git a/docs/src/api.md b/docs/src/api.md new file mode 100644 index 00000000..29b9c935 --- /dev/null +++ b/docs/src/api.md @@ -0,0 +1,6 @@ +# API Reference + +```@docs +CUTEst.prepare_input! +CUTEst.prepare_output! +``` From 321052a5486c064cd848a32f491aed38d6f99b2c Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Sun, 19 Oct 2025 04:16:31 +0530 Subject: [PATCH 05/11] make docs --- docs/make.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/make.jl b/docs/make.jl index 9f718d43..a0c98d8b 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -18,6 +18,7 @@ makedocs( "Classification of SIF problems" => "classification.md", "Using CUTEst core functions" => "core.md", "Reference" => "reference.md", + "API Reference" => "api.md", ], ) From 154be07fecebd8ee95b8e10b1a352bc1789b1b55 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Fri, 24 Oct 2025 00:28:04 +0530 Subject: [PATCH 06/11] Update julia_interface.jl --- src/julia_interface.jl | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/julia_interface.jl b/src/julia_interface.jl index 03abe35b..d9d945ca 100644 --- a/src/julia_interface.jl +++ b/src/julia_interface.jl @@ -2,7 +2,6 @@ export cons_coord, cons_coord!, consjac using NLPModels, SparseArrays -# Type conversion helpers for Issue #392: Preallocate more vectors """ prepare_input!(workspace::Vector{T}, x::AbstractVector{S}) where {T, S} @@ -14,7 +13,7 @@ with converted values if type conversion is required. """ @inline function prepare_input!(workspace::Vector{T}, x::AbstractVector{S}) where {T, S} if S === T && typeof(x) <: Vector{T} - return x # No conversion needed + return x else resize!(workspace, length(x)) workspace .= x @@ -77,7 +76,6 @@ end function NLPModels.grad!(nlp::CUTEstModel{T}, x::AbstractVector, g::AbstractVector) where {T} @lencheck nlp.meta.nvar x g - # Use type conversion helpers to avoid allocations (Issue #392) x_prepared = prepare_input!(nlp.input_workspace, x) if typeof(g) <: Vector{T} @@ -225,7 +223,6 @@ function cons_coord!( @lencheck nlp.meta.ncon c @lencheck nlp.meta.nnzj rows cols vals - # Resize workspace vectors on demand if needed (Issue #392 - double buffering) nnzj = nlp.meta.nnzj if length(nlp.jac_coord_rows) < nnzj resize!(nlp.jac_coord_rows, nnzj) @@ -236,12 +233,10 @@ function cons_coord!( resize!(nlp.cons_vals, nlp.meta.ncon) end - # Use preallocated vectors instead of allocating new ones cons_coord!(nlp, Vector{T}(x), view(nlp.cons_vals, 1:nlp.meta.ncon), view(nlp.jac_coord_rows, 1:nnzj), view(nlp.jac_coord_cols, 1:nnzj), view(nlp.jac_coord_vals, 1:nnzj)) - # Copy results to output vectors rows .= view(nlp.jac_coord_rows, 1:nnzj) cols .= view(nlp.jac_coord_cols, 1:nnzj) vals .= view(nlp.jac_coord_vals, 1:nnzj) @@ -268,7 +263,6 @@ Usage: function cons_coord(nlp::CUTEstModel{T}, x::StrideOneVector{T}) where {T} @lencheck nlp.meta.nvar x - # Resize workspace vectors on demand if needed (Issue #392 - double buffering) nnzj = nlp.meta.nnzj if length(nlp.jac_coord_rows) < nnzj resize!(nlp.jac_coord_rows, nnzj) @@ -279,12 +273,10 @@ function cons_coord(nlp::CUTEstModel{T}, x::StrideOneVector{T}) where {T} resize!(nlp.cons_vals, nlp.meta.ncon) end - # Use preallocated vectors to avoid allocations cons_coord!(nlp, x, view(nlp.cons_vals, 1:nlp.meta.ncon), view(nlp.jac_coord_rows, 1:nnzj), view(nlp.jac_coord_cols, 1:nnzj), view(nlp.jac_coord_vals, 1:nnzj)) - # Return copies of the results to maintain API compatibility c = copy(view(nlp.cons_vals, 1:nlp.meta.ncon)) rows = copy(view(nlp.jac_coord_rows, 1:nnzj)) cols = copy(view(nlp.jac_coord_cols, 1:nnzj)) From 325b7a72ca451a856c26bf7ce25e9b5e6e828b1e Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Fri, 24 Oct 2025 00:29:35 +0530 Subject: [PATCH 07/11] Update model.jl --- src/model.jl | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/model.jl b/src/model.jl index 9b7057ea..0b181832 100644 --- a/src/model.jl +++ b/src/model.jl @@ -15,19 +15,16 @@ mutable struct CUTEstModel{T} <: AbstractNLPModel{T, Vector{T}} workspace_nvar::Vector{T} workspace_ncon::Vector{T} - # Preallocated coordinate format vectors - jac_coord_rows::Vector{Cint} # nnzj elements for Jacobian row indices - jac_coord_cols::Vector{Cint} # nnzj elements for Jacobian column indices - jac_coord_vals::Vector{T} # nnzj elements for Jacobian values - hess_coord_vals::Vector{T} # nnzh elements for Hessian values + jac_coord_rows::Vector{Cint} + jac_coord_cols::Vector{Cint} + jac_coord_vals::Vector{T} + hess_coord_vals::Vector{T} - # Preallocated constraint evaluation vectors - cons_vals::Vector{T} # ncon elements for constraint values - cons_nln_vals::Vector{T} # nnln elements for nonlinear constraints subset + cons_vals::Vector{T} + cons_nln_vals::Vector{T} - # Type conversion workspace vectors - input_workspace::Vector{T} # nvar elements for input conversion - output_workspace::Vector{T} # max(nvar, ncon) elements for output conversion + input_workspace::Vector{T} + output_workspace::Vector{T} Jval::Vector{T} Jvar::Vector{Cint} From 044c7874435c3234181fbe60621568c440bc4279 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Fri, 24 Oct 2025 00:30:35 +0530 Subject: [PATCH 08/11] Update julia_interface.jl --- src/julia_interface.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/julia_interface.jl b/src/julia_interface.jl index d9d945ca..716cf6cc 100644 --- a/src/julia_interface.jl +++ b/src/julia_interface.jl @@ -700,12 +700,10 @@ function NLPModels.hess_coord!( @lencheck nlp.meta.ncon y @lencheck nlp.meta.nnzh vals - # Resize workspace vector on demand if needed (Issue #392 - double buffering) if length(nlp.hess_coord_vals) < nlp.meta.nnzh resize!(nlp.hess_coord_vals, nlp.meta.nnzh) end - # Use preallocated vector instead of allocating NLPModels.hess_coord!(nlp, Vector{T}(x), convert(Vector{T}, y), view(nlp.hess_coord_vals, 1:nlp.meta.nnzh), obj_weight = obj_weight) vals .= view(nlp.hess_coord_vals, 1:nlp.meta.nnzh) From 90f943d731a395027d684bd571fef6070c459784 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Fri, 24 Oct 2025 00:32:06 +0530 Subject: [PATCH 09/11] Update model.jl --- src/model.jl | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/model.jl b/src/model.jl index 0b181832..c16712c5 100644 --- a/src/model.jl +++ b/src/model.jl @@ -308,18 +308,15 @@ function CUTEstModel{T}( workspace_nvar = Vector{T}(undef, nvar) workspace_ncon = Vector{T}(undef, ncon) - # Preallocate new coordinate format vectors (Issue #392) jac_coord_rows = Vector{Cint}(undef, nnzj) jac_coord_cols = Vector{Cint}(undef, nnzj) jac_coord_vals = Vector{T}(undef, nnzj) hess_coord_vals = Vector{T}(undef, nnzh) - # Preallocate constraint evaluation vectors cons_vals = Vector{T}(undef, ncon) - nnln = count(.!linear) # Number of nonlinear constraints + nnln = count(.!linear) cons_nln_vals = Vector{T}(undef, nnln) - # Preallocate type conversion workspace vectors input_workspace = Vector{T}(undef, nvar) output_workspace = Vector{T}(undef, max(nvar, ncon)) From 2c03812773a0fe6f41b064a50bb23f4af3fd277d Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Fri, 24 Oct 2025 00:33:31 +0530 Subject: [PATCH 10/11] Update print statements for consistency in tests --- test/test_comprehensive.jl | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/test/test_comprehensive.jl b/test/test_comprehensive.jl index e7acb693..4bdea31a 100644 --- a/test/test_comprehensive.jl +++ b/test/test_comprehensive.jl @@ -8,13 +8,12 @@ println("Testing with unconstrained problem (ROSENBR):") nlp1 = CUTEstModel{Float64}("ROSENBR") x0 = nlp1.meta.x0 -# Test hess_coord allocations -println("✓ Problem loaded: $(nlp1.meta.name) ($(nlp1.meta.nvar) vars, $(nlp1.meta.ncon) cons)") +println("Problem loaded: $(nlp1.meta.name) ($(nlp1.meta.nvar) vars, $(nlp1.meta.ncon) cons)") vals_h = Vector{Float64}(undef, nlp1.meta.nnzh) hess_coord!(nlp1, x0, vals_h) b_hess = @benchmark hess_coord!($nlp1, $x0, $vals_h) samples=100 evals=5 -println("✓ hess_coord!: $(b_hess.allocs) allocations, $(BenchmarkTools.prettytime(median(b_hess).time))") +println("hess_coord!: $(b_hess.allocs) allocations, $(BenchmarkTools.prettytime(median(b_hess).time))") finalize(nlp1) @@ -23,19 +22,19 @@ println("\nTesting with constrained problem (HS6):") nlp2 = CUTEstModel{Float64}("HS6") x0 = nlp2.meta.x0 -println("✓ Problem loaded: $(nlp2.meta.name) ($(nlp2.meta.nvar) vars, $(nlp2.meta.ncon) cons)") -println(" nnzj: $(nlp2.meta.nnzj), nnzh: $(nlp2.meta.nnzh)") +println("Problem loaded: $(nlp2.meta.name) ($(nlp2.meta.nvar) vars, $(nlp2.meta.ncon) cons)") +println("nnzj: $(nlp2.meta.nnzj), nnzh: $(nlp2.meta.nnzh)") # Test basic functionality f = obj(nlp2, x0) g = similar(x0) grad!(nlp2, x0, g) -println("✓ obj/grad: f = $(round(f, digits=6)), ||g|| = $(round(norm(g), digits=6))") +println("obj/grad: f = $(round(f, digits=6)), ||g|| = $(round(norm(g), digits=6))") # Test constraint functions c = Vector{Float64}(undef, nlp2.meta.ncon) cons!(nlp2, x0, c) -println("✓ constraints: ||c|| = $(round(norm(c), digits=6))") +println("constraints: ||c|| = $(round(norm(c), digits=6))") # Test cons_coord - this should show major allocation improvements println("\nTesting cons_coord allocation improvements:") @@ -43,9 +42,9 @@ c1, rows1, cols1, vals1 = cons_coord(nlp2, x0) # Benchmark cons_coord b_cons = @benchmark cons_coord($nlp2, $x0) samples=100 evals=5 -println("✓ cons_coord: $(b_cons.allocs) allocations, $(BenchmarkTools.prettytime(median(b_cons).time))") -println(" Memory: $(BenchmarkTools.prettymemory(median(b_cons).memory))") -println(" Returned $(length(vals1)) Jacobian elements") +println("cons_coord: $(b_cons.allocs) allocations, $(BenchmarkTools.prettytime(median(b_cons).time))") +println("Memory: $(BenchmarkTools.prettymemory(median(b_cons).memory))") +println("Returned $(length(vals1)) Jacobian elements") # Test cons_coord! rows = Vector{Cint}(undef, nlp2.meta.nnzj) @@ -54,7 +53,7 @@ vals = Vector{Float64}(undef, nlp2.meta.nnzj) c_out = Vector{Float64}(undef, nlp2.meta.ncon) b_cons_inplace = @benchmark cons_coord!($nlp2, $x0, $c_out, $rows, $cols, $vals) samples=100 evals=5 -println("✓ cons_coord!: $(b_cons_inplace.allocs) allocations, $(BenchmarkTools.prettytime(median(b_cons_inplace).time))") +println("cons_coord!: $(b_cons_inplace.allocs) allocations, $(BenchmarkTools.prettytime(median(b_cons_inplace).time))") # Test type conversion println("\nTesting type conversion improvements:") @@ -62,8 +61,8 @@ x0_f32 = Float32.(x0) g_f32 = Vector{Float32}(undef, nlp2.meta.nvar) b_grad_conv = @benchmark grad!($nlp2, $x0_f32, $g_f32) samples=100 evals=5 -println("✓ grad! with Float32->Float64 conversion: $(b_grad_conv.allocs) allocations") -println(" Time: $(BenchmarkTools.prettytime(median(b_grad_conv).time))") +println("grad! with Float32->Float64 conversion: $(b_grad_conv.allocs) allocations") +println("Time: $(BenchmarkTools.prettytime(median(b_grad_conv).time))") # Test hess_coord with constraints vals_h2 = Vector{Float64}(undef, nlp2.meta.nnzh) @@ -71,6 +70,6 @@ y = zeros(nlp2.meta.ncon) hess_coord!(nlp2, x0, y, vals_h2) b_hess2 = @benchmark hess_coord!($nlp2, $x0, $y, $vals_h2) samples=100 evals=5 -println("✓ hess_coord! (constrained): $(b_hess2.allocs) allocations, $(BenchmarkTools.prettytime(median(b_hess2).time))") +println("hess_coord! (constrained): $(b_hess2.allocs) allocations, $(BenchmarkTools.prettytime(median(b_hess2).time))") finalize(nlp2) From 0066a3b96824e95e0a8f49672b59b1bbe19036e1 Mon Sep 17 00:00:00 2001 From: Arnav Kapoor Date: Fri, 24 Oct 2025 20:18:42 +0530 Subject: [PATCH 11/11] documentation changes --- docs/make.jl | 1 - docs/src/api.md | 6 ---- docs/src/reference.md | 2 ++ src/julia_interface.jl | 59 +++++++++++++++++++++++++------------- src/model.jl | 8 +++--- test/test_comprehensive.jl | 16 ++++++++--- 6 files changed, 57 insertions(+), 35 deletions(-) delete mode 100644 docs/src/api.md diff --git a/docs/make.jl b/docs/make.jl index a0c98d8b..9f718d43 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -18,7 +18,6 @@ makedocs( "Classification of SIF problems" => "classification.md", "Using CUTEst core functions" => "core.md", "Reference" => "reference.md", - "API Reference" => "api.md", ], ) diff --git a/docs/src/api.md b/docs/src/api.md deleted file mode 100644 index 29b9c935..00000000 --- a/docs/src/api.md +++ /dev/null @@ -1,6 +0,0 @@ -# API Reference - -```@docs -CUTEst.prepare_input! -CUTEst.prepare_output! -``` diff --git a/docs/src/reference.md b/docs/src/reference.md index c9990d96..044090f7 100644 --- a/docs/src/reference.md +++ b/docs/src/reference.md @@ -9,4 +9,6 @@ CUTEst.cons_coord CUTEst.cons_coord! CUTEst.consjac +CUTEst.prepare_input! +CUTEst.prepare_output! ``` diff --git a/src/julia_interface.jl b/src/julia_interface.jl index 716cf6cc..c448e57c 100644 --- a/src/julia_interface.jl +++ b/src/julia_interface.jl @@ -26,7 +26,11 @@ end Prepare output by copying from source to target, using workspace for type conversion if needed. """ -@inline function prepare_output!(workspace::Vector{T}, target::AbstractVector{S}, source::AbstractVector{T}) where {T, S} +@inline function prepare_output!( + workspace::Vector{T}, + target::AbstractVector{S}, + source::AbstractVector{T}, +) where {T, S} if S === T && typeof(target) <: Vector{T} target .= source else @@ -75,9 +79,9 @@ end function NLPModels.grad!(nlp::CUTEstModel{T}, x::AbstractVector, g::AbstractVector) where {T} @lencheck nlp.meta.nvar x g - + x_prepared = prepare_input!(nlp.input_workspace, x) - + if typeof(g) <: Vector{T} grad!(nlp, x_prepared, g) else @@ -222,7 +226,7 @@ function cons_coord!( @lencheck nlp.meta.nvar x @lencheck nlp.meta.ncon c @lencheck nlp.meta.nnzj rows cols vals - + nnzj = nlp.meta.nnzj if length(nlp.jac_coord_rows) < nnzj resize!(nlp.jac_coord_rows, nnzj) @@ -232,11 +236,16 @@ function cons_coord!( if length(nlp.cons_vals) < nlp.meta.ncon resize!(nlp.cons_vals, nlp.meta.ncon) end - - cons_coord!(nlp, Vector{T}(x), view(nlp.cons_vals, 1:nlp.meta.ncon), - view(nlp.jac_coord_rows, 1:nnzj), view(nlp.jac_coord_cols, 1:nnzj), - view(nlp.jac_coord_vals, 1:nnzj)) - + + cons_coord!( + nlp, + Vector{T}(x), + view(nlp.cons_vals, 1:nlp.meta.ncon), + view(nlp.jac_coord_rows, 1:nnzj), + view(nlp.jac_coord_cols, 1:nnzj), + view(nlp.jac_coord_vals, 1:nnzj), + ) + rows .= view(nlp.jac_coord_rows, 1:nnzj) cols .= view(nlp.jac_coord_cols, 1:nnzj) vals .= view(nlp.jac_coord_vals, 1:nnzj) @@ -262,7 +271,7 @@ Usage: """ function cons_coord(nlp::CUTEstModel{T}, x::StrideOneVector{T}) where {T} @lencheck nlp.meta.nvar x - + nnzj = nlp.meta.nnzj if length(nlp.jac_coord_rows) < nnzj resize!(nlp.jac_coord_rows, nnzj) @@ -272,16 +281,21 @@ function cons_coord(nlp::CUTEstModel{T}, x::StrideOneVector{T}) where {T} if length(nlp.cons_vals) < nlp.meta.ncon resize!(nlp.cons_vals, nlp.meta.ncon) end - - cons_coord!(nlp, x, view(nlp.cons_vals, 1:nlp.meta.ncon), - view(nlp.jac_coord_rows, 1:nnzj), view(nlp.jac_coord_cols, 1:nnzj), - view(nlp.jac_coord_vals, 1:nnzj)) - + + cons_coord!( + nlp, + x, + view(nlp.cons_vals, 1:nlp.meta.ncon), + view(nlp.jac_coord_rows, 1:nnzj), + view(nlp.jac_coord_cols, 1:nnzj), + view(nlp.jac_coord_vals, 1:nnzj), + ) + c = copy(view(nlp.cons_vals, 1:nlp.meta.ncon)) rows = copy(view(nlp.jac_coord_rows, 1:nnzj)) cols = copy(view(nlp.jac_coord_cols, 1:nnzj)) vals = copy(view(nlp.jac_coord_vals, 1:nnzj)) - + return c, rows, cols, vals end @@ -699,13 +713,18 @@ function NLPModels.hess_coord!( @lencheck nlp.meta.nvar x @lencheck nlp.meta.ncon y @lencheck nlp.meta.nnzh vals - + if length(nlp.hess_coord_vals) < nlp.meta.nnzh resize!(nlp.hess_coord_vals, nlp.meta.nnzh) end - - NLPModels.hess_coord!(nlp, Vector{T}(x), convert(Vector{T}, y), - view(nlp.hess_coord_vals, 1:nlp.meta.nnzh), obj_weight = obj_weight) + + NLPModels.hess_coord!( + nlp, + Vector{T}(x), + convert(Vector{T}, y), + view(nlp.hess_coord_vals, 1:nlp.meta.nnzh), + obj_weight = obj_weight, + ) vals .= view(nlp.hess_coord_vals, 1:nlp.meta.nnzh) return vals end diff --git a/src/model.jl b/src/model.jl index c16712c5..e527a866 100644 --- a/src/model.jl +++ b/src/model.jl @@ -19,10 +19,10 @@ mutable struct CUTEstModel{T} <: AbstractNLPModel{T, Vector{T}} jac_coord_cols::Vector{Cint} jac_coord_vals::Vector{T} hess_coord_vals::Vector{T} - + cons_vals::Vector{T} cons_nln_vals::Vector{T} - + input_workspace::Vector{T} output_workspace::Vector{T} @@ -312,11 +312,11 @@ function CUTEstModel{T}( jac_coord_cols = Vector{Cint}(undef, nnzj) jac_coord_vals = Vector{T}(undef, nnzj) hess_coord_vals = Vector{T}(undef, nnzh) - + cons_vals = Vector{T}(undef, ncon) nnln = count(.!linear) cons_nln_vals = Vector{T}(undef, nnln) - + input_workspace = Vector{T}(undef, nvar) output_workspace = Vector{T}(undef, max(nvar, ncon)) diff --git a/test/test_comprehensive.jl b/test/test_comprehensive.jl index 4bdea31a..b2ea722b 100644 --- a/test/test_comprehensive.jl +++ b/test/test_comprehensive.jl @@ -13,7 +13,9 @@ vals_h = Vector{Float64}(undef, nlp1.meta.nnzh) hess_coord!(nlp1, x0, vals_h) b_hess = @benchmark hess_coord!($nlp1, $x0, $vals_h) samples=100 evals=5 -println("hess_coord!: $(b_hess.allocs) allocations, $(BenchmarkTools.prettytime(median(b_hess).time))") +println( + "hess_coord!: $(b_hess.allocs) allocations, $(BenchmarkTools.prettytime(median(b_hess).time))", +) finalize(nlp1) @@ -42,7 +44,9 @@ c1, rows1, cols1, vals1 = cons_coord(nlp2, x0) # Benchmark cons_coord b_cons = @benchmark cons_coord($nlp2, $x0) samples=100 evals=5 -println("cons_coord: $(b_cons.allocs) allocations, $(BenchmarkTools.prettytime(median(b_cons).time))") +println( + "cons_coord: $(b_cons.allocs) allocations, $(BenchmarkTools.prettytime(median(b_cons).time))", +) println("Memory: $(BenchmarkTools.prettymemory(median(b_cons).memory))") println("Returned $(length(vals1)) Jacobian elements") @@ -53,7 +57,9 @@ vals = Vector{Float64}(undef, nlp2.meta.nnzj) c_out = Vector{Float64}(undef, nlp2.meta.ncon) b_cons_inplace = @benchmark cons_coord!($nlp2, $x0, $c_out, $rows, $cols, $vals) samples=100 evals=5 -println("cons_coord!: $(b_cons_inplace.allocs) allocations, $(BenchmarkTools.prettytime(median(b_cons_inplace).time))") +println( + "cons_coord!: $(b_cons_inplace.allocs) allocations, $(BenchmarkTools.prettytime(median(b_cons_inplace).time))", +) # Test type conversion println("\nTesting type conversion improvements:") @@ -70,6 +76,8 @@ y = zeros(nlp2.meta.ncon) hess_coord!(nlp2, x0, y, vals_h2) b_hess2 = @benchmark hess_coord!($nlp2, $x0, $y, $vals_h2) samples=100 evals=5 -println("hess_coord! (constrained): $(b_hess2.allocs) allocations, $(BenchmarkTools.prettytime(median(b_hess2).time))") +println( + "hess_coord! (constrained): $(b_hess2.allocs) allocations, $(BenchmarkTools.prettytime(median(b_hess2).time))", +) finalize(nlp2)