|
311 | 311 | @test check_no_stale_explicit_imports(SciMLJacobianOperators) === nothing |
312 | 312 | @test check_all_qualified_accesses_via_owners(SciMLJacobianOperators) === nothing |
313 | 313 | end |
| 314 | + |
| 315 | +@testitem "Copy with Tuple parameters (Issue #752)" begin |
| 316 | + using ADTypes, SciMLBase, ForwardDiff, FiniteDiff |
| 317 | + using SciMLJacobianOperators |
| 318 | + using SciMLJacobianOperators: _safe_copy |
| 319 | + |
| 320 | + # Test _safe_copy helper functions |
| 321 | + @testset "_safe_copy helpers" begin |
| 322 | + # Tuples should be returned as-is (immutable) |
| 323 | + t = (1.0, 2.0, 3.0, 4.0) |
| 324 | + @test _safe_copy(t) === t |
| 325 | + |
| 326 | + # NamedTuples should be returned as-is (immutable) |
| 327 | + nt = (a = 1.0, b = 2.0) |
| 328 | + @test _safe_copy(nt) === nt |
| 329 | + |
| 330 | + # Numbers should be returned as-is (immutable) |
| 331 | + @test _safe_copy(42.0) === 42.0 |
| 332 | + @test _safe_copy(42) === 42 |
| 333 | + |
| 334 | + # Arrays should be copied |
| 335 | + arr = [1.0, 2.0, 3.0] |
| 336 | + arr_copy = _safe_copy(arr) |
| 337 | + @test arr_copy == arr |
| 338 | + @test arr_copy !== arr # Different object |
| 339 | + end |
| 340 | + |
| 341 | + # Test copy(StatefulJacobianOperator) with Tuple parameter |
| 342 | + # This is a regression test for issue #752 where JFNK failed with: |
| 343 | + # MethodError: no method matching copy(::NTuple{4, Float64}) |
| 344 | + @testset "copy(StatefulJacobianOperator) with Tuple p" begin |
| 345 | + # Brusselator-like problem setup with Tuple parameters |
| 346 | + function brusselator_f(du, u, p) |
| 347 | + α, β, γ, δ = p |
| 348 | + du[1] = α + u[1]^2 * u[2] - (β + 1) * u[1] |
| 349 | + du[2] = β * u[1] - u[1]^2 * u[2] |
| 350 | + return nothing |
| 351 | + end |
| 352 | + |
| 353 | + # Parameters as a Tuple (this is what caused issue #752) |
| 354 | + p_tuple = (1.0, 3.0, 1.0, 1.0) |
| 355 | + u0 = [1.0, 1.0] |
| 356 | + fu0 = similar(u0) |
| 357 | + brusselator_f(fu0, u0, p_tuple) |
| 358 | + |
| 359 | + prob = NonlinearProblem( |
| 360 | + NonlinearFunction{true}(brusselator_f), u0, p_tuple) |
| 361 | + |
| 362 | + jac_op = JacobianOperator( |
| 363 | + prob, fu0, u0; jvp_autodiff = AutoForwardDiff(), vjp_autodiff = AutoFiniteDiff()) |
| 364 | + sop = StatefulJacobianOperator(jac_op, u0, p_tuple) |
| 365 | + |
| 366 | + # This should not throw MethodError: no method matching copy(::NTuple{4, Float64}) |
| 367 | + sop_copy = copy(sop) |
| 368 | + |
| 369 | + @test sop_copy.p === p_tuple # Same tuple (immutable, no copy needed) |
| 370 | + @test sop_copy.u == u0 |
| 371 | + @test sop_copy.u !== sop.u # Different array object (copied) |
| 372 | + |
| 373 | + # Verify the copied operator still works |
| 374 | + v = [1.0, 0.0] |
| 375 | + result_original = sop * v |
| 376 | + result_copy = sop_copy * v |
| 377 | + @test result_original ≈ result_copy |
| 378 | + end |
| 379 | + |
| 380 | + # Test copy(StatefulJacobianOperator) with NamedTuple parameter |
| 381 | + @testset "copy(StatefulJacobianOperator) with NamedTuple p" begin |
| 382 | + function simple_f(du, u, p) |
| 383 | + du[1] = p.a * u[1] + p.b * u[2] |
| 384 | + du[2] = p.c * u[1] + p.d * u[2] |
| 385 | + return nothing |
| 386 | + end |
| 387 | + |
| 388 | + p_namedtuple = (a = 1.0, b = 2.0, c = 3.0, d = 4.0) |
| 389 | + u0 = [1.0, 1.0] |
| 390 | + fu0 = similar(u0) |
| 391 | + simple_f(fu0, u0, p_namedtuple) |
| 392 | + |
| 393 | + prob = NonlinearProblem( |
| 394 | + NonlinearFunction{true}(simple_f), u0, p_namedtuple) |
| 395 | + |
| 396 | + jac_op = JacobianOperator( |
| 397 | + prob, fu0, u0; jvp_autodiff = AutoForwardDiff(), vjp_autodiff = AutoFiniteDiff()) |
| 398 | + sop = StatefulJacobianOperator(jac_op, u0, p_namedtuple) |
| 399 | + |
| 400 | + # This should not throw |
| 401 | + sop_copy = copy(sop) |
| 402 | + |
| 403 | + @test sop_copy.p === p_namedtuple # Same NamedTuple (immutable) |
| 404 | + end |
| 405 | +end |
0 commit comments