Package genice_core

Logo

GenIce-core

Core algorithms of GenIce2

version 0.8

Requirements

  • numpy
  • networkx

Installation

GenIce-core is registered to PyPI (Python Package Index). Install with pip3.

pip3 install genice-core

Uninstallation

pip3 uninstall genice-core

API

API manual is here.

Examples

Make an ice graph from a given undirected graph.

import networkx as nx
import matplotlib
import genice_core

# np.random.seed(12345)

g = nx.dodecahedral_graph()  # dodecahedral 20mer
pos = nx.spring_layout(g)

# set orientations of the hydrogen bonds.
dg = genice_core.ice_graph(g)

nx.draw_networkx(dg, pos)

Algorithms and how to cite them.

The algorithms to make a depolarized hydrogen-disordered ice are explained in these papers:

M. Matsumoto, T. Yagasaki, and H. Tanaka,"GenIce: Hydrogen-Disordered Ice Generator", J. Comput. Chem. 39, 61-64 (2017). DOI: 10.1002/jcc.25077

@article{Matsumoto:2017bk,
    author = {Matsumoto, Masakazu and Yagasaki, Takuma and Tanaka, Hideki},
    title = {GenIce: Hydrogen-Disordered Ice Generator},
    journal = {Journal of Computational Chemistry},
    volume = {39},
    pages = {61-64},
    year = {2017}
}

M. Matsumoto, T. Yagasaki, and H. Tanaka, “Novel Algorithm to Generate Hydrogen-Disordered Ice Structures.”, J. Chem. Info. Modeling 61 (6): 2542–46 (2021). DOI:10.1021/acs.jcim.1c00440

@article{Matsumoto:2021,
    author = {Matsumoto, Masakazu and Yagasaki, Takuma and Tanaka, Hideki},
    title = {Novel Algorithm to Generate Hydrogen-Disordered Ice Structures},
    journal = {Journal of Chemical Information and Modeling},
    volume = {61},
    pages = {2542-2546},
    year = {2021}
}

How to contribute

GenIce has been available as open source software on GitHub(https://github.com/vitroid/GenIce) since 2015. Feedback, suggestions for improvements and enhancements, bug fixes, etc. are sincerely welcome. Developers and test users are also welcome. If you have any ice that is publicly available but not included in GenIce, please let us know.

Expand source code
"""
.. include:: ../README.md
"""

"""
Optimizes the orientations of directed paths to reduce the net dipole moment.
"""
import numpy as np
import networkx as nx
from genice_core.topology import noodlize, split_into_simple_paths, balance
from genice_core.dipole import optimize, vector_sum, _dipole_moment_pbc
from typing import Union
from logging import getLogger, DEBUG


def ice_graph(
    g: nx.Graph,
    vertexPositions: Union[np.ndarray, None] = None,
    isPeriodicBoundary: bool = False,
    dipoleOptimizationCycles: int = 0,
    fixedEdges: nx.DiGraph = nx.DiGraph(),
) -> nx.DiGraph:
    """Make a digraph that obeys the ice rules.

    A new algorithm based on the suggestion by Prof. Sakuma, Yamagata University.

    Args:
        g (nx.Graph): A ice-like undirected graph.
        vertexPositions (Union[nx.ndarray, None], optional): Positions of the vertices. Defaults to None.
        isPeriodicBoundary (bool, optional): If True, the positions are considered to be in the fractional coordinate system. Defaults to False.
        dipoleOptimizationCycles (int, optional): Number of iterations to reduce the net dipole moment. Defaults to 0 (no iteration).
        fixed (nx.DiGraph, optional): A digraph made of edges whose directions are fixed. All edges in fixed must also be included in g. Defaults to an empty graph.

    Returns:
        nx.DiGraph: An ice graph. (CHANGED. BE CAREFUL この変更の影響をもっとも受けるのがGenIce)
    """
    logger = getLogger()

    # derived cycles in extending the fixed edges.
    derivedCycles = []

    if fixedEdges.size() > 0:
        # コメントが日本語の部分はまだデバッグ中と思え。
        if logger.isEnabledFor(DEBUG):
            for edge in fixedEdges.edges():
                logger.debug(f"EDGE {edge}")

        # balance fixed edges
        processedEdges = None
        while processedEdges is None:
            # It returns Nones when it fails to balance.
            # The processedEdges also include derivedCycles.
            processedEdges, derivedCycles = balance(fixedEdges, g)
    else:
        processedEdges = nx.DiGraph()

    # really fixed in balance()
    finallyFixedEdges = nx.DiGraph(processedEdges)
    for cycle in derivedCycles:
        for edge in zip(cycle, cycle[1:]):
            finallyFixedEdges.remove_edge(*edge)

    # Divide the remaining (unfixed) part of the graph into a noodle graph
    dividedGraph = noodlize(g, processedEdges)

    # Simplify paths ( paths with least crossings )
    paths = list(split_into_simple_paths(len(g), dividedGraph)) + derivedCycles

    # 欠陥がない氷なら、すべてcycleになっているはず。
    # for path in paths:
    #     assert path[0] == path[-1]

    # arrange the orientations here if you want to balance the polarization
    if vertexPositions is not None:
        # Set the targetPol in order to cancel the polarization in the fixed part.
        targetPol = -vector_sum(finallyFixedEdges, vertexPositions, isPeriodicBoundary)

        paths = optimize(
            paths,
            vertexPositions,
            isPeriodicBoundary=isPeriodicBoundary,
            dipoleOptimizationCycles=dipoleOptimizationCycles,
            targetPol=targetPol,
        )

    # 欠陥がない氷なら、すべてcycleになっているはず。
    # for path in paths:
    #     assert path[0] == path[-1]

    # Combine everything together
    dg = nx.DiGraph(finallyFixedEdges)

    # 30-->97-->31という辺はextendedFixedEdgesに含まれるべきではない
    # for edge in extendedFixedEdges.edges():
    #     logger.debug(f"EDGE eFE {edge}")
    # logger.debug(f"{list(dg.predecessors(97))} --> 97 --> {list(dg.successors(97))}")
    for path in paths:
        nx.add_path(dg, path)

    # Does the graph really obey the ice rules?
    # if logger.isEnabledFor(DEBUG):
    for node in dg:
        if fixedEdges.has_node(node):
            if fixedEdges.in_degree(node) > 2 or fixedEdges.out_degree(node) > 2:
                continue
        assert (
            dg.in_degree(node) <= 2
        ), f"{node} {list(dg.successors(node))} {list(dg.predecessors(node))}"
        assert dg.out_degree(node) <= 2

    # bug? まれにこのチェックでひっかかる場合があるようだ。

    # # この時点で、pathsを検査しておく。
    # if logger.isEnabledFor(DEBUG):
    #     gg = nx.Graph(extendedFixedEdges)
    #     for path in paths:
    #         nx.add_path(gg, path)
    #     logger.debug(f"Size g {g.number_of_nodes()} {g.number_of_edges()}")
    #     logger.debug(f"Size gg {gg.number_of_nodes()} {gg.number_of_edges()}")
    #     assert g.number_of_edges() == gg.number_of_edges()
    #     e1 = set([(min(i, j), max(i, j)) for i, j in g.edges()])
    #     e2 = set([(min(i, j), max(i, j)) for i, j in gg.edges()])
    #     logger.debug(
    #         f"{sorted(list(e1 - e2))} edges only in original undirected graph."
    #     )
    #     logger.debug(f"{sorted(list(e2 - e1))} edges only in derived directed graph.")

    return dg

Sub-modules

genice_core.dipole

Optimizes the orientations of directed paths to reduce the net dipole moment.

genice_core.topology

Arrange edges appropriately.

Functions

def ice_graph(g: networkx.classes.graph.Graph, vertexPositions: Optional[numpy.ndarray] = None, isPeriodicBoundary: bool = False, dipoleOptimizationCycles: int = 0, fixedEdges: networkx.classes.digraph.DiGraph = <networkx.classes.digraph.DiGraph object>) ‑> networkx.classes.digraph.DiGraph

Make a digraph that obeys the ice rules.

A new algorithm based on the suggestion by Prof. Sakuma, Yamagata University.

Args

g : nx.Graph
A ice-like undirected graph.
vertexPositions : Union[nx.ndarray, None], optional
Positions of the vertices. Defaults to None.
isPeriodicBoundary : bool, optional
If True, the positions are considered to be in the fractional coordinate system. Defaults to False.
dipoleOptimizationCycles : int, optional
Number of iterations to reduce the net dipole moment. Defaults to 0 (no iteration).
fixed : nx.DiGraph, optional
A digraph made of edges whose directions are fixed. All edges in fixed must also be included in g. Defaults to an empty graph.

Returns

nx.DiGraph
An ice graph. (CHANGED. BE CAREFUL この変更の影響をもっとも受けるのがGenIce)
Expand source code
def ice_graph(
    g: nx.Graph,
    vertexPositions: Union[np.ndarray, None] = None,
    isPeriodicBoundary: bool = False,
    dipoleOptimizationCycles: int = 0,
    fixedEdges: nx.DiGraph = nx.DiGraph(),
) -> nx.DiGraph:
    """Make a digraph that obeys the ice rules.

    A new algorithm based on the suggestion by Prof. Sakuma, Yamagata University.

    Args:
        g (nx.Graph): A ice-like undirected graph.
        vertexPositions (Union[nx.ndarray, None], optional): Positions of the vertices. Defaults to None.
        isPeriodicBoundary (bool, optional): If True, the positions are considered to be in the fractional coordinate system. Defaults to False.
        dipoleOptimizationCycles (int, optional): Number of iterations to reduce the net dipole moment. Defaults to 0 (no iteration).
        fixed (nx.DiGraph, optional): A digraph made of edges whose directions are fixed. All edges in fixed must also be included in g. Defaults to an empty graph.

    Returns:
        nx.DiGraph: An ice graph. (CHANGED. BE CAREFUL この変更の影響をもっとも受けるのがGenIce)
    """
    logger = getLogger()

    # derived cycles in extending the fixed edges.
    derivedCycles = []

    if fixedEdges.size() > 0:
        # コメントが日本語の部分はまだデバッグ中と思え。
        if logger.isEnabledFor(DEBUG):
            for edge in fixedEdges.edges():
                logger.debug(f"EDGE {edge}")

        # balance fixed edges
        processedEdges = None
        while processedEdges is None:
            # It returns Nones when it fails to balance.
            # The processedEdges also include derivedCycles.
            processedEdges, derivedCycles = balance(fixedEdges, g)
    else:
        processedEdges = nx.DiGraph()

    # really fixed in balance()
    finallyFixedEdges = nx.DiGraph(processedEdges)
    for cycle in derivedCycles:
        for edge in zip(cycle, cycle[1:]):
            finallyFixedEdges.remove_edge(*edge)

    # Divide the remaining (unfixed) part of the graph into a noodle graph
    dividedGraph = noodlize(g, processedEdges)

    # Simplify paths ( paths with least crossings )
    paths = list(split_into_simple_paths(len(g), dividedGraph)) + derivedCycles

    # 欠陥がない氷なら、すべてcycleになっているはず。
    # for path in paths:
    #     assert path[0] == path[-1]

    # arrange the orientations here if you want to balance the polarization
    if vertexPositions is not None:
        # Set the targetPol in order to cancel the polarization in the fixed part.
        targetPol = -vector_sum(finallyFixedEdges, vertexPositions, isPeriodicBoundary)

        paths = optimize(
            paths,
            vertexPositions,
            isPeriodicBoundary=isPeriodicBoundary,
            dipoleOptimizationCycles=dipoleOptimizationCycles,
            targetPol=targetPol,
        )

    # 欠陥がない氷なら、すべてcycleになっているはず。
    # for path in paths:
    #     assert path[0] == path[-1]

    # Combine everything together
    dg = nx.DiGraph(finallyFixedEdges)

    # 30-->97-->31という辺はextendedFixedEdgesに含まれるべきではない
    # for edge in extendedFixedEdges.edges():
    #     logger.debug(f"EDGE eFE {edge}")
    # logger.debug(f"{list(dg.predecessors(97))} --> 97 --> {list(dg.successors(97))}")
    for path in paths:
        nx.add_path(dg, path)

    # Does the graph really obey the ice rules?
    # if logger.isEnabledFor(DEBUG):
    for node in dg:
        if fixedEdges.has_node(node):
            if fixedEdges.in_degree(node) > 2 or fixedEdges.out_degree(node) > 2:
                continue
        assert (
            dg.in_degree(node) <= 2
        ), f"{node} {list(dg.successors(node))} {list(dg.predecessors(node))}"
        assert dg.out_degree(node) <= 2

    # bug? まれにこのチェックでひっかかる場合があるようだ。

    # # この時点で、pathsを検査しておく。
    # if logger.isEnabledFor(DEBUG):
    #     gg = nx.Graph(extendedFixedEdges)
    #     for path in paths:
    #         nx.add_path(gg, path)
    #     logger.debug(f"Size g {g.number_of_nodes()} {g.number_of_edges()}")
    #     logger.debug(f"Size gg {gg.number_of_nodes()} {gg.number_of_edges()}")
    #     assert g.number_of_edges() == gg.number_of_edges()
    #     e1 = set([(min(i, j), max(i, j)) for i, j in g.edges()])
    #     e2 = set([(min(i, j), max(i, j)) for i, j in gg.edges()])
    #     logger.debug(
    #         f"{sorted(list(e1 - e2))} edges only in original undirected graph."
    #     )
    #     logger.debug(f"{sorted(list(e2 - e1))} edges only in derived directed graph.")

    return dg