diff --git a/lib/SCCNonlinearSolve/LICENSE b/lib/SCCNonlinearSolve/LICENSE new file mode 100644 index 000000000..abb594d1e --- /dev/null +++ b/lib/SCCNonlinearSolve/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 SciML + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lib/SCCNonlinearSolve/Project.toml b/lib/SCCNonlinearSolve/Project.toml new file mode 100644 index 000000000..2ade8a788 --- /dev/null +++ b/lib/SCCNonlinearSolve/Project.toml @@ -0,0 +1,48 @@ +name = "SCCNonlinearSolve" +uuid = "9dfe8606-65a1-4bb3-9748-cb89d1561431" +authors = ["Avik Pal and contributors"] +version = "1.0.0" + +[deps] +CommonSolve = "38540f10-b2f7-11e9-35d8-d573e4eb0ff2" +MaybeInplace = "bb5d69b7-63fc-4a16-80bd-7e42200c7bdb" +NonlinearSolveBase = "be0214bd-f91f-a760-ac4e-3421ce2b2da0" +PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a" +Reexport = "189a3867-3050-52da-a836-e630ba90ab69" +SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462" + +[compat] +Aqua = "0.8" +BenchmarkTools = "1.5.0" +CommonSolve = "0.2.4" +ExplicitImports = "1.5" +Hwloc = "3" +InteractiveUtils = "<0.0.1, 1" +MaybeInplace = "0.1.4" +NonlinearProblemLibrary = "0.1.2" +NonlinearSolveBase = "1.1" +Pkg = "1.10" +PrecompileTools = "1.2" +ReTestItems = "1.24" +Reexport = "1" +SciMLBase = "2.58" +StableRNGs = "1" +StaticArrays = "1.9.8" +Test = "1.10" +julia = "1.10" + +[extras] +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +ExplicitImports = "7d51a73a-1435-4ff3-83d9-f097790105c7" +Hwloc = "0e44f5e4-bd66-52a0-8798-143a42290a1d" +InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" +NonlinearProblemLibrary = "b7050fa9-e91f-4b37-bcee-a89a063da141" +Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +ReTestItems = "817f1d60-ba6b-4fd5-9520-3cf149f6a823" +StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3" +StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["Aqua", "BenchmarkTools", "ExplicitImports", "Hwloc", "InteractiveUtils", "NonlinearProblemLibrary", "Pkg", "ReTestItems", "StableRNGs", "StaticArrays", "Test"] diff --git a/lib/SCCNonlinearSolve/src/SCCNonlinearSolve.jl b/lib/SCCNonlinearSolve/src/SCCNonlinearSolve.jl new file mode 100644 index 000000000..22e7f69f1 --- /dev/null +++ b/lib/SCCNonlinearSolve/src/SCCNonlinearSolve.jl @@ -0,0 +1,39 @@ +module SCCNonlinearSolve + +import SciMLBase + +function SciMLBase.solve(prob::SciMLBase.SCCNonlinearProblem, alg; kwargs...) + numscc = length(prob.probs) + sols = [SciMLBase.build_solution(prob, nothing, prob.u0, convert(eltype(prob.u0),NaN)*prob.u0) for prob in prob.probs] + u = reduce(vcat,[prob.u0 for prob in prob.probs]) + resid = copy(u) + + earlyexit = false + lasti = 1 + for i in 1:numscc + prob.explictfuns![i](prob.probs[i].p[1],sols) + sol = SciMLBase.solve(prob.probs[i], alg; kwargs...) + _sol = SciMLBase.build_solution(prob.probs[i], nothing, sol.u, sol.resid, retcode = sol.retcode) + sols[i] = _sol + + if !SciMLBase.successful_retcode(_sol) + earlyexit = true + lasti = i + break + end + end + + # TODO: fix allocations with a lazy concatenation + u .= reduce(vcat,sols) + resid .= reduce(vcat,getproperty.(sols,:resid)) + + if earlyexit + retcode = sols[lasti].retcode + else + retcode = SciMLBase.ReturnCode.Success + end + + SciMLBase.build_solution(prob, alg, u, resid; retcode, original = sols) +end + +end \ No newline at end of file diff --git a/lib/SCCNonlinearSolve/test/core_tests.jl b/lib/SCCNonlinearSolve/test/core_tests.jl new file mode 100644 index 000000000..f24a2d91c --- /dev/null +++ b/lib/SCCNonlinearSolve/test/core_tests.jl @@ -0,0 +1,80 @@ +@testsetup module CoreRootfindTesting + +include("../../../common/common_rootfind_testing.jl") + +end + +@testitem "Manual SCC" setup=[CoreRootfindTesting] tags=[:core] begin + function f(du,u,p) + du[1] = cos(u[2]) - u[1] + du[2] = sin(u[1] + u[2]) + u[2] + du[3] = 2u[4] + u[3] + 1.0 + du[4] = u[5]^2 + u[4] + du[5] = u[3]^2 + u[5] + du[6] = u[1] + u[2] + u[3] + u[4] + u[5] + 2.0u[6] + 2.5u[7] + 1.5u[8] + du[7] = u[1] + u[2] + u[3] + 2.0u[4] + u[5] + 4.0u[6] - 1.5u[7] + 1.5u[8] + du[8] = u[1] + 2.0u[2] + 3.0u[3] + 5.0u[4] + 6.0u[5] + u[6] - u[7] - u[8] + end + prob = NonlinearProblem(f, zeros(8)) + sol = solve(prob) + + u0 = zeros(2) + cache = zeros(3) + + function f1(du,u,(cache,p)) + du[1] = cos(u[2]) - u[1] + du[2] = sin(u[1] + u[2]) + u[2] + end + explicitfun1(cache,sols) = nothing + prob1 = NonlinearProblem(NonlinearFunction{true, SciMLBase.NoSpecialize}(f1), zeros(2),(cache,nothing)) + sol1 = solve(prob1, NewtonRaphson()) + + function f2(du,u,(cache,p)) + du[1] = 2u[2] + u[1] + 1.0 + du[2] = u[3]^2 + u[2] + du[3] = u[1]^2 + u[3] + end + explicitfun2(cache,sols) = nothing + prob2 = NonlinearProblem(NonlinearFunction{true, SciMLBase.NoSpecialize}(f2), zeros(3),(cache,nothing)) + sol2 = solve(prob2, NewtonRaphson()) + + function f3(du,u,(cache,p)) + du[1] = cache[1] + 2.0u[1] + 2.5u[2] + 1.5u[3] + du[2] = cache[2] + 4.0u[1] - 1.5u[2] + 1.5u[3] + du[3] = cache[3] + + u[1] - u[2] - u[3] + end + prob3 = NonlinearProblem(NonlinearFunction{true, SciMLBase.NoSpecialize}(f3), zeros(3),(cache,nothing)) + function explicitfun3(cache,sols) + cache[1] = sols[1][1] + sols[1][2] + sols[2][1] + sols[2][2] + sols[2][3] + cache[2] = sols[1][1] + sols[1][2] + sols[2][1] + 2.0sols[2][2] + sols[2][3] + cache[3] = sols[1][1] + 2.0sols[1][2] + 3.0sols[2][1] + 5.0sols[2][2] + 6.0sols[2][3] + end + explicitfun3(cache,[sol1,sol2]) + sol3 = solve(prob3, NewtonRaphson()) + manualscc = [sol1; sol2; sol3] + + function SciMLBase.solve(prob::SciMLBase.SCCNonlinearProblem, alg; kwargs...) + numscc = length(prob.probs) + sols = [SciMLBase.build_solution(prob, nothing, prob.u0, convert(eltype(u0),NaN)*prob.u0) for prob in prob.probs] + u = reduce(vcat,[prob.u0 for prob in prob.probs]) + resid = copy(u) + + for i in 1:numscc + prob.explictfuns![i](prob.probs[i].p[1],sols) + sol = solve(prob.probs[i], alg) + _sol = SciMLBase.build_solution(prob.probs[i], nothing, sol.u, sol.resid, retcode = sol.retcode) + sols[i] = _sol + end + + # TODO: fix allocations with a lazy concatenation + u .= reduce(vcat,sols) + resid .= reduce(vcat,getproperty.(sols,:resid)) + + SciMLBase.build_solution(prob, alg, u, resid, original = sols) + end + + sccprob = SciMLBase.SCCNonlinearProblem([prob1,prob2,prob3], SciMLBase.Void{Any}.([explicitfun1,explicitfun2,explicitfun3])) + scc_sol = solve(sccprob, NewtonRaphson()) + sol ≈ manualscc ≈ scc_sol + +end \ No newline at end of file diff --git a/lib/SCCNonlinearSolve/test/qa_tests.jl b/lib/SCCNonlinearSolve/test/qa_tests.jl new file mode 100644 index 000000000..219fea638 --- /dev/null +++ b/lib/SCCNonlinearSolve/test/qa_tests.jl @@ -0,0 +1,22 @@ +@testitem "Aqua" tags=[:core] begin + using Aqua, SCCNonlinearSolve + + Aqua.test_all( + SCCNonlinearSolve; + piracies = false, ambiguities = false, stale_deps = false, deps_compat = false + ) + Aqua.test_stale_deps(SCCNonlinearSolve; ignore = [:SciMLJacobianOperators]) + Aqua.test_deps_compat(SCCNonlinearSolve; ignore = [:SciMLJacobianOperators]) + Aqua.test_piracies(SCCNonlinearSolve) + Aqua.test_ambiguities(SCCNonlinearSolve; recursive = false) +end + +@testitem "Explicit Imports" tags=[:core] begin + using ExplicitImports, SCCNonlinearSolve + + @test check_no_implicit_imports( + SCCNonlinearSolve; skip = (Base, Core, SciMLBase) + ) === nothing + @test check_no_stale_explicit_imports(SCCNonlinearSolve) === nothing + @test check_all_qualified_accesses_via_owners(SCCNonlinearSolve) === nothing +end diff --git a/lib/SCCNonlinearSolve/test/runtests.jl b/lib/SCCNonlinearSolve/test/runtests.jl new file mode 100644 index 000000000..451760225 --- /dev/null +++ b/lib/SCCNonlinearSolve/test/runtests.jl @@ -0,0 +1,25 @@ +using ReTestItems, SCCNonlinearSolve, Hwloc, InteractiveUtils, Pkg + +@info sprint(InteractiveUtils.versioninfo) + +const GROUP = lowercase(get(ENV, "GROUP", "All")) + +const RETESTITEMS_NWORKERS = parse( + Int, get(ENV, "RETESTITEMS_NWORKERS", + string(min(ifelse(Sys.iswindows(), 0, Hwloc.num_physical_cores()), 4)) + ) +) +const RETESTITEMS_NWORKER_THREADS = parse(Int, + get( + ENV, "RETESTITEMS_NWORKER_THREADS", + string(max(Hwloc.num_virtual_cores() ÷ max(RETESTITEMS_NWORKERS, 1), 1)) + ) +) + +@info "Running tests for group: $(GROUP) with $(RETESTITEMS_NWORKERS) workers" + +ReTestItems.runtests( + SCCNonlinearSolve; tags = (GROUP == "all" ? nothing : [Symbol(GROUP)]), + nworkers = RETESTITEMS_NWORKERS, nworker_threads = RETESTITEMS_NWORKER_THREADS, + testitem_timeout = 3600 +)