Skip to content

Commit 26bbae7

Browse files
akisschinasakisschinas
and
akisschinas
authored
Add mmcs method to sampling (#108)
* Upgrade volesti and pass method as string avoiding uneeded integer checks * Add mmcs method to sampling * Add tests that call mmcs from sampling function * Update volesti submodule --------- Co-authored-by: akisschinas <[email protected]>
1 parent e89128c commit 26bbae7

File tree

8 files changed

+169
-229
lines changed

8 files changed

+169
-229
lines changed

build.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def build(setup_kwargs):
2323

2424
# gcc arguments hack: enable optimizations
2525
os.environ["CFLAGS"] = [
26-
"-std=c++11",
26+
"-std=c++17",
2727
"-O3",
2828
"-DBOOST_NO_AUTO_PTR",
2929
"-ldl",

dingo/PolytopeSampler.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,9 @@ def generate_steady_states(
159159
self._T_shift = np.add(self._T_shift, Tr_shift)
160160

161161
return steady_states
162-
162+
163163
def generate_steady_states_no_multiphase(
164-
self, method = 'billiard_walk', n=1000, burn_in=0, thinning=1, variance=1.0, bias_vector=None
164+
self, method = 'billiard_walk', n=1000, burn_in=0, thinning=1, variance=1.0, bias_vector=None, ess=1000
165165
):
166166
"""A member function to sample steady states.
167167
@@ -171,17 +171,17 @@ def generate_steady_states_no_multiphase(
171171
burn_in -- the number of points to burn before sampling
172172
thinning -- the walk length of the chain
173173
"""
174-
174+
175175
self.get_polytope()
176176

177177
P = HPolytope(self._A, self._b)
178-
178+
179179
if bias_vector is None:
180180
bias_vector = np.ones(self._A.shape[1], dtype=np.float64)
181181
else:
182182
bias_vector = bias_vector.astype('float64')
183183

184-
samples = P.generate_samples(method, n, burn_in, thinning, variance, bias_vector, self._parameters["solver"])
184+
samples = P.generate_samples(method.encode('utf-8'), n, burn_in, thinning, variance, bias_vector, self._parameters["solver"], ess)
185185
samples_T = samples.T
186186

187187
steady_states = map_samples_to_steady_states(
@@ -213,10 +213,10 @@ def sample_from_polytope(
213213

214214

215215
return samples
216-
216+
217217
@staticmethod
218218
def sample_from_polytope_no_multiphase(
219-
A, b, method = 'billiard_walk', n=1000, burn_in=0, thinning=1, variance=1.0, bias_vector=None, solver=None
219+
A, b, method = 'billiard_walk', n=1000, burn_in=0, thinning=1, variance=1.0, bias_vector=None, solver=None, ess=1000
220220
):
221221
"""A static function to sample from a full dimensional polytope with an MCMC method.
222222
@@ -232,10 +232,10 @@ def sample_from_polytope_no_multiphase(
232232
bias_vector = np.ones(A.shape[1], dtype=np.float64)
233233
else:
234234
bias_vector = bias_vector.astype('float64')
235-
235+
236236
P = HPolytope(A, b)
237237

238-
samples = P.generate_samples(method, n, burn_in, thinning, variance, bias_vector, solver)
238+
samples = P.generate_samples(method.encode('utf-8'), n, burn_in, thinning, variance, bias_vector, solver, ess)
239239

240240
samples_T = samples.T
241241
return samples_T
@@ -246,7 +246,7 @@ def round_polytope(
246246
):
247247
P = HPolytope(A, b)
248248
A, b, Tr, Tr_shift, round_value = P.rounding(method, solver)
249-
249+
250250
return A, b, Tr, Tr_shift
251251

252252
@staticmethod

dingo/bindings/bindings.cpp

Lines changed: 96 additions & 88 deletions
Large diffs are not rendered by default.

dingo/bindings/bindings.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
// for rounding
3939
#include "preprocess/min_sampling_covering_ellipsoid_rounding.hpp"
4040
#include "preprocess/svd_rounding.hpp"
41-
#include "preprocess/max_inscribed_ellipsoid_rounding.hpp"
41+
#include "preprocess/inscribed_ellipsoid_rounding.hpp"
4242

4343
typedef double NT;
4444
typedef Cartesian<NT> Kernel;
@@ -140,7 +140,8 @@ class HPolytopeCPP{
140140

141141
// the apply_sampling() function
142142
double apply_sampling(int walk_len, int number_of_points, int number_of_points_to_burn,
143-
int method, double* inner_point, double radius, double* samples, double variance_value, double* bias_vector);
143+
char* method, double* inner_point, double radius, double* samples,
144+
double variance_value, double* bias_vector, int ess);
144145

145146
void mmcs_initialize(int d, int ess, bool psrf_check, bool parallelism, int num_threads);
146147

dingo/volestipy.pyx

Lines changed: 19 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# This is a cython wrapper for the C++ library volesti
22
# volesti (volume computation and sampling library)
3-
3+
44
# Copyright (c) 2012-2021 Vissarion Fisikopoulos
55
# Copyright (c) 2018-2021 Apostolos Chalkis
66
# Copyright (c) 2020-2021 Pedro Zuidberg Dos Martires
@@ -54,8 +54,9 @@ cdef extern from "bindings.h":
5454

5555
# Random sampling
5656
double apply_sampling(int walk_len, int number_of_points, int number_of_points_to_burn, \
57-
int method, double* inner_point, double radius, double* samples, double variance_value, double* bias_vector)
58-
57+
char* method, double* inner_point, double radius, double* samples, \
58+
double variance_value, double* bias_vector, int ess)
59+
5960
# Initialize the parameters for the (m)ultiphase (m)onte (c)arlo (s)ampling algorithm
6061
void mmcs_initialize(unsigned int d, int ess, int psrf_check, int parallelism, int num_threads);
6162

@@ -117,45 +118,22 @@ cdef class HPolytope:
117118
raise Exception('"{}" is not implemented to compute volume. Available methods are: {}'.format(vol_method, volume_methods))
118119

119120
# Likewise, the generate_samples() function
120-
def generate_samples(self, method, number_of_points, number_of_points_to_burn, walk_len, variance_value, bias_vector, solver = None):
121+
def generate_samples(self, method, number_of_points, number_of_points_to_burn, walk_len,
122+
variance_value, bias_vector, solver = None, ess = 1000):
121123

122124
n_variables = self._A.shape[1]
123125
cdef double[:,::1] samples = np.zeros((number_of_points, n_variables), dtype = np.float64, order = "C")
124126

125127
# Get max inscribed ball for the initial polytope
126128
temp_center, radius = inner_ball(self._A, self._b, solver)
127-
129+
128130
cdef double[::1] inner_point_for_c = np.asarray(temp_center)
129-
131+
130132
cdef double[::1] bias_vector_ = np.asarray(bias_vector)
131-
132-
if method == 'cdhr':
133-
int_method = 1
134-
elif method == 'rdhr':
135-
int_method = 2
136-
elif method == 'billiard_walk':
137-
int_method = 3
138-
elif method == 'ball_walk':
139-
int_method = 4
140-
elif method == 'dikin_walk':
141-
int_method = 5
142-
elif method == 'john_walk':
143-
int_method = 6
144-
elif method == 'vaidya_walk':
145-
int_method = 7
146-
elif method == 'gaussian_hmc_walk':
147-
int_method = 8
148-
elif method == 'exponential_hmc_walk':
149-
int_method = 9
150-
elif method == 'hmc_leapfrog_gaussian':
151-
int_method = 10
152-
elif method == 'hmc_leapfrog_exponential':
153-
int_method = 11
154-
else:
155-
raise RuntimeError("Uknown MCMC sampling method")
156-
133+
157134
self.polytope_cpp.apply_sampling(walk_len, number_of_points, number_of_points_to_burn, \
158-
int_method, &inner_point_for_c[0], radius, &samples[0,0], variance_value, &bias_vector_[0])
135+
method, &inner_point_for_c[0], radius, &samples[0,0], \
136+
variance_value, &bias_vector_[0], ess)
159137
return np.asarray(samples)
160138

161139

@@ -172,12 +150,12 @@ cdef class HPolytope:
172150
cdef double[:,::1] T_matrix = np.zeros((n_variables, n_variables), dtype=np.float64, order="C")
173151
cdef double[::1] shift = np.zeros((n_variables), dtype=np.float64, order="C")
174152
cdef double round_value
175-
153+
176154
# Get max inscribed ball for the initial polytope
177155
center, radius = inner_ball(self._A, self._b, solver)
178-
156+
179157
cdef double[::1] inner_point_for_c = np.asarray(center)
180-
158+
181159
if rounding_method == 'john_position':
182160
int_method = 1
183161
elif rounding_method == 'isotropic_position':
@@ -190,8 +168,8 @@ cdef class HPolytope:
190168
self.polytope_cpp.apply_rounding(int_method, &new_A[0,0], &new_b[0], &T_matrix[0,0], &shift[0], round_value, &inner_point_for_c[0], radius)
191169

192170
return np.asarray(new_A),np.asarray(new_b),np.asarray(T_matrix),np.asarray(shift),np.asarray(round_value)
193-
194-
171+
172+
195173
# (m)ultiphase (m)onte (c)arlo (s)ampling algorithm to generate steady states of a metabolic network
196174
def mmcs(self, ess = 1000, psrf_check = True, parallelism = False, num_threads = 2, solver = None):
197175

@@ -205,7 +183,7 @@ cdef class HPolytope:
205183
cdef int N_ess = ess
206184
cdef bint check_psrf = bool(psrf_check) # restrict variables to {0,1} using Python's rules
207185
cdef bint parallel = bool(parallelism)
208-
186+
209187
self.polytope_cpp.mmcs_initialize(n_variables, ess, check_psrf, parallel, num_threads)
210188

211189
# Get max inscribed ball for the initial polytope
@@ -215,14 +193,14 @@ cdef class HPolytope:
215193
while True:
216194

217195
check = self.polytope_cpp.mmcs_step(&inner_point_for_c[0], radius, N_samples)
218-
196+
219197
if check > 1.0 and check < 2.0:
220198
break
221199

222200
self.polytope_cpp.get_polytope_as_matrices(&new_A[0,0], &new_b[0])
223201
new_temp_c, radius = inner_ball(np.asarray(new_A), np.asarray(new_b), solver)
224202
inner_point_for_c = np.asarray(new_temp_c)
225-
203+
226204
cdef double[:,::1] samples = np.zeros((n_variables, N_samples), dtype=np.float64, order="C")
227205
self.polytope_cpp.get_mmcs_samples(&T_matrix[0,0], &T_shift[0], &samples[0,0])
228206
self.polytope_cpp.get_polytope_as_matrices(&new_A[0,0], &new_b[0])

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727

2828
source_directory_list = ["dingo", join("dingo", "bindings")]
2929

30-
compiler_args = ["-std=c++11", "-O3", "-DBOOST_NO_AUTO_PTR", "-ldl", "-lm", "-fopenmp"]
30+
compiler_args = ["-std=c++17", "-O3", "-DBOOST_NO_AUTO_PTR", "-ldl", "-lm", "-fopenmp"]
3131
lp_solve_compiler_args = ["-DYY_NEVER_INTERACTIVE", "-DLoadInverseLib=0", "-DLoadLanguageLib=0",
3232
"-DRoleIsExternalInvEngine", "-DINVERSE_ACTIVE=3", "-DLoadableBlasLib=0"]
3333

tests/sampling_no_multiphase.py

Lines changed: 38 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -14,108 +14,61 @@
1414
from dingo import MetabolicNetwork, PolytopeSampler
1515
from dingo.pyoptinterface_based_impl import set_default_solver
1616

17+
def sampling(model, testing_class):
18+
sampler = PolytopeSampler(model)
19+
20+
#Gaussian hmc sampling
21+
steady_states = sampler.generate_steady_states_no_multiphase(method = 'mmcs', ess=1000)
22+
23+
testing_class.assertTrue( steady_states.shape[0] == 95 )
24+
testing_class.assertTrue( steady_states.shape[1] == 1000 )
25+
26+
#Gaussian hmc sampling
27+
steady_states = sampler.generate_steady_states_no_multiphase(method = 'gaussian_hmc_walk', n=500)
28+
29+
testing_class.assertTrue( steady_states.shape[0] == 95 )
30+
testing_class.assertTrue( steady_states.shape[1] == 500 )
31+
32+
#exponential hmc sampling
33+
steady_states = sampler.generate_steady_states_no_multiphase(method = 'exponential_hmc_walk', n=500, variance=50)
34+
35+
testing_class.assertTrue( steady_states.shape[0] == 95 )
36+
testing_class.assertTrue( steady_states.shape[1] == 500 )
37+
38+
#hmc sampling with Gaussian distribution
39+
steady_states = sampler.generate_steady_states_no_multiphase(method = 'hmc_leapfrog_gaussian', n=500)
40+
41+
testing_class.assertTrue( steady_states.shape[0] == 95 )
42+
testing_class.assertTrue( steady_states.shape[1] == 500 )
43+
44+
#hmc sampling with exponential distribution
45+
steady_states = sampler.generate_steady_states_no_multiphase(method = 'hmc_leapfrog_exponential', n=500, variance=50)
46+
47+
testing_class.assertTrue( steady_states.shape[0] == 95 )
48+
testing_class.assertTrue( steady_states.shape[1] == 500 )
49+
50+
#steady_states[12].mean() seems to have a lot of discrepancy between experiments, so we won't check the mean for now
51+
#self.assertTrue( abs( steady_states[12].mean() - 2.504 ) < 1e-03 )
1752

1853
class TestSampling(unittest.TestCase):
1954

2055
def test_sample_json(self):
2156

2257
input_file_json = os.getcwd() + "/ext_data/e_coli_core.json"
2358
model = MetabolicNetwork.from_json( input_file_json )
24-
sampler = PolytopeSampler(model)
25-
26-
#Gaussian hmc sampling
27-
steady_states = sampler.generate_steady_states_no_multiphase(method = 'gaussian_hmc_walk', n=500)
28-
29-
self.assertTrue( steady_states.shape[0] == 95 )
30-
self.assertTrue( steady_states.shape[1] == 500 )
31-
32-
#exponential hmc sampling
33-
steady_states = sampler.generate_steady_states_no_multiphase(method = 'exponential_hmc_walk', n=500, variance=50)
34-
35-
self.assertTrue( steady_states.shape[0] == 95 )
36-
self.assertTrue( steady_states.shape[1] == 500 )
37-
38-
#hmc sampling with Gaussian distribution
39-
steady_states = sampler.generate_steady_states_no_multiphase(method = 'hmc_leapfrog_gaussian', n=500)
40-
41-
self.assertTrue( steady_states.shape[0] == 95 )
42-
self.assertTrue( steady_states.shape[1] == 500 )
43-
44-
#hmc sampling with exponential distribution
45-
steady_states = sampler.generate_steady_states_no_multiphase(method = 'hmc_leapfrog_exponential', n=500, variance=50)
46-
47-
self.assertTrue( steady_states.shape[0] == 95 )
48-
self.assertTrue( steady_states.shape[1] == 500 )
49-
50-
#steady_states[12].mean() seems to have a lot of discrepancy between experiments, so we won't check the mean for now
51-
#self.assertTrue( abs( steady_states[12].mean() - 2.504 ) < 1e-03 )
59+
sampling(model, self)
5260

5361
def test_sample_mat(self):
5462

5563
input_file_mat = os.getcwd() + "/ext_data/e_coli_core.mat"
5664
model = MetabolicNetwork.from_mat(input_file_mat)
57-
sampler = PolytopeSampler(model)
58-
59-
#gaussian hmc sampling
60-
steady_states = sampler.generate_steady_states_no_multiphase(method = 'gaussian_hmc_walk', n=500)
61-
62-
self.assertTrue( steady_states.shape[0] == 95 )
63-
self.assertTrue( steady_states.shape[1] == 500 )
64-
65-
#exponential hmc sampling
66-
steady_states = sampler.generate_steady_states_no_multiphase(method = 'exponential_hmc_walk', n=500, variance=50)
67-
68-
self.assertTrue( steady_states.shape[0] == 95 )
69-
self.assertTrue( steady_states.shape[1] == 500 )
70-
71-
#hmc sampling with Gaussian distribution
72-
steady_states = sampler.generate_steady_states_no_multiphase(method = 'hmc_leapfrog_gaussian', n=500)
73-
74-
self.assertTrue( steady_states.shape[0] == 95 )
75-
self.assertTrue( steady_states.shape[1] == 500 )
76-
77-
#hmc sampling with exponential distribution
78-
steady_states = sampler.generate_steady_states_no_multiphase(method = 'hmc_leapfrog_exponential', n=500, variance=50)
79-
80-
self.assertTrue( steady_states.shape[0] == 95 )
81-
self.assertTrue( steady_states.shape[1] == 500 )
82-
83-
#steady_states[12].mean() seems to have a lot of discrepancy between experiments, so we won't check the mean for now
84-
#self.assertTrue( abs( steady_states[12].mean() - 2.504 ) < 1e-03 )
85-
65+
sampling(model, self)
8666

8767
def test_sample_sbml(self):
8868

8969
input_file_sbml = os.getcwd() + "/ext_data/e_coli_core.xml"
9070
model = MetabolicNetwork.from_sbml( input_file_sbml )
91-
sampler = PolytopeSampler(model)
92-
93-
#gaussian hmc sampling
94-
steady_states = sampler.generate_steady_states_no_multiphase(method = 'gaussian_hmc_walk', n=500)
95-
96-
self.assertTrue( steady_states.shape[0] == 95 )
97-
self.assertTrue( steady_states.shape[1] == 500 )
98-
99-
#exponential hmc sampling
100-
steady_states = sampler.generate_steady_states_no_multiphase(method = 'exponential_hmc_walk', n=500, variance=50)
101-
102-
self.assertTrue( steady_states.shape[0] == 95 )
103-
self.assertTrue( steady_states.shape[1] == 500 )
104-
105-
#hmc sampling with Gaussian distribution
106-
steady_states = sampler.generate_steady_states_no_multiphase(method = 'hmc_leapfrog_gaussian', n=500)
107-
108-
self.assertTrue( steady_states.shape[0] == 95 )
109-
self.assertTrue( steady_states.shape[1] == 500 )
110-
111-
#hmc sampling with exponential distribution
112-
steady_states = sampler.generate_steady_states_no_multiphase(method = 'hmc_leapfrog_exponential', n=500, variance=50)
113-
114-
self.assertTrue( steady_states.shape[0] == 95 )
115-
self.assertTrue( steady_states.shape[1] == 500 )
116-
117-
#steady_states[12].mean() seems to have a lot of discrepancy between experiments, so we won't check the mean for now
118-
#self.assertTrue( abs( steady_states[12].mean() - 2.504 ) < 1e-03 )
71+
sampling(model, self)
11972

12073

12174

volesti

Submodule volesti updated 263 files

0 commit comments

Comments
 (0)