cbrkit.reuse.build
1import itertools 2from collections.abc import Sequence 3from dataclasses import dataclass 4from inspect import signature as inspect_signature 5from multiprocessing.pool import Pool 6from typing import cast, override 7 8from ..helpers import ( 9 batchify_adaptation, 10 batchify_sim, 11 chunkify, 12 get_logger, 13 mp_count, 14 mp_map, 15 mp_starmap, 16 produce_factory, 17 use_mp, 18) 19from ..typing import ( 20 AnyAdaptationFunc, 21 AnySimFunc, 22 Casebase, 23 Float, 24 MapAdaptationFunc, 25 MaybeFactory, 26 ReduceAdaptationFunc, 27 ReuserFunc, 28 SimMap, 29 SimpleAdaptationFunc, 30) 31 32logger = get_logger(__name__) 33 34__all__ = ["build"] 35 36 37@dataclass(slots=True, frozen=True) 38class build[K, V, S: Float](ReuserFunc[K, V, S]): 39 """Builds a casebase by adapting cases using an adaptation function and a similarity function. 40 41 Args: 42 adaptation_func: The adaptation function that will be applied to the cases. 43 similarity_func: The similarity function that will be used to compare the adapted cases to the query. 44 multiprocessing: Multiprocessing configuration for adaptation. 45 chunksize: Number of batches to process at a time using the adaptation function. 46 If 0, it will be set to the number of batches divided by the number of processes. 47 48 Returns: 49 The adapted casebases and the similarities between the adapted cases and the query. 50 """ 51 52 adaptation_func: MaybeFactory[AnyAdaptationFunc[K, V]] 53 similarity_func: MaybeFactory[AnySimFunc[V, S]] 54 multiprocessing: Pool | int | bool = False 55 chunksize: int = 0 56 57 @override 58 def __call__( 59 self, 60 batches: Sequence[tuple[Casebase[K, V], V]], 61 ) -> Sequence[tuple[Casebase[K, V], SimMap[K, S]]]: 62 adaptation_func = cast( 63 AnyAdaptationFunc[K, V], produce_factory(self.adaptation_func) 64 ) 65 adapted_casebases = self._adapt(batches, adaptation_func) 66 adapted_batches = [ 67 (adapted_casebase, query) 68 for adapted_casebase, (_, query) in zip( 69 adapted_casebases, batches, strict=True 70 ) 71 ] 72 73 # Score adapted cases against queries 74 sim_func = batchify_sim(produce_factory(self.similarity_func)) 75 76 flat_batches: list[tuple[V, V]] = [] 77 flat_index: list[tuple[int, K]] = [] 78 79 for idx, (casebase, query) in enumerate(adapted_batches): 80 for key, case in casebase.items(): 81 flat_index.append((idx, key)) 82 flat_batches.append((case, query)) 83 84 scores = sim_func(flat_batches) 85 86 sim_maps: list[dict[K, S]] = [{} for _ in adapted_batches] 87 for (idx, key), score in zip(flat_index, scores, strict=True): 88 sim_maps[idx][key] = score 89 90 return [ 91 (adapted_casebase, sim_map) 92 for adapted_casebase, sim_map in zip( 93 adapted_casebases, sim_maps, strict=True 94 ) 95 ] 96 97 def _adapt( 98 self, 99 batches: Sequence[tuple[Casebase[K, V], V]], 100 adaptation_func: AnyAdaptationFunc[K, V], 101 ) -> Sequence[Casebase[K, V]]: 102 adaptation_func_signature = inspect_signature(adaptation_func) 103 104 if "casebase" in adaptation_func_signature.parameters: 105 adapt_func = cast( 106 MapAdaptationFunc[K, V] | ReduceAdaptationFunc[K, V], 107 adaptation_func, 108 ) 109 adaptation_results = mp_starmap( 110 adapt_func, 111 batches, 112 self.multiprocessing, 113 logger, 114 ) 115 116 if all(isinstance(item, tuple) for item in adaptation_results): 117 adaptation_results = cast(Sequence[tuple[K, V]], adaptation_results) 118 return [ 119 {adapted_key: adapted_case} 120 for adapted_key, adapted_case in adaptation_results 121 ] 122 123 return cast(Sequence[Casebase[K, V]], adaptation_results) 124 125 adapt_func = batchify_adaptation(cast(SimpleAdaptationFunc[V], adaptation_func)) 126 batches_index: list[tuple[int, K]] = [] 127 flat_batches: list[tuple[V, V]] = [] 128 129 for idx, (casebase, query) in enumerate(batches): 130 for key, case in casebase.items(): 131 batches_index.append((idx, key)) 132 flat_batches.append((case, query)) 133 134 adapted_cases: Sequence[V] 135 136 if use_mp(self.multiprocessing) or self.chunksize > 0: 137 chunksize = ( 138 self.chunksize 139 if self.chunksize > 0 140 else len(flat_batches) // mp_count(self.multiprocessing) 141 ) 142 batch_chunks = list(chunkify(flat_batches, chunksize)) 143 adapted_chunks = mp_map( 144 adapt_func, batch_chunks, self.multiprocessing, logger 145 ) 146 adapted_cases = list(itertools.chain.from_iterable(adapted_chunks)) 147 else: 148 adapted_cases = list(adapt_func(flat_batches)) 149 150 adapted_casebases: list[dict[K, V]] = [{} for _ in range(len(batches))] 151 152 for (idx, key), adapted_case in zip(batches_index, adapted_cases, strict=True): 153 adapted_casebases[idx][key] = adapted_case 154 155 return adapted_casebases
@dataclass(slots=True, frozen=True)
class
build38@dataclass(slots=True, frozen=True) 39class build[K, V, S: Float](ReuserFunc[K, V, S]): 40 """Builds a casebase by adapting cases using an adaptation function and a similarity function. 41 42 Args: 43 adaptation_func: The adaptation function that will be applied to the cases. 44 similarity_func: The similarity function that will be used to compare the adapted cases to the query. 45 multiprocessing: Multiprocessing configuration for adaptation. 46 chunksize: Number of batches to process at a time using the adaptation function. 47 If 0, it will be set to the number of batches divided by the number of processes. 48 49 Returns: 50 The adapted casebases and the similarities between the adapted cases and the query. 51 """ 52 53 adaptation_func: MaybeFactory[AnyAdaptationFunc[K, V]] 54 similarity_func: MaybeFactory[AnySimFunc[V, S]] 55 multiprocessing: Pool | int | bool = False 56 chunksize: int = 0 57 58 @override 59 def __call__( 60 self, 61 batches: Sequence[tuple[Casebase[K, V], V]], 62 ) -> Sequence[tuple[Casebase[K, V], SimMap[K, S]]]: 63 adaptation_func = cast( 64 AnyAdaptationFunc[K, V], produce_factory(self.adaptation_func) 65 ) 66 adapted_casebases = self._adapt(batches, adaptation_func) 67 adapted_batches = [ 68 (adapted_casebase, query) 69 for adapted_casebase, (_, query) in zip( 70 adapted_casebases, batches, strict=True 71 ) 72 ] 73 74 # Score adapted cases against queries 75 sim_func = batchify_sim(produce_factory(self.similarity_func)) 76 77 flat_batches: list[tuple[V, V]] = [] 78 flat_index: list[tuple[int, K]] = [] 79 80 for idx, (casebase, query) in enumerate(adapted_batches): 81 for key, case in casebase.items(): 82 flat_index.append((idx, key)) 83 flat_batches.append((case, query)) 84 85 scores = sim_func(flat_batches) 86 87 sim_maps: list[dict[K, S]] = [{} for _ in adapted_batches] 88 for (idx, key), score in zip(flat_index, scores, strict=True): 89 sim_maps[idx][key] = score 90 91 return [ 92 (adapted_casebase, sim_map) 93 for adapted_casebase, sim_map in zip( 94 adapted_casebases, sim_maps, strict=True 95 ) 96 ] 97 98 def _adapt( 99 self, 100 batches: Sequence[tuple[Casebase[K, V], V]], 101 adaptation_func: AnyAdaptationFunc[K, V], 102 ) -> Sequence[Casebase[K, V]]: 103 adaptation_func_signature = inspect_signature(adaptation_func) 104 105 if "casebase" in adaptation_func_signature.parameters: 106 adapt_func = cast( 107 MapAdaptationFunc[K, V] | ReduceAdaptationFunc[K, V], 108 adaptation_func, 109 ) 110 adaptation_results = mp_starmap( 111 adapt_func, 112 batches, 113 self.multiprocessing, 114 logger, 115 ) 116 117 if all(isinstance(item, tuple) for item in adaptation_results): 118 adaptation_results = cast(Sequence[tuple[K, V]], adaptation_results) 119 return [ 120 {adapted_key: adapted_case} 121 for adapted_key, adapted_case in adaptation_results 122 ] 123 124 return cast(Sequence[Casebase[K, V]], adaptation_results) 125 126 adapt_func = batchify_adaptation(cast(SimpleAdaptationFunc[V], adaptation_func)) 127 batches_index: list[tuple[int, K]] = [] 128 flat_batches: list[tuple[V, V]] = [] 129 130 for idx, (casebase, query) in enumerate(batches): 131 for key, case in casebase.items(): 132 batches_index.append((idx, key)) 133 flat_batches.append((case, query)) 134 135 adapted_cases: Sequence[V] 136 137 if use_mp(self.multiprocessing) or self.chunksize > 0: 138 chunksize = ( 139 self.chunksize 140 if self.chunksize > 0 141 else len(flat_batches) // mp_count(self.multiprocessing) 142 ) 143 batch_chunks = list(chunkify(flat_batches, chunksize)) 144 adapted_chunks = mp_map( 145 adapt_func, batch_chunks, self.multiprocessing, logger 146 ) 147 adapted_cases = list(itertools.chain.from_iterable(adapted_chunks)) 148 else: 149 adapted_cases = list(adapt_func(flat_batches)) 150 151 adapted_casebases: list[dict[K, V]] = [{} for _ in range(len(batches))] 152 153 for (idx, key), adapted_case in zip(batches_index, adapted_cases, strict=True): 154 adapted_casebases[idx][key] = adapted_case 155 156 return adapted_casebases
Builds a casebase by adapting cases using an adaptation function and a similarity function.
Arguments:
- adaptation_func: The adaptation function that will be applied to the cases.
- similarity_func: The similarity function that will be used to compare the adapted cases to the query.
- multiprocessing: Multiprocessing configuration for adaptation.
- chunksize: Number of batches to process at a time using the adaptation function. If 0, it will be set to the number of batches divided by the number of processes.
Returns:
The adapted casebases and the similarities between the adapted cases and the query.