def test_is_swap_possible(self): """Tests is_swap_possible function.""" # swaps are possible for i, sl in enumerate(self.cm.sublattices): self.assertTrue(self.cm.is_swap_possible(i)) # setup system with inactive sublattice prim = bulk('Al').repeat([2, 1, 1]) chemical_symbols = [['Al'], ['Ag', 'Al', 'Au']] cs = ClusterSpace(prim, cutoffs=[0], chemical_symbols=chemical_symbols) supercell = prim.repeat(2) supercell[1].symbol = 'Ag' supercell[3].symbol = 'Au' sublattices = cs.get_sublattices(supercell) cm = ConfigurationManager(supercell, sublattices) # check both sublattices self.assertTrue(cm.is_swap_possible(0)) self.assertFalse(cm.is_swap_possible(1)) # check both sublattices when specifying allowed species allowed_species = [13, 47] self.assertTrue(cm.is_swap_possible(0, allowed_species=allowed_species)) self.assertFalse(cm.is_swap_possible(1, allowed_species=allowed_species))
def _concentrations_fit_structure(structure: Atoms, cluster_space: ClusterSpace, concentrations: Dict[str, Dict[str, float]], tol: float = 1e-5) -> bool: """ Check if specified concentrations are commensurate with a certain supercell (including sublattices) Parameters ---------- structure atomic configuration to be checked cluster_space corresponding cluster space concentrations which concentrations, per sublattice, e.g., ``{'A': {'Ag': 0.3, 'Au': 0.7}}`` tol numerical tolerance """ # Check that concentrations are OK in each sublattice for sublattice in cluster_space.get_sublattices(structure): if sublattice.symbol in concentrations: sl_conc = concentrations[sublattice.symbol] for conc in sl_conc.values(): n_symbol = conc * len(sublattice.indices) if abs(int(round(n_symbol)) - n_symbol) > tol: return False return True
def __init__(self, *args, **kwargs): super(TestConfigurationManager, self).__init__(*args, **kwargs) self.structure = bulk('Al').repeat([2, 1, 1]) self.structure[1].symbol = 'Ag' self.structure = self.structure.repeat(3) cs = ClusterSpace(self.structure, cutoffs=[0], chemical_symbols=['Ag', 'Al']) self.sublattices = cs.get_sublattices(self.structure)
def test_get_swapped_state(self): """Tests the getting swap indices method.""" for _ in range(1000): indices, elements = self.cm.get_swapped_state(0) index1 = indices[0] index2 = indices[1] self.assertNotEqual( self.cm.occupations[index1], self.cm.occupations[index2]) self.assertNotEqual( elements[0], elements[1]) self.assertEqual(self.cm.occupations[index1], elements[1]) self.assertEqual(self.cm.occupations[index2], elements[0]) # set everything to Al and see that swap is not possible indices = [i for i in range(len(self.structure))] elements = [13] * len(self.structure) self.cm.update_occupations(indices, elements) with self.assertRaises(SwapNotPossibleError) as context: indices, elements = self.cm.get_swapped_state(0) self.assertTrue("Cannot swap on sublattice" in str(context.exception)) self.assertTrue("since it is full of" in str(context.exception)) # setup a ternary system prim = bulk('Al').repeat([3, 1, 1]) chemical_symbols = ['Ag', 'Al', 'Au'] cs = ClusterSpace(prim, cutoffs=[0], chemical_symbols=chemical_symbols) for i, symbol in enumerate(chemical_symbols): prim[i].symbol = symbol supercell = prim.repeat(2) sublattices = cs.get_sublattices(supercell) cm = ConfigurationManager(supercell, sublattices) allowed_species = [13, 47] for _ in range(1000): indices, elements = cm.get_swapped_state( 0, allowed_species=allowed_species) index1 = indices[0] index2 = indices[1] self.assertNotEqual( cm.occupations[index1], cm.occupations[index2]) self.assertNotEqual( elements[0], elements[1]) self.assertEqual(cm.occupations[index1], elements[1]) self.assertEqual(cm.occupations[index2], elements[0]) # set everything to Al and see that swap is not possible indices = [i for i in range(len(supercell))] elements = [13] * len(supercell) cm.update_occupations(indices, elements) with self.assertRaises(SwapNotPossibleError) as context: indices, elements = cm.get_swapped_state( 0, allowed_species=allowed_species) self.assertTrue("Cannot swap on sublattice" in str(context.exception)) self.assertTrue("since it is full of" in str(context.exception))
def occupy_structure_randomly(structure: Atoms, cluster_space: ClusterSpace, target_concentrations: dict) -> None: """ Occupy a structure with quasirandom order but fulfilling ``target_concentrations``. Parameters ---------- structure ASE Atoms object that will be occupied randomly cluster_space cluster space (needed as it carries information about sublattices) target_concentrations concentration of each species in the target structure, per sublattice (for example ``{'Au': 0.5, 'Pd': 0.5}`` for a single sublattice Au-Pd structure, or ``{'A': {'Au': 0.5, 'Pd': 0.5}, 'B': {'H': 0.25, 'X': 0.75}}`` for a system with two sublattices. The symbols defining sublattices ('A', 'B' etc) can be found by printing the `cluster_space` """ target_concentrations = _validate_concentrations( cluster_space=cluster_space, concentrations=target_concentrations) if not _concentrations_fit_structure(structure, cluster_space, target_concentrations): raise ValueError('Structure with {} atoms cannot accomodate ' 'target concentrations {}'.format( len(structure), target_concentrations)) symbols_all = [''] * len(structure) for sl in cluster_space.get_sublattices(structure): symbols = [] # type: List[str] # chemical_symbols in one sublattice chemical_symbols = sl.chemical_symbols if len(chemical_symbols) == 1: symbols += [chemical_symbols[0]] * len(sl.indices) else: sl_conc = target_concentrations[sl.symbol] for chemical_symbol in sl.chemical_symbols: n_symbol = int( round(len(sl.indices) * sl_conc[chemical_symbol])) symbols += [chemical_symbol] * n_symbol # Should not happen but you never know assert len(symbols) == len(sl.indices) # Shuffle to introduce randomness random.shuffle(symbols) # Assign symbols to the right indices for symbol, lattice_site in zip(symbols, sl.indices): symbols_all[lattice_site] = symbol assert symbols_all.count('') == 0 structure.set_chemical_symbols(symbols_all)
def _validate_concentrations(concentrations: dict, cluster_space: ClusterSpace, tol: float = 1e-5) -> dict: """ Validates concentration specification against a cluster space (raises `ValueError` if they do not match). Parameters ---------- concentrations concentration specification cluster_space cluster space to check against tol Numerical tolerance Returns ------- target concentrations An adapted version of concentrations, which is always a dictionary even if there is only one sublattice """ sls = cluster_space.get_sublattices(cluster_space.primitive_structure) if not isinstance(list(concentrations.values())[0], dict): concentrations = {'A': concentrations} # Ensure concentrations sum to 1 at each sublattice for sl_conc in concentrations.values(): conc_sum = sum(list(sl_conc.values())) if abs(conc_sum - 1.0) > tol: raise ValueError( 'Concentrations must sum up ' 'to 1 for each sublattice (not {})'.format(conc_sum)) # Symbols need to match on each sublattice for sl in sls: if sl.symbol not in concentrations: if len(sl.chemical_symbols) > 1: raise ValueError('A sublattice ({}: {}) is missing in ' 'target_concentrations'.format( sl.symbol, list(sl.chemical_symbols))) else: sl_conc = concentrations[sl.symbol] if tuple(sorted(sl.chemical_symbols)) != tuple( sorted(list(sl_conc.keys()))): raise ValueError( 'Chemical symbols on a sublattice ({}: {}) are ' 'not the same as those in the specified ' 'concentrations {}'.format(sl.symbol, list(sl.chemical_symbols), list(sl_conc.keys()))) return concentrations
def test_sublattice_uniqueness(self): """Tests that the number of sublattices are correct in the case of the allowed species have duplicates in them. """ structure = bulk("Al").repeat(2) chemical_symbols = [['H']] + [['Al', 'Ge']] * (len(structure) - 1) cs = ClusterSpace(structure=structure, chemical_symbols=chemical_symbols, cutoffs=[5]) sublattices = cs.get_sublattices(structure) self.assertEqual(len(sublattices), 2) self.assertEqual(sublattices.allowed_species, [('Al', 'Ge'), ('H', )])
def test_get_flip_index(self): """Tests the getting flip indices method.""" for _ in range(1000): index, element = self.cm.get_flip_state(0) self.assertNotEqual(self.cm.occupations[index], element) # setup a ternary system prim = bulk('Al').repeat([3, 1, 1]) chemical_symbols = ['Ag', 'Al', 'Au'] cs = ClusterSpace(prim, cutoffs=[0], chemical_symbols=chemical_symbols) for i, symbol in enumerate(chemical_symbols): prim[i].symbol = symbol supercell = prim.repeat(2) sublattices = cs.get_sublattices(supercell) cm = ConfigurationManager(supercell, sublattices) allowed_species = [13, 47] for _ in range(1000): index, element = cm.get_flip_state( 0, allowed_species=allowed_species) self.assertNotEqual(cm.occupations[index], element)
def _get_sqs_cluster_vector( cluster_space: ClusterSpace, target_concentrations: Dict[str, Dict[str, float]]) -> np.ndarray: """ Get the SQS vector for a certain cluster space and certain concentration. Here SQS vector refers to the cluster vector of an infintely large supercell with random occupation. Parameters ---------- cluster_space the kind of lattice to be occupied target_concentrations concentration of each species in the target structure, per sublattice (for example `{'A': {'Ag': 0.5, 'Pd': 0.5}}`) """ target_concentrations = _validate_concentrations( concentrations=target_concentrations, cluster_space=cluster_space) sublattice_to_index = { letter: index for index, letter in enumerate('ABCDEFGHIJKLMNOPQRSTUVWXYZ') } all_sublattices = cluster_space.get_sublattices( cluster_space.primitive_structure) # Make a map from chemical symbol to integer, later on used # for evaluating cluster functions. # Internally, icet sorts species according to atomic numbers. # Also check that each symbol only occurs in one sublattice. symbol_to_integer_map = {} found_species = [] # type: List[str] for sublattice in all_sublattices: if len(sublattice.chemical_symbols) < 2: continue atomic_numbers = [ periodic_table.index(sym) for sym in sublattice.chemical_symbols ] for i, species in enumerate(sorted(atomic_numbers)): found_species.append(species) symbol_to_integer_map[periodic_table[species]] = i # Target concentrations refer to all atoms, but probabilities only # to the sublattice. probabilities = {} for sl_conc in target_concentrations.values(): if len(sl_conc) == 1: continue for symbol in sl_conc.keys(): probabilities[symbol] = sl_conc[symbol] # For every orbit, calculate average cluster function cv = [1.0] for orbit in cluster_space.orbit_data: if orbit['order'] < 1: continue # What sublattices are there in this orbit? sublattices = [ all_sublattices[sublattice_to_index[letter]] for letter in orbit['sublattices'].split('-') ] # What chemical symbols do these sublattices refer to? symbol_groups = [ sublattice.chemical_symbols for sublattice in sublattices ] # How many allowed species in each of those sublattices? nbr_of_allowed_species = [ len(symbol_group) for symbol_group in symbol_groups ] # Calculate contribution from every possible combination of # symbols weighted with their probability cluster_product_average = 0 for symbols in itertools.product(*symbol_groups): cluster_product = 1 for i, symbol in enumerate(symbols): mc_vector_component = orbit['multi_component_vector'][i] species_i = symbol_to_integer_map[symbol] prod = cluster_space.evaluate_cluster_function( nbr_of_allowed_species[i], mc_vector_component, species_i) cluster_product *= probabilities[symbol] * prod cluster_product_average += cluster_product cv.append(cluster_product_average) return np.array(cv)
def generate_sqs_by_enumeration(cluster_space: ClusterSpace, max_size: int, target_concentrations: dict, include_smaller_cells: bool = True, pbc: Union[Tuple[bool, bool, bool], Tuple[int, int, int]] = None, optimality_weight: float = 1.0, tol: float = 1e-5) -> Atoms: """ Given a ``cluster_space``, generate a special quasirandom structure (SQS), i.e., a structure that for a given supercell size provides the best possible approximation to a random alloy [ZunWeiFer90]_. In the present case, this means that the generated structure will have a cluster vector that as closely as possible matches the cluster vector of an infintely large randomly occupied supercell. Internally the function uses a simulated annealing algorithm and the difference between two cluster vectors is calculated with the measure suggested by A. van de Walle et al. in Calphad **42**, 13-18 (2013) [WalTiwJon13]_ (for more information, see :class:`mchammer.calculators.TargetVectorCalculator`). This functions generates SQS cells by exhaustive enumeration, which means that the generated SQS cell is guaranteed to be optimal with regard to the specified measure and cell size. Parameters ---------- cluster_space a cluster space defining the lattice to be occupied max_size maximum supercell size target_concentrations concentration of each species in the target structure, per sublattice (for example ``{'Au': 0.5, 'Pd': 0.5}`` for a single sublattice Au-Pd structure, or ``{'A': {'Au': 0.5, 'Pd': 0.5}, 'B': {'H': 0.25, 'X': 0.75}}`` for a system with two sublattices. The symbols defining sublattices ('A', 'B' etc) can be found by printing the `cluster_space` include_smaller_cells if True, search among all supercell sizes including ``max_size``, else search only among those exactly matching ``max_size`` pbc Periodic boundary conditions for each direction, e.g., ``(True, True, False)``. The axes are defined by the cell of ``cluster_space.primitive_structure``. Default is periodic boundary in all directions. optimality_weight controls weighting :math:`L` of perfect correlations, see :class:`mchammer.calculators.TargetVectorCalculator` tol Numerical tolerance """ target_concentrations = _validate_concentrations(target_concentrations, cluster_space) sqs_vector = _get_sqs_cluster_vector( cluster_space=cluster_space, target_concentrations=target_concentrations) # Translate concentrations to the format required for concentration # restricted enumeration cr = {} # type: Dict[str, tuple] sublattices = cluster_space.get_sublattices( cluster_space.primitive_structure) for sl in sublattices: mult_factor = len(sl.indices) / len(cluster_space.primitive_structure) if sl.symbol in target_concentrations: sl_conc = target_concentrations[sl.symbol] else: sl_conc = {sl.chemical_symbols[0]: 1.0} for species, value in sl_conc.items(): c = value * mult_factor if species in cr: cr[species] = (cr[species][0] + c, cr[species][1] + c) else: cr[species] = (c, c) # Check to be sure... c_sum = sum(c[0] for c in cr.values()) assert abs(c_sum - 1) < tol # Should never happen, but... orbit_data = cluster_space.orbit_data best_score = 1e9 if include_smaller_cells: sizes = list(range(1, max_size + 1)) else: sizes = [max_size] # Prepare primitive structure with the right boundary conditions prim = cluster_space.primitive_structure if pbc is None: pbc = (True, True, True) prim.set_pbc(pbc) # Enumerate and calculate score for each structuer for structure in enumerate_structures(prim, sizes, cluster_space.chemical_symbols, concentration_restrictions=cr): cv = cluster_space.get_cluster_vector(structure) score = compare_cluster_vectors(cv_1=cv, cv_2=sqs_vector, orbit_data=orbit_data, optimality_weight=optimality_weight, tol=tol) if score < best_score: best_score = score best_structure = structure return best_structure
class TestGroundStateFinderInactiveSublatticeSameSpecies(unittest.TestCase): """Container for test of the class functionality for a system with an inactive sublattice occupied by a species found on the active sublattice.""" def __init__(self, *args, **kwargs): super(TestGroundStateFinderInactiveSublatticeSameSpecies, self).__init__(*args, **kwargs) self.chemical_symbols = [['Ag', 'Au'], ['Ag']] self.cutoffs = [4.3] a = 4.0 structure_prim = bulk(self.chemical_symbols[0][1], a=a) structure_prim.append( Atom(self.chemical_symbols[1][0], position=(a / 2, a / 2, a / 2))) self.structure_prim = structure_prim self.cs = ClusterSpace(self.structure_prim, self.cutoffs, self.chemical_symbols) self.ce = ClusterExpansion(self.cs, [0, 0, 0.1, -0.02]) self.all_possible_structures = [] self.supercell = self.structure_prim.repeat(2) sublattices = self.cs.get_sublattices(self.supercell) self.n_active_sites = [ len(subl.indices) for subl in sublattices.active_sublattices ] for i, sym in enumerate(self.supercell.get_chemical_symbols()): if sym not in self.chemical_symbols[0]: continue structure = self.supercell.copy() structure.symbols[i] = self.chemical_symbols[0][0] self.all_possible_structures.append(structure) def shortDescription(self): """Silences unittest from printing the docstrings in test cases.""" return None def setUp(self): """Setup before each test.""" self.gsf = icet.tools.ground_state_finder.GroundStateFinder( self.ce, self.supercell, verbose=False) def test_init(self): """Tests that initialization of tested class work.""" # initialize from ClusterExpansion instance gsf = icet.tools.ground_state_finder.GroundStateFinder(self.ce, self.supercell, verbose=False) self.assertIsInstance(gsf, icet.tools.ground_state_finder.GroundStateFinder) def test_get_ground_state(self): """Tests get_ground_state functionality.""" target_val = min([ self.ce.predict(structure) for structure in self.all_possible_structures ]) # Provide counts for first species species_count = {self.chemical_symbols[0][0]: 1} ground_state = self.gsf.get_ground_state(species_count=species_count, threads=1) predicted_species0 = self.ce.predict(ground_state) self.assertEqual(predicted_species0, target_val) species_count = { self.chemical_symbols[0][1]: self.n_active_sites[0] - 1 } ground_state = self.gsf.get_ground_state(species_count=species_count, threads=1) predicted_species1 = self.ce.predict(ground_state) self.assertEqual(predicted_species0, predicted_species1) def test_get_ground_state_fails_for_faulty_species_to_count(self): """Tests that get_ground_state fails if species_to_count is faulty.""" # Check that get_ground_state fails if counts are provided for multiple species species_count = { self.chemical_symbols[0][0]: 1, self.chemical_symbols[0][1]: self.n_active_sites[0] - 1 } with self.assertRaises(ValueError) as cm: self.gsf.get_ground_state(species_count=species_count) self.assertTrue( 'Provide counts for at most one of the species on each active sublattice ' '({}), not {}!'.format(self.gsf._active_species, list(species_count.keys())) in str( cm.exception)) # Check that get_ground_state fails if counts are provided for a # species not found on the active sublattice species_count = {'H': 1} with self.assertRaises(ValueError) as cm: self.gsf.get_ground_state(species_count=species_count) self.assertTrue( 'The species {} is not present on any of the active sublattices' ' ({})'.format( list(species_count.keys())[0], self.gsf._active_species) in str(cm.exception)) # Check that get_ground_state fails if the count exceeds the number sites on the active # sublattice faulty_species = self.chemical_symbols[0][0] faulty_count = len(self.supercell) species_count = {faulty_species: faulty_count} n_active_sites = len([ sym for sym in self.supercell.get_chemical_symbols() if sym == self.chemical_symbols[0][1] ]) with self.assertRaises(ValueError) as cm: self.gsf.get_ground_state(species_count=species_count) self.assertTrue( 'The count for species {} ({}) must be a positive integer and cannot ' 'exceed the number of sites on the active sublattice ' '({})'.format(faulty_species, faulty_count, n_active_sites) in str( cm.exception)) def test_create_cluster_maps(self): """Tests _create_cluster_maps functionality """ gsf = icet.tools.ground_state_finder.GroundStateFinder(self.ce, self.supercell, verbose=False) gsf._create_cluster_maps(self.structure_prim) # Test cluster to sites map target = [[0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]] self.assertEqual(target, gsf._cluster_to_sites_map) # Test cluster to orbit map target = [0, 1, 1, 1, 1, 1, 1, 2, 2, 2] self.assertEqual(target, gsf._cluster_to_orbit_map) # Test ncluster per orbit map target = [1, 1, 6, 3] self.assertEqual(target, gsf._nclusters_per_orbit)
class BinaryShortRangeOrderObserver(BaseObserver): """ This class represents a short range order (SRO) observer for a binary system. Parameters ---------- cluster_space : icet.ClusterSpace cluster space used for initialization structure : ase.Atoms defines the lattice which the observer will work on interval : int the observation interval, defaults to None meaning that if the observer is used in a Monte Carlo simulations, then the Ensemble object will set the interval. radius : float the maximum radius for the neigbhor shells considered Attributes ---------- tag : str human readable observer name (`BinaryShortRangeOrderObserver`) interval : int observation interval Example ------- The following snippet illustrate how to use the short-range order (SRO) observer in a Monte Carlo simulation of a bulk supercell. Here, the parameters of the cluster expansion are set to emulate a simple Ising model in order to obtain an example that can be run without modification. In practice, one should of course use a proper cluster expansion:: >>> from ase.build import bulk >>> from icet import ClusterExpansion, ClusterSpace >>> from mchammer.calculators import ClusterExpansionCalculator >>> from mchammer.ensembles import CanonicalEnsemble >>> from mchammer.observers import BinaryShortRangeOrderObserver >>> # prepare cluster expansion >>> # the setup emulates a second nearest-neighbor (NN) Ising model >>> # (zerolet and singlet ECIs are zero; only first and second neighbor >>> # pairs are included) >>> prim = bulk('Au') >>> cs = ClusterSpace(prim, cutoffs=[4.3], chemical_symbols=['Ag', 'Au']) >>> ce = ClusterExpansion(cs, [0, 0, 0.1, -0.02]) >>> # prepare initial configuration >>> nAg = 10 >>> structure = prim.repeat(3) >>> structure.set_chemical_symbols(nAg * ['Ag'] + (len(structure) - nAg) * ['Au']) >>> # set up MC simulation >>> calc = ClusterExpansionCalculator(structure, ce) >>> mc = CanonicalEnsemble(structure=structure, calculator=calc, temperature=600, ... dc_filename='myrun_sro.dc') # set up observer and attach it to the MC simulation sro = BinaryShortRangeOrderObserver(cs, structure, interval=len(structure), radius=4.3) mc.attach_observer(sro) # run 1000 trial steps mc.run(1000) After having run this snippet one can access the SRO parameters via the data container:: print(mc.data_container.data) """ def __init__(self, cluster_space, structure: Atoms, radius: float, interval: int = None) -> None: super().__init__(interval=interval, return_type=dict, tag='BinaryShortRangeOrderObserver') self._structure = structure self._cluster_space = ClusterSpace( structure=cluster_space.primitive_structure, cutoffs=[radius], chemical_symbols=cluster_space.chemical_symbols) self._cluster_count_observer = ClusterCountObserver( cluster_space=self._cluster_space, structure=structure, interval=interval) self._sublattices = self._cluster_space.get_sublattices(structure) binary_sublattice_counts = 0 for symbols in self._sublattices.allowed_species: if len(symbols) == 2: binary_sublattice_counts += 1 self._symbols = sorted(symbols) elif len(symbols) > 2: raise ValueError('Cluster space has more than two allowed' ' species on a sublattice. ' 'Allowed species: {}'.format(symbols)) if binary_sublattice_counts != 1: raise ValueError('Number of binary sublattices must equal one,' ' not {}'.format(binary_sublattice_counts)) def get_observable(self, structure: Atoms) -> Dict[str, float]: """Returns the value of the property from a cluster expansion model for a given atomic configurations. Parameters ---------- structure input atomic structure """ self._cluster_count_observer._generate_counts(structure) df = self._cluster_count_observer.count_frame symbol_counts = self._get_atom_count(structure) conc_B = self._get_concentrations(structure)[self._symbols[0]] pair_orbit_indices = set( df.loc[df['order'] == 2]['orbit_index'].tolist()) N = symbol_counts[self._symbols[0]] + symbol_counts[self._symbols[1]] sro_parameters = {} for k, orbit_index in enumerate(sorted(pair_orbit_indices)): orbit_df = df.loc[df['orbit_index'] == orbit_index] A_B_pair_count = 0 total_count = 0 total_A_count = 0 for i, row in orbit_df.iterrows(): total_count += row.cluster_count if self._symbols[0] in row.occupation: total_A_count += row.cluster_count if self._symbols[0] in row.occupation and \ self._symbols[1] in row.occupation: A_B_pair_count += row.cluster_count key = 'sro_{}_{}'.format(self._symbols[0], k + 1) Z_tot = symbol_counts[self._symbols[0]] * 2 * total_count / N if conc_B == 1 or Z_tot == 0: value = 0 else: value = 1 - A_B_pair_count / (Z_tot * (1 - conc_B)) sro_parameters[key] = value return sro_parameters def _get_concentrations(self, structure: Atoms) -> Dict[str, float]: """Returns concentrations for each species relative its sublattice. Parameters ---------- structure the configuration that will be analyzed """ occupation = np.array(structure.get_chemical_symbols()) concentrations = {} for sublattice in self._sublattices: if len(sublattice.chemical_symbols) == 1: continue for symbol in sublattice.chemical_symbols: symbol_count = occupation[sublattice.indices].tolist().count( symbol) concentration = symbol_count / len(sublattice.indices) concentrations[symbol] = concentration return concentrations def _get_atom_count(self, structure: Atoms) -> Dict[str, float]: """Returns atom counts for each species relative its sublattice. Parameters ---------- structure the configuration that will be analyzed """ occupation = np.array(structure.get_chemical_symbols()) counts = {} for sublattice in self._sublattices: if len(sublattice.chemical_symbols) == 1: continue for symbol in sublattice.chemical_symbols: symbol_count = occupation[sublattice.indices].tolist().count( symbol) counts[symbol] = symbol_count return counts