From f15b38bf7b1ae6e2cb38c3d8409a2a034430b7c7 Mon Sep 17 00:00:00 2001 From: cmhyett Date: Tue, 2 Dec 2025 13:56:50 -0600 Subject: [PATCH 1/6] Simple bypass to allow APs to be unitless with unitful connections --- src/systems/unit_check.jl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/systems/unit_check.jl b/src/systems/unit_check.jl index acf7451065..1aad1afa45 100644 --- a/src/systems/unit_check.jl +++ b/src/systems/unit_check.jl @@ -213,6 +213,11 @@ function _validate(terms::Vector, labels::Vector{String}; info::String = "") valid end +function _validate(ap::AnalysisPoint; info::String = "") + conn_eq = connect(ap.input, ap.outputs) + return _validate(conn_eq, info=info) +end + function _validate(conn::Connection; info::String = "") valid = true syss = get_systems(conn) @@ -277,7 +282,7 @@ function validate(jumps::Vector{JumpType}, t::Symbolic) end function validate(eq::Union{Inequality, Equation}; info::String = "") - if typeof(eq.lhs) == Connection + if typeof(eq.lhs) <: Union{Connection, AnalysisPoint} _validate(eq.rhs; info) else _validate([eq.lhs, eq.rhs], ["left", "right"]; info) From 4d7372a09bef3bcd367faa9e5ee677ebb7585987 Mon Sep 17 00:00:00 2001 From: cmhyett Date: Wed, 3 Dec 2025 11:08:46 -0600 Subject: [PATCH 2/6] Duplicating logic where necessary, adding tests. --- src/systems/unit_check.jl | 4 +-- src/systems/validation.jl | 9 ++++-- test/analysis_points.jl | 64 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 4 deletions(-) diff --git a/src/systems/unit_check.jl b/src/systems/unit_check.jl index 1aad1afa45..21ec01e094 100644 --- a/src/systems/unit_check.jl +++ b/src/systems/unit_check.jl @@ -214,8 +214,8 @@ function _validate(terms::Vector, labels::Vector{String}; info::String = "") end function _validate(ap::AnalysisPoint; info::String = "") - conn_eq = connect(ap.input, ap.outputs) - return _validate(conn_eq, info=info) + conn_eq = connect(ap.input, ap.outputs...) + return _validate(conn_eq.rhs, info=info) end function _validate(conn::Connection; info::String = "") diff --git a/src/systems/validation.jl b/src/systems/validation.jl index d416a02ea2..d93dfbd9a4 100644 --- a/src/systems/validation.jl +++ b/src/systems/validation.jl @@ -2,7 +2,7 @@ module UnitfulUnitCheck using ..ModelingToolkit, Symbolics, SciMLBase, Unitful, RecursiveArrayTools using ..ModelingToolkit: ValidationError, - ModelingToolkit, Connection, instream, JumpType, VariableUnit, + ModelingToolkit, Connection, instream, JumpType, VariableUnit, AnalysisPoint, get_systems, Conditional, Comparison using JumpProcesses: MassActionJump, ConstantRateJump, VariableRateJump @@ -182,6 +182,11 @@ function _validate(terms::Vector, labels::Vector{String}; info::String = "") valid end +function _validate(ap::AnalysisPoint; info::String = "") + conn_eq = connect(ap.input, ap.outputs...) + return _validate(conn_eq.rhs, info=info) +end + function _validate(conn::Connection; info::String = "") valid = true syss = get_systems(conn) @@ -242,7 +247,7 @@ function validate(jumps::Vector{JumpType}, t::Symbolic) end function validate(eq::MT.Equation; info::String = "") - if typeof(eq.lhs) == Connection + if typeof(eq.lhs) <: Union{AnalysisPoint, Connection} _validate(eq.rhs; info) else _validate([eq.lhs, eq.rhs], ["left", "right"]; info) diff --git a/test/analysis_points.jl b/test/analysis_points.jl index f718e7bc59..19ac888aa8 100644 --- a/test/analysis_points.jl +++ b/test/analysis_points.jl @@ -7,6 +7,70 @@ using ModelingToolkit: t_nounits as t, D_nounits as D, AnalysisPoint, AbstractSy import ModelingToolkit as MTK import ControlSystemsBase as CS using Symbolics: NAMESPACE_SEPARATOR +using Unitful + +@testset "AnalysisPoint is ignored when verifying units" begin + # no units first + @mtkmodel FirstOrderTest begin + @components begin + in = Blocks.Step() + fb = Blocks.Feedback() + fo = Blocks.SecondOrder(k = 1, w = 1, d = 0.1) + end + @equations begin + connect(in.output, :u, fb.input1) + connect(fb.output, :e, fo.input) + connect(fo.output, :y, fb.input2) + end + end + @named model = FirstOrderTest() + @test model isa System + + @connector function UnitfulOutput(; name) + vars = @variables begin + u(t), [unit=u"m", output=true] + end + return System(Equation[], t, vars, []; name) + end + @connector function UnitfulInput(; name) + vars = @variables begin + u(t), [unit=u"m", input=true] + end + return System(Equation[], t, vars, []; name) + end + @component function UnitfulBlock(; name) + pars = @parameters begin + offset, [unit=u"m"] + start_time + height, [unit=u"m"] + duration + end + systems = @named begin + output = UnitfulOutput() + end + eqs = [ + output.u ~ offset + height*(0.5 + (1/pi)*atan(1e5*(t - start_time))) + ] + return System(eqs, t, [], pars; systems, name) + end + @mtkmodel MySquare begin + @components begin + input = UnitfulInput() + end + @variables begin + output(t), [output=true, unit=u"m^2"] + end + @components begin + ub = UnitfulBlock() + end + @equations begin + connect(ub.output, :ap, input) + output ~ input.u^2 + end + end + @named sq = MySquare() + @test sq isa System +end @testset "AnalysisPoint is lowered to `connect`" begin @named P = FirstOrder(k = 1, T = 1) From cc324ce77f051d4bf5b08f766ce2a166e461bee2 Mon Sep 17 00:00:00 2001 From: cmhyett Date: Wed, 3 Dec 2025 12:43:32 -0600 Subject: [PATCH 3/6] Fixing logic for edge case of ap.outputs == nothing --- src/systems/unit_check.jl | 12 +++++++++--- src/systems/validation.jl | 10 ++++++++-- test/analysis_points.jl | 24 +++++++++++++++++++++--- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/systems/unit_check.jl b/src/systems/unit_check.jl index 21ec01e094..1a6e00c33f 100644 --- a/src/systems/unit_check.jl +++ b/src/systems/unit_check.jl @@ -214,10 +214,16 @@ function _validate(terms::Vector, labels::Vector{String}; info::String = "") end function _validate(ap::AnalysisPoint; info::String = "") - conn_eq = connect(ap.input, ap.outputs...) - return _validate(conn_eq.rhs, info=info) + is_valid = false + if (ap.outputs == nothing) + is_valid = true + else + conn_eq = connect(ap.input, ap.outputs...) + is_valid = _validate(conn_eq.rhs, info=info) + end + return is_valid end - + function _validate(conn::Connection; info::String = "") valid = true syss = get_systems(conn) diff --git a/src/systems/validation.jl b/src/systems/validation.jl index d93dfbd9a4..6372be1100 100644 --- a/src/systems/validation.jl +++ b/src/systems/validation.jl @@ -183,8 +183,14 @@ function _validate(terms::Vector, labels::Vector{String}; info::String = "") end function _validate(ap::AnalysisPoint; info::String = "") - conn_eq = connect(ap.input, ap.outputs...) - return _validate(conn_eq.rhs, info=info) + is_valid = false + if (ap.outputs == nothing) + is_valid = true + else + conn_eq = connect(ap.input, ap.outputs...) + is_valid = _validate(conn_eq.rhs, info=info) + end + return is_valid end function _validate(conn::Connection; info::String = "") diff --git a/test/analysis_points.jl b/test/analysis_points.jl index 19ac888aa8..3d0387bfd7 100644 --- a/test/analysis_points.jl +++ b/test/analysis_points.jl @@ -53,7 +53,7 @@ using Unitful ] return System(eqs, t, [], pars; systems, name) end - @mtkmodel MySquare begin + @mtkmodel TestAPAroundUnits begin @components begin input = UnitfulInput() end @@ -68,8 +68,26 @@ using Unitful output ~ input.u^2 end end - @named sq = MySquare() - @test sq isa System + @named sys = TestAPAroundUnits() + @test sys isa System + + @mtkmodel TestAPWithNoOutputs begin + @components begin + input = UnitfulInput() + end + @variables begin + output(t), [output=true, unit=u"m^2"] + end + @components begin + ub = UnitfulBlock() + end + @equations begin + connect(ub.output, :ap, input) + output ~ input.u^2 + end + end + @named sys2 = TestAPWithNoOutputs() + @test sys2 isa System end @testset "AnalysisPoint is lowered to `connect`" begin From eeabe8e2ab2202b5d3c9959a6301f6346536d0b5 Mon Sep 17 00:00:00 2001 From: Criston Date: Thu, 4 Dec 2025 08:53:33 -0600 Subject: [PATCH 4/6] Remove `MTKStdLib.Blocks` namespacing Submodule already loaded. --- test/analysis_points.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/analysis_points.jl b/test/analysis_points.jl index 3d0387bfd7..4c5594c653 100644 --- a/test/analysis_points.jl +++ b/test/analysis_points.jl @@ -13,9 +13,9 @@ using Unitful # no units first @mtkmodel FirstOrderTest begin @components begin - in = Blocks.Step() - fb = Blocks.Feedback() - fo = Blocks.SecondOrder(k = 1, w = 1, d = 0.1) + in = Step() + fb = Feedback() + fo = SecondOrder(k = 1, w = 1, d = 0.1) end @equations begin connect(in.output, :u, fb.input1) From 462586e69d3d8d004ae6423aebad76b439085eba Mon Sep 17 00:00:00 2001 From: Criston Date: Thu, 4 Dec 2025 10:40:42 -0600 Subject: [PATCH 5/6] Namespacing `Gain` block to avoid conflict --- test/analysis_points.jl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/analysis_points.jl b/test/analysis_points.jl index 4c5594c653..05c60087e8 100644 --- a/test/analysis_points.jl +++ b/test/analysis_points.jl @@ -92,7 +92,7 @@ end @testset "AnalysisPoint is lowered to `connect`" begin @named P = FirstOrder(k = 1, T = 1) - @named C = Gain(; k = -1) + @named C = ModelingToolkitStandardLibrary.Blocks.Gain(; k = -1) ap = AnalysisPoint(:plant_input) eqs = [connect(P.output, C.input) @@ -112,7 +112,7 @@ end @testset "Inverse causality throws a warning" begin @named P = FirstOrder(k = 1, T = 1) - @named C = Gain(; k = -1) + @named C = ModelingToolkitStandardLibrary.Blocks.Gain(; k = -1) ap = AnalysisPoint(:plant_input) @test_warn ["1-th argument", "plant_input", "not a output"] connect( @@ -123,7 +123,7 @@ end # also tests `connect(input, name::Symbol, outputs...)` syntax @testset "AnalysisPoint is accessible via `getproperty`" begin @named P = FirstOrder(k = 1, T = 1) - @named C = Gain(; k = -1) + @named C = ModelingToolkitStandardLibrary.Blocks.Gain(; k = -1) eqs = [connect(P.output, C.input), connect(C.output, :plant_input, P.input)] sys_ap = System(eqs, t, systems = [P, C], name = :hej) @@ -143,7 +143,7 @@ end ### Ported from MTKStdlib @named P = FirstOrder(k = 1, T = 1) -@named C = Gain(; k = -1) +@named C = ModelingToolkitStandardLibrary.Blocks.Gain(; k = -1) ap = AnalysisPoint(:plant_input) eqs = [connect(P.output, C.input), connect(C.output, ap, P.input)] @@ -348,7 +348,7 @@ end @testset "Duplicate `connect` statements across subsystems with AP transforms - standard `connect`" begin @named P = FirstOrder(k = 1, T = 1) - @named C = Gain(; k = 1) + @named C = ModelingToolkitStandardLibrary.Blocks.Gain(; k = 1) @named add = Blocks.Add(k2 = -1) eqs = [connect(P.output, :plant_output, add.input2) @@ -384,7 +384,7 @@ end @testset "Duplicate `connect` statements across subsystems with AP transforms - causal variable `connect`" begin @named P = FirstOrder(k = 1, T = 1) - @named C = Gain(; k = 1) + @named C = ModelingToolkitStandardLibrary.Blocks.Gain(; k = 1) @named add = Blocks.Add(k2 = -1) eqs = [connect(P.output.u, :plant_output, add.input2.u) @@ -420,7 +420,7 @@ end @testset "Duplicate `connect` statements across subsystems with AP transforms - mixed `connect`" begin @named P = FirstOrder(k = 1, T = 1) - @named C = Gain(; k = 1) + @named C = ModelingToolkitStandardLibrary.Blocks.Gain(; k = 1) @named add = Blocks.Add(k2 = -1) eqs = [connect(P.output.u, :plant_output, add.input2.u) @@ -542,7 +542,7 @@ end @testset "multiple analysis points" begin @named P = FirstOrder(k = 1, T = 1) - @named C = Gain(; k = 1) + @named C = ModelingToolkitStandardLibrary.Blocks.Gain(; k = 1) @named add = Blocks.Add(k2 = -1) eqs = [connect(P.output, :plant_output, add.input2) From 3017d916220bf8b321667f8cbdd859fc039f3187 Mon Sep 17 00:00:00 2001 From: Criston Date: Thu, 4 Dec 2025 12:04:17 -0600 Subject: [PATCH 6/6] Reorganize imports in analysis_points.jl --- test/analysis_points.jl | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/analysis_points.jl b/test/analysis_points.jl index 05c60087e8..77ad59d261 100644 --- a/test/analysis_points.jl +++ b/test/analysis_points.jl @@ -1,11 +1,13 @@ -using ModelingToolkit, ModelingToolkitStandardLibrary.Blocks, ControlSystemsBase -using ModelingToolkitStandardLibrary.Mechanical.Rotational -using ModelingToolkitStandardLibrary.Blocks -using OrdinaryDiffEq, LinearAlgebra -using Test +using ModelingToolkit using ModelingToolkit: t_nounits as t, D_nounits as D, AnalysisPoint, AbstractSystem import ModelingToolkit as MTK +using ModelingToolkitStandardLibrary +using ModelingToolkitStandardLibrary.Blocks +using ModelingToolkitStandardLibrary.Mechanical.Rotational +using ControlSystemsBase import ControlSystemsBase as CS +using OrdinaryDiffEq, LinearAlgebra +using Test using Symbolics: NAMESPACE_SEPARATOR using Unitful