def __init__(self, truth_problem, **kwargs): # Call to parent ParametrizedReducedDifferentialProblem_DerivedClass.__init__( self, truth_problem, **kwargs) # $$ ONLINE DATA STRUCTURES $$ # # Residual terms self.RieszExpansionStorage = OnlineAffineExpansionStorage self.riesz = dict() # from string to RieszExpansionStorage self.riesz_terms = list() self.ErrorEstimationOperatorExpansionStorage = OnlineAffineExpansionStorage self.error_estimation_operator = dict( ) # from string to ErrorEstimationOperatorExpansionStorage self.error_estimation_terms = list() # of tuple # $$ OFFLINE DATA STRUCTURES $$ # # Residual terms self._riesz_solve_storage = Function(self.truth_problem.V) self._riesz_solve_inner_product = None # setup by init() self._riesz_solve_homogeneous_dirichlet_bc = None # setup by init() self._error_estimation_inner_product = None # setup by init() # I/O self.folder["error_estimation"] = os.path.join( self.folder_prefix, "error_estimation") # Provide a default value for Riesz terms and Riesz product terms self.riesz_terms = [term for term in self.terms] self.error_estimation_terms = [ (term1, term2) for term1 in self.terms for term2 in self.terms if self.terms_order[term1] >= self.terms_order[term2] ]
def __init__(self, V, **kwargs): # Call to parent ParametrizedProblem.__init__(self, self.name()) # Input arguments self.V = V # Form names and order (to be filled in by child classes) self.terms = list() self.terms_order = dict() self.components = list() # Number of terms in the affine expansion self.Q = dict() # from string to integer # Matrices/vectors resulting from the truth discretization self.OperatorExpansionStorage = AffineExpansionStorage self.operator = dict() # from string to OperatorExpansionStorage self.inner_product = None # AffineExpansionStorage (for problems with one component) or dict of AffineExpansionStorage (for problem with several components), even though it will contain only one matrix self._combined_inner_product = None self.projection_inner_product = None # AffineExpansionStorage (for problems with one component) or dict of AffineExpansionStorage (for problem with several components), even though it will contain only one matrix self._combined_projection_inner_product = None self.dirichlet_bc = None # AffineExpansionStorage (for problems with one component) or dict of AffineExpansionStorage (for problem with several components) self.dirichlet_bc_are_homogeneous = None # bool (for problems with one component) or dict of bools (for problem with several components) self._combined_and_homogenized_dirichlet_bc = None # Solution self._solution = Function(self.V) self._solution_cache = dict() # of Functions self._output = 0 self._output_cache = dict() # of Numbers self._output_cache__current_cache_key = None # I/O self.folder["cache"] = os.path.join(self.folder_prefix, "cache") self.cache_config = config.get("problems", "cache")
def import_solution(self, folder=None, filename=None, solution_over_time=None, component=None, suffix=None): if folder is None: folder = self.folder_prefix if filename is None: filename = "solution" if solution_over_time is None: solution_over_time = self._solution_over_time if isinstance(solution_over_time, AbstractTimeSeries): solution = Function(self.V) assert suffix is None solution_over_time.clear() for (k, _) in enumerate( self._solution_over_time.expected_times()): ParametrizedDifferentialProblem_DerivedClass.import_solution( self, folder, filename, solution, component, suffix=k) solution_over_time.append(copy(solution)) else: # Used only for cache import solution = solution_over_time assert suffix is not None ParametrizedDifferentialProblem_DerivedClass.import_solution( self, folder, filename, solution, component=component, suffix=suffix)
def solve(self, **kwargs): """ Perform a truth solve in case no precomputed solution is imported. """ (cache_key, cache_file) = self._cache_key_and_file_from_kwargs(**kwargs) if "RAM" in self.cache_config and cache_key in self._solution_cache: log(PROGRESS, "Loading truth solution from cache") assign(self._solution, self._solution_cache[cache_key]) elif "Disk" in self.cache_config and self.import_solution( self.folder["cache"], cache_file): log(PROGRESS, "Loading truth solution from file") if "RAM" in self.cache_config: self._solution_cache[cache_key] = copy(self._solution) else: # No precomputed solution available. Truth solve is performed. log(PROGRESS, "Solving truth problem") assert not hasattr(self, "_is_solving") self._is_solving = True assign(self._solution, Function(self.V)) self._solve(**kwargs) delattr(self, "_is_solving") if "RAM" in self.cache_config: self._solution_cache[cache_key] = copy(self._solution) self.export_solution( self.folder["cache"], cache_file ) # Note that we export to file regardless of config options, because they may change across different runs return self._solution
def __init__(self, truth_problem, term, multiply_by_theta, spectrum, eigensolver_parameters, folder_prefix): # Call the parent initialization ParametrizedProblem.__init__(self, folder_prefix) # this class does not export anything self.truth_problem = truth_problem # Matrices/vectors resulting from the truth discretization self.term = term assert isinstance(self.term, (tuple, str)) if isinstance(self.term, tuple): assert len(self.term) == 2 isinstance(self.term[0], str) isinstance(self.term[1], int) self.multiply_by_theta = multiply_by_theta assert isinstance(self.multiply_by_theta, bool) self.operator = None # AffineExpansionStorage self.inner_product = None # AffineExpansionStorage, even though it will contain only one matrix self.spectrum = spectrum self.eigensolver_parameters = eigensolver_parameters # Avoid useless computations self._eigenvalue = 0. self._eigenvalue_cache = dict() self._eigenvector = Function(truth_problem.V) self._eigenvector_cache = dict() self.folder["cache"] = os.path.join(folder_prefix, "cache") self.cache_config = config.get("problems", "cache")
def solve(self, **kwargs): (cache_key, cache_file) = self._cache_key_and_file_from_kwargs(**kwargs) assert ( (cache_key in self._solution_cache) == (cache_key in self._solution_dot_cache) == (cache_key in self._solution_over_time_cache) == (cache_key in self._solution_dot_over_time_cache) ) if "RAM" in self.cache_config and cache_key in self._solution_cache: log(PROGRESS, "Loading truth solution from cache") assign(self._solution, self._solution_cache[cache_key]) assign(self._solution_dot, self._solution_dot_cache[cache_key]) assign(self._solution_over_time, self._solution_over_time_cache[cache_key]) assign(self._solution_dot_over_time, self._solution_dot_over_time_cache[cache_key]) elif "Disk" in self.cache_config and ( self.import_solution(self.folder["cache"], cache_file + "_solution", self._solution_over_time) and self.import_solution(self.folder["cache"], cache_file + "_solution_dot", self._solution_dot_over_time) ): log(PROGRESS, "Loading truth solution from file") assign(self._solution, self._solution_over_time[-1]) assign(self._solution_dot, self._solution_dot_over_time[-1]) if "RAM" in self.cache_config: self._solution_cache[cache_key] = copy(self._solution) self._solution_dot_cache[cache_key] = copy(self._solution_dot) self._solution_over_time_cache[cache_key] = copy(self._solution_over_time) self._solution_dot_over_time_cache[cache_key] = copy(self._solution_dot_over_time) else: log(PROGRESS, "Solving truth problem") assert not hasattr(self, "_is_solving") self._is_solving = True assign(self._solution, Function(self.V)) assign(self._solution_dot, Function(self.V)) self._solve(**kwargs) delattr(self, "_is_solving") if "RAM" in self.cache_config: self._solution_cache[cache_key] = copy(self._solution) self._solution_dot_cache[cache_key] = copy(self._solution_dot) self._solution_over_time_cache[cache_key] = copy(self._solution_over_time) self._solution_dot_over_time_cache[cache_key] = copy(self._solution_dot_over_time) # Note that we export to file regardless of config options, because they may change across different runs self.export_solution(self.folder["cache"], cache_file + "_solution", self._solution_over_time) self.export_solution(self.folder["cache"], cache_file + "_solution_dot", self._solution_dot_over_time) return self._solution_over_time
def __init__(self, truth_problem, term, multiply_by_theta, spectrum, eigensolver_parameters, folder_prefix): # Call the parent initialization ParametrizedProblem.__init__(self, folder_prefix) # this class does not export anything self.truth_problem = truth_problem # Matrices/vectors resulting from the truth discretization self.term = term assert isinstance(self.term, (tuple, str)) if isinstance(self.term, tuple): assert len(self.term) == 2 isinstance(self.term[0], str) isinstance(self.term[1], int) self.multiply_by_theta = multiply_by_theta assert isinstance(self.multiply_by_theta, bool) self.operator = None # AffineExpansionStorage self.inner_product = None # AffineExpansionStorage, even though it will contain only one matrix self.spectrum = spectrum self.eigensolver_parameters = eigensolver_parameters # Avoid useless computations self._eigenvalue = 0. self._eigenvector = Function(truth_problem.V) # I/O self.folder["cache"] = os.path.join(folder_prefix, "cache") def _eigenvalue_cache_key_generator(*args, **kwargs): return args def _eigenvalue_cache_import(filename): self.import_eigenvalue(self.folder["cache"], filename) return self._eigenvalue def _eigenvalue_cache_export(filename): self.export_eigenvalue(self.folder["cache"], filename) def _eigenvalue_cache_filename_generator(*args, **kwargs): return self._cache_file(args) self._eigenvalue_cache = Cache( "problems", key_generator=_eigenvalue_cache_key_generator, import_=_eigenvalue_cache_import, export=_eigenvalue_cache_export, filename_generator=_eigenvalue_cache_filename_generator ) def _eigenvector_cache_key_generator(*args, **kwargs): return args def _eigenvector_cache_import(filename): self.import_eigenvector(self.folder["cache"], filename) return self._eigenvector def _eigenvector_cache_export(filename): self.export_eigenvector(self.folder["cache"], filename) def _eigenvector_cache_filename_generator(*args, **kwargs): return self._cache_file(args) self._eigenvector_cache = Cache( "problems", key_generator=_eigenvector_cache_key_generator, import_=_eigenvector_cache_import, export=_eigenvector_cache_export, filename_generator=_eigenvector_cache_filename_generator )
def __init__(self, V, **kwargs): # Call to parent StokesProblem_Base.__init__(self, V, **kwargs) # Form names for saddle point problems self.terms = [ "a", "b", "bt", "f", "g", # Auxiliary terms for supremizer enrichment "bt_restricted" ] self.terms_order = { "a": 2, "b": 2, "bt": 2, "f": 1, "g": 1, # Auxiliary terms for supremizer enrichment "bt_restricted": 2 } self.components = ["u", "s", "p"] # Auxiliary storage for supremizer enrichment, using a subspace of V self._supremizer = Function(V, "s") # I/O def _supremizer_cache_key_generator(*args, **kwargs): assert len(args) is 1 assert args[0] == self.mu return self._supremizer_cache_key_from_kwargs(**kwargs) def _supremizer_cache_import(filename): supremizer = copy(self._supremizer) self.import_supremizer(self.folder["cache"], filename, supremizer) return supremizer def _supremizer_cache_export(filename): self.export_supremizer(self.folder["cache"], filename) def _supremizer_cache_filename_generator(*args, **kwargs): assert len(args) is 1 assert args[0] == self.mu return self._supremizer_cache_file_from_kwargs(**kwargs) self._supremizer_cache = Cache( "problems", key_generator=_supremizer_cache_key_generator, import_=_supremizer_cache_import, export=_supremizer_cache_export, filename_generator=_supremizer_cache_filename_generator)
def solve(self, **kwargs): """ Perform a truth solve in case no precomputed solution is imported. """ self._latest_solve_kwargs = kwargs try: assign(self._solution, self._solution_cache[self.mu, kwargs]) # **kwargs is not supported by __getitem__ except KeyError: assert not hasattr(self, "_is_solving") self._is_solving = True assign(self._solution, Function(self.V)) self._solve(**kwargs) # will also add to cache delattr(self, "_is_solving") return self._solution
def solve(self, **kwargs): self._latest_solve_kwargs = kwargs try: assign(self._solution_over_time, self._solution_over_time_cache[ self.mu, kwargs]) # **kwargs is not supported by __getitem__ assign(self._solution_dot_over_time, self._solution_dot_over_time_cache[self.mu, kwargs]) except KeyError: assert not hasattr(self, "_is_solving") self._is_solving = True assign(self._solution, Function(self.V)) assign(self._solution_dot, Function(self.V)) self._solve(**kwargs) delattr(self, "_is_solving") self._solution_over_time_cache[self.mu, kwargs] = copy( self._solution_over_time) self._solution_dot_over_time_cache[self.mu, kwargs] = copy( self._solution_dot_over_time) else: assign(self._solution, self._solution_over_time[-1]) assign(self._solution_dot, self._solution_dot_over_time[-1]) return self._solution_over_time
def import_solution(self, folder=None, filename=None, solution_over_time=None, component=None, suffix=None): if folder is None: folder = self.folder_prefix if filename is None: filename = "solution" solution = Function(self.V) if solution_over_time is None: solution_over_time = self._solution_over_time assert suffix is None solution_over_time.clear() for (k, _) in enumerate(self._solution_over_time.expected_times()): ParametrizedDifferentialProblem_DerivedClass.import_solution( self, folder, filename, solution, component, suffix=k) solution_over_time.append(copy(solution))
def import_solution(self, folder=None, filename=None, solution_over_time=None, component=None, suffix=None): if folder is None: folder = self.folder_prefix if filename is None: filename = "solution" solution = Function(self.V) if solution_over_time is None: solution_over_time = self._solution_over_time assert suffix is None k = 0 self.t = 0 del solution_over_time[:] while self.t <= self.T: import_solution = ParametrizedDifferentialProblem_DerivedClass.import_solution(self, folder, filename, solution, component, suffix=k) if import_solution: solution_over_time.append(copy(solution)) k += 1 self.t += self.dt else: return False return True
def ic_eval(self): problem = self.problem if len(problem.components) > 1: all_initial_conditions = list() all_initial_conditions_thetas = list() for component in problem.components: if problem.initial_condition[component] is not None: all_initial_conditions.extend( problem.initial_condition[component]) all_initial_conditions_thetas.extend( problem.compute_theta("initial_condition_" + component)) if len(all_initial_conditions) > 0: all_initial_conditions = tuple(all_initial_conditions) all_initial_conditions = AffineExpansionStorage( all_initial_conditions) all_initial_conditions_thetas = tuple( all_initial_conditions_thetas) else: all_initial_conditions = None all_initial_conditions_thetas = None else: if problem.initial_condition is not None: all_initial_conditions = problem.initial_condition all_initial_conditions_thetas = problem.compute_theta( "initial_condition") else: all_initial_conditions = None all_initial_conditions_thetas = None assert (all_initial_conditions is None) == (all_initial_conditions_thetas is None) if all_initial_conditions is not None: return sum( product(all_initial_conditions_thetas, all_initial_conditions)) else: return Function(problem.V)
def __init__(self, V, **kwargs): # Call the parent initialization ParametrizedDifferentialProblem_DerivedClass.__init__(self, V, **kwargs) # Store quantities related to the time discretization self.t = 0. self.t0 = 0. self.dt = None self.T = None # Additional options for time stepping may be stored in the following dict self._time_stepping_parameters = dict() self._time_stepping_parameters["initial_time"] = self.t0 # Matrices/vectors resulting from the truth discretization self.initial_condition = None # AffineExpansionStorage (for problems with one component) or dict of AffineExpansionStorage (for problem with several components) self.initial_condition_is_homogeneous = None # bool (for problems with one component) or dict of bools (for problem with several components) # Time derivative of the solution, at the current time self._solution_dot = Function(self.V) self._solution_dot_cache = dict() # of Functions # Solution and output over time self._solution_over_time = list() # of Functions self._solution_dot_over_time = list() # of Functions self._solution_over_time_cache = dict() # of list of Functions self._solution_dot_over_time_cache = dict() # of list of Functions self._output_over_time = list() # of numbers self._output_over_time_cache = dict() # of list of numbers
def __init__(self, V, **kwargs): # Call the parent initialization ParametrizedDifferentialProblem_DerivedClass.__init__( self, V, **kwargs) # Store quantities related to the time discretization self.t = 0. self.t0 = 0. self.dt = None self.T = None # Additional options for time stepping may be stored in the following dict self._time_stepping_parameters = dict() self._time_stepping_parameters["initial_time"] = self.t0 # Matrices/vectors resulting from the truth discretization self.initial_condition = None # AffineExpansionStorage (for problems with one component) or dict of AffineExpansionStorage (for problem with several components) self.initial_condition_is_homogeneous = None # bool (for problems with one component) or dict of bools (for problem with several components) # Time derivative of the solution, at the current time self._solution_dot = Function(self.V) # Solution and output over time self._solution_over_time = list() # of Functions self._solution_dot_over_time = list() # of Functions self._output_over_time = list() # of numbers # I/O def _solution_cache_key_generator(*args, **kwargs): assert len(args) is 1 assert args[0] == self.mu return self._cache_key_from_kwargs(**kwargs) def _solution_cache_import(filename): self.import_solution(self.folder["cache"], filename) return self._solution_over_time def _solution_cache_export(filename): self.export_solution(self.folder["cache"], filename) def _solution_cache_filename_generator(*args, **kwargs): assert len(args) is 1 assert args[0] == self.mu return self._cache_file_from_kwargs(**kwargs) self._solution_over_time_cache = Cache( "problems", key_generator=_solution_cache_key_generator, import_=_solution_cache_import, export=_solution_cache_export, filename_generator=_solution_cache_filename_generator) self._solution_dot_over_time_cache = Cache( "problems", key_generator=_solution_cache_key_generator, import_=_solution_cache_import, export=_solution_cache_export, filename_generator=_solution_cache_filename_generator) del self._solution_cache def _output_cache_key_generator(*args, **kwargs): assert len(args) is 1 assert args[0] == self.mu return self._cache_key_from_kwargs(**kwargs) def _output_cache_import(filename): self.import_output(self.folder["cache"], filename) return self._output_over_time def _output_cache_export(filename): self.export_output(self.folder["cache"], filename) def _output_cache_filename_generator(*args, **kwargs): assert len(args) is 1 assert args[0] == self.mu return self._cache_file_from_kwargs(**kwargs) self._output_over_time_cache = Cache( "problems", key_generator=_output_cache_key_generator, import_=_output_cache_import, export=_output_cache_export, filename_generator=_output_cache_filename_generator) del self._output_cache
def __init__(self, truth_problem, spectrum, eigensolver_parameters, folder_prefix, expansion_index=None): # Call the parent initialization ParametrizedProblem.__init__(self, folder_prefix) self.truth_problem = truth_problem # Matrices/vectors resulting from the truth discretization self.expansion_index = expansion_index self.operator = { "stability_factor_left_hand_matrix": None, # AffineExpansionStorage "stability_factor_right_hand_matrix": None # AffineExpansionStorage, even though it will contain only one matrix } self.dirichlet_bc = None # AffineExpansionStorage self.spectrum = spectrum self.eigensolver_parameters = eigensolver_parameters # Solution self._eigenvalue = 0. self._eigenvector = Function(truth_problem.stability_factor_V) # I/O self.folder["cache"] = os.path.join(folder_prefix, "cache") def _eigenvalue_cache_key_generator(*args, **kwargs): return args def _eigenvalue_cache_import(filename): self.import_eigenvalue(self.folder["cache"], filename) return self._eigenvalue def _eigenvalue_cache_export(filename): self.export_eigenvalue(self.folder["cache"], filename) def _eigenvalue_cache_filename_generator(*args, **kwargs): return self._cache_file(args) self._eigenvalue_cache = Cache( "problems", key_generator=_eigenvalue_cache_key_generator, import_=_eigenvalue_cache_import, export=_eigenvalue_cache_export, filename_generator=_eigenvalue_cache_filename_generator) def _eigenvector_cache_key_generator(*args, **kwargs): return args def _eigenvector_cache_import(filename): self.import_eigenvector(self.folder["cache"], filename) return self._eigenvector def _eigenvector_cache_export(filename): self.export_eigenvector(self.folder["cache"], filename) def _eigenvector_cache_filename_generator(*args, **kwargs): return self._cache_file(args) self._eigenvector_cache = Cache( "problems", key_generator=_eigenvector_cache_key_generator, import_=_eigenvector_cache_import, export=_eigenvector_cache_export, filename_generator=_eigenvector_cache_filename_generator)
def __init__(self, V, **kwargs): # Call to parent StokesOptimalControlProblem_Base.__init__(self, V, **kwargs) # Form names for saddle point problems self.terms = [ "a", "a*", "b", "b*", "bt", "bt*", "c", "c*", "m", "n", "f", "g", "h", "l", # Auxiliary terms for supremizer enrichment "bt_restricted", "bt*_restricted" ] self.terms_order = { "a": 2, "a*": 2, "b": 2, "b*": 2, "bt": 2, "bt*": 2, "c": 2, "c*": 2, "m": 2, "n": 2, "f": 1, "g": 1, "l": 1, "h": 0, # Auxiliary terms for supremizer enrichment "bt_restricted": 2, "bt*_restricted": 2 } self.components = ["v", "s", "p", "u", "w", "r", "q"] # Auxiliary storage for supremizer enrichment, using a subspace of V self._supremizer = {"s": Function(V, "s"), "r": Function(V, "r")} # I/O def _supremizer_cache_key_generator(*args, **kwargs): assert len(args) is 1 assert args[0] == self.mu return self._supremizer_cache_key_from_kwargs(**kwargs) def _supremizer_cache_import(component): def _supremizer_cache_import_impl(filename): supremizer = copy(self._supremizer[component]) self.import_supremizer(self.folder["cache"], filename, supremizer, component=component) return supremizer return _supremizer_cache_import_impl def _supremizer_cache_export(component): def _supremizer_cache_export_impl(filename): self.export_supremizer(self.folder["cache"], filename, component=component) return _supremizer_cache_export_impl def _supremizer_cache_filename_generator(*args, **kwargs): assert len(args) is 1 assert args[0] == self.mu return self._supremizer_cache_file_from_kwargs(**kwargs) self._supremizer_cache = { "s": Cache("problems", key_generator=_supremizer_cache_key_generator, import_=_supremizer_cache_import("s"), export=_supremizer_cache_export("s"), filename_generator=_supremizer_cache_filename_generator), "r": Cache("problems", key_generator=_supremizer_cache_key_generator, import_=_supremizer_cache_import("r"), export=_supremizer_cache_export("r"), filename_generator=_supremizer_cache_filename_generator) }
def __init__(self, V, **kwargs): # Call to parent ParametrizedProblem.__init__(self, self.name()) # Input arguments self.V = V # Form names and order (to be filled in by child classes) self.terms = list() self.terms_order = dict() self.components = list() # Number of terms in the affine expansion self.Q = dict() # from string to integer # Matrices/vectors resulting from the truth discretization self.OperatorExpansionStorage = AffineExpansionStorage self.operator = dict() # from string to OperatorExpansionStorage self.inner_product = None # AffineExpansionStorage (for problems with one component) or dict of AffineExpansionStorage (for problem with several components), even though it will contain only one matrix self._combined_inner_product = None self.projection_inner_product = None # AffineExpansionStorage (for problems with one component) or dict of AffineExpansionStorage (for problem with several components), even though it will contain only one matrix self._combined_projection_inner_product = None self.dirichlet_bc = None # AffineExpansionStorage (for problems with one component) or dict of AffineExpansionStorage (for problem with several components) self.dirichlet_bc_are_homogeneous = None # bool (for problems with one component) or dict of bools (for problem with several components) self._combined_and_homogenized_dirichlet_bc = None # Solution self._solution = Function(self.V) self._output = 0. # I/O self.folder["cache"] = os.path.join(self.folder_prefix, "cache") def _solution_cache_key_generator(*args, **kwargs): assert len(args) == 1 assert args[0] == self.mu return self._cache_key_from_kwargs(**kwargs) def _solution_cache_import(filename): solution = copy(self._solution) self.import_solution(self.folder["cache"], filename, solution) return solution def _solution_cache_export(filename): self.export_solution(self.folder["cache"], filename) def _solution_cache_filename_generator(*args, **kwargs): assert len(args) == 1 assert args[0] == self.mu return self._cache_file_from_kwargs(**kwargs) self._solution_cache = Cache( "problems", key_generator=_solution_cache_key_generator, import_=_solution_cache_import, export=_solution_cache_export, filename_generator=_solution_cache_filename_generator) def _output_cache_key_generator(*args, **kwargs): assert len(args) == 1 assert args[0] == self.mu return self._cache_key_from_kwargs(**kwargs) def _output_cache_import(filename): output = [0.] self.import_output(self.folder["cache"], filename, output) assert len(output) == 1 return output[0] def _output_cache_export(filename): self.export_output(self.folder["cache"], filename) def _output_cache_filename_generator(*args, **kwargs): assert len(args) == 1 assert args[0] == self.mu return self._cache_file_from_kwargs(**kwargs) self._output_cache = Cache( "problems", key_generator=_output_cache_key_generator, import_=_output_cache_import, export=_output_cache_export, filename_generator=_output_cache_filename_generator)