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

Add COBYQA optimizer #153

Open
wants to merge 53 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
508983a
Add qNParEGO Ax MOO Interface
ronald-jaepel Apr 22, 2024
fdb7dcf
Relax tolerance for MOO convergence test
ronald-jaepel Apr 22, 2024
69a5478
Unify calling evaluation functions for individuals and populations
schmoelder Apr 21, 2024
1a4debb
Ensure callback dirs are created for final callback
ronald-jaepel Apr 29, 2024
9720966
Update docstrings
schmoelder Nov 24, 2023
83fff8f
Add method to create fraction
schmoelder Nov 24, 2023
8cc02e1
Add `start` and `end` times to `Fraction`
schmoelder Jan 19, 2024
159b374
Specify and validate reference type for difference metrics
schmoelder Jan 19, 2024
11c59f4
Add classes to __all__
schmoelder Jan 20, 2024
4d7fc10
Add `only_transforms_solution` flag
schmoelder Jan 22, 2024
a8ed736
Only resample if flag is set
schmoelder Jan 22, 2024
d20d9cd
Formatting
schmoelder Jan 20, 2024
52e33bf
Fix typo
schmoelder Mar 2, 2024
a59372a
Add FractionationReference
schmoelder Jan 19, 2024
8f0faf6
Add FractionationSSE
schmoelder Jan 19, 2024
6bbb6c8
Fix add_concentration_profile (#140)
schmoelder Jun 19, 2024
12d862b
Always inherit cadet path
ronald-jaepel May 22, 2024
19e5457
Add method to calculate volumetric flow rate from velocity.
schmoelder Feb 15, 2023
3356cde
Rename .t0 to .calculate_interstitial_rt in Cstr
ronald-jaepel Mar 20, 2024
6fd5a44
Do not modify LD_LIBRARY_PATH when setting install_path
schmoelder Jun 17, 2024
618fb1f
Adapt to new DG interface in CADET-Core
schmoelder Mar 20, 2024
8ce41f3
Fix using minute for x-Axis
schmoelder Jun 17, 2024
1c9251f
Freeze discretization attributes
schmoelder Jun 17, 2024
4f1671b
Update docstrings
schmoelder Jun 17, 2024
7ddabeb
Fix incorrect declaration in AntiLangmuir as optional
AntoniaBerger May 22, 2024
cdc11b9
Change AntiLangmuir coefficient from SizedUnsignedList to SizedList
AntoniaBerger Jun 10, 2024
df97b3b
Raise exception when adding connections to Inlets or from Outlets
schmoelder Jun 25, 2024
fc521c7
Fix header level in documentation for unit operation
schmoelder Jun 25, 2024
3077270
Fix bug when adding linear constraints
schmoelder Jun 26, 2024
4039427
Fix bug when adding linear constraints
schmoelder Jun 26, 2024
158adfd
Explicitly specify support for bounds
schmoelder Jun 26, 2024
5e95258
Return np.array for linear constraints
schmoelder Jun 26, 2024
9376bdd
Fix comparison logic for linear equality constraints
schmoelder Jun 26, 2024
24a0da9
Use common optimizer interface for tolerances
schmoelder Jun 26, 2024
bae60e4
Make optimial solution a property
schmoelder Jun 27, 2024
0ce0ef9
Formatting
schmoelder Jun 26, 2024
cd2ecfe
Add option to (not) close cache on pruning
schmoelder Jun 26, 2024
0439f90
Do not plot figures if progress_frequency is None
schmoelder Jun 26, 2024
6be7ef3
Use in-memory cache for testing optimizer behaviour
schmoelder Jun 26, 2024
6efef47
Update population size in non-default parameters
schmoelder Jun 26, 2024
89759a4
Add NelderMead and COBYLA to optimizer tests
schmoelder Jun 27, 2024
9164d95
Add type hints and update docstrings (WIP)
schmoelder Jun 28, 2024
49b3dd1
Add method to evaluate bounds violation
schmoelder Jun 28, 2024
50e5d43
Cast np.bool_ to bool
schmoelder Jun 29, 2024
67a98a9
Distinguish between different constraint violations
schmoelder Jun 28, 2024
f97cd5b
Allow evaluation of population for getting (in)dependent values
schmoelder Jun 28, 2024
11df0fb
Add option to evaluate nonlinear constraints when checking individual
schmoelder Jun 28, 2024
fea0a12
Rename eps_eq -> eps_lineq
schmoelder Jun 28, 2024
7dd6b54
[WIP] Add option to specify some slack for linear constraints
schmoelder Jun 28, 2024
ee3a41f
Add COBYQA optimizer
schmoelder Jun 26, 2024
66ffcd1
Use COBYQA for fractionation
schmoelder Jun 26, 2024
c8cfb10
Remove SLSQP and COBYLA in preparation for scipy's new callback inter…
schmoelder Jun 27, 2024
0751b95
Update callback function for scipy optimizers
schmoelder Jun 27, 2024
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
26 changes: 13 additions & 13 deletions CADETProcess/comparison/comparator.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def metrics(self):
return self._metrics

@property
def n_diffference_metrics(self):
def n_difference_metrics(self):
"""int: Number of difference metrics in the Comparator."""
return len(self.metrics)

Expand Down Expand Up @@ -242,11 +242,11 @@ def setup_comparison_figure(
tuple
A tuple of the comparison figure(s) and axes object(s).
"""
if self.n_diffference_metrics == 0:
if self.n_difference_metrics == 0:
return (None, None)

comparison_fig_all, comparison_axs_all = plotting.setup_figure(
n_rows=self.n_diffference_metrics,
n_rows=self.n_difference_metrics,
squeeze=False
)

Expand All @@ -255,7 +255,7 @@ def setup_comparison_figure(

comparison_fig_ind: list[Figure] = []
comparison_axs_ind: list[Axes] = []
for i in range(self.n_diffference_metrics):
for i in range(self.n_difference_metrics):
fig, axs = plt.subplots()
comparison_fig_ind.append(fig)
comparison_axs_ind.append(axs)
Expand All @@ -271,33 +271,33 @@ def setup_comparison_figure(

def plot_comparison(
self,
simulation_results: list[SimulationResults],
simulation_results: SimulationResults,
axs: Axes | list[Axes] | None = None,
figs: Figure | list[Figure] | None = None,
file_name: str | None = None,
show: bool = True,
plot_individual: bool = False,
use_minutes: bool = True,
x_axis_in_minutes: bool = True,
) -> tuple[list[Figure], list[Axes]]:
"""
Plot the comparison of the simulation results with the reference data.

Parameters
----------
simulation_results : list of SimulationResults
List of simulation results to compare to reference data.
axs : list of AxesSubplot, optional
simulation_results : SimulationResults
Simulation results to compare to reference data.
axs : list of Axes, optional
List of subplot axes to use for plotting the metrics.
figs : list of Figure, optional
figs : list of Figures, optional
List of figures to use for plotting the metrics.
file_name : str, optional
Name of the file to save the figure to.
show : bool, optional
If True, displays the figure(s) on the screen.
plot_individual : bool, optional
If True, generates a separate figure for each metric.
use_minutes : bool, optional
Option to use x-aches (time) in minutes, default is set to True.
x_axis_in_minutes: bool, optional
If True, the x-axis will be plotted using minutes. The default is True.

Returns
-------
Expand Down Expand Up @@ -331,7 +331,7 @@ def plot_comparison(
'label': 'reference',
}
ref_time = metric.reference.time
if use_minutes:
if x_axis_in_minutes:
ref_time = ref_time / 60

plotting.add_overlay(ax, metric.reference.solution, ref_time, **plot_args)
Expand Down
118 changes: 107 additions & 11 deletions CADETProcess/comparison/difference.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@

from CADETProcess import CADETProcessError
from CADETProcess.dataStructure import UnsignedInteger
from CADETProcess.solution import SolutionBase, slice_solution
from CADETProcess.solution import SolutionIO, slice_solution
from CADETProcess.metric import MetricBase
from CADETProcess.reference import ReferenceIO, FractionationReference
from .shape import pearson, pearson_offset
from .peaks import find_peaks, find_breakthroughs

Expand All @@ -24,6 +25,7 @@
'Shape',
'PeakHeight', 'PeakPosition',
'BreakthroughHeight', 'BreakthroughPosition',
'FractionationSSE',
]


Expand Down Expand Up @@ -74,7 +76,7 @@ class DifferenceBase(MetricBase):

Parameters
----------
reference : ReferenceIO
reference : ReferenceBase
Reference used for calculating difference metric.
components : {str, list}, optional
Solution components to be considered.
Expand All @@ -97,6 +99,8 @@ class DifferenceBase(MetricBase):
If True, normalize data. The default is False.
"""

_valid_references = ()

def __init__(
self,
reference,
Expand All @@ -106,14 +110,15 @@ def __init__(
start=None,
end=None,
transform=None,
only_transforms_array=True,
resample=True,
smooth=False,
normalize=False):
"""Initialize an instance of DifferenceBase.

Parameters
----------
reference : ReferenceIO
reference : ReferenceBase
Reference used for calculating difference metric.
components : {str, list}, optional
Solution components to be considered.
Expand All @@ -128,6 +133,8 @@ def __init__(
End time of solution slice to be considerd. The default is None.
transform : callable, optional
Function to transform solution. The default is None.
only_transforms_array: bool, optional
If True, only transform np array of solution object. The default is True.
resample : bool, optional
If True, resample data. The default is True.
smooth : bool, optional
Expand All @@ -143,6 +150,7 @@ def __init__(
self.start = start
self.end = end
self.transform = transform
self.only_transforms_array = only_transforms_array
self.resample = resample
self.smooth = smooth
self.normalize = normalize
Expand All @@ -165,8 +173,11 @@ def reference(self):

@reference.setter
def reference(self, reference):
if not isinstance(reference, SolutionBase):
raise TypeError("Expected SolutionBase")
if not isinstance(reference, self._valid_references):
raise TypeError(
f"Invalid reference type: {type(reference)}. "
f"Expected types: {self._valid_references}."
)

self._reference = copy.deepcopy(reference)
if self.resample and not self._reference.is_resampled:
Expand Down Expand Up @@ -221,11 +232,12 @@ def resamples_smoothes_and_normalizes_solution(func):
@wraps(func)
def wrapper(self, solution, *args, **kwargs):
solution = copy.deepcopy(solution)
solution.resample(
self._reference.time[0],
self._reference.time[-1],
len(self._reference.time),
)
if self.resample:
solution.resample(
self._reference.time[0],
self._reference.time[-1],
len(self._reference.time),
)
if self.normalize and not solution.is_normalized:
solution.normalize()
if self.smooth and not solution.is_smoothed:
Expand All @@ -241,7 +253,11 @@ def transforms_solution(func):
def wrapper(self, solution, *args, **kwargs):
if self.transform is not None:
solution = copy.deepcopy(solution)
solution.solution = self.transform(solution.solution)

if self.only_transforms_array:
solution.solution = self.transform(solution.solution)
else:
solution = self.transform(solution)

value = func(self, solution, *args, **kwargs)
return value
Expand Down Expand Up @@ -321,6 +337,8 @@ def calculate_sse(simulation, reference):
class SSE(DifferenceBase):
"""Sum of squared errors (SSE) difference metric."""

_valid_references = (ReferenceIO, SolutionIO)

def _evaluate(self, solution):
sse = calculate_sse(solution.solution, self.reference.solution)

Expand Down Expand Up @@ -348,6 +366,8 @@ def calculate_rmse(simulation, reference):
class RMSE(DifferenceBase):
"""Root mean squared errors (RMSE) difference metric."""

_valid_references = (SolutionIO, ReferenceIO)

def _evaluate(self, solution):
rmse = calculate_rmse(solution.solution, self.reference.solution)

Expand All @@ -357,6 +377,8 @@ def _evaluate(self, solution):
class NRMSE(DifferenceBase):
"""Normalized root mean squared errors (RRMSE) difference metric."""

_valid_references = (SolutionIO, ReferenceIO)

def _evaluate(self, solution):
rmse = calculate_rmse(solution.solution, self.reference.solution)
nrmse = rmse / np.max(self.reference.solution, axis=0)
Expand All @@ -373,6 +395,8 @@ class Norm(DifferenceBase):
The order of the norm.
"""

_valid_references = (SolutionIO, ReferenceIO)

order = UnsignedInteger()

def _evaluate(self, solution):
Expand All @@ -398,6 +422,8 @@ class L2(Norm):
class AbsoluteArea(DifferenceBase):
"""Absolute difference in area difference metric."""

_valid_references = (SolutionIO, ReferenceIO)

def _evaluate(self, solution):
"""np.array: Absolute difference in area compared to reference.

Expand All @@ -418,6 +444,8 @@ def _evaluate(self, solution):
class RelativeArea(DifferenceBase):
"""Relative difference in area difference metric."""

_valid_references = (SolutionIO, ReferenceIO)

def _evaluate(self, solution):
"""np.array: Relative difference in area compared to reference.

Expand Down Expand Up @@ -462,6 +490,8 @@ class Shape(DifferenceBase):

"""

_valid_references = (SolutionIO, ReferenceIO)

@wraps(DifferenceBase.__init__)
def __init__(
self, *args,
Expand Down Expand Up @@ -645,6 +675,8 @@ class PeakHeight(DifferenceBase):
Contains the normalization factors for each peak in each component.
"""

_valid_references = (SolutionIO, ReferenceIO)

@wraps(DifferenceBase.__init__)
def __init__(
self, *args,
Expand Down Expand Up @@ -737,6 +769,8 @@ class PeakPosition(DifferenceBase):
Contains the normalization factors for each peak in each component.
"""

_valid_references = (SolutionIO, ReferenceIO)

@wraps(DifferenceBase.__init__)
def __init__(self, *args, normalize_metrics=True, normalization_factor=None, **kwargs):
"""Initialize PeakPosition object.
Expand Down Expand Up @@ -823,6 +857,8 @@ class BreakthroughHeight(DifferenceBase):

"""

_valid_references = (SolutionIO, ReferenceIO)

@wraps(DifferenceBase.__init__)
def __init__(self, *args, normalize_metrics=True, **kwargs):
"""Initialize BreakthroughHeight metric.
Expand Down Expand Up @@ -874,6 +910,8 @@ def _evaluate(self, solution):
class BreakthroughPosition(DifferenceBase):
"""Absolute difference in breakthrough curve position difference metric."""

_valid_references = (SolutionIO, ReferenceIO)

@wraps(DifferenceBase.__init__)
def __init__(self, *args, normalize_metrics=True, normalization_factor=None, **kwargs):
"""
Expand Down Expand Up @@ -935,3 +973,61 @@ def _evaluate(self, solution):
]

return np.abs(score)


class FractionationSSE(DifferenceBase):
"""Fractionation based score using SSE."""

_valid_references = (FractionationReference)

@wraps(DifferenceBase.__init__)
def __init__(self, *args, normalize_metrics=True, normalization_factor=None, **kwargs):
"""
Initialize the FractionationSSE object.

Parameters
----------
*args :
Positional arguments for DifferenceBase.
normalize_metrics : bool, optional
Whether to normalize the metrics. Default is True.
normalization_factor : float, optional
Factor to use for normalization.
If None, it is set to the maximum of the difference between the reference
breakthrough and the start time, and the difference between the end time and
the reference breakthrough.
**kwargs : dict
Keyword arguments passed to the base class constructor.

"""
super().__init__(*args, resample=False, only_transforms_array=False, **kwargs)

if not isinstance(self.reference, FractionationReference):
raise TypeError("FractionationSSE can only work with FractionationReference")

def transform(solution):
solution = copy.deepcopy(solution)
solution_fractions = [
solution.create_fraction(frac.start, frac.end)
for frac in self.reference.fractions
]

solution.time = np.array([(frac.start + frac.end)/2 for frac in solution_fractions])
solution.solution = np.array([frac.concentration for frac in solution_fractions])

return solution

self.transform = transform

def _evaluate(self, solution):
"""np.array: Difference in breakthrough position (time).

Parameters
----------
solution : SolutionIO
Concentration profile of simulation.

"""
sse = calculate_sse(solution.solution, self.reference.solution)

return sse
14 changes: 14 additions & 0 deletions CADETProcess/dataStructure/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,19 @@ def name(self):


class CachedPropertiesMixin(Structure):
"""
Mixin class for caching properties in a structured object.

This class is designed to be used as a mixin in conjunction with other classes
inheriting from `Structure`. It provides functionality for caching properties and
managing a lock state to control the caching behavior.

Notes
-----
- To prevent the return of outdated state, the cache is cleared whenever the `lock`
state is changed.
"""

_lock = Bool(default=False)

def __init__(self, *args, **kwargs):
Expand All @@ -31,6 +44,7 @@ def __init__(self, *args, **kwargs):

@property
def lock(self):
"""bool: If True, properties are cached. False otherwise."""
return self._lock

@lock.setter
Expand Down
2 changes: 1 addition & 1 deletion CADETProcess/dataStructure/parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ def cast_value(self, value):
Union[bool, Any]
Boolean equivalent if value is 0 or 1; otherwise, the original value.
"""
if isinstance(value, int) and value in [0, 1]:
if isinstance(value, (int, np.bool_)) and value in [0, 1]:
value = bool(value)
return value

Expand Down
Loading
Loading