Skip to content

Fix unit validation for dimensionally compatible quantities (fixes #3787) #3838

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions src/systems/unit_check.jl
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,27 @@ function get_unit(op::Integral, args)
return get_unit(args[1]) * unit
end

equivalent(x, y) = isequal(x, y)
function equivalent(x, y)
# For DynamicQuantities, check dimensional compatibility rather than exact equality
if x isa DQ.AbstractQuantity && y isa DQ.AbstractQuantity
return DQ.dimension(x) == DQ.dimension(y)
elseif !(x isa DQ.AbstractQuantity) && !(y isa DQ.AbstractQuantity)
# Both are not quantities, use exact equality
return isequal(x, y)
else
# One is quantity, one is not - check if quantity is dimensionless
q = x isa DQ.AbstractQuantity ? x : y
non_q = x isa DQ.AbstractQuantity ? y : x
# Only consider equivalent if the quantity is dimensionless and other is zero/number
return DQ.dimension(q) == DQ.dimension(DQ.Quantity(1.0)) && (isequal(non_q, 0) || non_q isa Number)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[JuliaFormatter] reported by reviewdog 🐶

Suggested change
return DQ.dimension(q) == DQ.dimension(DQ.Quantity(1.0)) && (isequal(non_q, 0) || non_q isa Number)
return DQ.dimension(q) == DQ.dimension(DQ.Quantity(1.0)) &&
(isequal(non_q, 0) || non_q isa Number)

end
end

# For connections, we may want stricter equivalence (same scale for safety)
function equivalent_strict(x, y)
# Use exact equality for connections to ensure same scales
return isequal(x, y)
end
function get_unit(op::Conditional, args)
terms = get_unit.(args)
terms[1] == unitless ||
Expand Down Expand Up @@ -234,7 +254,7 @@ function _validate(conn::Connection; info::String = "")
else
aunit = safe_get_unit(x, info * string(nameof(sys)) * "#$i")
bunit = safe_get_unit(sst[j], info * string(nameof(s)) * "#$j")
if !equivalent(aunit, bunit)
if !equivalent_strict(aunit, bunit)
valid = false
str = "$info: connected system unknowns $x ($aunit) and $(sst[j]) ($bunit) have mismatched units."
if oneunit(aunit) == oneunit(bunit)
Expand Down
22 changes: 22 additions & 0 deletions test/dq_units.jl
Original file line number Diff line number Diff line change
Expand Up @@ -283,3 +283,25 @@ end
@test ModelingToolkit.get_unit.(filter(x -> occursin("ˍt", string(x)), unknowns(pend))) ==
[u"m/s", u"m/s"]
end

# Test for issue #3787: Validation of units with non-SI units should work
@testset "Issue #3787: Non-SI unit validation" begin
@parameters τ_ms [unit = u"ms"]
@variables t_ms [unit = u"ms"] E_kJ(t_ms) [unit = u"kJ"] P_MW(t_ms) [unit = u"MW"]
D_ms = Differential(t_ms)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[JuliaFormatter] reported by reviewdog 🐶

Suggested change

# This should pass: kJ/ms = kW, and MW and kW are dimensionally compatible (both power)
eqs_3787 = [D_ms(E_kJ) ~ P_MW - E_kJ / τ_ms,
0 ~ P_MW]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[JuliaFormatter] reported by reviewdog 🐶

Suggested change
0 ~ P_MW]
0 ~ P_MW]

@test MT.validate(eqs_3787) == true

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[JuliaFormatter] reported by reviewdog 🐶

Suggested change

# Additional test: compatible units of different scales
@variables x_m [unit = u"m"] y_km [unit = u"km"]
eq_scales = [x_m ~ y_km]
@test MT.validate(eq_scales) == true

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[JuliaFormatter] reported by reviewdog 🐶

Suggested change

# Test that incompatible dimensions still fail
@variables a_length [unit = u"m"] b_time [unit = u"s"]
eq_incompatible = [a_length ~ b_time]
@test MT.validate(eq_incompatible) == false
end
Loading