@@ -121,6 +121,59 @@ def unique_configurations(self,
121121 )
122122 return self .enumerate_configurations (generator , verbose = verbose )
123123
124+ def random_unique_configurations (
125+ self ,
126+ site_distribution : dict [int , int ],
127+ n : int ,
128+ sampling : str = 'degeneracy_weighted' ,
129+ seed : int | None = None ,
130+ ) -> list [Configuration ]:
131+ """Generate n random symmetry-inequivalent configurations.
132+
133+ Args:
134+ site_distribution: Dictionary mapping species labels to counts.
135+ n: Number of unique configurations to generate.
136+ sampling: Sampling method. Either 'degeneracy_weighted' (default) or
137+ 'uniform'. 'degeneracy_weighted' samples configurations with
138+ probability proportional to their degeneracy. 'uniform' samples
139+ uniformly over equivalence classes.
140+ seed: Random seed for reproducibility.
141+
142+ Returns:
143+ List of n unique Configuration objects with count attributes set.
144+
145+ Raises:
146+ ValueError: If sampling is not 'degeneracy_weighted' or 'uniform'.
147+ """
148+ if sampling not in ('degeneracy_weighted' , 'uniform' ):
149+ raise ValueError (
150+ f"sampling must be 'degeneracy_weighted' or 'uniform', got '{ sampling } '"
151+ )
152+
153+ rng = np .random .default_rng (seed )
154+ seen : set [bytes ] = set ()
155+ unique_configs : list [Configuration ] = []
156+
157+ while len (unique_configs ) < n :
158+ config = self ._generate_random_configuration (site_distribution , rng )
159+ config_hash = config .as_bytes ()
160+
161+ if config_hash in seen :
162+ continue
163+
164+ equivalents = config .get_byte_equivalents (self .symmetry_group )
165+ degeneracy = len (equivalents )
166+
167+ if sampling == 'uniform' :
168+ if rng .random () >= 1.0 / degeneracy :
169+ continue
170+
171+ seen .update (equivalents )
172+ config .count = degeneracy
173+ unique_configs .append (config )
174+
175+ return unique_configs
176+
124177 def unique_colourings (self , colours , verbose = False ):
125178 """
126179 Find the symmetry inequivalent colourings for a given number of 'colours'.
@@ -221,6 +274,38 @@ def unique_configurations_by_composition(self,
221274 print (f" Total unique configurations: { sum (len (configs ) for configs in results .values ())} " )
222275
223276 return results
277+
278+ def _generate_random_configuration (
279+ self ,
280+ site_distribution : dict [int , int ],
281+ rng : np .random .Generator ,
282+ ) -> Configuration :
283+ """Generate a random configuration with the given site distribution.
284+
285+ Args:
286+ site_distribution: Dictionary mapping species labels to counts.
287+ rng: Random number generator.
288+
289+ Returns:
290+ A random Configuration with the specified distribution.
291+ """
292+ n_sites = sum (site_distribution .values ())
293+ config = np .empty (n_sites , dtype = int )
294+ available_indices = np .arange (n_sites )
295+
296+ # Process all but the last species
297+ species_list = list (site_distribution .items ())
298+ for species , count in species_list [:- 1 ]:
299+ selected = _select_random_indices (available_indices , count , rng )
300+ config [selected ] = species
301+ # Remove selected indices from available
302+ available_indices = np .setdiff1d (available_indices , selected )
303+
304+ # Last species gets remaining indices
305+ last_species , _ = species_list [- 1 ]
306+ config [available_indices ] = last_species
307+
308+ return Configuration (config )
224309
225310def apply_species_mapping (config , mapping_vector ):
226311 """
@@ -273,3 +358,20 @@ def permutation_as_config_number(p):
273358 tot *= 10
274359 tot += num
275360 return tot
361+
362+ def _select_random_indices (
363+ available_indices : np .ndarray ,
364+ count : int ,
365+ rng : np .random .Generator ,
366+ ) -> np .ndarray :
367+ """Select count random indices from available_indices.
368+
369+ Args:
370+ available_indices: Array of indices to select from.
371+ count: Number of indices to select.
372+ rng: Random number generator.
373+
374+ Returns:
375+ Array of selected indices.
376+ """
377+ return rng .choice (available_indices , size = count , replace = False )
0 commit comments