diff --git a/docs/src/API/model_building.md b/docs/src/API/model_building.md index 28069b535e..c9d7c2f249 100644 --- a/docs/src/API/model_building.md +++ b/docs/src/API/model_building.md @@ -226,6 +226,7 @@ change_independent_variable add_accumulations noise_to_brownians convert_system_indepvar +subset_tunables ``` ## Hybrid systems diff --git a/src/ModelingToolkit.jl b/src/ModelingToolkit.jl index 26efc0c7a9..bb2ba0285d 100644 --- a/src/ModelingToolkit.jl +++ b/src/ModelingToolkit.jl @@ -265,7 +265,8 @@ export isinput, isoutput, getbounds, hasbounds, getguess, hasguess, isdisturbanc istunable, getdist, hasdist, tunable_parameters, isirreducible, getdescription, hasdescription, hasunit, getunit, hasconnect, getconnect, - hasmisc, getmisc, state_priority + hasmisc, getmisc, state_priority, + subset_tunables export liouville_transform, change_independent_variable, substitute_component, add_accumulations, noise_to_brownians, Girsanov_transform, change_of_variables export PDESystem diff --git a/src/parameters.jl b/src/parameters.jl index 7633d8c1b7..93c74e36a9 100644 --- a/src/parameters.jl +++ b/src/parameters.jl @@ -147,3 +147,42 @@ function split_parameters_by_type(ps) end end end + +""" + $(TYPEDSIGNATURES) + +Change the tunable parameters of a system to a new set of tunables. + +The new tunable parameters must be a subset of the current tunables as discovered by [`tunable_parameters`](@ref). +The remaining parameters will be set as constants in the system. +""" +function subset_tunables(sys, new_tunables) + if !iscomplete(sys) + throw(ArgumentError("System must be `complete` before changing tunables.")) + end + if !is_split(sys) + throw(ArgumentError("Tunable parameters can only be changed for split systems.")) + end + + cur_tunables = tunable_parameters(sys, parameters(sys)) + diff_params = setdiff(cur_tunables, new_tunables) + + if !isempty(setdiff(new_tunables, cur_tunables)) + throw(ArgumentError("""New tunable parameters must be a subset of the current tunable parameters. Found tunable parameters not in the system: $(setdiff(new_tunables, cur_tunables)). + Note that array parameters can only be set as tunable or non-tunable, not partially tunable. They should be specified in the un-scalarized form. + """)) + end + cur_ps = get_ps(sys) + const_ps = toconstant.(diff_params) + + for (idx, p) in enumerate(cur_ps) + for (d, c) in zip(diff_params, const_ps) + if isequal(p, d) + setindex!(cur_ps, c, idx) + break + end + end + end + @set! sys.ps = cur_ps + complete(sys) +end diff --git a/test/parameter_dependencies.jl b/test/parameter_dependencies.jl index 627df5913f..7316a59bc4 100644 --- a/test/parameter_dependencies.jl +++ b/test/parameter_dependencies.jl @@ -173,6 +173,33 @@ end @test SciMLBase.successful_retcode(sol) end +@testset "Change Tunables" begin + @variables θ(t)=π/6 ω(t)=0. + @parameters g=9.81 L=1.0 b=0.1 errp=1 + eqs = [ + D(θ) ~ ω, + D(ω) ~ -(g/L)*sin(θ) - b*ω + ] + @named pendulum_sys = System(eqs, t, [θ, ω], [g, L, b]) + sys = mtkcompile(pendulum_sys) + + new_tunables = [L, b] + sys2 = ModelingToolkit.subset_tunables(sys, new_tunables) + sys2_tunables = ModelingToolkit.tunable_parameters(sys2, ModelingToolkit.parameters(sys2)) + @test length(sys2_tunables) == 2 + @test isempty(setdiff(sys2_tunables, new_tunables)) + @test_throws ArgumentError ModelingToolkit.subset_tunables(sys, [errp]) + @test_throws ArgumentError ModelingToolkit.subset_tunables(sys, [θ, L]) + sys3 = ModelingToolkit.subset_tunables(sys, []) + sys3_tunables = ModelingToolkit.tunable_parameters(sys3, ModelingToolkit.parameters(sys3)) + @test length(sys3_tunables) == 0 + + sys_incomplete = pendulum_sys + @test_throws ArgumentError ModelingToolkit.subset_tunables(sys_incomplete, new_tunables) + sys_nonsplit = mtkcompile(pendulum_sys; split = false) + @test_throws ArgumentError ModelingToolkit.subset_tunables(sys_nonsplit, new_tunables) +end + struct CallableFoo p::Any end