Source code for CADETProcess.performance

"""
=============================================
Performance (:mod:`CADETProcess.performance`)
=============================================

.. currentmodule:: CADETProcess.performance


Performance data
================

Classes for storing the performance parameters after fractionation.

.. autosummary::
    :toctree: generated/

    Performance
    RankedPerformance

Performance Indicators
======================

Individual performance indicators (extracted from Performance).
Mostly convenince method.

.. autosummary::
    :toctree: generated/

    PerformanceIndicator
    Mass
    Recovery
    Productivity
    EluentConsumption
    Purity
    Concentration
    PerformanceProduct
    MassBalanceDifference

Notes
-----
Performance Indicators might be deprecated in future since with new evaluation chains
it's no longer required for setting up as optimization problem.

"""  # noqa

from typing import Any, Optional

import numpy as np

from CADETProcess import CADETProcessError
from CADETProcess.dataStructure import SizedNdArray, Structure
from CADETProcess.metric import MetricBase
from CADETProcess.processModel import ComponentSystem


[docs] class Performance(Structure): """ Class for storing the performance parameters after fractionation. Attributes ---------- mass : np.ndarray Mass of each component in the system after fractionation. Size depends on number of components. concentration : np.ndarray Concentration of each component in the system after fractionation. Size depends on number of components. purity : np.ndarray Purity of each component in the system after fractionation. Size depends on number of components. recovery : np.ndarray Recovery of each component in the system after fractionation. Size depends on number of components. productivity : np.ndarray Productivity of each component in the system after fractionation. Size depends on number of components. eluent_consumption : np.ndarray Eluent consumption of each component in the system after fractionation. Size depends on number of components. mass_balance_difference : np.ndarray Mass balance difference of each component. Size depends on number of components. component_system : ComponentSystem The component system used for fractionation. If not provided, a default component system is used. See Also -------- CADETProcess.fractionation RankedPerformance """ _performance_keys = [ "mass", "concentration", "purity", "recovery", "productivity", "eluent_consumption", "mass_balance_difference", ] mass = SizedNdArray(size=("n_comp")) concentration = SizedNdArray(size=("n_comp")) purity = SizedNdArray(size=("n_comp")) recovery = SizedNdArray(size=("n_comp")) productivity = SizedNdArray(size=("n_comp")) eluent_consumption = SizedNdArray(size=("n_comp")) mass_balance_difference = SizedNdArray(size=("n_comp")) def __init__( self, mass: np.ndarray, concentration: np.ndarray, purity: np.ndarray, recovery: np.ndarray, productivity: np.ndarray, eluent_consumption: np.ndarray, mass_balance_difference: np.ndarray, component_system: Optional[ComponentSystem] = None, ) -> None: """ Initialize Performance. Parameters ---------- mass : ndarray The mass of each component. concentration : ndarray The concentration of each component. purity : ndarray The purity of each component. recovery : ndarray The recovery of each component. productivity : ndarray The productivity of each component. eluent_consumption : ndarray The eluent consumption of each component. mass_balance_difference : ndarray The difference in mass balance of each component. component_system : ComponentSystem The ComponentSystem object that describes the system's components. """ if component_system is None: component_system = ComponentSystem(mass.shape[0]) self.component_system = component_system self.mass = mass self.concentration = concentration self.purity = purity self.recovery = recovery self.productivity = productivity self.eluent_consumption = eluent_consumption self.mass_balance_difference = mass_balance_difference @property def n_comp(self) -> int: """int: Number of components in the system.""" return self.component_system.n_comp
[docs] def to_dict(self) -> dict: """Return dictionary representation of the object.""" return {key: getattr(self, key).tolist() for key in self._performance_keys}
[docs] def __getitem__(self, item: str) -> Any: """Get an attribute of the object by its name.""" if item not in self._performance_keys: raise AttributeError("Not a valid performance parameter") return getattr(self, item)
def __repr__(self) -> str: """str: String representation of the object.""" return ( f"{self.__class__.__name__}(mass={np.array_repr(self.mass)}, " f"concentration={np.array_repr(self.concentration)}, " f"purity={np.array_repr(self.purity)}, " f"recovery={np.array_repr(self.recovery)}, " f"productivity={np.array_repr(self.productivity)}, " f"eluent_consumption={np.array_repr(self.eluent_consumption)} " f"mass_balance_difference={np.array_repr(self.mass_balance_difference)})" )
[docs] class RankedPerformance: """ Class for calculating a weighted average of the Performance. See Also -------- Performance ranked_objective_decorator """ _performance_keys = Performance._performance_keys def __init__( self, performance: Performance, ranking: str | list[float] | int, ) -> None: """Initialize RankedPerformance.""" if not isinstance(performance, Performance): raise TypeError("Expected Performance") self._performance = performance self.ranking = ranking @property def performance(self) -> Performance: """Performance: Performance object.""" return self._performance @property def ranking(self) -> list[float]: """list[float]: Relative weighting factors for multi component evaluation.""" return self._ranking @ranking.setter def ranking(self, ranking: str | list[float] | int = None) -> None: if ranking == "equal": ranking = self.performance.n_comp * [1.0] if isinstance(ranking, int): index = ranking ranking = self.performance.n_comp * [0.0] ranking[index] = 1 if len(ranking) != self.performance.n_comp: raise CADETProcessError("Number of components does not match.") self._ranking = ranking
[docs] def to_dict(self) -> dict: """Return dictionary representation of the object.""" return {key: float(getattr(self, key)) for key in self._performance_keys}
def __getattr__(self, item: str) -> float: """Retrieve a performance attribute by its name, weighted by ranking.""" if item not in self._performance_keys: raise AttributeError return sum(self._performance[item] * self.ranking) / sum(self.ranking)
[docs] def __getitem__(self, item: str) -> Any: """Retrieve an attribute of the object by its name using indexing syntax.""" if item not in self._performance_keys: raise AttributeError("Not a valid performance parameter") return getattr(self, item)
def __repr__(self) -> str: """str: Sting representation of the object.""" return ( f"{self.__class__.__name__}(mass={np.array_repr(self.mass)}, " f"concentration={np.array_repr(self.concentration)}, " f"purity={np.array_repr(self.purity)}, " f"recovery={np.array_repr(self.recovery)}, " f"productivity={np.array_repr(self.productivity)}, " f"eluent_consumption={np.array_repr(self.eluent_consumption)} " f"mass_balance_difference={np.array_repr(self.mass_balance_difference)})" )
[docs] class PerformanceIndicator(MetricBase): """ Base class for performance indicators used in optimization and fractionation. See Also -------- RankedPerformance """ def __init__(self, ranking: Optional[str | list[float] | int] = None) -> None: """ Initialize PerformanceIndicator. Parameters ---------- ranking : list of floats, optional Weights to rank individual compoments. If None, all compoments are ranke """ self.ranking = ranking @property def ranking(self) -> str | list[float] | int | None: """list[float]: Relative weighting factors for multi component evaluation.""" return self._ranking @ranking.setter def ranking(self, ranking: Optional[str | list[float] | int]) -> None: self._ranking = ranking @property def bad_metrics(self) -> int: """int: Bad metrics to use when evaluation fails.""" return 0
[docs] def evaluate(self, performance: Performance) -> np.ndarray: """ Evaluate the performance indicator for the given performance data. Parameters ---------- performance : Performance Object containing performance data. Returns ------- np.ndarray Array of performance indicator values. """ try: performance = performance.performance except AttributeError: pass if self.ranking is not None: performance = RankedPerformance(performance, self.ranking) value = self._evaluate(performance).tolist() if self.ranking is not None: metric = [value] else: metric = [] for i, comp in enumerate(performance.component_system): metric.append(value[i]) return np.array(metric)
__call__ = evaluate def __str__(self) -> str: """str: String representation of the class.""" return self.__class__.__name__
[docs] class Mass(PerformanceIndicator): """ Performance indicator based on the mass of each component in the system. See Also -------- PerformanceIndicator """ def _evaluate(self, performance: Performance) -> np.ndarray: return performance.mass
[docs] class Recovery(PerformanceIndicator): """ Performance indicator based on the recovery of each component in the system. See Also -------- PerformanceIndicator """ def _evaluate(self, performance: Performance) -> np.ndarray: return performance.recovery
[docs] class Productivity(PerformanceIndicator): """ Performance indicator based on the productivity of each component in the system. See Also -------- PerformanceIndicator """ def _evaluate(self, performance: Performance) -> np.ndarray: return performance.productivity
[docs] class EluentConsumption(PerformanceIndicator): """ Performance indicator based on the specific eluent consumption of each component. See Also -------- PerformanceIndicator """ def _evaluate(self, performance: Performance) -> np.ndarray: return performance.eluent_consumption
[docs] class Purity(PerformanceIndicator): """ Performance indicator based on the purity of each component in the system. See Also -------- PerformanceIndicator """ def _evaluate(self, performance: Performance) -> np.ndarray: return performance.purity
[docs] class Concentration(PerformanceIndicator): """ Performance indicator based on the concentration of each component in the system. See Also -------- PerformanceIndicator """ def _evaluate(self, performance: Performance) -> np.ndarray: return performance.concentration
[docs] class PerformanceProduct(PerformanceIndicator): """ Performance indicator based on the product of several performance indicators. See Also -------- Productivity Recovery EluentConsumption PerformanceIndicator """ def _evaluate(self, performance: Performance) -> np.ndarray: return ( performance.productivity * performance.recovery * performance.eluent_consumption )
[docs] class MassBalanceDifference(PerformanceIndicator): """ Performance indicator based on the mass balance of each component in the system. See Also -------- PerformanceIndicator """ def _evaluate(self, performance: Performance) -> np.ndarray: return np.abs(performance.mass_balance_difference)