Skip to content
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

Add option to force dimensionless constants #310

Merged
merged 2 commits into from
Apr 28, 2024
Merged
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
3 changes: 3 additions & 0 deletions docs/src/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,9 @@ which can cancel out other units in the expression.) For example,
would indicate that the expression is dimensionally consistent, with
a constant `"2.6353e-22[m s⁻²]"`.

Note that you can also search for dimensionless units by settings
`dimensionless_constants_only` to `true`.

## 7. Additional features

For the many other features available in SymbolicRegression.jl,
Expand Down
39 changes: 28 additions & 11 deletions src/DimensionalAnalysis.jl
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,18 @@ end

# Define dimensionally-aware evaluation routine:
@inline function deg0_eval(
x::AbstractVector{T}, x_units::Vector{Q}, t::AbstractExpressionNode{T}
x::AbstractVector{T},
x_units::Vector{Q},
t::AbstractExpressionNode{T},
allow_wildcards::Bool,
) where {T,R,Q<:AbstractQuantity{T,R}}
t.constant && return WildcardQuantity{Q}(Quantity(t.val, R), true, false)
return WildcardQuantity{Q}(
(@inbounds x[t.feature]) * (@inbounds x_units[t.feature]), false, false
)
if t.constant
return WildcardQuantity{Q}(Quantity(t.val, R), allow_wildcards, false)
else
return WildcardQuantity{Q}(
(@inbounds x[t.feature]) * (@inbounds x_units[t.feature]), false, false
)
end
end
@inline function deg1_eval(
op::F, l::W
Expand Down Expand Up @@ -149,16 +155,26 @@ end
end

function violates_dimensional_constraints_dispatch(
tree::AbstractExpressionNode{T}, x_units::Vector{Q}, x::AbstractVector{T}, operators
tree::AbstractExpressionNode{T},
x_units::Vector{Q},
x::AbstractVector{T},
operators,
allow_wildcards,
) where {T,Q<:AbstractQuantity{T}}
if tree.degree == 0
return deg0_eval(x, x_units, tree)::WildcardQuantity{Q}
return deg0_eval(x, x_units, tree, allow_wildcards)::WildcardQuantity{Q}
elseif tree.degree == 1
l = violates_dimensional_constraints_dispatch(tree.l, x_units, x, operators)
l = violates_dimensional_constraints_dispatch(
tree.l, x_units, x, operators, allow_wildcards
)
return deg1_eval((@inbounds operators.unaops[tree.op]), l)::WildcardQuantity{Q}
else
l = violates_dimensional_constraints_dispatch(tree.l, x_units, x, operators)
r = violates_dimensional_constraints_dispatch(tree.r, x_units, x, operators)
l = violates_dimensional_constraints_dispatch(
tree.l, x_units, x, operators, allow_wildcards
)
r = violates_dimensional_constraints_dispatch(
tree.r, x_units, x, operators, allow_wildcards
)
return deg2_eval((@inbounds operators.binops[tree.op]), l, r)::WildcardQuantity{Q}
end
end
Expand Down Expand Up @@ -186,8 +202,9 @@ function violates_dimensional_constraints(
if X_units === nothing && y_units === nothing
return false
end
allow_wildcards = !(options.dimensionless_constants_only)
dimensional_output = violates_dimensional_constraints_dispatch(
tree, X_units, x, options.operators
tree, X_units, x, options.operators, allow_wildcards
)
# ^ Eventually do this with map_treereduce. However, right now it seems
# like we are passing around too many arguments, which slows things down.
Expand Down
6 changes: 5 additions & 1 deletion src/InterfaceDynamicExpressions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,11 @@ Convert an equation to a string.
tree,
options.operators;
f_variable=(feature, vname) -> string_variable(feature, vname, X_sym_units),
f_constant=(val,) -> string_constant(val, vprecision, WILDCARD_UNIT_STRING),
f_constant=let
unit_placeholder =
options.dimensionless_constants_only ? "" : WILDCARD_UNIT_STRING
(val,) -> string_constant(val, vprecision, unit_placeholder)
end,
variable_names=display_variable_names,
kws...,
)
Expand Down
4 changes: 4 additions & 0 deletions src/Options.jl
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,8 @@ const OPTION_DESCRIPTIONS = """- `binary_operators`: Vector of binary operators
punished.
- `dimensional_constraint_penalty`: An additive factor if the dimensional
constraint is violated.
- `dimensionless_constants_only`: Whether to only allow dimensionless
constants.
- `use_frequency`: Whether to use a parsimony that adapts to the
relative proportion of equations at each complexity; this will
ensure that there are a balanced number of equations considered
Expand Down Expand Up @@ -387,6 +389,7 @@ function Options end
complexity_of_variables::Union{Nothing,Real}=nothing,
parsimony::Real=0.0032,
dimensional_constraint_penalty::Union{Nothing,Real}=nothing,
dimensionless_constants_only::Bool=false,
alpha::Real=0.100000,
maxsize::Integer=20,
maxdepth::Union{Nothing,Integer}=nothing,
Expand Down Expand Up @@ -780,6 +783,7 @@ function Options end
tournament_selection_weights,
parsimony,
dimensional_constraint_penalty,
dimensionless_constants_only,
alpha,
maxsize,
maxdepth,
Expand Down
1 change: 1 addition & 0 deletions src/OptionsStruct.jl
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ struct Options{
tournament_selection_weights::W
parsimony::Float32
dimensional_constraint_penalty::Union{Float32,Nothing}
dimensionless_constants_only::Bool
alpha::Float32
maxsize::Int
maxdepth::Int
Expand Down
52 changes: 52 additions & 0 deletions test/test_units.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ using DynamicQuantities:
using Test
using MLJBase: MLJBase as MLJ
using MLJModelInterface: MLJModelInterface as MMI
include("utils.jl")

custom_op(x, y) = x + y

Expand Down Expand Up @@ -369,6 +370,57 @@ end
X_sym_units=dataset2.X_sym_units,
y_sym_units=dataset2.y_sym_units,
) == "x₅[5.0 m] * 3.2[?]"

# With dimensionless_constants_only, it will not print the [?]:
options = Options(;
binary_operators=[+, -, *, /],
unary_operators=[cos, sin],
dimensionless_constants_only=true,
)
@test string_tree(
x5 * 3.2,
options;
raw=false,
display_variable_names=dataset2.display_variable_names,
X_sym_units=dataset2.X_sym_units,
y_sym_units=dataset2.y_sym_units,
) == "x₅[5.0 m] * 3.2"
end

@testset "Dimensionless constants" begin
options = Options(;
binary_operators=[+, -, *, /, square, cube],
unary_operators=[cos, sin],
dimensionless_constants_only=true,
)
X = randn(5, 64)
y = randn(64)
dataset = Dataset(X, y; X_units=["m^3", "km/s", "kg", "hr", "1"], y_units="kg")
x1, x2, x3, x4, x5 = [Node(Float64; feature=i) for i in 1:5]

dimensionally_valid_equations = [
1.5 * x1 / (cube(x2) * cube(x4)) * x3, x3, (square(x3) / x3) + x3
]
for tree in dimensionally_valid_equations
onfail(@test !violates_dimensional_constraints(tree, dataset, options)) do
@warn "Failed on" tree
end
end
dimensionally_invalid_equations = [Node(Float64; val=1.5), 1.5 * x1, x3 - 1.0 * x1]
for tree in dimensionally_invalid_equations
onfail(@test violates_dimensional_constraints(tree, dataset, options)) do
@warn "Failed on" tree
end
end
# But, all of these would be fine if we allow dimensionless constants:
let
options = Options(; binary_operators=[+, -, *, /], unary_operators=[cos, sin])
for tree in dimensionally_invalid_equations
onfail(@test !violates_dimensional_constraints(tree, dataset, options)) do
@warn "Failed on" tree
end
end
end
end

@testset "Miscellaneous" begin
Expand Down
2 changes: 2 additions & 0 deletions test/utils.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
onfail(f, ::Test.Fail) = f()
onfail(_, ::Test.Pass) = nothing
Loading