Ejemplo n.º 1
0
    def stage1(self):
        """
        replicate water molecules to make a repeated cell
        """
        self.logger.info("Stage1: Replication.")
        self.reppositions = replicate_positions(self.waters, self.rep)
        # This must be done before the replication of the cell.
        self.logger.info("  Number of water molecules: {0}".format(
            len(self.reppositions)))
        self.graph = self.prepare_random_graph(self.fixed)
        # scale the cell
        self.repcell = Cell(self.cell)
        self.repcell.scale2(self.rep)

        if self.cagepos is not None:
            self.logger.info("  Hints:")
            self.repcagepos = replicate_positions(self.cagepos, self.rep)
            nrepcages = self.repcagepos.shape[0]
            self.repcagetype = [
                self.cagetype[i % len(self.cagetype)] for i in range(nrepcages)
            ]
            self.cagetypes = defaultdict(set)
            for i, typ in enumerate(self.repcagetype):
                self.cagetypes[typ].add(i)
            # INFO for cage types
            self.logger.info("    Cage types: {0}".format(list(
                self.cagetypes)))
            for typ, cages in self.cagetypes.items():
                self.logger.info("    Cage type {0}: {1}".format(typ, cages))
            # Up here move to stage 1.

        self.logger.info("Stage1: end.")
Ejemplo n.º 2
0
    def stage1_analice(self):
        """
        Do nothing.

        Provided variables:
        repposition: replicated molecular positions (CoM, relative)
        repcell:     replicated simulation cell shape matrix
        """
        self.logger.info("Stage1: (...)")
        self.reppositions = self.waters
        # This must be done before the replication of the cell.
        self.logger.info("  Number of water molecules: {0}".format(
            len(self.reppositions)))
        # self.graph = self.prepare_random_graph(self.fixed)
        self.graph = self.prepare_random_graph(self.pairs)
        # scale the cell
        self.repcell = Cell(self.cell)
        # self.repcell.scale2(self.rep)

        self.logger.info("Stage1: end.")
Ejemplo n.º 3
0
    def stage1_analice(self):
        """
        Do nothing.

        Provided variables:
        repposition: replicated molecular positions (CoM, relative)
        repcell:     replicated simulation cell shape matrix
        """
        self.logger.info("Stage1: (...)")
        self.reppositions = self.waters
        # This must be done before the replication of the cell.
        self.logger.info("  Number of water molecules: {0}".format(
            len(self.reppositions)))
        # self.graph = self.prepare_random_graph(self.fixed)
        self.graph = self.prepare_random_graph(self.pairs)
        # scale the cell
        self.repcell = Cell(self.cell)
        # self.repcell.scale2(self.rep)

        self.logger.info("Stage1: end.")
Ejemplo n.º 4
0
    def stage1(self):
        """
        Replicate water molecules to make a repeated cell

        Provided variables:
        repposition: replicated molecular positions (CoM, relative)
        repcell:     replicated simulation cell shape matrix
        repcagetype: replicated cage types array
        repcagepos:  replicated cage positions (CoM, relative)
        cagetypes:   set of cage types
        """
        self.logger.info("Stage1: Replication.")
        self.reppositions = replicate_positions(self.waters, self.rep)
        # This must be done before the replication of the cell.
        self.logger.info("  Number of water molecules: {0}".format(
            len(self.reppositions)))
        self.graph = self.prepare_random_graph(self.fixed)
        # scale the cell
        self.repcell = Cell(self.cell)
        self.repcell.scale2(self.rep)

        if self.cagepos is not None:
            self.logger.info("  Hints:")
            self.repcagepos = replicate_positions(self.cagepos, self.rep)
            nrepcages = self.repcagepos.shape[0]
            self.repcagetype = [self.cagetype[i % len(self.cagetype)]
                               for i in range(nrepcages)]
            self.cagetypes = defaultdict(set)
            for i, typ in enumerate(self.repcagetype):
                self.cagetypes[typ].add(i)
            # INFO for cage types
            self.logger.info("    Cage types: {0}".format(list(self.cagetypes)))
            for typ, cages in self.cagetypes.items():
                self.logger.info("    Cage type {0}: {1}".format(typ, cages))
            # Up here move to stage 1.
        
        self.logger.info("Stage1: end.")
Ejemplo n.º 5
0
"""
Test for generating water positions from cage positions
"""

cages = """
12 0.5 0.5 0.5
14 0.5 0.0 -0.25
14 0.0 0.25 0.5
14 0.75 0.5 0.0
14 0.5 0.0 0.25
12 0.0 0.0 0.0
14 0.25 0.5 0.0
14 0.0 -0.25 0.5
"""

celltype = 'rect'

cell = """
12.747893943706936 12.747893943706936 12.747893943706936
"""

from genice import FrankKasper
from genice.lattice import parse_cages
from genice.cells import Cell
cagepos, cagetype = parse_cages(cages)
cell9 = Cell(cell, celltype)
waters = [w for w in FrankKasper.toWater(cagepos, cell9.mat)]
coord = "relative"
density = FrankKasper.estimate_density(waters, cell9.mat, 2.76)
bondlen = 2.76 * 1.2
Ejemplo n.º 6
0
    def __init__(self,
                 lattice_type=None,
                 density=0,
                 rep=(1, 1, 1),
                 depolarize=True,
                 asis=False,
                 cations=dict(),
                 anions=dict(),
                 spot_guests=dict(),
                 spot_groups=dict(),
                 ):
        self.logger      = logging.getLogger()
        self.lattice_type = lattice_type
        self.rep         = rep
        self.depolarize  = depolarize
        self.asis        = asis
        self.cations     = cations
        self.anions      = anions
        self.spot_guests = spot_guests
        self.spot_groups = spot_groups
        if lattice_type is None:
            return
        lat = safe_import("lattice", lattice_type)
        # Show the document of the module
        try:
            self.doc = lat.__doc__.splitlines()
        except:
            self.doc = []
        self.doc.append("")
        self.doc.append("Command line: {0}".format(" ".join(sys.argv)))
        for line in self.doc:
            self.logger.info("!!! {0}".format(line))
        # ================================================================
        # rotmatrices (analice)
        #
        try:
            self.rotmatrices = lat.rotmat
        except:
            self.logger.info("No rotmatrices in lattice")
            pass
        # ================================================================
        # waters: positions of water molecules
        #
        self.waters = load_numbers(lat.waters)
        self.logger.debug("Waters: {0}".format(len(self.waters)))
        self.waters = self.waters.reshape((self.waters.size // 3, 3))

        # ================================================================
        # cell: cell dimension
        # celltype: symmetry of the cell
        #   see parse_cell for syntax.
        #
        self.cell = Cell(lat.cell, lat.celltype)
        #self.cell = parse_cell(lat.cell, lat.celltype)

        # ================================================================
        # coord: "relative" or "absolute"
        #   Inside genice, molecular positions are always treated as "relative"
        #
        if lat.coord == "absolute":
            self.waters = self.cell.abs2rel(self.waters)
        self.waters = np.array([w - np.floor(w) for w in self.waters])

        # ================================================================
        # pairs: specify the pairs of molecules that are connected.
        #   Bond orientation will be shuffled later
        #   unless it is "fixed".
        #
        self.pairs = None
        try:
            if type(lat.pairs) is str:
                lines = lat.pairs.split("\n")
                self.pairs = []
                for line in lines:
                    columns = line.split()
                    if len(columns) == 2:
                        i, j = [int(x) for x in columns]
                        self.pairs.append((i, j))
            elif type(lat.pairs) is list:
                self.pairs = lat.pairs
                # for pair in lat.pairs:
                #    self.pairs.append(pair)
        except AttributeError:
            self.logger.info("Graph is not defined.")

        # ================================================================
        # bondlen: specify the bond length threshold.
        #   This is used when "pairs" are not specified.
        #   It is applied to the original positions of molecules (before density setting).
        #
        self.bondlen = None
        try:
            self.bondlen = lat.bondlen
            self.logger.info("Bond length (specified): {0}".format(self.bondlen))
        except AttributeError:
            self.bondlen = 1.1 * shortest_distance(self.waters, self.cell)
            self.logger.info("Bond length (assumed): {0}".format(self.bondlen))
        # Set density
        mass = 18  # water
        NB = 6.022e23
        nmol = self.waters.shape[0]  # nmol in a unit cell
        volume = self.cell.volume()  # volume of a unit cell in nm**3
        density0 = mass * nmol / (NB * volume * 1e-21)
        if density <= 0:
            try:
                self.density = lat.density
            except AttributeError:
                self.logger.info(
                    "Density is not specified. Assume the density from lattice.")
                dmin = shortest_distance(self.waters, self.cell)
                self.logger.info(
                    "Closest pair distance: {0} (should be around 0.276 nm)".format(dmin))
                self.density = density0 / (0.276 / dmin)**3
                # self.density = density0
        else:
            self.density = density
        self.logger.info("Target Density: {0}".format(self.density))
        self.logger.info("Original Density: {0}".format(density0))

        # scale the cell according to the (specified) density
        ratio = (density0 / self.density)**(1.0 / 3.0)
        self.cell.scale(ratio)
        if self.bondlen is not None:
            self.bondlen *= ratio
        self.logger.info("Bond length (scaled, nm): {0}".format(self.bondlen))

        # ================================================================
        # double_network: True or False
        #   This is a special option for ices VI and VII that have
        #   interpenetrating double network.
        #   GenIce's fast depolarization algorithm fails in some case.
        #
        try:
            self.double_network = lat.double_network
        except AttributeError:
            self.double_network = False

        # ================================================================
        # cages: positions of the centers of cages
        #   In fractional coordinate.
        #
        self.cagepos = None
        self.cagetype = None
        if "cages" in lat.__dict__:
            self.cagepos, self.cagetype = parse_cages(lat.cages)

        # ================================================================
        # fixed: specify the bonds whose directions are fixed.
        #   you can specify them in pairs at a time.
        #   You can also leave it undefined.
        #
        try:
            if type(lat.fixed) is str:
                lines = lat.fixed.split("\n")
                self.fixed = []
                for line in lines:
                    columns = line.split()
                    if len(columns) == 2:
                        i, j = [int(x) for x in columns]
                        self.fixed.append((i, j))  # Is a tuple
            elif type(lat.fixed) is list:
                self.fixed = []
                for pair in lat.fixed:
                    self.fixed.append(tuple(pair[:2]))  # Must be a tuple
        except AttributeError:
            self.fixed = []
        if "dopeIonsToUnitCell" in lat.__dict__:
            self.dopeIonsToUnitCell = lat.dopeIonsToUnitCell
        else:
            self.dopeIonsToUnitCell = None
        self.dopants = set()
        # if asis, make pairs to be fixed.
        if self.asis and len(self.fixed) == 0:
            self.fixed = self.pairs

        # filled cages
        self.filled_cages = set()
        # groups info
        self.groups = defaultdict(dict)

        # groups for the semi-guest
        # experimental; there are many variation of semi-guest inclusion.
        self.groups_placer = {"Bu-": butyl,
                              "Butyl-": butyl,
                              "Pentyl-": pentyl,
                              "Propyl-": propyl,
                              "2,2-dimethylpropyl-": _2_2_dimethylpropyl,
                              "2,3-dimethylbutyl-": _2_3_dimethylbutyl,
                              "3,3-dimethylbutyl-": _3_3_dimethylbutyl,
                              "3-methylbutyl-": _3_methylbutyl,
                              "Ethyl-": ethyl}
Ejemplo n.º 7
0
class Lattice():
    def __init__(self,
                 lattice_type=None,
                 density=0,
                 rep=(1, 1, 1),
                 depolarize=True,
                 asis=False,
                 cations=dict(),
                 anions=dict(),
                 spot_guests=dict(),
                 spot_groups=dict(),
                 ):
        self.logger      = logging.getLogger()
        self.lattice_type = lattice_type
        self.rep         = rep
        self.depolarize  = depolarize
        self.asis        = asis
        self.cations     = cations
        self.anions      = anions
        self.spot_guests = spot_guests
        self.spot_groups = spot_groups
        if lattice_type is None:
            return
        lat = safe_import("lattice", lattice_type)
        # Show the document of the module
        try:
            self.doc = lat.__doc__.splitlines()
        except:
            self.doc = []
        self.doc.append("")
        self.doc.append("Command line: {0}".format(" ".join(sys.argv)))
        for line in self.doc:
            self.logger.info("!!! {0}".format(line))
        # ================================================================
        # rotmatrices (analice)
        #
        try:
            self.rotmatrices = lat.rotmat
        except:
            self.logger.info("No rotmatrices in lattice")
            pass
        # ================================================================
        # waters: positions of water molecules
        #
        self.waters = load_numbers(lat.waters)
        self.logger.debug("Waters: {0}".format(len(self.waters)))
        self.waters = self.waters.reshape((self.waters.size // 3, 3))

        # ================================================================
        # cell: cell dimension
        # celltype: symmetry of the cell
        #   see parse_cell for syntax.
        #
        self.cell = Cell(lat.cell, lat.celltype)
        #self.cell = parse_cell(lat.cell, lat.celltype)

        # ================================================================
        # coord: "relative" or "absolute"
        #   Inside genice, molecular positions are always treated as "relative"
        #
        if lat.coord == "absolute":
            self.waters = self.cell.abs2rel(self.waters)
        self.waters = np.array([w - np.floor(w) for w in self.waters])

        # ================================================================
        # pairs: specify the pairs of molecules that are connected.
        #   Bond orientation will be shuffled later
        #   unless it is "fixed".
        #
        self.pairs = None
        try:
            if type(lat.pairs) is str:
                lines = lat.pairs.split("\n")
                self.pairs = []
                for line in lines:
                    columns = line.split()
                    if len(columns) == 2:
                        i, j = [int(x) for x in columns]
                        self.pairs.append((i, j))
            elif type(lat.pairs) is list:
                self.pairs = lat.pairs
                # for pair in lat.pairs:
                #    self.pairs.append(pair)
        except AttributeError:
            self.logger.info("Graph is not defined.")

        # ================================================================
        # bondlen: specify the bond length threshold.
        #   This is used when "pairs" are not specified.
        #   It is applied to the original positions of molecules (before density setting).
        #
        self.bondlen = None
        try:
            self.bondlen = lat.bondlen
            self.logger.info("Bond length (specified): {0}".format(self.bondlen))
        except AttributeError:
            self.bondlen = 1.1 * shortest_distance(self.waters, self.cell)
            self.logger.info("Bond length (assumed): {0}".format(self.bondlen))
        # Set density
        mass = 18  # water
        NB = 6.022e23
        nmol = self.waters.shape[0]  # nmol in a unit cell
        volume = self.cell.volume()  # volume of a unit cell in nm**3
        density0 = mass * nmol / (NB * volume * 1e-21)
        if density <= 0:
            try:
                self.density = lat.density
            except AttributeError:
                self.logger.info(
                    "Density is not specified. Assume the density from lattice.")
                dmin = shortest_distance(self.waters, self.cell)
                self.logger.info(
                    "Closest pair distance: {0} (should be around 0.276 nm)".format(dmin))
                self.density = density0 / (0.276 / dmin)**3
                # self.density = density0
        else:
            self.density = density
        self.logger.info("Target Density: {0}".format(self.density))
        self.logger.info("Original Density: {0}".format(density0))

        # scale the cell according to the (specified) density
        ratio = (density0 / self.density)**(1.0 / 3.0)
        self.cell.scale(ratio)
        if self.bondlen is not None:
            self.bondlen *= ratio
        self.logger.info("Bond length (scaled, nm): {0}".format(self.bondlen))

        # ================================================================
        # double_network: True or False
        #   This is a special option for ices VI and VII that have
        #   interpenetrating double network.
        #   GenIce's fast depolarization algorithm fails in some case.
        #
        try:
            self.double_network = lat.double_network
        except AttributeError:
            self.double_network = False

        # ================================================================
        # cages: positions of the centers of cages
        #   In fractional coordinate.
        #
        self.cagepos = None
        self.cagetype = None
        if "cages" in lat.__dict__:
            self.cagepos, self.cagetype = parse_cages(lat.cages)

        # ================================================================
        # fixed: specify the bonds whose directions are fixed.
        #   you can specify them in pairs at a time.
        #   You can also leave it undefined.
        #
        try:
            if type(lat.fixed) is str:
                lines = lat.fixed.split("\n")
                self.fixed = []
                for line in lines:
                    columns = line.split()
                    if len(columns) == 2:
                        i, j = [int(x) for x in columns]
                        self.fixed.append((i, j))  # Is a tuple
            elif type(lat.fixed) is list:
                self.fixed = []
                for pair in lat.fixed:
                    self.fixed.append(tuple(pair[:2]))  # Must be a tuple
        except AttributeError:
            self.fixed = []
        if "dopeIonsToUnitCell" in lat.__dict__:
            self.dopeIonsToUnitCell = lat.dopeIonsToUnitCell
        else:
            self.dopeIonsToUnitCell = None
        self.dopants = set()
        # if asis, make pairs to be fixed.
        if self.asis and len(self.fixed) == 0:
            self.fixed = self.pairs

        # filled cages
        self.filled_cages = set()
        # groups info
        self.groups = defaultdict(dict)

        # groups for the semi-guest
        # experimental; there are many variation of semi-guest inclusion.
        self.groups_placer = {"Bu-": butyl,
                              "Butyl-": butyl,
                              "Pentyl-": pentyl,
                              "Propyl-": propyl,
                              "2,2-dimethylpropyl-": _2_2_dimethylpropyl,
                              "2,3-dimethylbutyl-": _2_3_dimethylbutyl,
                              "3,3-dimethylbutyl-": _3_3_dimethylbutyl,
                              "3-methylbutyl-": _3_methylbutyl,
                              "Ethyl-": ethyl}


    def generate_ice(self, water_type, guests, formatter):
        if 0 in formatter.hooks:
            formatter.hooks[0](self)
        if max(0,*formatter.hooks.keys()) < 1:
            return
        self.stage1()
        if 1 in formatter.hooks:
            formatter.hooks[1](self)
        if max(0,*formatter.hooks.keys()) < 2:
            return
        res = self.stage2()
        if 2 in formatter.hooks:
            formatter.hooks[2](self)
        if max(0,*formatter.hooks.keys()) < 3:
            return
        self.stage3()
        if 3 in formatter.hooks:
            formatter.hooks[3](self)
        if max(0,*formatter.hooks.keys()) < 4:
            return
        self.stage4()
        if 4 in formatter.hooks:
            formatter.hooks[4](self)
        if max(0,*formatter.hooks.keys()) < 5:
            return
        self.stage5()
        if 5 in formatter.hooks:
            formatter.hooks[5](self)
        if max(0,*formatter.hooks.keys()) < 6:
            return
        self.stage6(water_type)
        if 6 in formatter.hooks:
            formatter.hooks[6](self)
        if max(0,*formatter.hooks.keys()) < 7:
            return
        self.stage7(guests)
        if 7 in formatter.hooks:
            formatter.hooks[7](self)


    def analize_ice(self, water_type, formatter):
        """
        Protocol for analice
        """
        if 0 in formatter.hooks:
            formatter.hooks[0](self)
        if max(0,*formatter.hooks.keys()) < 1:
            return
        self.stage1_analice()
        if 1 in formatter.hooks:
            formatter.hooks[1](self)
        if max(0,*formatter.hooks.keys()) < 2:
            return
        # res = self.stage2_analice()
        if 2 in formatter.hooks:
            formatter.hooks[2](self)
        if max(0,*formatter.hooks.keys()) < 3:
            return
        # self.stage3_analice()
        if 3 in formatter.hooks:
            formatter.hooks[3](self)
        if max(0,*formatter.hooks.keys()) < 4:
            return
        self.stage4_analice()
        if 4 in formatter.hooks:
            formatter.hooks[4](self)
        if max(0,*formatter.hooks.keys()) < 5:
            return
        # self.stage5_analice()
        if 5 in formatter.hooks:
            formatter.hooks[5](self)
        if max(0,*formatter.hooks.keys()) < 6:
            return
        self.stage6(water_type)
        if 6 in formatter.hooks:
            formatter.hooks[6](self)
        if max(0,*formatter.hooks.keys()) < 7:
            return
        # self.stage7_analice(guests)
        if 7 in formatter.hooks:
            formatter.hooks[7](self)
            
        
    def stage1(self):
        """
        Replicate water molecules to make a repeated cell

        Provided variables:
        repposition: replicated molecular positions (CoM, relative)
        repcell:     replicated simulation cell shape matrix
        repcagetype: replicated cage types array
        repcagepos:  replicated cage positions (CoM, relative)
        cagetypes:   set of cage types
        """
        self.logger.info("Stage1: Replication.")
        self.reppositions = replicate_positions(self.waters, self.rep)
        # This must be done before the replication of the cell.
        self.logger.info("  Number of water molecules: {0}".format(
            len(self.reppositions)))
        self.graph = self.prepare_random_graph(self.fixed)
        # scale the cell
        self.repcell = Cell(self.cell)
        self.repcell.scale2(self.rep)

        if self.cagepos is not None:
            self.logger.info("  Hints:")
            self.repcagepos = replicate_positions(self.cagepos, self.rep)
            nrepcages = self.repcagepos.shape[0]
            self.repcagetype = [self.cagetype[i % len(self.cagetype)]
                               for i in range(nrepcages)]
            self.cagetypes = defaultdict(set)
            for i, typ in enumerate(self.repcagetype):
                self.cagetypes[typ].add(i)
            # INFO for cage types
            self.logger.info("    Cage types: {0}".format(list(self.cagetypes)))
            for typ, cages in self.cagetypes.items():
                self.logger.info("    Cage type {0}: {1}".format(typ, cages))
            # Up here move to stage 1.
        
        self.logger.info("Stage1: end.")

    def stage2(self):
        """
        Make a random graph and replicate.

        Provided variables:
        dopants:
        groups:  replicated positions of the chemical groups (CoM, relative)
        filled_cages:
        graph:   replicated network topology (bond orientation may be random)
        """
        self.logger.info("Stage2: Graph preparation.")
        # Some edges are directed when ions are doped.
        if self.dopeIonsToUnitCell is not None:
            self.dopeIonsToUnitCell(self)  # may be defined in the plugin
        # Replicate the dopants in the unit cell
        self.dopants = replicate_labeldict(
            self.dopants, len(self.waters), self.rep)
        self.groups = replicate_groups(
            self.groups, self.waters, self.cagepos, self.rep)
        # self.groups_info(self.groups)
        for root, cages in self.groups.items():
            self.filled_cages |= set(cages)
        # self.logger.info(("filled",self.filled_cages))
        # Replicate the graph
        self.graph = replicate_graph(self.graph, self.waters, self.rep)
        # Dope ions by options.
        if len(self.anions) > 0:
            self.logger.info("  Anionize: {0}.".format(self.anions))
            for site, name in self.anions.items():
                self.graph.anionize(site)
                self.dopants[site] = name
        if len(self.cations) > 0:
            self.logger.info("  Cationize: {0}.".format(self.cations))
            for site, name in self.cations.items():
                self.graph.cationize(site)
                self.dopants[site] = name
        # Count bonds
        nrandom = 0
        nfixed = 0
        for i, j, data in self.graph.edges(data=True):
            if self.graph[i][j]['fixed']:  # fixed pair
                nfixed += 1
            else:
                nrandom += 1
        self.logger.info(
            "  Number of pre-oriented hydrogen bonds: {0}".format(nfixed))
        self.logger.info(
            "  Number of unoriented hydrogen bonds: {0}".format(nrandom))
        self.logger.info("  Number of hydrogen bonds: {0} (regular num: {1})".format(
            nfixed + nrandom, len(self.reppositions) * 2))
        # test2==True means it is a z=4 graph.
        self.test2 = self.test_undirected_graph(self.graph)
        if not self.test2:
            self.logger.info("Test2 failed.")

        self.logger.info("Stage2: end.")
        return self.test2

    def stage3(self):
        """
        Make a true ice graph.

        Provided variables:
        graph: network obeying B-F rule.
        """
        self.logger.info("Stage3: Bernal-Fowler rule.")
        if self.asis:
            self.logger.info("  Skip applying the ice rule by request.")
        else:
            self.graph.purge_ice_defects()
        self.logger.info("Stage3: end.")

    def stage4(self):
        """
        Depolarize.

        Provided variables:
        spacegraph: depolarized network with node positions.
        yapresult:  Animation of the depolarization process in YaPlot format.
        """
        self.logger.info("Stage4: Depolarization.")
        if not self.depolarize or self.asis:
            self.logger.info("  Skip depolarization by request.")
            self.yapresult = ""
            self.spacegraph = dg.SpaceIceGraph(self.graph,
                                               coord=self.reppositions,
                                               ignores=self.graph.ignores)
        else:
            if self.double_network:
                if (self.rep[0] % 2 == 0) and (self.rep[1] % 2 == 0) and (self.rep[2] % 2 == 0):
                    pass
                else:
                    self.logger.error(
                        "In making the ice structure having the double network (e.g. ices 6 and 7), all the repetition numbers (--rep) must be even.")
                    sys.exit(1)
            self.spacegraph = dg.SpaceIceGraph(self.graph,
                                               coord=self.reppositions,
                                               ignores=self.graph.ignores)
            draw = dg.YaplotDraw(
                self.reppositions, self.repcell.mat, data=self.spacegraph)
            self.yapresult = dg.depolarize(
                self.spacegraph, self.repcell.mat, draw=draw)
        self.logger.info("Stage4: end.")

    def stage5(self):
        """
        Prepare orientations for rigid molecules.

        Provided variables:
        reppositions: molecular positions with random noise.
        rotmatrices:  rotation matrices for water molecules
        """
        self.logger.info("Stage5: Orientation.")
        # Add small noises to the molecular positions
        # Will be removed in v0.24.
        # if not self.asis:
        #     self.reppositions += self.repcell.abs2rel(np.random.random(self.reppositions.shape)* 0.01 - 0.005)
        # determine the orientations of the water molecules based on edge
        # directions.
        self.rotmatrices = orientations(
            self.reppositions, self.spacegraph, self.repcell)

        # Activate it.
        # logger.info("The network is not specified.  Water molecules will be orinented randomly.")
        # rotmatrices = [rigid.rand_rotation_matrix() for pos in positions]
        self.logger.info("Stage5: end.")

    def stage6(self, water_type):
        """
        Arrange water atoms and replacements

        Provided variables:
        atoms: atomic positions of water molecules. (absolute)
        """
        self.logger.info("Stage6: Atomic positions of water.")
        # assert audit_name(water_type), "Dubious water name: {0}".format(water_type)
        # water = importlib.import_module("genice.molecules."+water_type)
        water = safe_import("molecule", water_type)
        self.atoms = arrange_atoms(self.reppositions,
                                   self.repcell,
                                   self.rotmatrices,
                                   water.sites,
                                   water.labels,
                                   water.name,
                                   ignores=set(self.dopants))
        self.logger.info("Stage6: end.")

    def stage7(self, guests):
        """
        Arrange guest atoms

        Provided variables:
        atoms: atomic positions of all molecules.
        """
        self.logger.info("Stage7: Atomic positions of the guest.")
        if self.cagepos is not None:
            # the cages around the dopants.
            dopants_neighbors = self.dopants_info(
                self.dopants, self.reppositions, self.repcagepos, self.repcell)
            # put the (one-off) groups
            if len(self.spot_groups) > 0:
                # process the -H option
                for cage, group_to in self.spot_groups.items():
                    group, root = group_to.split(":")
                    self.add_group(cage, group, int(root))
            molecules = defaultdict(list)
            if len(self.spot_guests) > 0:
                # process the -G option
                for cage, molec in self.spot_guests.items():
                    molecules[molec].append(cage)
                    self.filled_cages.add(cage)
            if guests is not None:
                # process the -g option
                for arg in guests:
                    cagetype, spec = arg[0].split("=")
                    assert cagetype in self.cagetypes, "Nonexistent cage type: {0}".format(
                        cagetype)
                    resident = dict()
                    rooms = list(self.cagetypes[cagetype] - self.filled_cages)
                    for room in rooms:
                        resident[room] = None
                    # spec contains a formula consisting of "+" and "*"
                    contents = spec.split("+")
                    vacant = len(rooms)
                    for content in contents:
                        if "*" in content:
                            molec, frac = content.split("*")
                            frac = float(frac)
                        else:
                            molec = content
                            frac = 1.0
                        nmolec = int(frac * len(rooms) + 0.5)
                        vacant -= nmolec
                        assert vacant >= 0, "Too many guests."
                        remain = nmolec
                        movedin = []
                        while remain > 0:
                            r = random.randint(0, len(rooms) - 1)
                            room = rooms[r]
                            if resident[room] is None:
                                resident[room] = molec
                                molecules[molec].append(room)
                                movedin.append(room)
                                remain -= 1
                        #self.logger.info(
                        #    "    {0} * {1} @ {2}".format(molec, nmolec, movedin))
            # Now ge got the address book of the molecules.
            if len(molecules):
                self.logger.info("  Summary of guest placements:")
                self.guests_info(self.cagetypes, molecules)
            if len(self.spot_groups) > 0:
                self.logger.info("  Summary of groups:")
                self.groups_info(self.groups)
            # semi-guests
            for root, cages in self.groups.items():
                assert root in self.dopants
                name = self.dopants[root]
                molname = "G{0}".format(root)
                pos = self.reppositions[root]
                rot = self.rotmatrices[root]
                self.atoms.append([0, molname, name, self.repcell.rel2abs(pos), 0])
                del self.dopants[root]  # processed.
                self.logger.debug((root,cages,name,molname,pos,rot))
                for cage, group in cages.items():
                    assert group in self.groups_placer
                    assert cage in dopants_neighbors[root]
                    cpos = self.repcagepos[cage]
                    self.atoms += self.groups_placer[group](cpos,
                                                            pos,
                                                            self.repcell,
                                                            molname)
            # molecular guests
            for molec, cages in molecules.items():
                gmol = safe_import("molecule", molec)
                cpos = [self.repcagepos[i] for i in cages]
                cmat = [np.identity(3) for i in cages]
                self.atoms += arrange_atoms(cpos, self.repcell,
                                            cmat, gmol.sites, gmol.labels, gmol.name)
        # Assume the dopant is monatomic and replaces one water molecule
        atomset = defaultdict(set)
        for label, name in self.dopants.items():
            atomset[name].add(label)
        for name, labels in atomset.items():
            pos = [self.reppositions[i] for i in sorted(labels)]
            rot = [self.rotmatrices[i] for i in sorted(labels)]
            self.atoms += arrange_atoms(pos,
                                        self.repcell,
                                        rot,
                                        [[0., 0., 0.], ],
                                        [name],
                                        name)
        self.logger.info("Stage7: end.")



        
        
    def prepare_random_graph(self, fixed):
        if self.pairs is None:
            self.logger.info("  Pairs are not given explicitly.")
            self.logger.info(
                "  Start estimating the bonds according to the pair distances.")
            # make bonded pairs according to the pair distance.
            # make before replicating them.
            grid = pl.determine_grid(self.cell.mat, self.bondlen)
            assert np.product(grid) > 0, "Too thin unit cell. Consider use of --rep option if the cell was made by cif2ice."
            self.pairs = [v for v in pl.pairs_fine(
                self.waters, self.bondlen, self.cell.mat, grid, distance=False)]
            # Check using a simpler algorithm.
            if self.logger.level <= logging.DEBUG:
                pairs2 = [v for v in pl.pairs_crude(
                    self.waters, self.bondlen, self.cell.mat, distance=False)]
                self.logger.debug("pairs: {0}".format(len(self.pairs)))
                self.logger.debug("pairs2: {0}".format(len(pairs2)))
                for pair in self.pairs:
                    i, j = pair
                    assert (i, j) in pairs2 or (j, i) in pairs2
                for pair in pairs2:
                    i, j = pair
                    assert (i, j) in self.pairs or (j, i) in self.pairs

        graph = dg.IceGraph()
        for i, j in fixed:
            graph.add_edge(i, j, fixed=True)
        # Fixed pairs are default.
        for pair in self.pairs:
            i, j = pair
            if graph.has_edge(i, j) or graph.has_edge(j, i):
                pass
            else:
                if random.randint(0, 1) == 0:
                    graph.add_edge(i, j, fixed=False)
                else:
                    graph.add_edge(j, i, fixed=False)
        return graph

    def test_undirected_graph(self, graph):
        # Test
        undir = graph.to_undirected()
        for node in range(undir.number_of_nodes()):
            if node not in undir:
                self.logger.debug("z=0 at {0}".format(node))
            else:
                z = len(list(undir.neighbors(node)))
                if z != 4:
                    self.logger.debug("z={0} at {1}".format(z, node))
        if graph.number_of_edges() != len(self.reppositions) * 2:
            self.logger.info("Inconsistent number of HBs {0} for number of molecules {1}.".format(
                graph.number_of_edges(), len(self.reppositions)))
            return False
        return True

    def dopants_info(self, dopants=None, waters=None, cagepos=None, cell=None):
        if dopants is None:
            dopants = self.dopants
        if waters is None:
            waters = self.waters
        if cagepos is None:
            cagepos = self.cagepos
        if cell is None:
            cell = self.cell
        dopants_neighbors = neighbor_cages_of_dopants(
            dopants, waters, cagepos, cell)
        for dopant, cages in dopants_neighbors.items():
            self.logger.info(
                "    Cages adjacent to dopant {0}: {1}".format(dopant, cages))
        return dopants_neighbors

    def groups_info(self, groups):
        for root, cages in groups.items():
            for cage, group in cages.items():
                self.logger.info(
                    "    Group {0} of dopant {1} in cage {2}".format(group, root, cage))

    def guests_info(self, cagetypes, molecules):
        for cagetype, cageid in cagetypes.items():
            self.logger.info("    Guests in cage type {0}:".format(cagetype))
            for molec, cages in molecules.items():
                cages = set(cages)
                cages &= cageid
                if len(cages):
                    self.logger.info("      {0} * {1} @ {2}".format(molec, len(cages), cages))
        
    def add_group(self, cage, group, root):
        self.groups[root][cage] = group
        self.filled_cages.add(cage)

    def __del__(self):
        self.logger.info("Completed.")


    def stage1_analice(self):
        """
        Do nothing.

        Provided variables:
        repposition: replicated molecular positions (CoM, relative)
        repcell:     replicated simulation cell shape matrix
        """
        self.logger.info("Stage1: (...)")
        self.reppositions = self.waters
        # This must be done before the replication of the cell.
        self.logger.info("  Number of water molecules: {0}".format(
            len(self.reppositions)))
        # self.graph = self.prepare_random_graph(self.fixed)
        self.graph = self.prepare_random_graph(self.pairs)
        # scale the cell
        self.repcell = Cell(self.cell)
        # self.repcell.scale2(self.rep)

        self.logger.info("Stage1: end.")



    def stage4_analice(self):
        """
        Depolarize.

        Provided variables:
        spacegraph: depolarized network with node positions.
        yapresult:  Animation of the depolarization process in YaPlot format.
        """
        self.logger.info("Stage4: (...)")
        self.yapresult = ""
        self.spacegraph = dg.SpaceIceGraph(self.graph,
                                           coord=self.reppositions,
                                           ignores=self.graph.ignores)
        self.logger.info("Stage4: end.")
Ejemplo n.º 8
0
    def __init__(self,
                 lattice_type=None,
                 density=0,
                 rep=(1, 1, 1),
                 depolarize=True,
                 asis=False,
                 cations=dict(),
                 anions=dict(),
                 spot_guests=dict(),
                 spot_groups=dict(),
                 ):
        self.logger      = logging.getLogger()
        self.lattice_type = lattice_type
        self.rep         = rep
        self.depolarize  = depolarize
        self.asis        = asis
        self.cations     = cations
        self.anions      = anions
        self.spot_guests = spot_guests
        self.spot_groups = spot_groups
        if lattice_type is None:
            return
        lat = safe_import("lattice", lattice_type)
        # Show the document of the module
        try:
            self.doc = lat.__doc__.splitlines()
        except:
            self.doc = []
        self.doc.append("")
        self.doc.append("Command line: {0}".format(" ".join(sys.argv)))
        for line in self.doc:
            self.logger.info("!!! {0}".format(line))
        # ================================================================
        # rotmatrices (analice)
        #
        try:
            self.rotmatrices = lat.rotmat
        except:
            self.logger.info("No rotmatrices in lattice")
            pass
        # ================================================================
        # waters: positions of water molecules
        #
        self.waters = load_numbers(lat.waters)
        self.logger.debug("Waters: {0}".format(len(self.waters)))
        self.waters = self.waters.reshape((self.waters.size // 3, 3))

        # ================================================================
        # cell: cell dimension
        # celltype: symmetry of the cell
        #   see parse_cell for syntax.
        #
        self.cell = Cell(lat.cell, lat.celltype)
        #self.cell = parse_cell(lat.cell, lat.celltype)

        # ================================================================
        # coord: "relative" or "absolute"
        #   Inside genice, molecular positions are always treated as "relative"
        #
        if lat.coord == "absolute":
            self.waters = self.cell.abs2rel(self.waters)
        self.waters = np.array([w - np.floor(w) for w in self.waters])

        # ================================================================
        # pairs: specify the pairs of molecules that are connected.
        #   Bond orientation will be shuffled later
        #   unless it is "fixed".
        #
        self.pairs = None
        try:
            if type(lat.pairs) is str:
                lines = lat.pairs.split("\n")
                self.pairs = []
                for line in lines:
                    columns = line.split()
                    if len(columns) == 2:
                        i, j = [int(x) for x in columns]
                        self.pairs.append((i, j))
            elif type(lat.pairs) is list:
                self.pairs = lat.pairs
                # for pair in lat.pairs:
                #    self.pairs.append(pair)
        except AttributeError:
            self.logger.info("Graph is not defined.")

        # ================================================================
        # bondlen: specify the bond length threshold.
        #   This is used when "pairs" are not specified.
        #   It is applied to the original positions of molecules (before density setting).
        #
        self.bondlen = None
        try:
            self.bondlen = lat.bondlen
            self.logger.info("Bond length (specified): {0}".format(self.bondlen))
        except AttributeError:
            self.bondlen = 1.1 * shortest_distance(self.waters, self.cell)
            self.logger.info("Bond length (assumed): {0}".format(self.bondlen))
        # Set density
        mass = 18  # water
        NB = 6.022e23
        nmol = self.waters.shape[0]  # nmol in a unit cell
        volume = self.cell.volume()  # volume of a unit cell in nm**3
        density0 = mass * nmol / (NB * volume * 1e-21)
        if density <= 0:
            try:
                self.density = lat.density
            except AttributeError:
                self.logger.info(
                    "Density is not specified. Assume the density from lattice.")
                dmin = shortest_distance(self.waters, self.cell)
                self.logger.info(
                    "Closest pair distance: {0} (should be around 0.276 nm)".format(dmin))
                self.density = density0 / (0.276 / dmin)**3
                # self.density = density0
        else:
            self.density = density
        self.logger.info("Target Density: {0}".format(self.density))
        self.logger.info("Original Density: {0}".format(density0))

        # scale the cell according to the (specified) density
        ratio = (density0 / self.density)**(1.0 / 3.0)
        self.cell.scale(ratio)
        if self.bondlen is not None:
            self.bondlen *= ratio
        self.logger.info("Bond length (scaled, nm): {0}".format(self.bondlen))

        # ================================================================
        # double_network: True or False
        #   This is a special option for ices VI and VII that have
        #   interpenetrating double network.
        #   GenIce's fast depolarization algorithm fails in some case.
        #
        try:
            self.double_network = lat.double_network
        except AttributeError:
            self.double_network = False

        # ================================================================
        # cages: positions of the centers of cages
        #   In fractional coordinate.
        #
        self.cagepos = None
        self.cagetype = None
        if "cages" in lat.__dict__:
            self.cagepos, self.cagetype = parse_cages(lat.cages)

        # ================================================================
        # fixed: specify the bonds whose directions are fixed.
        #   you can specify them in pairs at a time.
        #   You can also leave it undefined.
        #
        try:
            if type(lat.fixed) is str:
                lines = lat.fixed.split("\n")
                self.fixed = []
                for line in lines:
                    columns = line.split()
                    if len(columns) == 2:
                        i, j = [int(x) for x in columns]
                        self.fixed.append((i, j))  # Is a tuple
            elif type(lat.fixed) is list:
                self.fixed = []
                for pair in lat.fixed:
                    self.fixed.append(tuple(pair[:2]))  # Must be a tuple
        except AttributeError:
            self.fixed = []
        if "dopeIonsToUnitCell" in lat.__dict__:
            self.dopeIonsToUnitCell = lat.dopeIonsToUnitCell
        else:
            self.dopeIonsToUnitCell = None
        self.dopants = set()
        # if asis, make pairs to be fixed.
        if self.asis and len(self.fixed) == 0:
            self.fixed = self.pairs

        # filled cages
        self.filled_cages = set()
        # groups info
        self.groups = defaultdict(dict)

        # groups for the semi-guest
        # experimental; there are many variation of semi-guest inclusion.
        self.groups_placer = {"Bu-": butyl,
                              "Butyl-": butyl,
                              "Pentyl-": pentyl,
                              "Propyl-": propyl,
                              "2,2-dimethylpropyl-": _2_2_dimethylpropyl,
                              "2,3-dimethylbutyl-": _2_3_dimethylbutyl,
                              "3,3-dimethylbutyl-": _3_3_dimethylbutyl,
                              "3-methylbutyl-": _3_methylbutyl,
                              "Ethyl-": ethyl}
Ejemplo n.º 9
0
class Lattice():
    def __init__(self,
                 lattice_type=None,
                 density=0,
                 rep=(1, 1, 1),
                 depolarize=True,
                 asis=False,
                 cations=dict(),
                 anions=dict(),
                 spot_guests=dict(),
                 spot_groups=dict(),
                 ):
        self.logger      = logging.getLogger()
        self.lattice_type = lattice_type
        self.rep         = rep
        self.depolarize  = depolarize
        self.asis        = asis
        self.cations     = cations
        self.anions      = anions
        self.spot_guests = spot_guests
        self.spot_groups = spot_groups
        if lattice_type is None:
            return
        lat = safe_import("lattice", lattice_type)
        # Show the document of the module
        try:
            self.doc = lat.__doc__.splitlines()
        except:
            self.doc = []
        self.doc.append("")
        self.doc.append("Command line: {0}".format(" ".join(sys.argv)))
        for line in self.doc:
            self.logger.info("!!! {0}".format(line))
        # ================================================================
        # rotmatrices (analice)
        #
        try:
            self.rotmatrices = lat.rotmat
        except:
            self.logger.info("No rotmatrices in lattice")
            pass
        # ================================================================
        # waters: positions of water molecules
        #
        self.waters = load_numbers(lat.waters)
        self.logger.debug("Waters: {0}".format(len(self.waters)))
        self.waters = self.waters.reshape((self.waters.size // 3, 3))

        # ================================================================
        # cell: cell dimension
        # celltype: symmetry of the cell
        #   see parse_cell for syntax.
        #
        self.cell = Cell(lat.cell, lat.celltype)
        #self.cell = parse_cell(lat.cell, lat.celltype)

        # ================================================================
        # coord: "relative" or "absolute"
        #   Inside genice, molecular positions are always treated as "relative"
        #
        if lat.coord == "absolute":
            self.waters = self.cell.abs2rel(self.waters)
        self.waters = np.array([w - np.floor(w) for w in self.waters])

        # ================================================================
        # pairs: specify the pairs of molecules that are connected.
        #   Bond orientation will be shuffled later
        #   unless it is "fixed".
        #
        self.pairs = None
        try:
            if type(lat.pairs) is str:
                lines = lat.pairs.split("\n")
                self.pairs = []
                for line in lines:
                    columns = line.split()
                    if len(columns) == 2:
                        i, j = [int(x) for x in columns]
                        self.pairs.append((i, j))
            elif type(lat.pairs) is list:
                self.pairs = lat.pairs
                # for pair in lat.pairs:
                #    self.pairs.append(pair)
        except AttributeError:
            self.logger.info("Graph is not defined.")

        # ================================================================
        # bondlen: specify the bond length threshold.
        #   This is used when "pairs" are not specified.
        #   It is applied to the original positions of molecules (before density setting).
        #
        self.bondlen = None
        try:
            self.bondlen = lat.bondlen
            self.logger.info("Bond length (specified): {0}".format(self.bondlen))
        except AttributeError:
            self.bondlen = 1.1 * shortest_distance(self.waters, self.cell)
            self.logger.info("Bond length (assumed): {0}".format(self.bondlen))
        # Set density
        mass = 18  # water
        NB = 6.022e23
        nmol = self.waters.shape[0]  # nmol in a unit cell
        volume = self.cell.volume()  # volume of a unit cell in nm**3
        density0 = mass * nmol / (NB * volume * 1e-21)
        if density <= 0:
            try:
                self.density = lat.density
            except AttributeError:
                self.logger.info(
                    "Density is not specified. Assume the density from lattice.")
                dmin = shortest_distance(self.waters, self.cell)
                self.logger.info(
                    "Closest pair distance: {0} (should be around 0.276 nm)".format(dmin))
                self.density = density0 / (0.276 / dmin)**3
                # self.density = density0
        else:
            self.density = density
        self.logger.info("Target Density: {0}".format(self.density))
        self.logger.info("Original Density: {0}".format(density0))

        # scale the cell according to the (specified) density
        ratio = (density0 / self.density)**(1.0 / 3.0)
        self.cell.scale(ratio)
        if self.bondlen is not None:
            self.bondlen *= ratio
        self.logger.info("Bond length (scaled, nm): {0}".format(self.bondlen))

        # ================================================================
        # double_network: True or False
        #   This is a special option for ices VI and VII that have
        #   interpenetrating double network.
        #   GenIce's fast depolarization algorithm fails in some case.
        #
        try:
            self.double_network = lat.double_network
        except AttributeError:
            self.double_network = False

        # ================================================================
        # cages: positions of the centers of cages
        #   In fractional coordinate.
        #
        self.cagepos = None
        self.cagetype = None
        if "cages" in lat.__dict__:
            self.cagepos, self.cagetype = parse_cages(lat.cages)

        # ================================================================
        # fixed: specify the bonds whose directions are fixed.
        #   you can specify them in pairs at a time.
        #   You can also leave it undefined.
        #
        try:
            if type(lat.fixed) is str:
                lines = lat.fixed.split("\n")
                self.fixed = []
                for line in lines:
                    columns = line.split()
                    if len(columns) == 2:
                        i, j = [int(x) for x in columns]
                        self.fixed.append((i, j))  # Is a tuple
            elif type(lat.fixed) is list:
                self.fixed = []
                for pair in lat.fixed:
                    self.fixed.append(tuple(pair[:2]))  # Must be a tuple
        except AttributeError:
            self.fixed = []
        if "dopeIonsToUnitCell" in lat.__dict__:
            self.dopeIonsToUnitCell = lat.dopeIonsToUnitCell
        else:
            self.dopeIonsToUnitCell = None
        self.dopants = set()
        # if asis, make pairs to be fixed.
        if self.asis and len(self.fixed) == 0:
            self.fixed = self.pairs

        # filled cages
        self.filled_cages = set()
        # groups info
        self.groups = defaultdict(dict)

        # groups for the semi-guest
        # experimental; there are many variation of semi-guest inclusion.
        self.groups_placer = {"Bu-": butyl,
                              "Butyl-": butyl,
                              "Pentyl-": pentyl,
                              "Propyl-": propyl,
                              "2,2-dimethylpropyl-": _2_2_dimethylpropyl,
                              "2,3-dimethylbutyl-": _2_3_dimethylbutyl,
                              "3,3-dimethylbutyl-": _3_3_dimethylbutyl,
                              "3-methylbutyl-": _3_methylbutyl,
                              "Ethyl-": ethyl}


    def generate_ice(self, water_type, guests, formatter):
        if 0 in formatter.hooks:
            formatter.hooks[0](self)
        if max(0,*formatter.hooks.keys()) < 1:
            return
        self.stage1()
        if 1 in formatter.hooks:
            formatter.hooks[1](self)
        if max(0,*formatter.hooks.keys()) < 2:
            return
        res = self.stage2()
        if 2 in formatter.hooks:
            formatter.hooks[2](self)
        if max(0,*formatter.hooks.keys()) < 3:
            return
        self.stage3()
        if 3 in formatter.hooks:
            formatter.hooks[3](self)
        if max(0,*formatter.hooks.keys()) < 4:
            return
        self.stage4()
        if 4 in formatter.hooks:
            formatter.hooks[4](self)
        if max(0,*formatter.hooks.keys()) < 5:
            return
        self.stage5()
        if 5 in formatter.hooks:
            formatter.hooks[5](self)
        if max(0,*formatter.hooks.keys()) < 6:
            return
        self.stage6(water_type)
        if 6 in formatter.hooks:
            formatter.hooks[6](self)
        if max(0,*formatter.hooks.keys()) < 7:
            return
        self.stage7(guests)
        if 7 in formatter.hooks:
            formatter.hooks[7](self)


    def analize_ice(self, water_type, formatter):
        """
        Protocol for analice
        """
        if 0 in formatter.hooks:
            formatter.hooks[0](self)
        if max(0,*formatter.hooks.keys()) < 1:
            return
        self.stage1_analice()
        if 1 in formatter.hooks:
            formatter.hooks[1](self)
        if max(0,*formatter.hooks.keys()) < 2:
            return
        # res = self.stage2_analice()
        if 2 in formatter.hooks:
            formatter.hooks[2](self)
        if max(0,*formatter.hooks.keys()) < 3:
            return
        # self.stage3_analice()
        if 3 in formatter.hooks:
            formatter.hooks[3](self)
        if max(0,*formatter.hooks.keys()) < 4:
            return
        self.stage4_analice()
        if 4 in formatter.hooks:
            formatter.hooks[4](self)
        if max(0,*formatter.hooks.keys()) < 5:
            return
        # self.stage5_analice()
        if 5 in formatter.hooks:
            formatter.hooks[5](self)
        if max(0,*formatter.hooks.keys()) < 6:
            return
        self.stage6(water_type)
        if 6 in formatter.hooks:
            formatter.hooks[6](self)
        if max(0,*formatter.hooks.keys()) < 7:
            return
        # self.stage7_analice(guests)
        if 7 in formatter.hooks:
            formatter.hooks[7](self)
            
        
    def stage1(self):
        """
        Replicate water molecules to make a repeated cell

        Provided variables:
        repposition: replicated molecular positions (CoM, relative)
        repcell:     replicated simulation cell shape matrix
        repcagetype: replicated cage types array
        repcagepos:  replicated cage positions (CoM, relative)
        cagetypes:   set of cage types
        """
        self.logger.info("Stage1: Replication.")
        self.reppositions = replicate_positions(self.waters, self.rep)
        # This must be done before the replication of the cell.
        self.logger.info("  Number of water molecules: {0}".format(
            len(self.reppositions)))
        self.graph = self.prepare_random_graph(self.fixed)
        # scale the cell
        self.repcell = Cell(self.cell)
        self.repcell.scale2(self.rep)

        if self.cagepos is not None:
            self.logger.info("  Hints:")
            self.repcagepos = replicate_positions(self.cagepos, self.rep)
            nrepcages = self.repcagepos.shape[0]
            self.repcagetype = [self.cagetype[i % len(self.cagetype)]
                               for i in range(nrepcages)]
            self.cagetypes = defaultdict(set)
            for i, typ in enumerate(self.repcagetype):
                self.cagetypes[typ].add(i)
            # INFO for cage types
            self.logger.info("    Cage types: {0}".format(list(self.cagetypes)))
            for typ, cages in self.cagetypes.items():
                self.logger.info("    Cage type {0}: {1}".format(typ, cages))
            # Up here move to stage 1.
        
        self.logger.info("Stage1: end.")

    def stage2(self):
        """
        Make a random graph and replicate.

        Provided variables:
        dopants:
        groups:  replicated positions of the chemical groups (CoM, relative)
        filled_cages:
        graph:   replicated network topology (bond orientation may be random)
        """
        self.logger.info("Stage2: Graph preparation.")
        # Some edges are directed when ions are doped.
        if self.dopeIonsToUnitCell is not None:
            self.dopeIonsToUnitCell(self)  # may be defined in the plugin
        # Replicate the dopants in the unit cell
        self.dopants = replicate_labeldict(
            self.dopants, len(self.waters), self.rep)
        self.groups = replicate_groups(
            self.groups, self.waters, self.cagepos, self.rep)
        # self.groups_info(self.groups)
        for root, cages in self.groups.items():
            self.filled_cages |= set(cages)
        # self.logger.info(("filled",self.filled_cages))
        # Replicate the graph
        self.graph = replicate_graph(self.graph, self.waters, self.rep)
        # Dope ions by options.
        if len(self.anions) > 0:
            self.logger.info("  Anionize: {0}.".format(self.anions))
            for site, name in self.anions.items():
                self.graph.anionize(site)
                self.dopants[site] = name
        if len(self.cations) > 0:
            self.logger.info("  Cationize: {0}.".format(self.cations))
            for site, name in self.cations.items():
                self.graph.cationize(site)
                self.dopants[site] = name
        # Count bonds
        nrandom = 0
        nfixed = 0
        for i, j, data in self.graph.edges(data=True):
            if self.graph[i][j]['fixed']:  # fixed pair
                nfixed += 1
            else:
                nrandom += 1
        self.logger.info(
            "  Number of pre-oriented hydrogen bonds: {0}".format(nfixed))
        self.logger.info(
            "  Number of unoriented hydrogen bonds: {0}".format(nrandom))
        self.logger.info("  Number of hydrogen bonds: {0} (regular num: {1})".format(
            nfixed + nrandom, len(self.reppositions) * 2))
        # test2==True means it is a z=4 graph.
        self.test2 = self.test_undirected_graph(self.graph)
        if not self.test2:
            self.logger.info("Test2 failed.")

        self.logger.info("Stage2: end.")
        return self.test2

    def stage3(self):
        """
        Make a true ice graph.

        Provided variables:
        graph: network obeying B-F rule.
        """
        self.logger.info("Stage3: Bernal-Fowler rule.")
        if self.asis:
            self.logger.info("  Skip applying the ice rule by request.")
        else:
            self.graph.purge_ice_defects()
        self.logger.info("Stage3: end.")

    def stage4(self):
        """
        Depolarize.

        Provided variables:
        spacegraph: depolarized network with node positions.
        yapresult:  Animation of the depolarization process in YaPlot format.
        """
        self.logger.info("Stage4: Depolarization.")
        if not self.depolarize or self.asis:
            self.logger.info("  Skip depolarization by request.")
            self.yapresult = ""
            self.spacegraph = dg.SpaceIceGraph(self.graph,
                                               coord=self.reppositions,
                                               ignores=self.graph.ignores)
        else:
            if self.double_network:
                if (self.rep[0] % 2 == 0) and (self.rep[1] % 2 == 0) and (self.rep[2] % 2 == 0):
                    pass
                else:
                    self.logger.error(
                        "In making the ice structure having the double network (e.g. ices 6 and 7), all the repetition numbers (--rep) must be even.")
                    sys.exit(1)
            self.spacegraph = dg.SpaceIceGraph(self.graph,
                                               coord=self.reppositions,
                                               ignores=self.graph.ignores)
            draw = dg.YaplotDraw(
                self.reppositions, self.repcell.mat, data=self.spacegraph)
            self.yapresult = dg.depolarize(
                self.spacegraph, self.repcell.mat, draw=draw)
        self.logger.info("Stage4: end.")

    def stage5(self):
        """
        Prepare orientations for rigid molecules.

        Provided variables:
        reppositions: molecular positions with random noise.
        rotmatrices:  rotation matrices for water molecules
        """
        self.logger.info("Stage5: Orientation.")
        # Add small noises to the molecular positions
        if not self.asis:
            self.reppositions += self.repcell.abs2rel(np.random.random(self.reppositions.shape)* 0.01 - 0.005)
        # determine the orientations of the water molecules based on edge
        # directions.
        self.rotmatrices = orientations(
            self.reppositions, self.spacegraph, self.repcell)

        # Activate it.
        # logger.info("The network is not specified.  Water molecules will be orinented randomly.")
        # rotmatrices = [rigid.rand_rotation_matrix() for pos in positions]
        self.logger.info("Stage5: end.")

    def stage6(self, water_type):
        """
        Arrange water atoms and replacements

        Provided variables:
        atoms: atomic positions of water molecules. (absolute)
        """
        self.logger.info("Stage6: Atomic positions of water.")
        # assert audit_name(water_type), "Dubious water name: {0}".format(water_type)
        # water = importlib.import_module("genice.molecules."+water_type)
        water = safe_import("molecule", water_type)
        self.atoms = arrange_atoms(self.reppositions,
                                   self.repcell,
                                   self.rotmatrices,
                                   water.sites,
                                   water.labels,
                                   water.name,
                                   ignores=set(self.dopants))
        self.logger.info("Stage6: end.")

    def stage7(self, guests):
        """
        Arrange guest atoms

        Provided variables:
        atoms: atomic positions of all molecules.
        """
        self.logger.info("Stage7: Atomic positions of the guest.")
        if self.cagepos is not None:
            # the cages around the dopants.
            dopants_neighbors = self.dopants_info(
                self.dopants, self.reppositions, self.repcagepos, self.repcell)
            # put the (one-off) groups
            if len(self.spot_groups) > 0:
                # process the -H option
                for cage, group_to in self.spot_groups.items():
                    group, root = group_to.split(":")
                    self.add_group(cage, group, int(root))
            molecules = defaultdict(list)
            if len(self.spot_guests) > 0:
                # process the -G option
                for cage, molec in self.spot_guests.items():
                    molecules[molec].append(cage)
                    self.filled_cages.add(cage)
            if guests is not None:
                # process the -g option
                for arg in guests:
                    cagetype, spec = arg[0].split("=")
                    assert cagetype in self.cagetypes, "Nonexistent cage type: {0}".format(
                        cagetype)
                    resident = dict()
                    rooms = list(self.cagetypes[cagetype] - self.filled_cages)
                    for room in rooms:
                        resident[room] = None
                    # spec contains a formula consisting of "+" and "*"
                    contents = spec.split("+")
                    vacant = len(rooms)
                    for content in contents:
                        if "*" in content:
                            molec, frac = content.split("*")
                            frac = float(frac)
                        else:
                            molec = content
                            frac = 1.0
                        nmolec = int(frac * len(rooms) + 0.5)
                        vacant -= nmolec
                        assert vacant >= 0, "Too many guests."
                        remain = nmolec
                        movedin = []
                        while remain > 0:
                            r = random.randint(0, len(rooms) - 1)
                            room = rooms[r]
                            if resident[room] is None:
                                resident[room] = molec
                                molecules[molec].append(room)
                                movedin.append(room)
                                remain -= 1
                        #self.logger.info(
                        #    "    {0} * {1} @ {2}".format(molec, nmolec, movedin))
            # Now ge got the address book of the molecules.
            if len(molecules):
                self.logger.info("  Summary of guest placements:")
                self.guests_info(self.cagetypes, molecules)
            if len(self.spot_groups) > 0:
                self.logger.info("  Summary of groups:")
                self.groups_info(self.groups)
            # semi-guests
            for root, cages in self.groups.items():
                assert root in self.dopants
                name = self.dopants[root]
                molname = "G{0}".format(root)
                pos = self.reppositions[root]
                rot = self.rotmatrices[root]
                self.atoms.append([0, molname, name, self.repcell.rel2abs(pos), 0])
                del self.dopants[root]  # processed.
                self.logger.debug((root,cages,name,molname,pos,rot))
                for cage, group in cages.items():
                    assert group in self.groups_placer
                    assert cage in dopants_neighbors[root]
                    cpos = self.repcagepos[cage]
                    self.atoms += self.groups_placer[group](cpos,
                                                            pos,
                                                            self.repcell,
                                                            molname)
            # molecular guests
            for molec, cages in molecules.items():
                gmol = safe_import("molecule", molec)
                cpos = [self.repcagepos[i] for i in cages]
                cmat = [np.identity(3) for i in cages]
                self.atoms += arrange_atoms(cpos, self.repcell,
                                            cmat, gmol.sites, gmol.labels, gmol.name)
        # Assume the dopant is monatomic and replaces one water molecule
        atomset = defaultdict(set)
        for label, name in self.dopants.items():
            atomset[name].add(label)
        for name, labels in atomset.items():
            pos = [self.reppositions[i] for i in sorted(labels)]
            rot = [self.rotmatrices[i] for i in sorted(labels)]
            self.atoms += arrange_atoms(pos,
                                        self.repcell,
                                        rot,
                                        [[0., 0., 0.], ],
                                        [name],
                                        name)
        self.logger.info("Stage7: end.")



        
        
    def prepare_random_graph(self, fixed):
        if self.pairs is None:
            self.logger.info("  Pairs are not given explicitly.")
            self.logger.info(
                "  Start estimating the bonds according to the pair distances.")
            # make bonded pairs according to the pair distance.
            # make before replicating them.
            grid = pl.determine_grid(self.cell.mat, self.bondlen)
            assert np.product(grid) > 0, "Too thin unit cell. Consider use of --rep option if the cell was made by cif2ice."
            self.pairs = [v for v in pl.pairs_fine(
                self.waters, self.bondlen, self.cell.mat, grid, distance=False)]
            # Check using a simpler algorithm.
            if self.logger.level <= logging.DEBUG:
                pairs2 = [v for v in pl.pairs_crude(
                    self.waters, self.bondlen, self.cell.mat, distance=False)]
                self.logger.debug("pairs: {0}".format(len(self.pairs)))
                self.logger.debug("pairs2: {0}".format(len(pairs2)))
                for pair in self.pairs:
                    i, j = pair
                    assert (i, j) in pairs2 or (j, i) in pairs2
                for pair in pairs2:
                    i, j = pair
                    assert (i, j) in self.pairs or (j, i) in self.pairs

        graph = dg.IceGraph()
        for i, j in fixed:
            graph.add_edge(i, j, fixed=True)
        # Fixed pairs are default.
        for pair in self.pairs:
            i, j = pair
            if graph.has_edge(i, j) or graph.has_edge(j, i):
                pass
            else:
                if random.randint(0, 1) == 0:
                    graph.add_edge(i, j, fixed=False)
                else:
                    graph.add_edge(j, i, fixed=False)
        return graph

    def test_undirected_graph(self, graph):
        # Test
        undir = graph.to_undirected()
        for node in range(undir.number_of_nodes()):
            if node not in undir:
                self.logger.debug("z=0 at {0}".format(node))
            else:
                z = len(list(undir.neighbors(node)))
                if z != 4:
                    self.logger.debug("z={0} at {1}".format(z, node))
        if graph.number_of_edges() != len(self.reppositions) * 2:
            self.logger.info("Inconsistent number of HBs {0} for number of molecules {1}.".format(
                graph.number_of_edges(), len(self.reppositions)))
            return False
        return True

    def dopants_info(self, dopants=None, waters=None, cagepos=None, cell=None):
        if dopants is None:
            dopants = self.dopants
        if waters is None:
            waters = self.waters
        if cagepos is None:
            cagepos = self.cagepos
        if cell is None:
            cell = self.cell
        dopants_neighbors = neighbor_cages_of_dopants(
            dopants, waters, cagepos, cell)
        for dopant, cages in dopants_neighbors.items():
            self.logger.info(
                "    Cages adjacent to dopant {0}: {1}".format(dopant, cages))
        return dopants_neighbors

    def groups_info(self, groups):
        for root, cages in groups.items():
            for cage, group in cages.items():
                self.logger.info(
                    "    Group {0} of dopant {1} in cage {2}".format(group, root, cage))

    def guests_info(self, cagetypes, molecules):
        for cagetype, cageid in cagetypes.items():
            self.logger.info("    Guests in cage type {0}:".format(cagetype))
            for molec, cages in molecules.items():
                cages = set(cages)
                cages &= cageid
                if len(cages):
                    self.logger.info("      {0} * {1} @ {2}".format(molec, len(cages), cages))
        
    def add_group(self, cage, group, root):
        self.groups[root][cage] = group
        self.filled_cages.add(cage)

    def __del__(self):
        self.logger.info("Completed.")


    def stage1_analice(self):
        """
        Do nothing.

        Provided variables:
        repposition: replicated molecular positions (CoM, relative)
        repcell:     replicated simulation cell shape matrix
        """
        self.logger.info("Stage1: (...)")
        self.reppositions = self.waters
        # This must be done before the replication of the cell.
        self.logger.info("  Number of water molecules: {0}".format(
            len(self.reppositions)))
        # self.graph = self.prepare_random_graph(self.fixed)
        self.graph = self.prepare_random_graph(self.pairs)
        # scale the cell
        self.repcell = Cell(self.cell)
        # self.repcell.scale2(self.rep)

        self.logger.info("Stage1: end.")



    def stage4_analice(self):
        """
        Depolarize.

        Provided variables:
        spacegraph: depolarized network with node positions.
        yapresult:  Animation of the depolarization process in YaPlot format.
        """
        self.logger.info("Stage4: (...)")
        self.yapresult = ""
        self.spacegraph = dg.SpaceIceGraph(self.graph,
                                           coord=self.reppositions,
                                           ignores=self.graph.ignores)
        self.logger.info("Stage4: end.")