def get_sbu_dict(self, sbu_names, coercion=False): """Return a dictionary of SBU by corresponding fragment. This stage get a one to one correspondance between each topology slot and an available SBU from the list of names. Parameters ---------- topology: autografs.utils.topology.Topology the topology to use as map for creation of the framework. sbu_names: [str,...] or [(str,float),...] the list of SBU names as strings. accepts probabilistic SBU scheduling. coercion: bool, optional If True, force compatibility by coordination alone Returns ------- dict {slot index: autografs.utils.sbu.SBU, ...} the dictionary is the map that will generate the final Framework object """ assert self.topology is not None weights = defaultdict(list) by_shape = defaultdict(list) for name in sbu_names: # check if probabilities included if isinstance(name, tuple): name, p = name p = float(p) name = str(name) else: p = 1.0 # create the SBU object sbu = SBU(name=name, atoms=self.sbu[name]) slots = self.topology.has_compatible_slots(sbu=sbu, coercion=coercion) if not slots: continue for slot in slots: weights[slot].append(p) by_shape[slot].append(sbu) # now fill the choices sbu_dict = {} for index, shape in self.topology.shapes.items(): # here, should accept weights also shape = tuple(shape) if shape not in by_shape.keys(): logger.info("Unfilled slot at index {idx}".format(idx=index)) p = weights[shape] # no weights means same proba p /= numpy.sum(p) sbu_chosen = numpy.random.choice(by_shape[shape], p=p).copy() sbu_dict[index] = sbu_chosen return sbu_dict
def list_available_sbu(self, topology_name=None, from_list=[], coercion=False): """Return the dictionary of compatible SBU. Filters the existing SBU by shape until only those compatible with a slot within the topology are left. topology -- name of the topology in the database from_list -- only consider SBU from this list """ av_sbu = defaultdict(list) if from_list: sbu_names = from_list else: sbu_names = list(self.sbu.keys()) if topology_name is not None or self.topology is not None: if topology_name is not None: topology = Topology(name=topology_name, atoms=self.topologies[topology_name]) else: topology = self.topology logger.info("List of compatible SBU with topology {t}:".format( t=topology.name)) sbu_list = [] logger.info("\tShape analysis of {} available SBU...".format( len(self.sbu))) for sbuk in sbu_names: sbuv = self.sbu[sbuk] try: sbu = SBU(name=sbuk, atoms=sbuv) except Exception as exc: logger.debug("SBU {k} not loaded: {exc}".format(k=sbuk, exc=exc)) continue sbu_list.append(sbu) for sites in topology.equivalent_sites: logger.info("\tSites considered : {}".format(", ".join( [str(s) for s in sites]))) shape = topology.shapes[sites[0]] for sbu in sbu_list: is_compatible = sbu.is_compatible(shape, coercion=coercion) if is_compatible: logger.info("\t\t|--> {k}".format(k=sbu.name)) av_sbu[tuple(sites)].append(sbu.name) else: logger.info("Listing full database of SBU.") av_sbu = list(self.sbu.keys()) return dict(av_sbu)
def get_sbu_dict(self, sbu_names, coercion=False): """Return a dictionary of SBU by corresponding fragment. This stage get a one to one correspondance between each topology slot and an available SBU from the list of names. topology -- the Topology object sbu_names -- the list of SBU names as strings coercion -- wether to force compatibility by coordination alone """ logger.debug("Generating slot to SBU map.") assert self.topology is not None weights = defaultdict(list) by_shape = defaultdict(list) for name in sbu_names: # check if probabilities included if isinstance(name, tuple): name, p = name p = float(p) name = str(name) else: p = 1.0 # create the SBU object sbu = SBU(name=name, atoms=self.sbu[name]) slots = self.topology.has_compatible_slots(sbu=sbu, coercion=coercion) if not slots: logger.debug( "SBU {s} has no compatible slot in topology {t}".format( s=name, t=self.topology.name)) continue for slot in slots: weights[slot].append(p) by_shape[slot].append(sbu) # now fill the choices sbu_dict = {} for index, shape in self.topology.shapes.items(): # here, should accept weights also shape = tuple(shape) if shape not in by_shape.keys(): logger.info("Unfilled slot at index {idx}".format(idx=index)) p = weights[shape] # no weights means same proba p /= numpy.sum(p) sbu_chosen = numpy.random.choice(by_shape[shape], p=p).copy() logger.debug("Slot {sl}: {sb} chosen with p={p}.".format( sl=index, sb=sbu_chosen.name, p=p)) sbu_dict[index] = sbu_chosen return sbu_dict
def list_available_sbu(self, topology_name=None, from_list=[], coercion=False): """Return the dictionary of compatible SBU. Filters the existing SBU by shape until only those compatible with a slot within the topology are left. Parameters ---------- topology_name: str Name of the topology to analyse from_list: [str, ...] subset of SBU to consider coercion: bool, optional If True, force compatibility by coordination alone Returns ------- list list of SBU names """ av_sbu = defaultdict(list) if from_list: sbu_names = from_list else: sbu_names = list(self.sbu.keys()) sbu_names = sorted(sbu_names) if topology_name is not None or self.topology is not None: if topology_name is not None: topology = Topology(name=topology_name, atoms=self.topologies[topology_name]) else: topology = self.topology logger.info(("List of compatible SBU" " with topology {t}:").format(t=topology.name)) sbu_list = [] logger.info(("\tShape analysis of" " {le} available SBU...").format(le=len(self.sbu))) for sbuk in sbu_names: sbuv = self.sbu[sbuk] sbu = SBU(name=sbuk, atoms=sbuv) if sbu is None: continue sbu_list.append(sbu) for sites in topology.equivalent_sites: logger.info(("\tSites considered" " : {s}").format(s=", ".join(map(str, sites)))) shape = topology.shapes[sites[0]] for sbu in sbu_list: is_compatible = sbu.is_compatible(shape, coercion=coercion) if is_compatible: logger.info("\t\t|--> {k}".format(k=sbu.name)) av_sbu[tuple(sites)].append(sbu.name) return dict(av_sbu) else: logger.info("Listing full database of SBU.") av_sbu = list(self.sbu.keys()) av_sbu = sorted(av_sbu) return av_sbu
def list_available_topologies(self, sbu_names=[], full=True, max_size=100, from_list=[], pbc="all", coercion=False): """Return a list of topologies compatible with the SBUs For each sbu in the list given in input, refines first by coordination then by shapes within the topology. Thus, we do not need to analyze every topology. Parameters ---------- sbu_names: [str,...] the list of SBU names as strings full: bool, optional if True, only list topologies fully filled with the given SBU names max_size: int maximum size of topologies to consider, in number of building units from_list: [str, ...] subset of topologies to consider coercion: bool, optional If True, force compatibility by coordination alone pbc: str, optional can be '2D', '3D' or 'all'. Restrits the search to topologies of the given periodicity Returns ------- list list of topology names """ these_topologies_names = self.topologies.keys() if max_size is None: max_size = 999999 if from_list: these_topologies_names = from_list if pbc == "2D": logger.info("only considering 2D periodic topologies.") these_topologies_names = [ tk for tk, tv in self.topologies.items() if sum(tv.pbc) == 2 ] elif pbc == "3D": logger.info("only considering 3D periodic topologies.") these_topologies_names = [ tk for tk, tv in self.topologies.items() if sum(tv.pbc) == 3 ] elif pbc != "all": logger.info(("pbc keyword has to be '2D','3D'" " or 'all'. Assumed 'all'.")) these_topologies_names = sorted(these_topologies_names) if sbu_names: logger.info("Checking topology compatibility.") topologies = [] sbu = [SBU(name=n, atoms=self.sbu[n]) for n in sbu_names] for tk in these_topologies_names: tv = self.topologies[tk] if max_size is None or len(tv) > max_size: continue topology = Topology(name=tk, atoms=tv) if topology is None: continue # For now, no shape compatibilities filled = { shape: False for shape in topology.get_unique_shapes() } fslots = [ topology.has_compatible_slots(s, coercion=coercion) for s in sbu ] for slots in fslots: for slot in slots: filled[slot] = True if all(filled.values()): logger.info(("\tTopology {tk}" " fully available.").format(tk=tk)) topologies.append(tk) elif any(filled.values()) and not full: logger.info(("\tTopology {tk}" " partially available.").format(tk=tk)) topologies.append(tk) else: logger.info("Listing full database of topologies.") topologies = list(self.topologies.keys()) topologies = sorted(topologies) return topologies
def make(self, topology_name=None, sbu_names=None, sbu_dict=None, supercell=(1, 1, 1), coercion=False): """Create a framework using given topology and sbu. Main funtion of Autografs. The sbu names and topology's are to be taken from the compiled databases. The sbu_dict can also be passed for multiple components frameworks. If the sbu_names is a list of tuples in the shape (name,n), the number n will be used as a drawing probability when multiple options are available for the same shape. Parameters ---------- topology_name: str, optional name of the topology to use. If not given, Autografs will try to use its stored topology attribute. sbu_names: [str,...], optional list of names of the sbu to use. The names are the keys used to search the SBU database. If not used, the sbu_dict option has to be given. sbu_dict: {int:str,...}, optional slot index to sbu name mapping. ASE Atoms can also be given. If used, this argument will take precedence over sbu_names. supercell: int or (int, int, int), optional multiplicator for generation of a supercell of the topology, done before any framework generation. Useful for statistical defect introduction. coercion: bool, optional force the compatibility detection to only consider the multiplicity of SBU: any 4 connected SBU can fit any 4-connected slot. Returns ------- autografs.framework.Framework the scaled, aligned version of the framework built using the defined options. """ logger.info("{0:-^50}".format(" Starting Framework Generation ")) logger.info("") self.sbudict = None # only set the topology if not already done if topology_name is not None: self.set_topology(topology_name=topology_name, supercell=supercell) # container for the aligned SBUs aligned = Framework() aligned.set_topology(self.topology) # identify the corresponding SBU if sbu_dict is None and sbu_names is not None: logger.info("Scheduling the SBU to slot alignment.") self.sbu_dict = self.get_sbu_dict(sbu_names=sbu_names, coercion=coercion) elif sbu_dict is not None: logger.info("SBU to slot alignment is user defined.") # the sbu_dict has been passed. if not SBU object, create them for k, v in sbu_dict.items(): if not isinstance(v, SBU): if not isinstance(v, ase.Atoms): name = str(v) v = self.sbu[name].copy() elif "name" in v.info.keys(): name = v.info["name"] else: name = str(k) sbu_dict[k] = SBU(name=name, atoms=v) self.sbu_dict = sbu_dict else: raise ValueError("Either supply sbu_names or sbu_dict.") # some logging for pretty information for idx, sbu in self.sbu_dict.items(): logging.info("\tSlot {sl}".format(sl=idx)) logging.info("\t |--> SBU {sbn}".format(sbn=sbu.name)) # carry on alpha = 0.0 # should be parrallelized one of these days for idx, sbu in self.sbu_dict.items(): # now align and get the scaling factor sbu, f = self.align(fragment=self.topology.fragments[idx], sbu=sbu) alpha += f aligned.append(index=idx, sbu=sbu) logger.info("") # refine the cell of the aligned object aligned.refine(alpha0=alpha) # inform user of finished generation logger.info("") logger.info("Finished framework generation.") logger.info("") logger.info("{0:-^50}".format(" Post-treatment ")) logger.info("") return aligned
def make(self, topology_name=None, sbu_names=None, sbu_dict=None, supercell=(1, 1, 1), coercion=False): """Create a framework using given topology and sbu. Main funtion of Autografs. The sbu names and topology's are to be taken from the compiled databases. The sbu_dict can also be passed for multiple components frameworks. If the sbu_names is a list of tuples in the shape (name,n), the number n will be used as a drawing probability when multiple options are available for the same shape. topology_name -- name of the topology to use sbu_names -- list of names of the sbu to use sbu_dict -- (optional) one to one sbu to slot correspondance in the shape {index of slot : 'name of sbu'} supercell -- (optional) creates a supercell pre-treatment coercion -- (optional) force the compatibility to only consider the multiplicity of SBU """ logger.info("{0:*^80}".format(" STARTING THE MOF GENERATION ")) self.sbudict = None # only set the topology if not already done if topology_name is not None: self.set_topology(topology_name=topology_name, supercell=supercell) # container for the aligned SBUs aligned = Framework() aligned.set_topology(self.topology) # identify the corresponding SBU try: if sbu_dict is None and sbu_names is not None: logger.info("Scheduling the SBU to slot alignment.") self.sbu_dict = self.get_sbu_dict(sbu_names=sbu_names, coercion=coercion) elif sbu_dict is not None: logger.info("SBU to slot alignment is user defined.") # the sbu_dict has been passed. if not SBU object, create them for k, v in sbu_dict.items(): if not isinstance(v, SBU): if not isinstance(v, ase.Atoms): name = str(v) v = self.sbu[name].copy() elif "name" in v.info.keys(): name = v.info["name"] else: name = str(k) sbu_dict[k] = SBU(name=name, atoms=v) self.sbu_dict = sbu_dict else: raise RuntimeError("Either supply sbu_names or sbu_dict.") except RuntimeError as exc: logger.error("Slot to SBU mappping interrupted.") logger.error("{exc}".format(exc=exc)) logger.info( "No valid framework was generated. Please check your input.") logger.info( "You can coerce sbu assignment by directly passing a slot to sbu dictionary." ) return # some logging self.log_sbu_dict(sbu_dict=self.sbu_dict, topology=self.topology) # carry on alpha = 0.0 for idx, sbu in self.sbu_dict.items(): logger.debug("Treating slot number {idx}".format(idx=idx)) logger.debug("\t|--> Aligning SBU {name}".format(name=sbu.name)) # now align and get the scaling factor sbu, f = self.align(fragment=self.topology.fragments[idx], sbu=sbu) alpha += f aligned.append(index=idx, sbu=sbu) aligned.refine(alpha0=alpha) return aligned
def list_available_topologies(self, sbu_names=[], full=True, max_size=100, from_list=[], pbc="all", coercion=False): """Return a list of topologies compatible with the SBUs For each sbu in the list given in input, refines first by coordination then by shapes within the topology. Thus, we do not need to analyze every topology. sbu -- list of sbu names full -- wether the topology is entirely represented by the sbu max_size -- maximum size of in SBU numbers of topologies to consider from_list -- only consider topologies from this list """ these_topologies_names = self.topologies.keys() if max_size is None: max_size = 999999 if from_list: these_topologies_names = from_list if pbc == "2D": logger.info("only considering 2D periodic topologies.") these_topologies_names = [ tk for tk, tv in self.topologies.items() if sum(tv.pbc) == 2 ] elif pbc == "3D": logger.info("only considering 3D periodic topologies.") these_topologies_names = [ tk for tk, tv in self.topologies.items() if sum(tv.pbc) == 3 ] elif pbc != "all": logger.info( "pbc keyword has to be '2D','3D' or 'all'. Assumed 'all'.") if sbu_names: logger.info("Checking topology compatibility.") topologies = [] sbu = [SBU(name=n, atoms=self.sbu[n]) for n in sbu_names] for tk in these_topologies_names: tv = self.topologies[tk] if max_size is None or len(tv) > max_size: logger.debug("\tTopology {tk} to big : size = {s}.".format( tk=tk, s=len(tv))) continue try: topology = Topology(name=tk, atoms=tv) except Exception as exc: logger.debug("Topology {tk} not loaded: {exc}".format( tk=tk, exc=exc)) continue filled = { shape: False for shape in topology.get_unique_shapes() } slots_full = [ topology.has_compatible_slots(s, coercion=coercion) for s in sbu ] for slots in slots_full: for slot in slots: filled[slot] = True if all(filled.values()): logger.info( "\tTopology {tk} fully available.".format(tk=tk)) topologies.append(tk) elif any(filled.values()) and not full: logger.info( "\tTopology {tk} partially available.".format(tk=tk)) topologies.append(tk) else: logger.debug( "\tTopology {tk} not available.".format(tk=tk)) else: logger.info("Listing full database of topologies.") topologies = list(self.topologies.keys()) return topologies