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

Make convergence configurable #322

Merged
merged 8 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
14 changes: 14 additions & 0 deletions alphadia/constants/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -234,26 +234,40 @@ optimization:
# Parameters for the update rule for each parameter:
# - update_interval: the percentile interval to use (as a decimal)
# - update_factor: the factor by which to multiply the result from the percentile interval to get the new parameter value for the next round of search
# - favour_narrower_parameter: if True, the optimization will not take the value that maximizes the feature used for optimization, but instead the smallest value compatible with the minimum_proportion_of_maximum value.
# This setting can be useful for optimizing parameters for which many parameter values have similar feature values and therefore favouring narrower parameters helps to overcome noise.
# - maximal_decrease: the maximal decrease of the parameter value before stopping optimization (only relevant if favour_narrower_parameter is True)
# - minimum_proportion_of_maximum: the minimum proportion of the maximum value of the parameter that the designated optimum should have (only relevant if favour_narrower_parameter is True)
ms2_error:
targeted_update_interval: 0.95
targeted_update_factor: 1.0
automatic_update_interval: 0.99
automatic_update_factor: 1.1
favour_narrower_parameter: False
maximal_decrease: 0.8
minimum_proportion_of_maximum: 0.95
ms1_error:
targeted_update_interval: 0.95
targeted_update_factor: 1.0
automatic_update_interval: 0.99
automatic_update_factor: 1.1
favour_narrower_parameter: False
maximal_decrease: 0.8
minimum_proportion_of_maximum: 0.95
mobility_error:
targeted_update_interval: 0.95
targeted_update_factor: 1.0
automatic_update_interval: 0.99
automatic_update_factor: 1.1
favour_narrower_parameter: False
maximal_decrease: 0.8
minimum_proportion_of_maximum: 0.95
rt_error:
targeted_update_interval: 0.95
targeted_update_factor: 1.0
automatic_update_interval: 0.99
automatic_update_factor: 1.1
favour_narrower_parameter: True
maximal_decrease: 0.8
minimum_proportion_of_maximum: 0.95

Expand Down
131 changes: 71 additions & 60 deletions alphadia/workflow/optimization.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,15 @@ def __init__(
self.update_interval = workflow.config["optimization"][self.parameter_name][
"automatic_update_interval"
]
self.favour_narrower_parameter = workflow.config["optimization"][
self.parameter_name
]["favour_narrower_parameter"]
self.maximal_decrease = workflow.config["optimization"][self.parameter_name][
"maximal_decrease"
]
self.minimum_proportion_of_maximum = workflow.config["optimization"][
self.parameter_name
]["minimum_proportion_of_maximum"]

def step(
self,
Expand Down Expand Up @@ -211,31 +220,67 @@ def _propose_new_parameter(self, df: pd.DataFrame):

def _check_convergence(self):
"""Optimization should stop if continued narrowing of the parameter is not improving the feature value.
This function checks if the previous rounds of optimization have led to a meaningful improvement in the feature value.
If so, it continues optimization and appends the proposed new parameter to the list of parameters. If not, it stops optimization and sets the optimal parameter attribute.
If self.favour_narrower_parameter is False:
1) This function checks if the previous rounds of optimization have led to a meaningful improvement in the feature value.
2) If so, it continues optimization and appends the proposed new parameter to the list of parameters. If not, it stops optimization and sets the optimal parameter attribute.
If self.favour_narrower_parameter is True:
1) This function checks if the previous rounds of optimization have led to a meaningful disimprovement in the feature value or if the parameter has not changed substantially.
2) If not, it continues optimization and appends the proposed new parameter to the list of parameters. If so, it stops optimization and sets the optimal parameter attribute.

Notes
-----
Because the check for an increase in feature value requires two previous rounds, the function will also initialize for another round of optimization if there have been fewer than 3 rounds.
This function may be overwritten in child classes.

"""
if self.favour_narrower_parameter: # This setting can be useful for optimizing parameters for which many parameter values have similar feature values.
if len(self.history_df) < 3:
return False
odespard marked this conversation as resolved.
Show resolved Hide resolved

min_steps_reached = (
self.num_prev_optimizations
>= self.workflow.config["calibration"]["min_steps"]
)
return (
min_steps_reached
and len(self.history_df) > 2
and self.history_df[self.feature_name].iloc[-1]
< 1.1 * self.history_df[self.feature_name].iloc[-2]
and self.history_df[self.feature_name].iloc[-1]
< 1.1 * self.history_df[self.feature_name].iloc[-3]
)
min_steps_reached = (
self.num_prev_optimizations
>= self.workflow.config["calibration"]["min_steps"]
)

feature_history = self.history_df[self.feature_name]
feature_substantially_decreased = (
feature_history.iloc[-1]
< self.maximal_decrease * feature_history.iloc[-2]
and feature_history.iloc[-1]
< self.maximal_decrease * feature_history.iloc[-3]
)

parameter_history = self.history_df["parameter"]
parameter_not_substantially_changed = (
parameter_history.iloc[-1] / parameter_history.iloc[-2]
> self.minimum_proportion_of_maximum
)

return min_steps_reached and (
feature_substantially_decreased or parameter_not_substantially_changed
)

else:
min_steps_reached = (
self.num_prev_optimizations
>= self.workflow.config["calibration"]["min_steps"]
)

feature_history = self.history_df[self.feature_name]

return (
min_steps_reached
and len(self.history_df) > 2
and feature_history.iloc[-1] < 1.1 * feature_history.iloc[-2]
and feature_history.iloc[-1] < 1.1 * feature_history.iloc[-3]
)

def _find_index_of_optimum(self):
"""Finds the index of the row in the history dataframe with the optimal value of the feature used for optimization.
if self.favour_narrower_parameter is False:
The index at optimum is the index of the parameter value that maximizes the feature.
if self.favour_narrower_parameter is True:
The index at optimum is the index of the minimal parameter value whose feature value is at least self.minimum_proportion_of_maximum of the maximum value of the feature.

Returns
-------
Expand All @@ -244,10 +289,20 @@ def _find_index_of_optimum(self):

Notes
-----
This method may be overwritten in child classes if the "optimal" value differs from the value that maximizes the feature used for optimization.
This method may be overwritten in child classes.

"""
return self.history_df[self.feature_name].idxmax()

if self.favour_narrower_parameter: # This setting can be useful for optimizing parameters for which many parameter values have similar feature values.
rows_within_thresh_of_max = self.history_df.loc[
self.history_df[self.feature_name]
> self.history_df[self.feature_name].max() * 0.9
]
index_of_optimum = rows_within_thresh_of_max["parameter"].idxmin()
return index_of_optimum

else:
return self.history_df[self.feature_name].idxmax()

def _update_optimization_manager(self):
"""Updates the optimization manager with the results of the optimization, namely:
Expand Down Expand Up @@ -439,50 +494,6 @@ def _get_feature_value(
):
return len(precursors_df) / self.workflow.optlock.total_elution_groups

def _check_convergence(self):
"""Optimization should stop if continued narrowing of the parameter is not improving the feature value.
This function checks if the previous rounds of optimization have led to a meaningful improvement in the feature value.
If so, it continues optimization and appends the proposed new parameter to the list of parameters. If not, it stops optimization and sets the optimal parameter attribute.

Notes
-----
Because the check for an increase in feature value requires two previous rounds, the function will also initialize for another round of optimization if there have been fewer than 3 rounds.


"""
if len(self.history_df) < 3:
return False

min_steps_reached = (
self.num_prev_optimizations
>= self.workflow.config["calibration"]["min_steps"]
)

feature_history = self.history_df[self.feature_name]
feature_substantially_decreased = (
feature_history.iloc[-1] < self.maximal_decrease * feature_history.iloc[-2]
and feature_history.iloc[-1]
< self.maximal_decrease * feature_history.iloc[-3]
)

parameter_history = self.history_df["parameter"]
parameter_not_substantially_changed = (
parameter_history.iloc[-1] / parameter_history.iloc[-2]
> self.minimum_proportion_of_maximum
)

return min_steps_reached and (
feature_substantially_decreased or parameter_not_substantially_changed
)

def _find_index_of_optimum(self):
rows_within_thresh_of_max = self.history_df.loc[
self.history_df[self.feature_name]
> self.history_df[self.feature_name].max() * 0.9
]
index_of_optimum = rows_within_thresh_of_max["parameter"].idxmin()
return index_of_optimum


class AutomaticMS2Optimizer(AutomaticOptimizer):
def __init__(
Expand Down
Loading