amachine.am_structured_symbol_set
1from dataclasses import dataclass 2from collections import Counter 3import math 4import json 5import numpy as np 6 7from .am_random import exp_uniform_blend, resolve_rng 8from .am_vocabulary import Vocabulary 9 10@dataclass 11class StructuredSymbolSet : 12 13 categories : set[int] 14 category_cohesion : np.ndarray # 2d 15 symbols : list[str] 16 symbol_categories : dict[str,int] 17 symbol_cohesion : np.ndarray # 2d 18 19 def save_json( 20 self, 21 filename: str, 22 eoc_symbols : list[set[str]] | None = None ) -> None : 23 24 if eoc_symbols is None : 25 eoc_symbols = [] 26 27 data = { 28 "categories" : sorted(self.categories), 29 "category_cohesion" : self.category_cohesion.tolist(), 30 "symbols" : self.symbols, 31 "symbol_categories" : self.symbol_categories, 32 "symbol_cohesion" : self.symbol_cohesion.tolist(), 33 "eoc_symbols" : [ sorted( syms ) for syms in eoc_symbols ] 34 } 35 36 with open(filename, 'w') as f: 37 json.dump(data, f, indent=4) 38 39 def symbol_counts_by_category( self ) -> dict[int,int] : 40 return dict(Counter(self.symbol_categories.values())) 41 42 def cyclic_orbit_size(self, invariant_categories: set[int] | None = None) -> int: 43 44 if invariant_categories is None: 45 invariant_categories = set() 46 47 category_sizes = self.symbol_counts_by_category() 48 49 effective_sizes = [ 50 size for category, size in category_sizes.items() 51 if category not in invariant_categories 52 ] 53 54 if not effective_sizes: 55 return 1 56 57 return math.lcm(*effective_sizes) 58 59 def category_symbols( self ) -> dict[ int,list[str] ] : 60 res = {} 61 for c in self.categories : 62 res[ c ] = sorted( [ 63 s for s in self.symbols if self.symbol_categories[ s ] == c 64 ] ) 65 return res 66 67 @classmethod 68 def generate( 69 cls, 70 n_categories : int, 71 symbols_per_category : int | list[int], 72 rigidity : float, 73 within_category_variability : float, 74 category_repetition_penalty : float = 0.5, 75 symbol_repetition_penalty : float = 0.85, 76 np_rng : np.random.Generator | None = None ) -> "StructuredSymbolSet" : 77 78 if isinstance( symbols_per_category, int ) : 79 symbols_per_category = [symbols_per_category]*n_categories 80 81 elif len( symbols_per_category ) != n_categories : 82 raise ValueError( "len( symbols_per_category ) != n_categories" ) 83 84 if within_category_variability < 0.0 or within_category_variability > 1.0 : 85 raise ValueError( f"within_category_variability should range from 0 to 1" ) 86 87 if category_repetition_penalty < 0.0 or category_repetition_penalty > 1.0 : 88 raise ValueError( f"category_repetition_penalty should range from 0 to 1" ) 89 90 if symbol_repetition_penalty < 0.0 or symbol_repetition_penalty > 1.0 : 91 raise ValueError( f"symbol_repetition_penalty should range from 0 to 1" ) 92 93 rng = resolve_rng( np_rng ) 94 95 n_symbols = sum( count for count in symbols_per_category ) 96 97 symbol_pool = Vocabulary.digits() + Vocabulary.letters_lower() + Vocabulary.greek_lower() + Vocabulary.greek_upper() 98 99 if len( symbol_pool ) < n_symbols : 100 raise ValueError( f"Too many symbols required, n_categories*symbols_per_category must be < {len(symbol_pool)}." ) 101 102 symbol_categories = {} 103 alphabet = [] 104 s_idx = 0 105 106 for i in range( n_categories ) : 107 for j in range( symbols_per_category[ i ] ) : 108 alphabet.append( symbol_pool[ s_idx ] ) 109 symbol_categories[ symbol_pool[ s_idx ] ] = i 110 s_idx += 1 111 112 category_cohesion = np.zeros( ( n_categories, n_categories ) ) 113 for i in range( n_categories ) : 114 115 category_cohesion[ i, : ] = exp_uniform_blend( 116 n=n_categories, 117 alpha=(1.0-rigidity), 118 np_rng=rng ) 119 120 category_cohesion[ i, i ] *= (1.0-category_repetition_penalty) 121 122 symbol_cohesion = np.zeros( ( n_symbols, n_symbols ) ) 123 for i, symb_a in enumerate( alphabet ) : 124 for j, symb_b in enumerate( alphabet ) : 125 126 c_a = symbol_categories[ symb_a ] 127 c_b = symbol_categories[ symb_b ] 128 129 symbol_cohesion[ i, j ] = category_cohesion[ c_a, c_b ] + category_cohesion[ c_a, c_b ] * rng.random() * within_category_variability 130 131 if i == j : 132 symbol_cohesion[ i, j ] *= ( 1.0 - symbol_repetition_penalty ) 133 134 return cls( 135 categories=set( symbol_categories.values() ), 136 category_cohesion=category_cohesion, 137 symbols=alphabet, 138 symbol_categories=symbol_categories, 139 symbol_cohesion=symbol_cohesion 140 ) 141 142 @classmethod 143 def generate_principled( 144 cls, 145 n_categories : int, 146 min_category_size : int, 147 category_growth_rate : float, 148 small_category_bias : float, 149 within_category_variability : float, 150 category_repetition_penalty : float = 0.5, 151 symbol_repetition_penalty : float = 0.85, 152 np_rng : np.random.Generator | None = None ) -> "StructuredSymbolSet" : 153 154 if small_category_bias < 0.0 or small_category_bias > 1.0 : 155 raise ValueError( f"small_category_bias should range from 0 to 1" ) 156 157 if within_category_variability < 0.0 or within_category_variability > 1.0 : 158 raise ValueError( f"within_category_variability should range from 0 to 1" ) 159 160 if category_repetition_penalty < 0.0 or category_repetition_penalty > 1.0 : 161 raise ValueError( f"category_repetition_penalty should range from 0 to 1" ) 162 163 if symbol_repetition_penalty < 0.0 or symbol_repetition_penalty > 1.0 : 164 raise ValueError( f"symbol_repetition_penalty should range from 0 to 1" ) 165 166 rng = resolve_rng( np_rng ) 167 168 category_sizes = [ min_category_size ] 169 last_term = min_category_size 170 171 for c in range( n_categories - 1 ) : 172 next_term = last_term * category_growth_rate 173 category_sizes.append( int( round( next_term ) ) ) 174 last_term = next_term 175 176 n_symbols = sum( category_sizes ) 177 symbol_pool = Vocabulary.digits() + Vocabulary.letters_lower() + Vocabulary.greek_lower() + Vocabulary.greek_upper() 178 179 if len( symbol_pool ) < n_symbols : 180 raise ValueError( f"Not enough symbols available. {n_symbols} requested, but pool has {len(symbol_pool)}." ) 181 182 symbol_categories = {} 183 alphabet = [] 184 s_idx = 0 185 186 for i in range( n_categories ) : 187 for j in range( category_sizes[ i ] ) : 188 alphabet.append( symbol_pool[ s_idx ] ) 189 symbol_categories[ symbol_pool[ s_idx ] ] = i 190 s_idx += 1 191 192 min_size = min( category_sizes ) 193 max_size = max( category_sizes ) 194 195 max_size_difference = max_size - min_size 196 uniform = 1.0 / n_categories 197 198 category_cohesion = np.zeros( ( n_categories, n_categories ) ) 199 200 if max_size_difference == 0 : 201 category_cohesion[ :, : ] = uniform 202 203 else : 204 205 # maximum small category bias 206 biased = min_category_size / np.array(category_sizes, dtype=float) 207 208 # apply bias factor 209 biased = small_category_bias * biased + ( 1.0 - small_category_bias ) * uniform 210 211 # normalized 212 biased = biased / np.sum( biased ) 213 214 for i in range( n_categories ) : 215 216 # Small category is more uniformly selective, 217 # Larger categories biased towareds selecting smaller categories 218 # r_i = ( category_sizes[i] - min_size ) / ( max_size - min_size ) 219 220 r_i = 1.0 - 1.0 / math.pow( category_sizes[i], 0.5 ) 221 222 # Intentionally not vectorized for readability 223 for j in range( n_categories ) : 224 category_cohesion[ i, j ] = r_i * biased[ j ] + ( 1.0 - r_i ) * uniform 225 226 # repetition 227 category_cohesion[ i, i ] *= ( 1.0 - category_repetition_penalty ) 228 229 # normalize row 230 row_sum = np.sum( category_cohesion[ i, : ] ) 231 if row_sum > 0 : 232 category_cohesion[ i, : ] = category_cohesion[ i, : ] / row_sum 233 234 symbol_cohesion = np.zeros( ( n_symbols, n_symbols ) ) 235 for i, symb_a in enumerate( alphabet ) : 236 237 c_a = symbol_categories[ symb_a ] 238 239 for j, symb_b in enumerate( alphabet ) : 240 241 c_b = symbol_categories[ symb_b ] 242 243 symbol_cohesion[ i, j ] = category_cohesion[ c_a, c_b ] + category_cohesion[ c_a, c_b ] * rng.random() * within_category_variability 244 245 if i == j : 246 symbol_cohesion[ i, j ] *= ( 1.0 - symbol_repetition_penalty ) 247 248 row_sum = np.sum( symbol_cohesion[i, : ] ) 249 if row_sum > 0 : 250 symbol_cohesion[i, : ] = symbol_cohesion[i, : ] / row_sum 251 252 return cls( 253 categories=set( symbol_categories.values() ), 254 category_cohesion=category_cohesion, 255 symbols=alphabet, 256 symbol_categories=symbol_categories, 257 symbol_cohesion=symbol_cohesion 258 )
@dataclass
class
StructuredSymbolSet:
11@dataclass 12class StructuredSymbolSet : 13 14 categories : set[int] 15 category_cohesion : np.ndarray # 2d 16 symbols : list[str] 17 symbol_categories : dict[str,int] 18 symbol_cohesion : np.ndarray # 2d 19 20 def save_json( 21 self, 22 filename: str, 23 eoc_symbols : list[set[str]] | None = None ) -> None : 24 25 if eoc_symbols is None : 26 eoc_symbols = [] 27 28 data = { 29 "categories" : sorted(self.categories), 30 "category_cohesion" : self.category_cohesion.tolist(), 31 "symbols" : self.symbols, 32 "symbol_categories" : self.symbol_categories, 33 "symbol_cohesion" : self.symbol_cohesion.tolist(), 34 "eoc_symbols" : [ sorted( syms ) for syms in eoc_symbols ] 35 } 36 37 with open(filename, 'w') as f: 38 json.dump(data, f, indent=4) 39 40 def symbol_counts_by_category( self ) -> dict[int,int] : 41 return dict(Counter(self.symbol_categories.values())) 42 43 def cyclic_orbit_size(self, invariant_categories: set[int] | None = None) -> int: 44 45 if invariant_categories is None: 46 invariant_categories = set() 47 48 category_sizes = self.symbol_counts_by_category() 49 50 effective_sizes = [ 51 size for category, size in category_sizes.items() 52 if category not in invariant_categories 53 ] 54 55 if not effective_sizes: 56 return 1 57 58 return math.lcm(*effective_sizes) 59 60 def category_symbols( self ) -> dict[ int,list[str] ] : 61 res = {} 62 for c in self.categories : 63 res[ c ] = sorted( [ 64 s for s in self.symbols if self.symbol_categories[ s ] == c 65 ] ) 66 return res 67 68 @classmethod 69 def generate( 70 cls, 71 n_categories : int, 72 symbols_per_category : int | list[int], 73 rigidity : float, 74 within_category_variability : float, 75 category_repetition_penalty : float = 0.5, 76 symbol_repetition_penalty : float = 0.85, 77 np_rng : np.random.Generator | None = None ) -> "StructuredSymbolSet" : 78 79 if isinstance( symbols_per_category, int ) : 80 symbols_per_category = [symbols_per_category]*n_categories 81 82 elif len( symbols_per_category ) != n_categories : 83 raise ValueError( "len( symbols_per_category ) != n_categories" ) 84 85 if within_category_variability < 0.0 or within_category_variability > 1.0 : 86 raise ValueError( f"within_category_variability should range from 0 to 1" ) 87 88 if category_repetition_penalty < 0.0 or category_repetition_penalty > 1.0 : 89 raise ValueError( f"category_repetition_penalty should range from 0 to 1" ) 90 91 if symbol_repetition_penalty < 0.0 or symbol_repetition_penalty > 1.0 : 92 raise ValueError( f"symbol_repetition_penalty should range from 0 to 1" ) 93 94 rng = resolve_rng( np_rng ) 95 96 n_symbols = sum( count for count in symbols_per_category ) 97 98 symbol_pool = Vocabulary.digits() + Vocabulary.letters_lower() + Vocabulary.greek_lower() + Vocabulary.greek_upper() 99 100 if len( symbol_pool ) < n_symbols : 101 raise ValueError( f"Too many symbols required, n_categories*symbols_per_category must be < {len(symbol_pool)}." ) 102 103 symbol_categories = {} 104 alphabet = [] 105 s_idx = 0 106 107 for i in range( n_categories ) : 108 for j in range( symbols_per_category[ i ] ) : 109 alphabet.append( symbol_pool[ s_idx ] ) 110 symbol_categories[ symbol_pool[ s_idx ] ] = i 111 s_idx += 1 112 113 category_cohesion = np.zeros( ( n_categories, n_categories ) ) 114 for i in range( n_categories ) : 115 116 category_cohesion[ i, : ] = exp_uniform_blend( 117 n=n_categories, 118 alpha=(1.0-rigidity), 119 np_rng=rng ) 120 121 category_cohesion[ i, i ] *= (1.0-category_repetition_penalty) 122 123 symbol_cohesion = np.zeros( ( n_symbols, n_symbols ) ) 124 for i, symb_a in enumerate( alphabet ) : 125 for j, symb_b in enumerate( alphabet ) : 126 127 c_a = symbol_categories[ symb_a ] 128 c_b = symbol_categories[ symb_b ] 129 130 symbol_cohesion[ i, j ] = category_cohesion[ c_a, c_b ] + category_cohesion[ c_a, c_b ] * rng.random() * within_category_variability 131 132 if i == j : 133 symbol_cohesion[ i, j ] *= ( 1.0 - symbol_repetition_penalty ) 134 135 return cls( 136 categories=set( symbol_categories.values() ), 137 category_cohesion=category_cohesion, 138 symbols=alphabet, 139 symbol_categories=symbol_categories, 140 symbol_cohesion=symbol_cohesion 141 ) 142 143 @classmethod 144 def generate_principled( 145 cls, 146 n_categories : int, 147 min_category_size : int, 148 category_growth_rate : float, 149 small_category_bias : float, 150 within_category_variability : float, 151 category_repetition_penalty : float = 0.5, 152 symbol_repetition_penalty : float = 0.85, 153 np_rng : np.random.Generator | None = None ) -> "StructuredSymbolSet" : 154 155 if small_category_bias < 0.0 or small_category_bias > 1.0 : 156 raise ValueError( f"small_category_bias should range from 0 to 1" ) 157 158 if within_category_variability < 0.0 or within_category_variability > 1.0 : 159 raise ValueError( f"within_category_variability should range from 0 to 1" ) 160 161 if category_repetition_penalty < 0.0 or category_repetition_penalty > 1.0 : 162 raise ValueError( f"category_repetition_penalty should range from 0 to 1" ) 163 164 if symbol_repetition_penalty < 0.0 or symbol_repetition_penalty > 1.0 : 165 raise ValueError( f"symbol_repetition_penalty should range from 0 to 1" ) 166 167 rng = resolve_rng( np_rng ) 168 169 category_sizes = [ min_category_size ] 170 last_term = min_category_size 171 172 for c in range( n_categories - 1 ) : 173 next_term = last_term * category_growth_rate 174 category_sizes.append( int( round( next_term ) ) ) 175 last_term = next_term 176 177 n_symbols = sum( category_sizes ) 178 symbol_pool = Vocabulary.digits() + Vocabulary.letters_lower() + Vocabulary.greek_lower() + Vocabulary.greek_upper() 179 180 if len( symbol_pool ) < n_symbols : 181 raise ValueError( f"Not enough symbols available. {n_symbols} requested, but pool has {len(symbol_pool)}." ) 182 183 symbol_categories = {} 184 alphabet = [] 185 s_idx = 0 186 187 for i in range( n_categories ) : 188 for j in range( category_sizes[ i ] ) : 189 alphabet.append( symbol_pool[ s_idx ] ) 190 symbol_categories[ symbol_pool[ s_idx ] ] = i 191 s_idx += 1 192 193 min_size = min( category_sizes ) 194 max_size = max( category_sizes ) 195 196 max_size_difference = max_size - min_size 197 uniform = 1.0 / n_categories 198 199 category_cohesion = np.zeros( ( n_categories, n_categories ) ) 200 201 if max_size_difference == 0 : 202 category_cohesion[ :, : ] = uniform 203 204 else : 205 206 # maximum small category bias 207 biased = min_category_size / np.array(category_sizes, dtype=float) 208 209 # apply bias factor 210 biased = small_category_bias * biased + ( 1.0 - small_category_bias ) * uniform 211 212 # normalized 213 biased = biased / np.sum( biased ) 214 215 for i in range( n_categories ) : 216 217 # Small category is more uniformly selective, 218 # Larger categories biased towareds selecting smaller categories 219 # r_i = ( category_sizes[i] - min_size ) / ( max_size - min_size ) 220 221 r_i = 1.0 - 1.0 / math.pow( category_sizes[i], 0.5 ) 222 223 # Intentionally not vectorized for readability 224 for j in range( n_categories ) : 225 category_cohesion[ i, j ] = r_i * biased[ j ] + ( 1.0 - r_i ) * uniform 226 227 # repetition 228 category_cohesion[ i, i ] *= ( 1.0 - category_repetition_penalty ) 229 230 # normalize row 231 row_sum = np.sum( category_cohesion[ i, : ] ) 232 if row_sum > 0 : 233 category_cohesion[ i, : ] = category_cohesion[ i, : ] / row_sum 234 235 symbol_cohesion = np.zeros( ( n_symbols, n_symbols ) ) 236 for i, symb_a in enumerate( alphabet ) : 237 238 c_a = symbol_categories[ symb_a ] 239 240 for j, symb_b in enumerate( alphabet ) : 241 242 c_b = symbol_categories[ symb_b ] 243 244 symbol_cohesion[ i, j ] = category_cohesion[ c_a, c_b ] + category_cohesion[ c_a, c_b ] * rng.random() * within_category_variability 245 246 if i == j : 247 symbol_cohesion[ i, j ] *= ( 1.0 - symbol_repetition_penalty ) 248 249 row_sum = np.sum( symbol_cohesion[i, : ] ) 250 if row_sum > 0 : 251 symbol_cohesion[i, : ] = symbol_cohesion[i, : ] / row_sum 252 253 return cls( 254 categories=set( symbol_categories.values() ), 255 category_cohesion=category_cohesion, 256 symbols=alphabet, 257 symbol_categories=symbol_categories, 258 symbol_cohesion=symbol_cohesion 259 )
StructuredSymbolSet( categories: set[int], category_cohesion: numpy.ndarray, symbols: list[str], symbol_categories: dict[str, int], symbol_cohesion: numpy.ndarray)
def
save_json(self, filename: str, eoc_symbols: list[set[str]] | None = None) -> None:
20 def save_json( 21 self, 22 filename: str, 23 eoc_symbols : list[set[str]] | None = None ) -> None : 24 25 if eoc_symbols is None : 26 eoc_symbols = [] 27 28 data = { 29 "categories" : sorted(self.categories), 30 "category_cohesion" : self.category_cohesion.tolist(), 31 "symbols" : self.symbols, 32 "symbol_categories" : self.symbol_categories, 33 "symbol_cohesion" : self.symbol_cohesion.tolist(), 34 "eoc_symbols" : [ sorted( syms ) for syms in eoc_symbols ] 35 } 36 37 with open(filename, 'w') as f: 38 json.dump(data, f, indent=4)
def
cyclic_orbit_size(self, invariant_categories: set[int] | None = None) -> int:
43 def cyclic_orbit_size(self, invariant_categories: set[int] | None = None) -> int: 44 45 if invariant_categories is None: 46 invariant_categories = set() 47 48 category_sizes = self.symbol_counts_by_category() 49 50 effective_sizes = [ 51 size for category, size in category_sizes.items() 52 if category not in invariant_categories 53 ] 54 55 if not effective_sizes: 56 return 1 57 58 return math.lcm(*effective_sizes)
@classmethod
def
generate( cls, n_categories: int, symbols_per_category: int | list[int], rigidity: float, within_category_variability: float, category_repetition_penalty: float = 0.5, symbol_repetition_penalty: float = 0.85, np_rng: numpy.random._generator.Generator | None = None) -> StructuredSymbolSet:
68 @classmethod 69 def generate( 70 cls, 71 n_categories : int, 72 symbols_per_category : int | list[int], 73 rigidity : float, 74 within_category_variability : float, 75 category_repetition_penalty : float = 0.5, 76 symbol_repetition_penalty : float = 0.85, 77 np_rng : np.random.Generator | None = None ) -> "StructuredSymbolSet" : 78 79 if isinstance( symbols_per_category, int ) : 80 symbols_per_category = [symbols_per_category]*n_categories 81 82 elif len( symbols_per_category ) != n_categories : 83 raise ValueError( "len( symbols_per_category ) != n_categories" ) 84 85 if within_category_variability < 0.0 or within_category_variability > 1.0 : 86 raise ValueError( f"within_category_variability should range from 0 to 1" ) 87 88 if category_repetition_penalty < 0.0 or category_repetition_penalty > 1.0 : 89 raise ValueError( f"category_repetition_penalty should range from 0 to 1" ) 90 91 if symbol_repetition_penalty < 0.0 or symbol_repetition_penalty > 1.0 : 92 raise ValueError( f"symbol_repetition_penalty should range from 0 to 1" ) 93 94 rng = resolve_rng( np_rng ) 95 96 n_symbols = sum( count for count in symbols_per_category ) 97 98 symbol_pool = Vocabulary.digits() + Vocabulary.letters_lower() + Vocabulary.greek_lower() + Vocabulary.greek_upper() 99 100 if len( symbol_pool ) < n_symbols : 101 raise ValueError( f"Too many symbols required, n_categories*symbols_per_category must be < {len(symbol_pool)}." ) 102 103 symbol_categories = {} 104 alphabet = [] 105 s_idx = 0 106 107 for i in range( n_categories ) : 108 for j in range( symbols_per_category[ i ] ) : 109 alphabet.append( symbol_pool[ s_idx ] ) 110 symbol_categories[ symbol_pool[ s_idx ] ] = i 111 s_idx += 1 112 113 category_cohesion = np.zeros( ( n_categories, n_categories ) ) 114 for i in range( n_categories ) : 115 116 category_cohesion[ i, : ] = exp_uniform_blend( 117 n=n_categories, 118 alpha=(1.0-rigidity), 119 np_rng=rng ) 120 121 category_cohesion[ i, i ] *= (1.0-category_repetition_penalty) 122 123 symbol_cohesion = np.zeros( ( n_symbols, n_symbols ) ) 124 for i, symb_a in enumerate( alphabet ) : 125 for j, symb_b in enumerate( alphabet ) : 126 127 c_a = symbol_categories[ symb_a ] 128 c_b = symbol_categories[ symb_b ] 129 130 symbol_cohesion[ i, j ] = category_cohesion[ c_a, c_b ] + category_cohesion[ c_a, c_b ] * rng.random() * within_category_variability 131 132 if i == j : 133 symbol_cohesion[ i, j ] *= ( 1.0 - symbol_repetition_penalty ) 134 135 return cls( 136 categories=set( symbol_categories.values() ), 137 category_cohesion=category_cohesion, 138 symbols=alphabet, 139 symbol_categories=symbol_categories, 140 symbol_cohesion=symbol_cohesion 141 )
@classmethod
def
generate_principled( cls, n_categories: int, min_category_size: int, category_growth_rate: float, small_category_bias: float, within_category_variability: float, category_repetition_penalty: float = 0.5, symbol_repetition_penalty: float = 0.85, np_rng: numpy.random._generator.Generator | None = None) -> StructuredSymbolSet:
143 @classmethod 144 def generate_principled( 145 cls, 146 n_categories : int, 147 min_category_size : int, 148 category_growth_rate : float, 149 small_category_bias : float, 150 within_category_variability : float, 151 category_repetition_penalty : float = 0.5, 152 symbol_repetition_penalty : float = 0.85, 153 np_rng : np.random.Generator | None = None ) -> "StructuredSymbolSet" : 154 155 if small_category_bias < 0.0 or small_category_bias > 1.0 : 156 raise ValueError( f"small_category_bias should range from 0 to 1" ) 157 158 if within_category_variability < 0.0 or within_category_variability > 1.0 : 159 raise ValueError( f"within_category_variability should range from 0 to 1" ) 160 161 if category_repetition_penalty < 0.0 or category_repetition_penalty > 1.0 : 162 raise ValueError( f"category_repetition_penalty should range from 0 to 1" ) 163 164 if symbol_repetition_penalty < 0.0 or symbol_repetition_penalty > 1.0 : 165 raise ValueError( f"symbol_repetition_penalty should range from 0 to 1" ) 166 167 rng = resolve_rng( np_rng ) 168 169 category_sizes = [ min_category_size ] 170 last_term = min_category_size 171 172 for c in range( n_categories - 1 ) : 173 next_term = last_term * category_growth_rate 174 category_sizes.append( int( round( next_term ) ) ) 175 last_term = next_term 176 177 n_symbols = sum( category_sizes ) 178 symbol_pool = Vocabulary.digits() + Vocabulary.letters_lower() + Vocabulary.greek_lower() + Vocabulary.greek_upper() 179 180 if len( symbol_pool ) < n_symbols : 181 raise ValueError( f"Not enough symbols available. {n_symbols} requested, but pool has {len(symbol_pool)}." ) 182 183 symbol_categories = {} 184 alphabet = [] 185 s_idx = 0 186 187 for i in range( n_categories ) : 188 for j in range( category_sizes[ i ] ) : 189 alphabet.append( symbol_pool[ s_idx ] ) 190 symbol_categories[ symbol_pool[ s_idx ] ] = i 191 s_idx += 1 192 193 min_size = min( category_sizes ) 194 max_size = max( category_sizes ) 195 196 max_size_difference = max_size - min_size 197 uniform = 1.0 / n_categories 198 199 category_cohesion = np.zeros( ( n_categories, n_categories ) ) 200 201 if max_size_difference == 0 : 202 category_cohesion[ :, : ] = uniform 203 204 else : 205 206 # maximum small category bias 207 biased = min_category_size / np.array(category_sizes, dtype=float) 208 209 # apply bias factor 210 biased = small_category_bias * biased + ( 1.0 - small_category_bias ) * uniform 211 212 # normalized 213 biased = biased / np.sum( biased ) 214 215 for i in range( n_categories ) : 216 217 # Small category is more uniformly selective, 218 # Larger categories biased towareds selecting smaller categories 219 # r_i = ( category_sizes[i] - min_size ) / ( max_size - min_size ) 220 221 r_i = 1.0 - 1.0 / math.pow( category_sizes[i], 0.5 ) 222 223 # Intentionally not vectorized for readability 224 for j in range( n_categories ) : 225 category_cohesion[ i, j ] = r_i * biased[ j ] + ( 1.0 - r_i ) * uniform 226 227 # repetition 228 category_cohesion[ i, i ] *= ( 1.0 - category_repetition_penalty ) 229 230 # normalize row 231 row_sum = np.sum( category_cohesion[ i, : ] ) 232 if row_sum > 0 : 233 category_cohesion[ i, : ] = category_cohesion[ i, : ] / row_sum 234 235 symbol_cohesion = np.zeros( ( n_symbols, n_symbols ) ) 236 for i, symb_a in enumerate( alphabet ) : 237 238 c_a = symbol_categories[ symb_a ] 239 240 for j, symb_b in enumerate( alphabet ) : 241 242 c_b = symbol_categories[ symb_b ] 243 244 symbol_cohesion[ i, j ] = category_cohesion[ c_a, c_b ] + category_cohesion[ c_a, c_b ] * rng.random() * within_category_variability 245 246 if i == j : 247 symbol_cohesion[ i, j ] *= ( 1.0 - symbol_repetition_penalty ) 248 249 row_sum = np.sum( symbol_cohesion[i, : ] ) 250 if row_sum > 0 : 251 symbol_cohesion[i, : ] = symbol_cohesion[i, : ] / row_sum 252 253 return cls( 254 categories=set( symbol_categories.values() ), 255 category_cohesion=category_cohesion, 256 symbols=alphabet, 257 symbol_categories=symbol_categories, 258 symbol_cohesion=symbol_cohesion 259 )