# -*- coding: utf-8 -*-
#
# This file is subject to the terms and conditions defined in
# file 'LICENSE', which is part of this source code package.
#
"""
This module is dedicated to the definition of a class for generic optimization problems
"""
from __future__ import annotations
from typing import Union, Literal, Optional, List
from abc import ABC
import numpy as np
from Muscat.Helpers.TextFormatHelper import TFormat
[docs]class OptimProblemBase(ABC):
"""Base class for all optimization problems"""
def __init__(self, other:Optional[any] = None, dataDeepCopy:bool = True):
super().__init__()
[docs] def TakeValuesFrom(self,other:OptimProblemBase):
"""Retrieve values from another optimization problem
Parameters
----------
other : OptimProblemBase
Another instance of OptimProblemBase
"""
raise NotImplementedError
[docs] def GetCurrentPoint(self)->any:
"""Retrieve current point of the optimization problem
Returns
-------
any
current point of the optimization problem
"""
raise NotImplementedError
## the objective function val and Sensitivity df/dp
[docs] def GetObjectiveFunctionName(self)->str:
"""Retrieve problem objective function name
Returns
-------
str
problem objective function name
"""
raise NotImplementedError
[docs] def GetObjectiveFunctionVal(self)->float:
"""Retrieve problem objective function value
Returns
-------
float
problem objective function value
"""
raise NotImplementedError
[docs] def GetObjectiveFunctionSensitivity(self)->np.ndarray:
"""Retrieve problem objective function sensitivity
Returns
-------
float
problem objective function sensitivity
"""
raise NotImplementedError
## constraints number, val and sensistivity dC/dp
[docs] def GetNumberOfInequalityConstraints(self)->int:
"""Retrieve number of inequality constraints
Returns
-------
int
number of inequality constraints
"""
raise NotImplementedError
[docs] def GetNumberOfEqualityConstraints(self)->int:
"""Retrieve number of equality constraints
Returns
-------
int
number of equality constraints
"""
raise NotImplementedError
[docs] def GetInequalityConstraintName(self,i:int)->str:
"""Retrieve name of the i-th inequality constraints
Parameters
----------
i : int
inequality constraints id
Returns
-------
str
name of the i-th inequality constraints
"""
raise NotImplementedError
[docs] def GetEqualityConstraintName(self,i:int)->str:
"""Retrieve name of the i-th equality constraints
Parameters
----------
i : int
equality constraints id
Returns
-------
str
name of the i-th equality constraints
"""
raise NotImplementedError
[docs] def GetInequalityConstraintVal(self,i:int)->float:
"""Retrieve value of the i-th inequality constraints
Parameters
----------
i : int
inequality constraints id
Returns
-------
float
value of the i-th inequality constraints
"""
raise NotImplementedError
[docs] def GetEqualityConstraintVal(self,i:int)->float:
"""Retrieve name of the i-th equality constraints
Parameters
----------
i : int
equality constraints id
Returns
-------
float
value of the i-th equality constraints
"""
raise NotImplementedError
[docs] def GetInequalityConstraintUpperBound(self,i:int)->float:
"""Retrieve upper bound of the i-th inequality constraints
Parameters
----------
i : int
inequality constraints id
Returns
-------
float
upper bound of the i-th inequality constraints
"""
raise NotImplementedError
[docs] def GetInequalityConstraintSensitivityVal(self,i:int)->np.ndarray:
"""Retrieve sensitivity of the i-th inequality constraints
Parameters
----------
i : int
inequality constraints id
Returns
-------
float
sensitivity of the i-th inequality constraints
"""
raise NotImplementedError
[docs] def SetInequalityConstraintStatus(self,i:int,status:Union[Literal[0], Literal[1]]):
"""Set inequality constraint status based on the upper bound value
Inactive = 0,
Automatic = 1
Parameters
----------
i : int
inequality constraints id
status : Union[Literal[0], Literal[1]]
either 0 or 1
"""
raise NotImplementedError
[docs] def GetNumberOfActiveInequalityConstraints(self)->int:
"""Retrieve number of inequalities constraints
Returns
-------
int
number of inequality constraints
"""
return int(np.sum([ self.GetInequalityConstraintStatus(i) for i in range(self.GetNumberOfInequalityConstraints()) ]))
[docs] def GetActiveInequalityConstraintsVals(self)->List[float]:
"""Retrieve all active inequalities constraints values
Returns
-------
List[float]
all active inequality constraints values
"""
res = []
for i in range(self.GetNumberOfInequalityConstraints()):
if self.GetInequalityConstraintStatus(i):
res.append(self.GetInequalityConstraintVal(i))
return res
[docs] def GetActiveInequalityConstraintsUpperBound(self)->List[float]:
"""Retrieve active inequalities constraints upperbound
Returns
-------
List[float]
active inequalities constraints upperbound
"""
res = []
for i in range(self.GetNumberOfInequalityConstraints()):
if self.GetInequalityConstraintStatus(i):
res.append(self.GetInequalityConstraintUpperBound(i))
return res
[docs] def GetActiveInequalityConstraintsSensitivityVals(self)->List[np.ndarray]:
"""Retrieve active inequalities sensitivity values
Returns
-------
List[np.ndarray]
active inequalities constraints sensitivity values
"""
res = []
for i in range(self.GetNumberOfInequalityConstraints()):
if self.GetInequalityConstraintStatus(i):
criteriaSensitivity = self.GetInequalityConstraintSensitivityVal(i)
res.append(criteriaSensitivity)
return res
[docs] def GetActiveEqualityConstraintsVals(self)->List[float]:
"""Retrieve all active equalities constraints values
Returns
-------
List[float]
all active equality constraints values
"""
res = []
for i in range(self.GetNumberOfEqualityConstraints()):
if self.GetEqualityConstraintStatus(i):
res.append(self.GetEqualityConstraintVal(i))
return res
[docs] def GetActiveEqualityConstraintsTargetValues(self)->List[float]:
"""Retrieve active equalities constraints target values
Returns
-------
List[float]
active equalities constraints target values
"""
res = []
for i in range(self.GetNumberOfEqualityConstraints()):
if self.GetEqualityConstraintStatus(i):
res.append(self.GetEqualityConstraintTargetValue(i))
return res
[docs] def GetActiveEqualityConstraintsSensitivityVals(self)->List[np.ndarray]:
"""Retrieve active equalities sensitivity values
Returns
-------
List[np.ndarray]
active equalities constraints sensitivity values
"""
res = []
for i in range(self.GetNumberOfEqualityConstraints()):
if self.GetEqualityConstraintStatus(i):
res.append(self.GetEqualityConstraintSensitivityVal(i))
return res
[docs] def GetInequalityConstraintStatus(self,i:int)->bool:
"""Retrieve inequality constraints status
Parameters
----------
i : int
inequality constraints id
Returns
-------
float
inequality constraints status
"""
raise NotImplementedError
[docs] def UpdateProblemWhenIterationAccepted(self):
"""Update optimization problem when iteration accepted (only required for very specific criteria)"""
raise NotImplementedError
## for pretty output
[docs] def GetNumberOfInternalVariables(self)->int:
"""Compute number of internal variables (namely objective+ number of constraints)
Returns
-------
int
number of internal variables
"""
return 1+self.GetNumberOfInequalityConstraints()+self.GetNumberOfEqualityConstraints()
[docs] def GetVariableName(self,i:int)->str:
"""Retrieve i-th internal variable name
Parameters
----------
i : int
optimization problem internal variable id
Returns
-------
str
i-th internal variable name
"""
if i == 0:
return self.GetObjectiveFunctionName()
numberOfInequalityConstraints=self.GetNumberOfInequalityConstraints()
if i <= numberOfInequalityConstraints:
return self.GetInequalityConstraintName(i-1)
return self.GetEqualityConstraintName(i-1-numberOfInequalityConstraints)
[docs] def GetVariableValue(self,i:int)->float:
"""Retrieve i-th internal variable value
Parameters
----------
i : int
optimization problem internal variable id
Returns
-------
str
i-th internal variable value
"""
if i == 0: return self.GetObjectiveFunctionVal()
numberOfInequalityConstraints=self.GetNumberOfInequalityConstraints()
if i <= numberOfInequalityConstraints:
return self.GetInequalityConstraintVal(i-1)
return self.GetEqualityConstraintVal(i-1-numberOfInequalityConstraints)
[docs] def PreStartCheck(self)->bool:
"""Start check, if anything must be verified before running the optimization problem
Returns
-------
bool
The current default behviour is to simply return True
"""
return True
[docs] def UpdateValues(self):
"""Update the optimization problem, classically by updating each of its components"""
raise NotImplementedError
[docs] def Advance(self, direction:np.ndarray, stepSize:float)->bool:
"""
Parameters
----------
direction : np.ndarray
Vector-valued direction.
stepSize : float
A multiplicative factor for `direction`.
Returns
-------
bool
Whether the advance succeeded
"""
self.point += stepSize * direction
return True
[docs] def GetDirectionFromGradient(self,gradient:np.ndarray)->np.ndarray:
"""Compute the descent direction from the gradient
Parameters
----------
gradient : np.ndarray
gradient
Returns
-------
np.ndarray
direction, simply the opposite of the gradient in the default configuration
"""
return -gradient
[docs] def SaveData(self,data:any,point:Optional[any]=None, gradient:Optional[np.ndarray]=None, direction:Optional[np.ndarray]=None):
"""Save data on disk, it is supposed to be called to track the history of the optimization problem
Parameters
----------
data : data
data
point : Optional[any], optional
point, by default None
gradient : Optional[np.ndarray], optional
gradient, by default None
direction : Optional[np.ndarray], optional
direction, by default None
"""
pass
[docs] def PrintState(self)->str:
"""Retrieve proper format compatible with the number of internal variables (used for formatting current state of optimization problem)
Returns
-------
str
formatted state
"""
res = ""
for i in range(self.GetNumberOfInternalVariables()):
res += '|' + TFormat.Center('%.8e' % self.GetVariableValue(i), fill=" ", width=16)
return res
[docs] def PrintCurrentState(self, newPoint:OptimProblemBase)->str:
"""Print current and former state of the optimization problem.
Allow to see whether there is an improvement between the current problem values and former problem value for any internal variable.
If there is, the value appears in green. Else, it appears in red.
Parameters
----------
newPoint : OptimProblemBase
current problem
Returns
-------
str
Colored internal values for current/former optimization problems
"""
def PrintOneState(oldval:float, newval:float, tocompare:float)->str:
res = ""
if tocompare == np.inf:
if newval < oldval:
res += TFormat.InGreen(TFormat.Center( "%.8e"% newval,fill=" ",width=16) )+"|"
else:
res += TFormat.InRed(TFormat.Center("%.8e"% newval,fill=" ",width=16) )+"|"
else:
if newval < tocompare:
res += TFormat.InGreen(TFormat.Center( "%.8e"% newval,fill=" ",width=16) )+"|"
else:
res += TFormat.InRed(TFormat.Center("%.8e"% newval,fill=" ",width=16) )+"|"
return res
res = ""
for i in range(self.GetNumberOfInternalVariables()):
oldval = self.GetVariableValue(i)
newval = newPoint.GetVariableValue(i)
if i == 0:
res += PrintOneState(oldval,newval, np.inf )
elif i-1 < self.GetNumberOfInequalityConstraints():
res += PrintOneState(oldval,newval, newPoint.GetInequalityConstraintUpperBound(i-1) )
else:
equalityConstraintId=i-1-self.GetNumberOfInequalityConstraints()
res += PrintOneState(oldval,newval, newPoint.GetEqualityConstraintTargetValue(equalityConstraintId) )
return res
[docs]def CheckIntegrity():
return "OK"
if __name__ == '__main__':
print(CheckIntegrity()) # pragma: no cover