@@ -245,6 +245,7 @@ def get_atoms(
245245 builder : Optional [str ] = 'bulk' ,
246246 atoms : Optional [Atoms ] = None ,
247247 repeat : Optional [Tuple [int , int , int ]] = None ,
248+ repeat_to_N_args : Optional [Dict ] = None ,
248249 rattle_stdev : Optional [float ] = None ,
249250 mp_id : Optional [str ] = None ,
250251 user_api_key : Optional [str ] = None ,
@@ -329,14 +330,16 @@ def get_atoms(
329330 >>> get_atoms(image_file='molecules.xyz', index=0) # Pick out one structure using indexing
330331 Atoms(symbols='OH2', pbc=False)
331332
332- You can also make supercells and rattle the atoms
333+ You can also make supercells and rattle the atoms or repeat to a target
333334
334335 >>> li_bulk = get_atoms(name='Li')
335336 >>> li_bulk.write('POSCAR', format='vasp')
336337 >>> get_atoms(image_file='POSCAR', repeat=[3,3,3])
337338 Atoms(symbols='Li27', pbc=True, cell=[[-5.235, 5.235, 5.235], [5.235, -5.235, 5.235], [5.235, 5.235, -5.235]])
338339 >>> get_atoms(builder='bulk', name='Li', repeat=[2,2,2], rattle_stdev=0.01)
339340 Atoms(symbols='Li8', pbc=True, cell=[[-3.49, 3.49, 3.49], [3.49, -3.49, 3.49], [3.49, 3.49, -3.49]])
341+ >>> get_atoms(builder='bulk', name='Li', repeat_to_N_args={'N': 16, 'max_dim': 10.0})
342+ Atoms(symbols='Li16', pbc=True, cell=[[-6.98, 6.98, 6.98], [6.98, -6.98, 6.98], [6.98, 6.98, -6.98]])
340343
341344 Mostly for internal use and use in asimmodules, one can specify atoms
342345 directly
@@ -439,6 +442,13 @@ def get_atoms(
439442 elif rattle_stdev is not None and interface == 'pymatgen' :
440443 struct .perturb (distance = rattle_stdev , min_distance = 0 )
441444
445+ if repeat_to_N_args is not None and interface == 'ase' :
446+ atoms = repeat_to_N (atoms , ** repeat_to_N_args )
447+ elif repeat_to_N_args is not None and interface == 'pymatgen' :
448+ raise NotImplementedError (
449+ 'repeat_to_N_args is only implemented for ASE interface'
450+ )
451+
442452 if constraints is not None and interface == 'ase' :
443453 consts = []
444454 for constraint_args in constraints :
@@ -491,6 +501,47 @@ def parse_slice(value: str, bash: bool = False) -> slice:
491501 parts .append ('1' )
492502 return f'$(seq { parts [0 ]} { parts [2 ]} { parts [1 ]} )'
493503
504+ def repeat_to_N (
505+ atoms : Atoms ,
506+ N : int ,
507+ max_dim : float = 50.0 ,
508+ ) -> Atoms :
509+ """Scale a structure to have approximately N atoms in the unit cell.
510+ The function repeats the shortest axis of the unit cell until the
511+ number of atoms >= N or the longest axis of the unit cell is > max_dim.
512+
513+ :param atoms: Input atoms object
514+ :type atoms: Atoms
515+ :param N: Target number of atoms
516+ :type N: int
517+ :param max_dim: Maximum length of the longest axis of the unit cell,
518+ defaults to 50.0
519+ :type max_dim: float, optional
520+ :raises ValueError: If it fails to scale the structure to N atoms
521+ :return: Scaled atoms object
522+ :rtype: Atoms
523+ """
524+ cell = atoms .get_cell ()
525+ lengths = [np .linalg .norm (vec ) for vec in cell ]
526+ num_atoms = len (atoms )
527+ new_atoms = atoms .copy ()
528+
529+ while num_atoms < N and np .max (lengths ) < max_dim :
530+ shortest_axis = np .argmin (lengths )
531+ repeat_vec = [1 , 1 , 1 ]
532+ repeat_vec [shortest_axis ] += 1
533+ new_atoms = new_atoms .repeat (repeat_vec )
534+ cell = new_atoms .get_cell ()
535+ lengths = [np .linalg .norm (vec ) for vec in cell ]
536+ num_atoms = len (new_atoms )
537+
538+ if num_atoms < N :
539+ raise ValueError (
540+ f'Failed to scale structure to { N } atoms without exceeding \
541+ max_dim of { max_dim } Angstroms'
542+ )
543+ return new_atoms
544+
494545def get_images (
495546 image_file : str = None ,
496547 pattern : str = None ,
0 commit comments