|
| 1 | +using Test |
| 2 | +using CUTEst |
| 3 | +using NLPModels |
| 4 | + |
| 5 | +@testset "Double Buffering Tests" begin |
| 6 | + # Test with a simple problem that has constraints and Jacobian |
| 7 | + nlp = CUTEstModel("HS9") |
| 8 | + |
| 9 | + try |
| 10 | + # Test initial state - workspace vectors should start empty or be resized on demand |
| 11 | + x = [1.0, 1.0] |
| 12 | + |
| 13 | + # Test cons_coord! function |
| 14 | + c = zeros(nlp.meta.ncon) |
| 15 | + rows = zeros(Int, nlp.meta.nnzj) |
| 16 | + cols = zeros(Int, nlp.meta.nnzj) |
| 17 | + vals = zeros(nlp.meta.nnzj) |
| 18 | + |
| 19 | + # This should trigger workspace allocation/resizing |
| 20 | + @test_nowarn cons_coord!(nlp, x, c, rows, cols, vals) |
| 21 | + |
| 22 | + # Verify results are reasonable |
| 23 | + @test length(c) == nlp.meta.ncon |
| 24 | + @test length(rows) == nlp.meta.nnzj |
| 25 | + @test length(cols) == nlp.meta.nnzj |
| 26 | + @test length(vals) == nlp.meta.nnzj |
| 27 | + |
| 28 | + # Test cons_coord function (should reuse workspace) |
| 29 | + c2, rows2, cols2, vals2 = cons_coord(nlp, x) |
| 30 | + |
| 31 | + @test c ≈ c2 |
| 32 | + @test rows == rows2 |
| 33 | + @test cols == cols2 |
| 34 | + @test vals ≈ vals2 |
| 35 | + |
| 36 | + # Test with different vector types |
| 37 | + x_view = view([1.0, 1.0, 0.0], 1:2) |
| 38 | + c3, rows3, cols3, vals3 = cons_coord(nlp, x_view) |
| 39 | + |
| 40 | + @test c2 ≈ c3 |
| 41 | + @test rows2 == rows3 |
| 42 | + @test cols2 == cols3 |
| 43 | + @test vals2 ≈ vals3 |
| 44 | + |
| 45 | + # Test hess_coord! function if problem has Hessian |
| 46 | + if nlp.meta.nnzh > 0 |
| 47 | + hvals = zeros(nlp.meta.nnzh) |
| 48 | + y = zeros(nlp.meta.ncon) |
| 49 | + |
| 50 | + @test_nowarn NLPModels.hess_coord!(nlp, x, y, hvals) |
| 51 | + @test length(hvals) == nlp.meta.nnzh |
| 52 | + |
| 53 | + # Test with different y vector - use larger multipliers to ensure difference |
| 54 | + y2 = 10.0 * ones(nlp.meta.ncon) |
| 55 | + hvals2 = zeros(nlp.meta.nnzh) |
| 56 | + @test_nowarn NLPModels.hess_coord!(nlp, x, y2, hvals2) |
| 57 | + |
| 58 | + # Test with zero objective weight to isolate constraint Hessian |
| 59 | + hvals3 = zeros(nlp.meta.nnzh) |
| 60 | + @test_nowarn NLPModels.hess_coord!(nlp, x, y2, hvals3; obj_weight = 0.0) |
| 61 | + |
| 62 | + # At least one of these should be different unless all constraint Hessians are zero |
| 63 | + different_found = !(hvals ≈ hvals2) || !(hvals ≈ hvals3) || !(hvals2 ≈ hvals3) |
| 64 | + @test different_found || all(abs.(hvals3) .< 1e-12) # Either different or constraint Hessians are essentially zero |
| 65 | + end |
| 66 | + |
| 67 | + finally |
| 68 | + finalize(nlp) |
| 69 | + end |
| 70 | +end |
| 71 | + |
| 72 | +@testset "Memory Efficiency Tests" begin |
| 73 | + # Test that workspace vectors are only allocated when needed |
| 74 | + nlp = CUTEstModel("HS9") |
| 75 | + |
| 76 | + try |
| 77 | + x = [1.0, 1.0] |
| 78 | + |
| 79 | + # First, check if workspace vectors exist |
| 80 | + has_jac_workspace = hasfield(typeof(nlp), :jac_coord_rows) && |
| 81 | + hasfield(typeof(nlp), :jac_coord_cols) && |
| 82 | + hasfield(typeof(nlp), :jac_coord_vals) && |
| 83 | + hasfield(typeof(nlp), :cons_vals) |
| 84 | + |
| 85 | + if has_jac_workspace |
| 86 | + println("Model has jacobian workspace vectors") |
| 87 | + else |
| 88 | + println("Model does not have jacobian workspace vectors - workspace not implemented yet") |
| 89 | + end |
| 90 | + |
| 91 | + # Check that multiple calls don't cause excessive allocations |
| 92 | + initial_allocations = @allocated begin |
| 93 | + c, rows, cols, vals = cons_coord(nlp, x) |
| 94 | + end |
| 95 | + |
| 96 | + # Second call should have minimal allocations due to workspace reuse (if implemented) |
| 97 | + second_allocations = @allocated begin |
| 98 | + c2, rows2, cols2, vals2 = cons_coord(nlp, x) |
| 99 | + end |
| 100 | + |
| 101 | + # Third call to verify consistency |
| 102 | + third_allocations = @allocated begin |
| 103 | + c3, rows3, cols3, vals3 = cons_coord(nlp, x) |
| 104 | + end |
| 105 | + |
| 106 | + println("Initial allocations: $initial_allocations bytes") |
| 107 | + println("Second allocations: $second_allocations bytes") |
| 108 | + println("Third allocations: $third_allocations bytes") |
| 109 | + |
| 110 | + if has_jac_workspace |
| 111 | + # The current implementation might still allocate for return values |
| 112 | + # Test that allocations are reasonable and consistent |
| 113 | + @test second_allocations <= initial_allocations # Should not increase |
| 114 | + @test third_allocations == second_allocations # Should be consistent |
| 115 | + |
| 116 | + # Test that workspace reuse is working by checking if internal calls allocate less |
| 117 | + # This tests the cons_coord! function directly which should use workspace |
| 118 | + c_test = zeros(nlp.meta.ncon) |
| 119 | + rows_test = zeros(Int, nlp.meta.nnzj) |
| 120 | + cols_test = zeros(Int, nlp.meta.nnzj) |
| 121 | + vals_test = zeros(nlp.meta.nnzj) |
| 122 | + |
| 123 | + # First call to cons_coord! to initialize workspace |
| 124 | + cons_coord!(nlp, x, c_test, rows_test, cols_test, vals_test) |
| 125 | + |
| 126 | + # Second call should have minimal allocations |
| 127 | + workspace_allocations = @allocated begin |
| 128 | + cons_coord!(nlp, x, c_test, rows_test, cols_test, vals_test) |
| 129 | + end |
| 130 | + |
| 131 | + println("Direct cons_coord! allocations (after warmup): $workspace_allocations bytes") |
| 132 | + |
| 133 | + # Direct workspace usage should have very low allocations |
| 134 | + @test workspace_allocations < initial_allocations |
| 135 | + |
| 136 | + else |
| 137 | + # If workspace not implemented yet, allocations will be similar |
| 138 | + # Test that function still works correctly |
| 139 | + @test second_allocations >= 0 # Just ensure it doesn't error |
| 140 | + @test initial_allocations > 0 # Ensure some allocation happened |
| 141 | + end |
| 142 | + |
| 143 | + finally |
| 144 | + finalize(nlp) |
| 145 | + end |
| 146 | +end |
| 147 | + |
| 148 | +@testset "Workspace Vector Initialization" begin |
| 149 | + # Test that the model has the required workspace vectors |
| 150 | + nlp = CUTEstModel("HS9") |
| 151 | + |
| 152 | + try |
| 153 | + # Print field names for debugging |
| 154 | + field_names = fieldnames(typeof(nlp)) |
| 155 | + println("CUTEstModel fields: ", field_names) |
| 156 | + |
| 157 | + # Check if workspace vectors exist |
| 158 | + jac_workspace_fields = [:jac_coord_rows, :jac_coord_cols, :jac_coord_vals, :cons_vals] |
| 159 | + hess_workspace_fields = [:hess_coord_vals] |
| 160 | + |
| 161 | + for field in jac_workspace_fields |
| 162 | + if field in field_names |
| 163 | + println("✓ Found workspace field: $field") |
| 164 | + @test isdefined(nlp, field) |
| 165 | + if isdefined(nlp, field) |
| 166 | + workspace_vec = getfield(nlp, field) |
| 167 | + @test isa(workspace_vec, Vector) |
| 168 | + println(" - Type: $(typeof(workspace_vec)), Length: $(length(workspace_vec))") |
| 169 | + end |
| 170 | + else |
| 171 | + println("✗ Missing workspace field: $field") |
| 172 | + end |
| 173 | + end |
| 174 | + |
| 175 | + for field in hess_workspace_fields |
| 176 | + if field in field_names |
| 177 | + println("✓ Found hessian workspace field: $field") |
| 178 | + @test isdefined(nlp, field) |
| 179 | + else |
| 180 | + println("✗ Missing hessian workspace field: $field") |
| 181 | + end |
| 182 | + end |
| 183 | + |
| 184 | + finally |
| 185 | + finalize(nlp) |
| 186 | + end |
| 187 | +end |
0 commit comments