Advanced operations
The following tutorial will show you how to:
- optimize multiple objectives simultaneously
- use multiple workstations in a single template
- set parameter constraints in your template
Pre-requisites
You should already be familiar with SDLabs and have followed the Getting started and the API-connected workstations or the Nexus custom workstations tutorials.
Multi-objective optimization
Atinary supports different algorithms able to combine multiple objectives into a single one, including weighted sum and Chimera. The main steps to create a multi-objective function are:
Tasks
- Create at least two individual objectives
- Define the individual objectives configurations, where you will set attributes to your objectives such as relative weight, hierarchy, relative tolerance, etc. depending on the selected algorithm (Chimera or Weighted Sum)
- Create a multi-objective function, where you will add the objectives and select the algorithm
The weighted sum simply defines weights applied to each objective, ranging from 0 to 10.
Weights are normalized internally, meaning that setting the weights A = 0.3, B = 0.3, C = 0.9 is strictly equivalent to setting the weights A = 1, B = 1, C = 3.
The rest of this section will focus on the Chimera multi-objective function.
Import additional dependencies
Note that these dependencies are the same as for previous tutorials, except for the MultiObjectiveFunctionApi that is used to handle multi-objective functions.
Create individual objectives
Let us assume that your workstation produces two measurements: yield and selectivity (as in the API-connected workstations and the Nexus custom workstations tutorials), which you want to maximize simultaneously. Let us create the objectives:
Warning
Objective names must match the names of your workstation measurements.
obj_1 = obj_api.objective_create(
objective_obj=sct.ObjectiveObj(
name='selectivity',
description='Maximize selectivity',
goal=sct.Goal.MAX,
)
).object
obj_2 = obj_api.objective_create(
objective_obj=sct.ObjectiveObj(
name='yield',
description='Maximize yield',
goal=sct.Goal.MAX,
)
).object
Create individual objective configurations
Let us assume we want to give priority to the selectivity over the yield. Then we can assign the following parameters:
Chimera hierarchy
In Chimera, the hierarchy starts at 0 (the most important objective), and every following objective must have its hierarchy set incrementally (1, 2, etc. - the lower, the more important).
Chimera tolerance
In Chimera, the relative and absolute tolerances represent the proportion of objective the optimizer should accept as valid before starting to optimize the next objective.
relative(from0to1) is the ratio relative to the best result seen so farabsoluteis a positive numerical value that matches the unit of your objective's measurement (e.g. if the measurement isbyproduct (in mL), an absolute tolerance of10would be10 mL)- it is safer to set the tolerance to 0 for the last objective to be optimized (the one with the highest
hierarchy)
cfg_1 = sct.MofObjConfig(
objective_id=obj_1.id,
hierarchy=0, # add set to highest hierarchy of 0
weight=0.7, # relative weight
relative=0.1 # relative tolerance Chimera (proportion of the result you are willing to sacrifice before starting to optimize the next objective)
)
cfg_2 = sct.MofObjConfig(
objective_id=obj_2.id,
hierarchy=1,
weight=0.3,
relative=0
)
Create multi-objective object
multi_obj = mof_api.mof_create(
mof_obj=sct.MofObj(
configuration=[cfg_1, cfg_2],
description="Maximize selectivity and yield",
name="Yield and Selectivity objectives",
function=sct.MofType.CHIMERA
)
).object
Create template referencing Multi Objective Function
Requirements
- It is assumed that you already have chosen your optimizer algorithm as
my_opt, and your workstation asmy_wst, following previous tutorials. - When creating a TemplateObj, we should provide either a single
objectiveor a singlemulti_objective_function. In this case we will be passing amulti_objective_function.
tpl_name = 'My Multi-Objective Template' # enter name here.
tpl = tpl_api.template_create(
template_obj=sct.TemplateObj(
# budget: total number of objective function measurements allowed
budget=5,
# define optimizer
optimizer=my_opt.id,
# name of the optimization template
name=tpl_name,
# define the objectives, here a multi-objective function
multi_objective_function=multi_obj.id,
# define the parameters for each workstation
parameters=[
sct.StepObj(
level=1,
parameters=[
sct.ParameterCpgObj(
parameter_id=param_a.id, # Copy of the workstation's parameter / parameters names should match!
workstation_id=my_wst.id,
),
sct.ParameterCpgObj(
parameter_id=param_b.id, # Copy of the workstation's parameter / parameters names should match!
workstation_id=my_wst.id,
),
],
)
],
)
).object
You can now run this template with a multi-objective function!
Using multiple workstations in the same template
So far we have only seen examples of using a single workstation, with one set of parameters and one set of measurements, but using multiple workstations in the same template is also possible! You just need to add more steps (StepObj) in the parameters property:
Create template with the sequential steps wst_1, wst_2, wst_3 (we assume that these workstations, the given parameters, the optimizer and multi-objective function have all been defined).
tpl_name = 'My Multi-Workstation Template' # enter name here
tpl = tpl_api.template_create(
template_obj=sct.TemplateObj(
# template properties
budget=5,
optimizer=my_opt.id,
name=tpl_name,
multi_objective_function=my_multi_obj.id, # Objective names must match the workstations measurements!
# steps with several workstations
parameters=[
# 1st step workstation has 2 parameters
sct.StepObj(
level=1,
parameters=[
sct.ParameterCpgObj(
parameter_id=param_1_a_copy.id,
workstation_id=wst_1.id,
),
sct.ParameterCpgObj(
parameter_id=param_1_b_copy.id,
workstation_id=wst_1.id,
)
]
),
# 2nd step workstation has only one parameter
sct.StepObj(
level=2,
parameters=[
sct.ParameterCpgObj(
parameter_id=param_2_a_copy.id,
workstation_id=wst_2.id,
)
]
),
# 3rd step workstation has NO parameter (for instance, it only measures the output of the 2nd workstation)
sct.StepObj(
level=3,
parameters=[
sct.ParameterCpgObj(
workstation_id=wst_3.id,
),
],
)
],
)
).object
You can now run this multi-steps template!
Setting constraints on template parameters
Info
Available constraints types as of today are:
- exclusion
- conditional_exclusion
- linear_eq
- linear_lte
- linear_gte
- linear_between
Warning
- keep in mind that you need to provide IDs of the Template Parameters, not the Workstation Parameters
- at the moment, providing a
nameto any new Constraint object is mandatory - when choosing a linear constraint, you should always provide
weightsin the definition of all the involved parameters - when choosing an exclusion (or conditional exclusion) constraint, you should always provide
bounds, which is a list of lists, where each internal list corresponds to an excluded region - if you are creating a simple exclusion constraint on a categorical parameter, you should actually consider simply removing the excluded categories from the Template Parameter definition directly
Import additional dependencies
Note that these dependencies are the same as for previous tutorials, except for the ConstraintApi that is used to create and manage constraints.
Create an equality constraint
Adding an equality constraint on continuous parameters in your template will mean that in your next campaign, whenever the optimizer suggests new parameters, the constrained continuous parameters will add up to a certain value (e.g. if you have 3 parameters A, B, C all ranging from 0 to 1 and a target equality constraint of 2.0, then optimizer may suggest something like A = 1.2, B = 0.1, C = 0.7).
# If you do NOT yet have your template parameters objects listed in `tpl_params` you can retrieve them from `my_template`as follows:
# otherwise, go to the next code cell
tpl_params = []
for step in my_template.parameters:
for prm in step.parameters:
tpl_params.append(prm.parameter)
print(tpl_params)
# All numerical parameters in the template (i.e. that are not Categorical)
# will add up to the target (here, `5`) when suggested by the optimizer:
eq_cstr_obj = sct.ConstraintObj(
name="linear equality",
type=sct.ConstraintType.LINEAR_EQ,
targets=[5],
definitions=[
# Weight is mandatory here (must range from 1 to 10, and is normalized internally)
sct.ConstraintDefinition(parameter=prm.id, weight=1.0)
for prm in tpl_params
if prm.type != sct.ParameterType.CATEGORICAL
]
)
# Please note that all linear_* constraints work the same way as 'linear_eq',
# except Between, which requires 2 different targets (low and high bounds);
# also, they all apply only to numerical parameters.
my_constraints: List[sct.ConstraintObjServer] = cstr_api.constraint_create_many(
constraint_obj=[eq_cstr_obj]
).objects
We can now create a template with these constraints as follows:
tpl_name = 'My Constrained Template' # enter name here
# define template object
my_template = tpl_api.template_create(
template_obj=sct.TemplateObj(
budget=tpl_budget,
optimizer=my_opt_id,
name=tpl_name,
multi_objective_function=my_multi_obj.id, # Objective names must match the workstations measurements!
parameters=[
sct.StepObj(
level=1,
parameters=[
sct.ParameterCpgObj(
parameter_id=prm.id, # Copy of the workstation's parameter / parameters names should match!
workstation_id=my_workstation.id,
)
for prm in tpl_params
],
)
],
constraints=[cstr.id for cstr in my_constraints],
)
).object
print(my_template)
You can now run this template with constraints!