SymbolicML / DynamicExpressions.jl

Ridiculously fast symbolic expressions
https://symbolicml.org/DynamicExpressions.jl/dev
Apache License 2.0
103 stars 15 forks source link

Add support for setting constants by name #15

Closed AlCap23 closed 1 year ago

AlCap23 commented 1 year ago

Related to #14 .

This allows users to reuse constants within a node. This can be useful if substructures shares parameters or parameters are shared between equations ( e.g. PK/PD systems with transition compartments ). Should be useable with ComponentArrays (hence the dispatch on C rather than a NamedTuple ).

coveralls commented 1 year ago

Pull Request Test Coverage Report for Build 4250650704


Totals Coverage Status
Change from base Build 3558750421: 0.2%
Covered Lines: 984
Relevant Lines: 1086

💛 - Coveralls
MilesCranmer commented 1 year ago

Hey @AlCap23, thanks for the PR. Just to help me understand the use-case, why is the naming necessary? The nodes are already well-ordered and can be get/set based on that order (which returns a vector of nodes).

AlCap23 commented 1 year ago

Sometimes, in the constant optimization, I'd like to have equality within a subset of the constants ( not on default, though ). Imagine a system like:

$$ f(x, c_1, c_2) = sin(c_1 x) cos(c_2 * x) $$

Where the frequency should match. So instead of collecting individual constants, I use the same one based on the generated name. Given that this is an easy example, think about it on a system level:

$$ \begin{aligned} y_1 &= a_1 x_1 \ y_2 &= x_2 - a_1 x_1 \end{aligned} $$

Where the identifiability might be enhanced by using this matching. Additionally, this allows for constraining specific variables with bounds ( using TransformVariables ) within the optimization.

So in total: It might add more structure to the overall process without loss of generality.

Quick Copy-Paste from my last answer in the issue:

op = OperatorEnum(binary_operators = [-, *], unary_operators = [sin])
param = Node(; val = 3.0)
feat = Node(; feature = 1)
ex = sin(feat*param) - param
get_constants(ex) # Returns [3.0, 3.0], so each Node is still treated as an individual
set_constants(ex, [2.0]) # Error which makes sense

And I tried to constraint it by using the hash map, but this did not work out either. Another approach would be to just store the parameters and set them, which could also be done ( I think ).

AlCap23 commented 1 year ago

Allright, feel free to close or keep 😅 . Figured it out. Simple solutions are not always my strong suite here. I am to used to immutable structs right now.

op = OperatorEnum(binary_operators = [-, *], unary_operators = [sin])
param = Node(; val = 3.0)
feat = Node(; feature = 1)
ex = sin(feat*param) - param
param.val = 2.0
ex # Works
MilesCranmer commented 1 year ago

Cool, no problem at all! Indeed I think using objectid(node) is a good alternative to introducing a new parameter for naming them – and it also means reduced memory allocation when sharing a child between two parents.