Specifying indices of multidimensional parameters for Optimization Variables

Specifying indices of multidimensional parameters for Optimization Variables#

Similar to events, sometimes we only want to add individual entries of a parameter as an optimization variable. In fact, optimization variables can only be scalar. Consequently, an index must be provided if the parameter is not scalar.

For this tutorial, consider the following process model and optimization problem:

import copy
import numpy as np

from CADETProcess.processModel import ComponentSystem
component_system = ComponentSystem(2)

from CADETProcess.processModel import MassActionLaw
reaction_system = MassActionLaw(component_system)
reaction_system.add_reaction(
    indices=[0, 1],
    coefficients=[-1, 1],
    k_fwd=0.1,
    k_bwd=0,
)
reaction_system.add_reaction(
    indices=[1, 0],
    coefficients=[-1, 1],
    k_fwd=0.2,
    k_bwd=0,
)

from CADETProcess.processModel import Inlet
inlet = Inlet(component_system, 'inlet')

from CADETProcess.processModel import Inlet, LumpedRateModelWithPores, Outlet
inlet = Inlet(component_system, name='inlet')
column = LumpedRateModelWithPores(component_system, 'column')
column.bulk_reaction_model = reaction_system
outlet = Outlet(component_system, 'outlet')

from CADETProcess.processModel import FlowSheet, Process

flow_sheet = FlowSheet(component_system)
flow_sheet.add_unit(inlet)
flow_sheet.add_unit(column)
flow_sheet.add_unit(outlet)

flow_sheet.add_connection(inlet, column)
flow_sheet.add_connection(column, outlet)

def setup_process():
    process = Process(flow_sheet, 'Demo Indices')
    process.cycle_time = 10

    return process

from CADETProcess.optimization import OptimizationProblem

def setup_optimization_problem():
    optimization_problem = OptimizationProblem('Demo Indices', use_diskcache=False)
    optimization_problem.add_evaluation_object(process)

    return optimization_problem

Specifying indices for (multidimensional) arrays#

The procedure of adding indices is similar to that of the Event indices (see here). By default, if no indices are specified, all entries of that array are set to the same value. E.g. to let the film_diffusion coefficients all have the same value, specify the following:

process = setup_process()
optimization_problem = setup_optimization_problem()

var = optimization_problem.add_variable(
    'film_diffusion_all', evaluation_objects=process, parameter_path='flow_sheet.column.film_diffusion'
)
var.value = 1
print(process.flow_sheet.column.film_diffusion)
[1.0, 1.0]

To add a variable that only modifies a single entry of the parameter array, add an indices flag. E.g. for the first component of the (1D) film_diffusion array:

optimization_problem = setup_optimization_problem()

var = optimization_problem.add_variable(
    'film_diffusion_0', evaluation_objects=process, parameter_path='flow_sheet.column.film_diffusion', indices=0
)
var.value = 2
print(process.flow_sheet.column.film_diffusion)
[2.0, 1.0]

Note, for polynomial parameters, if no index is specified, only the constant coefficient is set and the rest of the coefficients are set to zero. So, to add the constant term for the inlet.flow_rate, use:

var = optimization_problem.add_variable(
    'flow_rate_fill', evaluation_objects=process, parameter_path='flow_sheet.inlet.flow_rate'
)
var.value = 1
print(process.flow_sheet.inlet.flow_rate)
[1. 0. 0. 0.]

However, adding indices still works as expected. E.g. for the linear coefficient of the flow_rate:

optimization_problem = setup_optimization_problem()

var = optimization_problem.add_variable(
    'flow_rate_single', evaluation_objects=process, parameter_path='flow_sheet.inlet.flow_rate', indices=1
)
var.value = 2
print(process.flow_sheet.inlet.flow_rate)
[1. 2. 0. 0.]

Just as with 1D arrays, the value is set to all entries of that array if no indices are specified.

optimization_problem = setup_optimization_problem()

var = optimization_problem.add_variable(
    'exponents', evaluation_objects=process, parameter_path='flow_sheet.column.bulk_reaction_model.exponents_fwd'
)
var.value = 1
print(process.flow_sheet.column.bulk_reaction_model.exponents_fwd)
[[1. 1.]
 [1. 1.]]

Multidimensional parameters can also be indexed by specifying a tuple with the index for each of the parameter dimensions.

optimization_problem = setup_optimization_problem()

var = optimization_problem.add_variable(
    'exponents_single', evaluation_objects=process, parameter_path='flow_sheet.column.bulk_reaction_model.exponents_fwd', indices=(0, 0)
)
var.value = 2
print(process.flow_sheet.column.bulk_reaction_model.exponents_fwd)
[[2. 1.]
 [1. 1.]]

Just as with Events, slicing notation is also supported:

optimization_problem = setup_optimization_problem()

var = optimization_problem.add_variable(
    'exponents_slice', evaluation_objects=process, parameter_path='flow_sheet.column.bulk_reaction_model.exponents_fwd', indices=np.s_[0, :]
)
var.value = 3
print(process.flow_sheet.column.bulk_reaction_model.exponents_fwd)
[[3. 1.]
 [3. 1.]]

Also the procedure for polynomial parameters is analogous to the 1D case. For example, to optimize the linear coefficient of the first component of the inlet concentration:

optimization_problem = setup_optimization_problem()

var = optimization_problem.add_variable(
    'concentration_constant_all', evaluation_objects=process, parameter_path='flow_sheet.inlet.c'
)
var.value = 1
print(process.flow_sheet.inlet.c)
[[1. 0. 0. 0.]
 [1. 0. 0. 0.]]

To modify only the constant coefficient of a single entry:

optimization_problem = setup_optimization_problem()

var = optimization_problem.add_variable(
    'concentration_fill_values_single', evaluation_objects=process, parameter_path='flow_sheet.inlet.c', indices=0
)
var.value = 2
print(process.flow_sheet.inlet.c)
[[2. 0. 0. 0.]
 [1. 0. 0. 0.]]

Explicitly modify linear coefficient of single entry.

optimization_problem = setup_optimization_problem()

var = optimization_problem.add_variable(
    'concentration_single_entry', evaluation_objects=process, parameter_path='flow_sheet.inlet.c', indices=(0, 1)
)
var.value = 3
print(process.flow_sheet.inlet.c)
[[2. 3. 0. 0.]
 [1. 0. 0. 0.]]

Specifying Indices of Multidimensional Event States#

In certain scenarios, optimizing the state of an event becomes essential. Depending on which parameter dimensions the event modifies, and the indices chosen for the event state, it might be imperative to include indices in the optimization variable as well.

Consider the slope of an elution gradient that starts at a specific time. Here, the first component starts at \(c = 0~mM\) with a slope of \(1~mM / s\) whereas the second component’s concentration is always \(0~mM\).

process = setup_process()
optimization_problem = setup_optimization_problem()

evt = process.add_event(
    'c_poly', 'flow_sheet.inlet.c', [[0, 1], 0], time=1
)
print(inlet.c)
[[0. 1. 0. 0.]
 [0. 0. 0. 0.]]

To add an optimization variable that only modifies the linear coefficient, add the event state as parameter_path and the corresponding index.

var = optimization_problem.add_variable(
    'concentration_single_entry', evaluation_objects=process, parameter_path='c_poly.state', indices=(0, 1)
)
var.value = 2

print(inlet.c)
[[0. 2. 0. 0.]
 [0. 0. 0. 0.]]