Source code for gemseo.caches.simple_cache
# Copyright 2021 IRT Saint Exupéry, https://www.irt-saintexupery.com
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License version 3 as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# Contributors:
# INITIAL AUTHORS - initial API and implementation and/or initial
# documentation
# :author: Francois Gallard, Matthias De Lozzo
# OTHER AUTHORS - MACROSCOPIC CHANGES
"""Caching module to store only one entry."""
from __future__ import annotations
from typing import Generator
from gemseo.core.cache import AbstractCache
from gemseo.core.cache import CacheEntry
from gemseo.core.cache import Data
from gemseo.core.cache import JacobianData
from gemseo.utils.data_conversion import deepcopy_dict_of_arrays
from gemseo.utils.testing import compare_dict_of_arrays
[docs]class SimpleCache(AbstractCache):
"""Dictionary-based cache storing a unique entry.
When caching an input data different from this entry, this entry is replaced by a new
one initialized with this input data.
"""
def __init__( # noqa:D107
self,
tolerance: float = 0.0,
name: str | None = None,
) -> None:
super().__init__(tolerance, name)
self.__input_data_for_outputs = {}
self.__output_data = {}
self.__input_data_for_jacobian = {}
self.__jacobian_data = {}
self.__last_input_data = {}
self.__penultimate_input_data = {}
[docs] def clear(self) -> None: # noqa:D102
super().clear()
self.__input_data_for_outputs = {}
self.__output_data = {}
self.__input_data_for_jacobian = {}
self.__jacobian_data = {}
self.__last_input_data = {}
self.__penultimate_input_data = {}
def __iter__(self) -> Generator[CacheEntry]:
if self.penultimate_entry.inputs:
yield self.penultimate_entry
yield self.last_entry
def __len__(self) -> int:
return bool(self.__penultimate_input_data) + bool(self.__last_input_data)
def __create_input_cache(self, input_data: Data) -> Data:
"""Create the input data.
Args:
input_data: The data containing the input data to cache.
Returns:
A copy of the input data.
"""
cached_input_data = deepcopy_dict_of_arrays(input_data)
if not self.__is_cached(self.__last_input_data, cached_input_data):
self.__penultimate_input_data = self.__last_input_data
self.__last_input_data = cached_input_data
return cached_input_data
def __is_cached(
self,
cached_input_data: Data,
input_data: Data,
) -> bool:
"""Check if an input data is cached.
Args:
cached_input_data: The cached input data.
input_data: The input data to be verified.
Returns:
Whether the input data is cached.
"""
if not cached_input_data:
return False
if self.tolerance == 0.0:
if compare_dict_of_arrays(input_data, cached_input_data):
return True
else:
if compare_dict_of_arrays(input_data, cached_input_data, self.tolerance):
return True
return False
[docs] def cache_outputs( # noqa:D102
self,
input_data: Data,
output_data: Data,
) -> None:
self.__input_data_for_outputs = self.__create_input_cache(input_data)
self.__output_data = deepcopy_dict_of_arrays(output_data)
if not self._output_names:
self._output_names = sorted(output_data.keys())
def __getitem__(
self,
input_data: Data,
) -> CacheEntry:
output_data, jacobian_data = {}, {}
if self.__is_cached(self.__input_data_for_outputs, input_data):
output_data = self.__output_data
if self.__is_cached(self.__input_data_for_jacobian, input_data):
jacobian_data = self.__jacobian_data
return CacheEntry(input_data, output_data, jacobian_data)
[docs] def cache_jacobian( # noqa:D102
self,
input_data: Data,
jacobian_data: JacobianData,
) -> None:
self.__input_data_for_jacobian = self.__create_input_cache(input_data)
self.__jacobian_data = jacobian_data
def __retrieve_entry(
self,
cached_input_data: Data,
) -> CacheEntry:
"""Return the cache entry corresponding to a cached input data.
Args:
cached_input_data: The cached input data.
Returns:
The cache entry corresponding to this cached input data.
"""
input_data = cached_input_data
output_data = {}
jacobian_data = {}
if self.__is_cached(self.__input_data_for_outputs, cached_input_data):
input_data = self.__input_data_for_outputs
output_data = self.__output_data
if self.__is_cached(self.__input_data_for_jacobian, cached_input_data):
input_data = self.__input_data_for_jacobian
jacobian_data = self.__jacobian_data
return CacheEntry(input_data, output_data, jacobian_data)
@property
def penultimate_entry(self) -> CacheEntry:
"""The penultimate cache entry."""
entry = self.__retrieve_entry(self.__penultimate_input_data)
if entry.outputs or entry.jacobian:
return entry
return CacheEntry({}, {}, {})
@property
def last_entry(self) -> CacheEntry: # noqa:D102
return self.__retrieve_entry(self.__last_input_data)