Contact
CoCalc Logo Icon
StoreFeaturesDocsShareSupport News AboutSign UpSign In
| Download
Project: RNApolys
Views: 491
1
from sage.all import *
2
from sage.geometry.polyhedron.parent import Polyhedra
3
from sage.geometry.polyhedron.backend_ppl import Polyhedron_QQ_ppl
4
from sage.rings.rational_field import QQ
5
from sage.geometry.fan import Fan, Cone_of_fan
6
from collections import namedtuple
7
from pickle import UnpicklingError
8
import os.path
9
10
pickle_version = 2
11
12
class RNAPolytope(Polyhedron_QQ_ppl):
13
def __init__(self, points):
14
"""
15
Construct an RNAPolytope from a collection of points.
16
17
Keyword arguments:
18
points -- An Iterable of points, each of which has (at least) members `structure` (holding arbitrary data about the point) and `vector` (giving the coordinates of the point in a format which can be handled by `Polytope`)
19
"""
20
self._normal_fan = None
21
self._vertex_from_cone = None
22
23
parent = Polyhedra(QQ, len(points[0].vector))
24
25
vertex_list = [point.vector for point in points]
26
super(RNAPolytope, self).__init__(parent, [vertex_list, None, None], None)
27
28
structure_dict = {tuple(map(QQ, point.vector)): point.structure for point in points}
29
self._structures = structure_dict
30
31
def _build_normal_fan(self):
32
if self._normal_fan is None:
33
cones = [[ieq.index() for ieq in vertex.incident()] for vertex in self.vertices()]
34
rays = [ ieq.A() for ieq in self.inequalities() ]
35
36
fan = Fan(cones, rays, check=False, is_complete = True)
37
self._normal_fan = fan
38
39
if self._vertex_from_cone is None:
40
conedict = {}
41
for vertex in self.vertices():
42
cone = self.normal_fan().cone_containing([ieq.A() for ieq in vertex.incident()])
43
conedict[cone] = vertex
44
self._vertex_from_cone = conedict
45
46
def normal_fan(self):
47
"""
48
Return the normal fan of `self`.
49
"""
50
if not hasattr(self, "_normal_fan"):
51
self._build_normal_fan()
52
53
return self._normal_fan
54
55
def _build_d1_slices(self):
56
subspace = Polyhedron(eqns=[(-1, 0, 0, 0, 1)]) # Hyperplane d = 1
57
slices = set()
58
59
# Construct the slices
60
for cone in self.normal_fan().cones(codim=0):
61
coneslice = cone.polyhedron().intersection(subspace)
62
63
# If the slice does not have volume, we're done
64
if coneslice.dimension() < 3:
65
continue
66
67
# Otherwise, project to Q3
68
vecprojector = lambda vec: vec[0:3] # Drop the d coordinate
69
newverts = lambda polyslice: [vecprojector(vert) for vert in polyslice.vertices()] # Projects the defining vertices of a slice into Q3
70
newrays = lambda polyslice: [vecprojector(ray) for ray in polyslice.rays()] # Projects the defining rays of a slice into Q3
71
polyprojector = lambda polyslice: Polyhedron(vertices = newverts(polyslice), rays = newrays(polyslice)) # Projects a polytope from Q4 to Q3 using the helper functions we just defined
72
73
# We can then apply polyprojector to our slice to move it down to Q3
74
projslice = polyprojector(coneslice)
75
76
# For future use, we'll store the original cone and the slice in Q4 as a member of this object
77
projslice.original_cone = cone
78
projslice.original_slice = coneslice
79
80
# As well as the original vertex and structure associated to those cones
81
projslice.original_vertex = self.vertex_from_cone(cone)
82
projslice.original_structure = self[projslice.original_vertex]
83
84
# And, finally, we add this slice to our list
85
slices.add(projslice)
86
87
# Once we've finished the loop, all the slices have been processed, so we store the results
88
self._d1_slices = slices
89
90
def d1_slices(self):
91
"""
92
Return the d=1 slices of self as Polyhedra in Q3
93
"""
94
95
if not hasattr(self, "_d1_slices"):
96
self._build_d1_slices()
97
98
return self._d1_slices
99
100
def vertex_from_cone(self, cone):
101
assert cone in self.normal_fan().cones(self.dim())
102
return self._vertex_from_cone[cone]
103
104
def __getitem__(self, key):
105
try:
106
processed_key = tuple(map(QQ, key)) # Try to cast the input to a tuple of rationals
107
return self._structures[processed_key] # Cast the input to a tuple of rationals
108
except TypeError:
109
return None # Bad key!
110
111
@classmethod
112
def random_nonsense_polytope(cls,npoints=100):
113
dimension=4
114
points=[]
115
for i in range(npoints):
116
v = vector(floor(gauss(0,1)*1000)/1000 for d in range(dimension))
117
#v = v / v.norm()
118
119
newpoint = namedtuple('RNApoint', ['structure', 'vector', 'energy'])
120
newpoint.vector = v
121
newpoint.structure = ''
122
newpoint.energy = 0.0
123
points.append(newpoint)
124
125
thepoly = RNAPolytope(points)
126
thepoly._pickle_version = pickle_version
127
thepoly._build_normal_fan()
128
thepoly._build_d1_slices()
129
130
return thepoly
131
132
@classmethod
133
def construct_from_file(cls, polyfile):
134
"""
135
Construct an RNAPolytope from a file in polyfile format.
136
137
Keyword arguments:
138
polyfile -- A file representing the polytope structure
139
"""
140
141
filebase = os.path.splitext(polyfile)[0]
142
143
# Construct the polytope
144
try:
145
# If the polytope is available in a pickle, we should use that
146
thepoly = load(filebase) # Attempt to load the polytope from a pickle file
147
148
# If the pickled polytope is obsolete, however, we need to rebuild it
149
if cls.poly_is_obsolete(thepoly):
150
raise UnpicklingError
151
152
except (IOError, UnpicklingError, AssertionError):
153
# In any of these exception cases, the load failed, so we generate the polytope from the points
154
155
# Read the point data from the specified file
156
points = []
157
with open(polyfile) as f:
158
for line in f:
159
newline = line.split('#')[0] # Only use content before the comment symbol
160
if newline.strip() != '':
161
elts = newline.split()
162
structure = elts[1]
163
multiloops = QQ(elts[2])
164
unpaired = QQ(elts[3])
165
branches = QQ(elts[4])
166
w = QQ(elts[5])
167
energy = QQ(elts[6])
168
coords = (multiloops, unpaired, branches, w)
169
170
newpoint = namedtuple('RNApoint', ['structure', 'vector', 'energy'])
171
newpoint.vector = coords
172
newpoint.structure = structure
173
newpoint.energy = energy
174
175
points.append(newpoint)
176
177
thepoly = RNAPolytope(points)
178
thepoly._pickle_version = pickle_version
179
thepoly._build_normal_fan()
180
thepoly._build_d1_slices()
181
thepoly.dump(filebase, -1)
182
183
return thepoly
184
185
@classmethod
186
def poly_is_obsolete(cls, poly):
187
"""
188
Test whether the polytope object is obsolete, in which case it should be upgraded
189
"""
190
191
if (not hasattr(poly, "_pickle_version") or poly._pickle_version < pickle_version):
192
return true
193
else:
194
return false
195
196