예제 #1
0
def hook6(lattice):
    global nwateratoms
    lattice.logger.info("Hook6: Output water molecules in Yaplot format.")
    lattice.logger.info("  Total number of atoms: {0}".format(
        len(lattice.atoms)))
    # prepare the reverse dict
    waters = defaultdict(dict)
    for atom in lattice.atoms:
        resno, resname, atomname, position, order = atom
        if "O" in atomname:
            waters[order]["O"] = position
        elif "H" in atomname:
            if "H0" not in waters[order]:
                waters[order]["H0"] = position
            else:
                waters[order]["H1"] = position
    s = ""
    s += yp.Color(3)
    for order, water in waters.items():
        O = water["O"]
        H0 = water["H0"]
        H1 = water["H1"]
        s += yp.Layer(4)
        s += yp.Color(3)
        s += yp.Size(0.03)
        s += yp.Circle(O)
        s += yp.Line(O, H0)
        s += yp.Line(O, H1)
        s += yp.Size(lattice.yaplot_size_H)
        s += yp.Circle(H0)
        s += yp.Circle(H1)
        s += yp.Color(2)
        s += yp.Layer(1)
        s += yp.Text(O, "{0}".format(order))
    s += yp.Layer(3)
    s += yp.Color(4)
    s += yp.ArrowType(1)
    s += yp.Size(0.03)
    for i, j in lattice.spacegraph.edges(data=False):
        if i in waters and j in waters:  # edge may connect to the dopant
            O = waters[j]["O"]
            H0 = waters[i]["H0"]
            H1 = waters[i]["H1"]
            d0 = H0 - O
            d1 = H1 - O
            rr0 = np.dot(d0, d0)
            rr1 = np.dot(d1, d1)
            if rr0 < rr1 and rr0 < 0.245**2:
                s += yp.Arrow(H0, O)
            if rr1 < rr0 and rr1 < 0.245**2:
                s += yp.Arrow(H1, O)
    print(s, end="")
    nwateratoms = len(lattice.atoms)
    lattice.logger.info("Hook6: end.")
예제 #2
0
def main():
    import sys
    groname, twiname = sys.argv[1:3]

    with open(twiname) as twifile:
        with open(groname) as grofile:
            while True:
                if groname[-4:] == ".pdb":
                    gr = pdb.PDB(grofile, "O", "H", resname="WAT")
                else:
                    # assume it is gromacs
                    gr = gro.Gromacs(grofile, "Ow", "Hw", "Mw")
                btw = btwc.load_BTWC(twifile)
                if btw is None or gr is None:
                    break
                celli = np.linalg.inv(gr.cell)
                # relative coord
                rs = np.array([np.dot(x, celli) for x in gr.solutes])
                ro = np.array([np.dot(x, celli) for x in gr.waters])
                print(len(rs))
                print(len(ro))
                grid = pl.determine_grid(gr.cell, 0.245)
                shell1 = np.vstack(
                    pl.pairs_fine_hetero(rs,
                                         ro,
                                         0.4,
                                         gr.cell,
                                         grid,
                                         distance=False))
                # some solute atoms are isolated from water.
                shell1s = set(shell1[:, 0])
                shell1w = set(shell1[:, 1])
                #show all bonds in yaplot
                s = ""
                for i in range(-10, 0):
                    s += yap.SetPalette(i + 10 + 3, 255 * (-i) // 10, 0, 0)
                for i in range(11):
                    s += yap.SetPalette(i + 10 + 3, 0, 0, 255 * i // 10)
                for b in btw:
                    i, j = b[0]
                    if i in shell1w or j in shell1w:
                        s += yap.Layer(1)
                    else:
                        s += yap.Layer(2)
                    r = abs(b[2])
                    sine = b[2].imag
                    s += yap.Size(r)
                    s += yap.Color(int(sine * 10) + 10 + 3)
                    s += yap.Circle(b[1])
                print(s)
예제 #3
0
def hook2(lattice):
    logger = getLogger()
    logger.info("Hook2: Show rings in Yaplot format.")
    # copied from svg_poly
    graph = nx.Graph(lattice.graph)  #undirected
    cellmat = lattice.repcell.mat
    s = ""
    s += yp.Layer(2)
    s += yp.Color(0)
    for i, j in graph.edges():
        pi, pj = lattice.reppositions[i], lattice.reppositions[j]
        d = pj - pi
        d -= np.floor(d + 0.5)
        s += yp.Line(pi @ cellmat, (pi + d) @ cellmat)
    for ring in cr.CountRings(graph, pos=lattice.reppositions).rings_iter(
            lattice.largestring):
        deltas = np.zeros((len(ring), 3))
        d2 = np.zeros(3)
        for k, i in enumerate(ring):
            d = lattice.reppositions[i] - lattice.reppositions[ring[0]]
            d -= np.floor(d + 0.5)
            deltas[k] = d
        comofs = np.sum(deltas, axis=0) / len(ring)
        deltas -= comofs
        com = lattice.reppositions[ring[0]] + comofs
        com -= np.floor(com)
        # rel to abs
        com = np.dot(com, cellmat)
        deltas = np.dot(deltas, cellmat)
        s += face(com, deltas)
    print(s)
    logger.info("Hook2: end.")
예제 #4
0
def face(center, rpos):
    pos = rpos + center
    n = rpos.shape[0]
    s = yp.Color(n)
    s += yp.Layer(n)
    s += yp.Polygon(pos)
    return s
예제 #5
0
def hook1(lattice):
    lattice.logger.info("Hook1: Diffraction.")
    lattice.logger.info("  3D FFT.")
    cellmat = lattice.repcell.mat
    atoms = lattice.reppositions - np.floor(lattice.reppositions)
    grid = [32, 32, 32]
    distrib = np.zeros(grid)
    grid = np.array(grid)
    iatoms = np.array(np.floor(atoms * grid), dtype=int)
    for x, y, z in iatoms:
        distrib[x, y, z] += 1
    diffr = np.fft.fftn(distrib, axes=(0, 1, 2))
    power = np.real(diffr * np.conj(diffr)) / len(atoms)
    lattice.logger.info("  Rendering in Yaplot format.")
    cnt = contour3d.Contour(power, pbc=True, center=True)
    cnt.double()
    s = ""
    for layer, threshold in enumerate(lattice.thresholds):
        s += yp.Color(layer + 3)
        s += yp.Layer(layer + 1)
        nfacet = 0
        #for face in cnt.facets(threshold):
        for face in cnt.contour_flakes(threshold):
            s += yp.Polygon(face)
            nfacet += 1
        lattice.logger.info(
            "    {0} facets rendered for threshold {1}.".format(
                nfacet, threshold))
    print(s)

    lattice.logger.info("Hook1: end.")
예제 #6
0
def hook7(lattice):
    global nwateratoms
    lattice.logger.info("Hook7: Output water molecules in Yaplot format.")
    lattice.logger.info("  Total number of atoms: {0}".format(
        len(lattice.atoms)))
    gatoms = lattice.atoms[nwateratoms:]
    palettes = dict()
    s = ""
    s += yp.Layer(4)
    s += yp.ArrowType(1)
    H = []
    O = ""
    for atom in gatoms:
        resno, resname, atomname, position, order = atom
        if atomname in palettes:
            pal = palettes[atomname]
        else:
            pal = 4 + len(palettes)
            palettes[atomname] = pal
        s += yp.Color(pal)
        s += yp.Size(0.04)
        s += yp.Circle(position)
    s = '#' + "\n#".join(lattice.doc) + "\n" + s
    print(s)
    lattice.logger.info("Hook7: end.")
예제 #7
0
def hook1(lattice):
    lattice.logger.info("Hook1: Draw the cell in Yaplot format.")
    s = yp.Layer(2)
    x, y, z = lattice.repcell.mat
    for p, q, r in ((x, y, z), (y, z, x), (z, x, y)):
        for a in (np.zeros(3), p, q, p + q):
            s += yp.Line(a, a + r)
    print(s, end="")
    lattice.logger.info("Hook1: end.")
예제 #8
0
def hook2(lattice):
    global options
    logger = getLogger()
    logger.info("Hook2: Petal statistics.")
    database = options.database
    if database is None:
        logger.info("  Using temporary database. (volatile)")
        gc = graphstat.GraphStat()
    elif database[:4] == "http":
        logger.info("  Using MySQL: {0}".format(database))
        gc = graphstat_mysql.GraphStat(database)
    else:
        logger.info("  Using local Sqlite3: {0}".format(database))
        import os.path
        create = not os.path.isfile(database)
        if create:
            logger.info("  Create new DB.")
        gc = graphstat_sqlite3.GraphStat(database, create=create)

    cell = lattice.repcell.mat
    positions = lattice.reppositions
    graph = nx.Graph(lattice.graph)  #undirected
    rings, subgraphs, rings_at = prepare(graph, positions)
    gids = collect(subgraphs, gc)
    if options.json:
        # In JSON, a key must be a string.
        ids = {str(i): gids[i] for i in gids}
        print(json.dumps(ids, indent=2, sort_keys=True))
    elif options.yaplot:
        # Draw top 10 most popular petal types with Yaplot.
        count = defaultdict(int)
        typical = dict()
        for node in gids:
            count[gids[node]] += 1
            typical[gids[node]] = node
        top10 = sorted(count, key=lambda gid: -count[gid])[:30]
        ranks = {gid: i for i, gid in enumerate(top10)}
        for i, gid in enumerate(ranks):
            node = typical[gid]
            logger.info("  Ranking {0}: {2} x {1} (id {3})".format(
                i, [len(rings[ringid]) for ringid in rings_at[node]],
                count[gid], gid))
        s = ""
        for node in gids:
            gid = gids[node]
            if gid in ranks:
                rank = ranks[gid]
                s += yp.Color(rank + 3)
                s += yp.Layer(rank + 1)
                for ringid in rings_at[node]:
                    s += draw_ring(rings[ringid],
                                   positions,
                                   cell=cell,
                                   center=positions[node])
        print(s)
    logger.info("Hook2: end.")
예제 #9
0
def draw_water(water, povray=False):
    s = ""
    if povray:
        s += pov.Sphere(water[0], r="RH", material="MATH")
        s += pov.Sphere(water[1], r="RH", material="MATH")
        s += pov.Sphere(water[2], r="RO", material="MATO")
        s += pov.Cylinder(water[0], water[2], r="ROH", material="MATOH")
        s += pov.Cylinder(water[1], water[2], r="ROH", material="MATOH")
    else:
        s += yp.Layer(1)
        s += yp.Color(5)
        s += yp.Size(0.2)
        s += yp.Circle(water[0])
        s += yp.Circle(water[1])
        s += yp.Color(4)
        s += yp.Size(0.4)
        s += yp.Circle(water[2])
        s += yp.Color(2)
        d0 = water[0] - water[2]
        L0 = np.linalg.norm(d0)
        s += yp.Line(water[2] + d0 / L0 * 0.4, water[0] - d0 / L0 * 0.2)
        d1 = water[1] - water[2]
        L1 = np.linalg.norm(d1)
        s += yp.Line(water[2] + d1 / L1 * 0.4, water[1] - d1 / L1 * 0.2)
        # draw a tetrahedron
        y = water[1] - water[0]
        z = (water[1] + water[0]) / 2 - water[2]
        x = np.cross(y, z)
        x /= np.linalg.norm(x)
        y /= np.linalg.norm(y)
        z /= np.linalg.norm(z)
        com = (water[1] + water[0] + water[2] * 16) / 18
        R = 2.76 / 2
        a = y * (2 / 3)**0.5 + z * (1 / 3)**0.5
        b = -y * (2 / 3)**0.5 + z * (1 / 3)**0.5
        c = x * (2 / 3)**0.5 - z * (1 / 3)**0.5
        d = -x * (2 / 3)**0.5 - z * (1 / 3)**0.5
        s += yp.Layer(2)
        for e, f in it.combinations((a, b, c, d), 2):
            s += yp.Line(com + e * R, com + f * R)
    return s
예제 #10
0
def to_yaplot(cellmat, rpos, anions, cations, G, cation="N", anion="F"):
    """
    cellmat: セル行列(後置記法)
    rpos:    分子のセル内相対位置
    anions:  アニオン番号
    cations: カチオン番号
    G:       水素結合ネットワーク
    """
    # 水分子の原子位置
    water = tip4picesites()

    s = ""
    for mol in G:
        if mol not in anions and mol not in cations:
            # genuine water molecule.
            rO = rpos[mol]
            H1, H2 = G[mol]
            rH1 = rpos[H1] - rO
            rH1 -= np.floor(rH1 + 0.5)
            rH2 = rpos[H2] - rO
            rH2 -= np.floor(rH2 + 0.5)
            # 実座標 (angstrom)
            pO = rO @ cellmat
            pH1 = rH1 @ cellmat
            pH2 = rH2 @ cellmat
            ey = pH1 - pH2
            ez = pH1 + pH2
            ex = np.cross(ey, ez)
            # 分子内座標方向の単位ベクトル
            ex /= np.linalg.norm(ex)
            ey /= np.linalg.norm(ey)
            ez /= np.linalg.norm(ez)
            # 回転行列
            R = np.array([ex, ey, ez])
            # 水分子の原子位置
            intra = water @ R + pO
            s += draw_water(intra)
    s += yp.Layer(3)
    s += yp.Size(0.5)
    s += yp.Color(6)
    for mol in G:
        if mol in anions:
            pos = rpos[mol] @ cellmat
            s += yp.Circle(pos)
    s += yp.Size(0.3)
    s += yp.Color(7)
    for mol in G:
        if mol in cations:
            pos = rpos[mol] @ cellmat
            s += yp.Circle(pos)
    s += yp.NewPage()
    return s
예제 #11
0
def hook2(lattice):
    global nwateratoms
    if lattice.yaplot_size_H > 0:
        return
    lattice.logger.info(
        "Hook2: Output CoM of water molecules in Yaplot format.")
    # prepare the reverse dict
    waters = defaultdict(dict)
    pos = lattice.reppositions @ lattice.repcell.mat
    s = ""
    for p in pos:
        s += yp.Layer(4)
        s += yp.Color(3)
        s += yp.Size(0.03)
        s += yp.Circle(p)
    print(s, end="")
    lattice.logger.info("Hook2: end.")
    return True
예제 #12
0
def draw_cell(voro_cell, box, kind=0):
    layer = kind + 2
    if layer > 30:
        layer = 30
    # draw frame
    s = yp.Layer(layer)
    s += yp.Color(0)
    origin = voro_cell['original']
    voro_vertices = voro_cell['vertices']
    voro_faces = voro_cell['faces']
    g = voro_cell['graph']
    for i, j in g.edges():
        di = voro_vertices[i] - origin
        dj = voro_vertices[j] - origin
        s += yp.Line(origin + di * 0.9, origin + dj * 0.9)
    # draw faces
    s += yp.Color(kind + 10)
    for face in voro_faces:
        points = []
        for point in face['vertices']:
            d = voro_vertices[point] - origin
            points.append(d * 0.89 + origin)
        s += yp.Polygon(points)
    return s
예제 #13
0
def main():
    basicConfig(level=INFO, format="%(levelname)s %(message)s")
    logger = getLogger()
    every = 1
    maxval = 1.0
    adjdens = False
    while sys.argv[1][0] == "-":
        if sys.argv[1] == "-e":
            sys.argv.pop(1)
            every = int(sys.argv[2])
            sys.argv.pop(1)
        elif sys.argv[1] == "-v":
            maxval = float(sys.argv[2])
            sys.argv.pop(1)
            sys.argv.pop(1)
        elif sys.argv[1] == "-a":
            adjdens = True
            sys.argv.pop(1)
        else:
            usage()
    gcell, gatoms = LoadGRO(open(sys.argv[1]))  # in AA, in AA
    ucell, uatoms = LoadGRO(open(sys.argv[2]))  # in AA
    dens0 = gatoms.shape[0] / np.linalg.det(gcell)
    dens1 = uatoms.shape[0] / np.linalg.det(ucell)
    ratio = (dens1 / dens0)**(1. / 3.)
    if adjdens:
        logger.info(f"Scaling ratio: {ratio}")
        ucell *= ratio
        uatoms *= ratio
    gcelli = np.linalg.inv(gcell)
    ucelli = np.linalg.inv(ucell)

    mode = ""
    if len(sys.argv) > 3:
        mode = sys.argv[3]

    s = ""
    s += yp.Color(2)
    s += yp.Layer(1)
    origin = np.zeros(3)
    s += drawbox(origin, gcell, halfshift=False)
    #in absolute coord
    # unitatoms = np.dot(unitatoms, ucell)
    #s += unitatoms)

    #s = ""
    nline = 0
    matched = set()
    palette = dict()
    while True:
        line = sys.stdin.readline()
        if len(line) == 0:
            break
        nline += 1
        #parse the line
        cols = line.split()
        if len(cols) < 13:
            break
        # 2018-4-9 New output format of matcher.c
        msd = float(cols[0])
        gcenter = gatoms[int(cols[1])].copy()  #atom at the matching center
        gcenter -= np.floor(gcenter @ gcelli) @ gcell
        ucenter = int(cols[2])
        rotmat = np.array([float(x) for x in cols[3:12]]).reshape((3, 3))
        N = int(cols[12])
        if len(cols) < 13 + N:
            break
        irot = np.linalg.inv(rotmat)
        members = [int(x) for x in cols[13:N + 13]]
        #draw matched box

        # roll the unit cell to center (abs)
        rel = uatoms - uatoms[ucenter]
        rel -= np.floor(rel @ ucelli + 0.5) @ ucell
        # rel to abs
        Slid = rel
        # rotate box
        Rotucell = ucell @ rotmat
        #
        Boxslide = -uatoms[ucenter] @ rotmat + gcenter
        # rotate atoms in the unit cell
        Slidunit = (Slid @ rotmat) + gcenter
        #s += yp.Color(3)
        if mode == "R":
            color = direction2color(rotmat[0] + rotmat[1] + rotmat[2])
        elif mode == "T":
            color = direction2color(rotmat[2])
        else:
            color = (0, 3, 0)  #green
        if color not in palette:
            palette[color] = len(palette) + 6
            s += yp.SetPalette(palette[color], color[0] * 255 // 3,
                               color[1] * 255 // 3, color[2] * 255 // 3)
        if msd < maxval:
            matched |= set(members)
        if every != 0 and nline % every == 0 and msd < maxval:
            s += yp.Color(palette[color])
            # matched box
            s += yp.Layer(4)
            s += drawbox(gcenter, Rotucell, halfshift=True, diag=(mode == "R"))
            # s += yp.Layer(2)
            # # unit cell
            # s += drawbox(Boxslide,Rotucell,halfshift=True)

            s += yp.Layer(2)
            #s += drawbox2(Boxslide,Rotucell)

            s += yp.Layer(5)

            s += yp.Size(0.3)
            s += yp.Color(5)
            s += drawatoms(Slidunit)
            for i in range(len(Slidunit)):
                g = gatoms[members[i]]
                d = Slidunit[i] - gatoms[members[i]]
                d -= np.floor(d @ gcelli + 0.5) @ gcell
                s += yp.Line(g, g + d)
                # 変位ベクトルはどうやってもうまくいかないので、やめる。

    s += yp.Size(0.3)
    s += yp.Color(4)
    s += yp.Layer(3)
    s += drawatoms(gatoms, members=matched)
    print(s)  # end of frame
예제 #14
0
def main():
    gro = open(sys.argv[1])
    box, atoms = LoadGRO(gro)
    ratoms = atoms/box
    cellmat = np.diag(box)
    page1 = ""
    page2 = ""
    page3 = ""
    page4 = ""
    page1 += yp.Size(0.05)
    grid = None
    while True:
        line = sys.stdin.readline() #expect *.smatch result of slide-matcher2
        if len(line) == 0:
            break
        cols = line.split()
        if len(cols)<7:
            break # broken line; maybe end of the file.
        i,j = [int(x) for x in cols[:2]]
        rad, dx,dy,dz, rmsd = [float(x) for x in cols[2:]]
        if grid is None:
            grid = pairlist.determine_grid(cellmat, rad)
            lastrad = rad
            p,q,r = pairlist.pairs_fine(ratoms, rad, cellmat, grid, distance=True, raw=True)
            g = nx.Graph()
            for k,pq in enumerate(zip(p,q)):
                p,q = pq
                g.add_edge(p,q,length=r[k])
        else:
            assert lastrad == rad
        d = np.array([dx,dy,dz])
        if rmsd < 0.09: # 0.09 for hyper ice T
            # page 1: displacement vectors
            page1 += yp.Line(atoms[i], atoms[i]+d)
            page1 += yp.Circle(atoms[j])
            # pages 2: displacement vectors (centered)
            page2 += yp.Size(0.05)
            page2 += yp.Line(np.zeros(3), d)
            page2 += yp.Circle(d)
            page2 += yp.Text(d, "{0} {1}".format(i,j))
            # page 3..: matching
            s = ""
            s += yp.Size(0.05)
            s += yp.Layer(1)
            s += yp.Color(3)
            for ni in g[i]:
                s += yp.Circle(atoms[ni])
            s += yp.Layer(2)
            s += yp.Color(4)
            for nj in g[j]:
                s += yp.Circle(atoms[nj]-atoms[j]+atoms[i])
            page3 += s
            s = ""
            s += yp.Size(0.05)
            s += yp.Layer(1)
            s += yp.Color(3)
            for ni in g[i]:
                s += yp.Circle(atoms[ni]-atoms[i])
            s += yp.Layer(2)
            s += yp.Color(4)
            for nj in g[j]:
                s += yp.Circle(atoms[nj]-atoms[j])
            page4 += s
            page4 += yp.NewPage()

    print(page1)
    print(page2)
    print(page3)
    print(page4)
예제 #15
0
파일: cage.py 프로젝트: vitroid/genice-cage
def hook2(lattice):
    global options

    logger = getLogger()
    logger.info("Hook2: Cages and vitrites")

    cell = lattice.repcell.mat
    positions = lattice.reppositions
    graph = nx.Graph(lattice.graph)  #undirected
    ringsize = options.ring
    ringlist = [[
        int(x) for x in ring
    ] for ring in cr.CountRings(graph, pos=positions).rings_iter(max(ringsize))
                ]
    ringpos = [centerOfMass(ringnodes, positions) for ringnodes in ringlist]
    logger.info("  Rings: {0}".format(len(ringlist)))
    maxcagesize = max(options.sizes)
    cages = []
    for cage in Polyhed(ringlist, maxcagesize):
        if len(cage) in options.sizes:
            valid = True
            for ringid in cage:
                if len(ringlist[ringid]) not in ringsize:
                    valid = False
            if valid:
                cages.append(list(cage))
    logger.info("  Cages: {0}".format(len(cages)))
    cagepos = np.array([centerOfMass(cage, ringpos) for cage in cages])
    if options.gromacs:
        options.rings = ringlist
        options.cages = cages
        logger.debug("##2 {0}".format(cages))
        logger.info("Hook2: end.")
        return

    if options.quad:
        oncage = defaultdict(list)
        for cage in cages:
            nodes = set()
            for ringid in cage:
                nodes |= set(ringlist[ringid])
            for node in nodes:
                oncage[node].append(len(cage))
        op = dict()
        for node in oncage:
            count = [0 for i in range(17)]
            for v in oncage[node]:
                count[v] += 1
            v = "{0}{1}{2}{3}".format(count[12], count[14], count[15],
                                      count[16])
            op[node] = v

        stat = defaultdict(int)
        for node in sorted(op):
            v = op[node]
            stat[v] += 1

        if options.json:
            output = dict()
            N = positions.shape[0]
            output["op"] = {str(k): v for k, v in op.items()}
            output["stat"] = {k: v / N for k, v in stat.items()}
            print(json.dumps(output, indent=2, sort_keys=True))
        else:
            for node in sorted(op):
                print(node, op[node])
            print("# Statistics")
            for v in sorted(stat):
                print("{0} {1} {2}/{3}".format(v, stat[v] / positions.shape[0],
                                               stat[v], positions.shape[0]))
        #ideal = {"CS2": {"2002": 0.7058823529411765,
        #                 "3001": 0.23529411764705882,
        #                 "4000": 0.058823529411764705},
        #         "CS1": {"0400": 0.13043478260869565,
        #                 "1300": 0.8695652173913043},
        #}
        #for ref in ideal:
        #    dKL = 0
        #    for v in sorted(stat):
        #        if v in ideal[ref]:
        #            dKL += ideal[ref][v]*(log2(ideal[ref][v]) - log2(stat[v]/positions.shape[0]))
        #    print("{0} dKL={1}".format(ref, dKL))
    elif options.json:
        output = dict()
        output["rings"] = ringlist
        output["cages"] = cages
        output["ringpos"] = [[x, y, z] for x, y, z in ringpos]
        output["cagepos"] = [[x, y, z] for x, y, z in cagepos]
        print(json.dumps(output, indent=2, sort_keys=True))
    elif options.yaplot:
        s = ""
        for c, cage in enumerate(cages):
            nodes = dict()
            cagesize = len(cage)
            for ringid in cage:
                ns = ringlist[ringid]
                for node in ns:
                    if node not in nodes:
                        # relative pos of the node
                        nodepos = positions[node] - cagepos[c]
                        nodepos -= np.floor(nodepos + 0.5)
                        # shrink a little
                        nodes[node] = nodepos * 0.9
                s += yp.Color(len(ns))
                s += yp.Layer(cagesize)
                polygon = (np.array([nodes[node]
                                     for node in ns]) + cagepos[c]) @ cell
                s += yp.Polygon(polygon)
        print(s + "\n")
    elif options.python:
        import graphstat as gs
        db = gs.GraphStat()
        labels = set()
        g_id2label = dict()
        print('cages="""')
        for c, cage in enumerate(cages):
            g = cage_to_graph(cage, ringlist)
            cagesize = len(cage)
            g_id = db.query_id(g)
            if g_id < 0:
                g_id = db.register()
                enum = 0
                label = "{0}".format(cagesize, enum)
                while label in labels:
                    enum += 1
                    label = "{0}_{1}".format(cagesize, enum)
                g_id2label[g_id] = label
                labels.add(label)
            else:
                label = g_id2label[g_id]
            print("{0:10s} {1:.4f} {2:.4f} {3:.4f}".format(label, *cagepos[c]))
        print('"""')
    else:
        # human-friendly redundant format
        for cageid, cage in enumerate(cages):
            print("Cage {0}: ({1}, {2}, {3}) {4} hedron".format(
                cageid, *cagepos[cageid], len(cage)))
            for ringid in sorted(cage):
                print("  Ring {0}: ({1}, {2}, {3}) {4} gon".format(
                    ringid, *ringpos[ringid], len(ringlist[ringid])))
                print("    Nodes: {0}".format(ringlist[ringid]))
    logger.info("Hook2: end.")
    return True  # terminate