Skip to content

Commit

Permalink
Add variable dependencies to examples and docs.
Browse files Browse the repository at this point in the history
  • Loading branch information
ronald-jaepel authored and schmoelder committed May 3, 2024
1 parent a9a34a0 commit 94bada6
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 3 deletions.
65 changes: 62 additions & 3 deletions docs/source/user_guide/optimization/variable_dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@ With linear combinations, variables are combined using weights or coefficients,
For example, consider a process where the same parameter is used in multiple unit operations.
To reduce the number of variables that the optimizer needs to consider, it is possible to add a single variable, which is then set on both evaluation objects in pre-processing.
In other cases, the ratio between model parameters may be essential for the optimization problem.
For instance, consider the equilibrium constant $k_{eq} = k_a / k_d$ for an adsorption process with adsorption rate $k_a$ and desorption rate $k_d$.
Instead of exposing both $k_a$ and $k_d$ to the optimizer, it is usually beneficial to expose $k_a$ and $k_{eq}$.
This way, the values for the equilibrium and the kinetics of the reaction can be found independently.


```{figure} ./figures/transform_dependency.svg
Expand Down Expand Up @@ -61,3 +58,65 @@ optimization_problem.add_variable_dependency('var_2', ['var_0', 'var_1'], transf
```

Note that generally bounds and linear constraints can still be specified independently for all variables.

## Adsorption rates example

For instance, consider an adsorption proces with an adsorption rate $k_a$ and a desorption rate $k_d$.
Both influence the strength of the interaction as well as the dynamics of the interaction.
By using the transformation $k_{eq} = k_a / k_d$ to calculate the equilibrium constant and $k_{kin} = 1 / k_d$ to calculate the kinetics constant, the values for the equilibrium and the kinetics of the reaction can be identified independently.
First, the dependent variables $k_a$ and $k_d$ must be added as they are implemented in the underlying model.

```{code-cell} ipython3
optimization_problem.add_variable(
name='adsorption_rate',
parameter_path='flow_sheet.column.binding_model.adsorption_rate',
lb=1e-3, ub=1e3,
transform='auto',
indices=[1] # modify only the protein (component index 1) parameter
)
optimization_problem.add_variable(
name='desorption_rate',
parameter_path='flow_sheet.column.binding_model.desorption_rate',
lb=1e-3, ub=1e3,
transform='auto',
indices=[1]
)
```

Then, the independent variables $k_{eq}$ and $k_{kin}$ are added. To ensure, that CADET-Process does not try to write
these variables into the CADET-Core model, where they do not have a place, `evaluation_objects` is set to `None`.

```{code-cell} ipython3
optimization_problem.add_variable(
name='equilibrium_constant',
evaluation_objects=None,
lb=1e-4, ub=1e3,
transform='auto',
indices=[1]
)
optimization_problem.add_variable(
name='kinetic_constant',
evaluation_objects=None,
lb=1e-4, ub=1e3,
transform='auto',
indices=[1]
)
```

Lasty, the dependency between the variables is added with the `.add_variable_dependency()` method.

```{code-cell} ipython3
optimization_problem.add_variable_dependency(
dependent_variable="desorption_rate",
independent_variables=["kinetic_constant", ],
transform=lambda k_kin: 1 / k_kin
)
optimization_problem.add_variable_dependency(
dependent_variable="adsorption_rate",
independent_variables=["kinetic_constant", "equilibrium_constant"],
transform=lambda k_kin, k_eq: k_eq / k_kin
)
```
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,30 @@ if __name__ == '__main__':
indices=[1] # modify only the protein (component index 1) parameter
)
optimization_problem.add_variable(
name='desorption_rate',
parameter_path='flow_sheet.column.binding_model.desorption_rate',
lb=1e-3, ub=1e3,
transform='auto',
indices=[1] # modify only the protein (component index 1) parameter
)
optimization_problem.add_variable(
name='equilibrium_constant',
evaluation_objects=None,
lb=1e-4, ub=1e3,
transform='auto',
indices=[1] # modify only the protein (component index 1) parameter
)
optimization_problem.add_variable(
name='kinetic_constant',
evaluation_objects=None,
lb=1e-4, ub=1e3,
transform='auto',
indices=[1] # modify only the protein (component index 1) parameter
)
optimization_problem.add_variable(
name='characteristic_charge',
parameter_path='flow_sheet.column.binding_model.characteristic_charge',
Expand All @@ -254,6 +278,18 @@ if __name__ == '__main__':
indices=[1] # modify only the protein (component index 1) parameter
)
optimization_problem.add_variable_dependency(
dependent_variable="desorption_rate",
independent_variables=["kinetic_constant", ],
transform=lambda k_kin: 1 / k_kin
)
optimization_problem.add_variable_dependency(
dependent_variable="adsorption_rate",
independent_variables=["kinetic_constant", "equilibrium_constant"],
transform=lambda k_kin, k_eq: k_eq / k_kin
)
def callback(simulation_results, individual, evaluation_object, callbacks_dir='./'):
comparator = comparators[evaluation_object.name]
Expand All @@ -265,6 +301,11 @@ if __name__ == '__main__':
optimization_problem.add_callback(callback, requires=[simulator])
print(optimization_problem.variable_names)
x0 = [1, 1, 1e-2, 1e-3, 10]
ind = optimization_problem.create_individual(x0)
optimization_problem.evaluate_callbacks(ind)
```

```{note}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,30 @@ def create_comparator(reference):
indices=[1] # modify only the protein (component index 1) parameter
)

optimization_problem.add_variable(
name='desorption_rate',
parameter_path='flow_sheet.column.binding_model.desorption_rate',
lb=1e-3, ub=1e3,
transform='auto',
indices=[1] # modify only the protein (component index 1) parameter
)

optimization_problem.add_variable(
name='equilibrium_constant',
evaluation_objects=None,
lb=1e-4, ub=1e3,
transform='auto',
indices=[1] # modify only the protein (component index 1) parameter
)

optimization_problem.add_variable(
name='kinetic_constant',
evaluation_objects=None,
lb=1e-4, ub=1e3,
transform='auto',
indices=[1] # modify only the protein (component index 1) parameter
)

optimization_problem.add_variable(
name='characteristic_charge',
parameter_path='flow_sheet.column.binding_model.characteristic_charge',
Expand All @@ -256,6 +280,18 @@ def create_comparator(reference):
indices=[1] # modify only the protein (component index 1) parameter
)

optimization_problem.add_variable_dependency(
dependent_variable="desorption_rate",
independent_variables=["kinetic_constant", ],
transform=lambda k_kin: 1 / k_kin
)

optimization_problem.add_variable_dependency(
dependent_variable="adsorption_rate",
independent_variables=["kinetic_constant", "equilibrium_constant"],
transform=lambda k_kin, k_eq: k_eq / k_kin
)


def callback(simulation_results, individual, evaluation_object, callbacks_dir='./'):
comparator = comparators[evaluation_object.name]
Expand All @@ -268,6 +304,11 @@ def callback(simulation_results, individual, evaluation_object, callbacks_dir='.

optimization_problem.add_callback(callback, requires=[simulator])

print(optimization_problem.variable_names)
x0 = [1, 1, 1e-2, 1e-3, 10]
ind = optimization_problem.create_individual(x0)
optimization_problem.evaluate_callbacks(ind)

# %% [markdown]
# ```{note}
# For performance reasons, the optimization is currently not run when building the documentation.
Expand Down

0 comments on commit 94bada6

Please sign in to comment.