Commit 864c5f68 authored by Tiago Peixoto's avatar Tiago Peixoto
Browse files

inference: some improvements to the multicanonical code

parent 413c28a1
Pipeline #148 failed with stage
...@@ -139,14 +139,12 @@ boost::python::tuple bethe_entropy(GraphInterface& gi, size_t B, boost::any op, ...@@ -139,14 +139,12 @@ boost::python::tuple bethe_entropy(GraphInterface& gi, size_t B, boost::any op,
boost::any opv) boost::any opv)
{ {
typedef vprop_map_t<vector<double>>::type vmap_t; typedef vprop_map_t<vector<double>>::type vmap_t;
typedef eprop_map_t<vector<int32_t>>::type emap_t;
emap_t p = any_cast<emap_t>(op);
vmap_t pv = any_cast<vmap_t>(opv); vmap_t pv = any_cast<vmap_t>(opv);
double H=0, sH=0, Hmf=0, sHmf=0; double H=0, sH=0, Hmf=0, sHmf=0;
run_action<graph_tool::all_graph_views, boost::mpl::true_>() run_action<>()
(gi, (gi,
[&](auto& g) [&](auto& g, auto p)
{ {
for (auto v : vertices_range(g)) for (auto v : vertices_range(g))
{ {
...@@ -204,7 +202,8 @@ boost::python::tuple bethe_entropy(GraphInterface& gi, size_t B, boost::any op, ...@@ -204,7 +202,8 @@ boost::python::tuple bethe_entropy(GraphInterface& gi, size_t B, boost::any op,
sHmf += pow((log(pi) + 1) * sqrt(pi / sum), 2); sHmf += pow((log(pi) + 1) * sqrt(pi / sum), 2);
} }
} }
})(); },
edge_scalar_vector_properties())(op);
return boost::python::make_tuple(H, sH, Hmf, sHmf); return boost::python::make_tuple(H, sH, Hmf, sHmf);
} }
......
...@@ -63,7 +63,8 @@ python::object multicanonical_layered_sweep(python::object omulticanonical_state ...@@ -63,7 +63,8 @@ python::object multicanonical_layered_sweep(python::object omulticanonical_state
[&](auto& s) [&](auto& s)
{ {
auto ret_ = multicanonical_sweep(s, rng); auto ret_ = multicanonical_sweep(s, rng);
ret = python::make_tuple(ret_.first, ret_.second); ret = python::make_tuple(ret_.first, ret_.second,
s._f, s._time, s._refine);
}); });
}, },
false); false);
......
...@@ -64,7 +64,8 @@ multicanonical_layered_overlap_sweep(python::object omulticanonical_state, ...@@ -64,7 +64,8 @@ multicanonical_layered_overlap_sweep(python::object omulticanonical_state,
[&](auto& s) [&](auto& s)
{ {
auto ret_ = multicanonical_sweep(s, rng); auto ret_ = multicanonical_sweep(s, rng);
ret = python::make_tuple(ret_.first, ret_.second); ret = python::make_tuple(ret_.first, ret_.second,
s._f, s._time, s._refine);
}); });
}, },
false); false);
......
...@@ -36,8 +36,8 @@ GEN_DISPATCH(multicanonical_block_state, ...@@ -36,8 +36,8 @@ GEN_DISPATCH(multicanonical_block_state,
MULTICANONICAL_BLOCK_STATE_params(State)) MULTICANONICAL_BLOCK_STATE_params(State))
python::object do_multicanonical_sweep(python::object omulticanonical_state, python::object do_multicanonical_sweep(python::object omulticanonical_state,
python::object oblock_state, python::object oblock_state,
rng_t& rng) rng_t& rng)
{ {
python::object ret; python::object ret;
auto dispatch = [&](auto& block_state) auto dispatch = [&](auto& block_state)
...@@ -50,7 +50,8 @@ python::object do_multicanonical_sweep(python::object omulticanonical_state, ...@@ -50,7 +50,8 @@ python::object do_multicanonical_sweep(python::object omulticanonical_state,
[&](auto& s) [&](auto& s)
{ {
auto ret_ = multicanonical_sweep(s, rng); auto ret_ = multicanonical_sweep(s, rng);
ret = python::make_tuple(ret_.first, ret_.second); ret = python::make_tuple(ret_.first, ret_.second, s._f,
s._time);
}); });
}; };
block_state::dispatch(oblock_state, dispatch); block_state::dispatch(oblock_state, dispatch);
......
...@@ -40,6 +40,8 @@ using namespace std; ...@@ -40,6 +40,8 @@ using namespace std;
((S_min, , double, 0)) \ ((S_min, , double, 0)) \
((S_max, , double, 0)) \ ((S_max, , double, 0)) \
((f, , double, 0)) \ ((f, , double, 0)) \
((time, , double, 0)) \
((refine, , bool, 0)) \
((S, , double, 0)) \ ((S, , double, 0)) \
((E,, size_t, 0)) \ ((E,, size_t, 0)) \
((vlist,&, std::vector<size_t>&, 0)) \ ((vlist,&, std::vector<size_t>&, 0)) \
......
...@@ -26,7 +26,7 @@ using namespace boost; ...@@ -26,7 +26,7 @@ using namespace boost;
using namespace graph_tool; using namespace graph_tool;
void collect_vertex_marginals(GraphInterface& gi, boost::any ob, void collect_vertex_marginals(GraphInterface& gi, boost::any ob,
boost::any op) boost::any op, double update)
{ {
typedef vprop_map_t<int32_t>::type vmap_t; typedef vprop_map_t<int32_t>::type vmap_t;
auto b = any_cast<vmap_t>(ob).get_unchecked(); auto b = any_cast<vmap_t>(ob).get_unchecked();
...@@ -34,22 +34,24 @@ void collect_vertex_marginals(GraphInterface& gi, boost::any ob, ...@@ -34,22 +34,24 @@ void collect_vertex_marginals(GraphInterface& gi, boost::any ob,
run_action<>() run_action<>()
(gi, [&](auto& g, auto p) (gi, [&](auto& g, auto p)
{ {
typename property_traits<decltype(p)>::value_type::value_type
up = update;
parallel_vertex_loop parallel_vertex_loop
(g, (g,
[&](auto v) [&](auto v)
{ {
auto r = b[v]; auto r = b[v];
auto& pv = p[v]; auto& pv = p[v];
if (pv.size() <= size_t(r)) if (pv.size() <= size_t(r))
pv.resize(r + 1); pv.resize(r + 1);
pv[r]++; pv[r] += up;
}); });
}, },
vertex_scalar_vector_properties())(op); vertex_scalar_vector_properties())(op);
} }
void collect_edge_marginals(GraphInterface& gi, size_t B, boost::any ob, void collect_edge_marginals(GraphInterface& gi, size_t B, boost::any ob,
boost::any op) boost::any op, double update)
{ {
typedef vprop_map_t<int32_t>::type vmap_t; typedef vprop_map_t<int32_t>::type vmap_t;
auto b = any_cast<vmap_t>(ob).get_unchecked(); auto b = any_cast<vmap_t>(ob).get_unchecked();
...@@ -58,6 +60,8 @@ void collect_edge_marginals(GraphInterface& gi, size_t B, boost::any ob, ...@@ -58,6 +60,8 @@ void collect_edge_marginals(GraphInterface& gi, size_t B, boost::any ob,
(gi, (gi,
[&](auto& g, auto p) [&](auto& g, auto p)
{ {
typename property_traits<decltype(p)>::value_type::value_type
up = update;
parallel_edge_loop parallel_edge_loop
(g, (g,
[&](const auto& e) [&](const auto& e)
...@@ -72,7 +76,7 @@ void collect_edge_marginals(GraphInterface& gi, size_t B, boost::any ob, ...@@ -72,7 +76,7 @@ void collect_edge_marginals(GraphInterface& gi, size_t B, boost::any ob,
if (pv.size() < B * B) if (pv.size() < B * B)
pv.resize(B * B); pv.resize(B * B);
size_t j = r + B * s; size_t j = r + B * s;
pv[j]++; pv[j] += up;
}); });
}, },
edge_scalar_vector_properties())(op); edge_scalar_vector_properties())(op);
......
...@@ -49,7 +49,6 @@ auto multicanonical_sweep(MulticanonicalState& state, RNG& rng) ...@@ -49,7 +49,6 @@ auto multicanonical_sweep(MulticanonicalState& state, RNG& rng)
int M = hist.size(); int M = hist.size();
double S_min = state._S_min; double S_min = state._S_min;
double S_max = state._S_max; double S_max = state._S_max;
double f = state._f;
auto get_bin = [&](double x) -> int auto get_bin = [&](double x) -> int
{ {
...@@ -100,7 +99,11 @@ auto multicanonical_sweep(MulticanonicalState& state, RNG& rng) ...@@ -100,7 +99,11 @@ auto multicanonical_sweep(MulticanonicalState& state, RNG& rng)
} }
hist[i]++; hist[i]++;
dens[i] += f; dens[i] += state._f;
state._time += 1./M;
if (state._refine)
state._f = 1. / state._time;
} }
return make_pair(S, nmoves); return make_pair(S, nmoves);
} }
......
...@@ -3086,7 +3086,7 @@ def _set_array_view(self, v): ...@@ -3086,7 +3086,7 @@ def _set_array_view(self, v):
self.get_array()[:] = v self.get_array()[:] = v
vector_types = [Vector_bool, Vector_int16_t, Vector_int32_t, Vector_int64_t, vector_types = [Vector_bool, Vector_int16_t, Vector_int32_t, Vector_int64_t,
Vector_double, Vector_long_double] Vector_double, Vector_long_double, Vector_size_t]
for vt in vector_types: for vt in vector_types:
vt.a = property(_get_array_view, _set_array_view, vt.a = property(_get_array_view, _set_array_view,
doc=r"""Shortcut to the `get_array` method as an attribute.""") doc=r"""Shortcut to the `get_array` method as an attribute.""")
......
...@@ -286,7 +286,6 @@ class BlockState(object): ...@@ -286,7 +286,6 @@ class BlockState(object):
def __setstate__(self, state): def __setstate__(self, state):
conv_pickle_state(state) conv_pickle_state(state)
self.__init__(**state) self.__init__(**state)
return state
def get_block_state(self, b=None, vweight=False, deg_corr=False, **kwargs): def get_block_state(self, b=None, vweight=False, deg_corr=False, **kwargs):
r"""Returns a :class:`~graph_tool.community.BlockState`` corresponding to the r"""Returns a :class:`~graph_tool.community.BlockState`` corresponding to the
...@@ -950,9 +949,9 @@ class BlockState(object): ...@@ -950,9 +949,9 @@ class BlockState(object):
return libinference.multicanonical_sweep(multicanonical_state, return libinference.multicanonical_sweep(multicanonical_state,
self._state, _get_rng()) self._state, _get_rng())
def multicanonical_sweep(self, m_state, f=1., c=1., niter=1, def multicanonical_sweep(self, m_state, c=1., niter=1, entropy_args={},
entropy_args={}, allow_empty=True, vertices=None, allow_empty=True, vertices=None, block_list=None,
block_list=None, verbose=False): verbose=False):
r"""Perform ``niter`` sweeps of a non-markovian multicanonical sampling using r"""Perform ``niter`` sweeps of a non-markovian multicanonical sampling using
the Wang-Landau algorithm. the Wang-Landau algorithm.
...@@ -961,8 +960,6 @@ class BlockState(object): ...@@ -961,8 +960,6 @@ class BlockState(object):
m_state : :class:`~graph_tool.inference.MulticanonicalState` m_state : :class:`~graph_tool.inference.MulticanonicalState`
:class:`~graph_tool.inference.MulticanonicalState` instance :class:`~graph_tool.inference.MulticanonicalState` instance
containing the current state of the Wang-Landau run. containing the current state of the Wang-Landau run.
f : ``float`` (optional, default: ``1.``)
Density of states update step.
c : ``float`` (optional, default: ``1.``) c : ``float`` (optional, default: ``1.``)
Sampling parameter ``c`` for move proposals: For :math:`c\to 0` the Sampling parameter ``c`` for move proposals: For :math:`c\to 0` the
blocks are sampled according to the local neighbourhood of a given blocks are sampled according to the local neighbourhood of a given
...@@ -992,13 +989,18 @@ class BlockState(object): ...@@ -992,13 +989,18 @@ class BlockState(object):
Entropy difference after the sweeps. Entropy difference after the sweeps.
nmoves : ``int`` nmoves : ``int``
Number of vertices moved. Number of vertices moved.
References References
---------- ----------
.. [wang-efficient-2001] Fugao Wang, D. P. Landau, "An efficient, multiple .. [wang-efficient-2001] Fugao Wang, D. P. Landau, "An efficient, multiple
range random walk algorithm to calculate the density of states", Phys. range random walk algorithm to calculate the density of states", Phys.
Rev. Lett. 86, 2050 (2001), :doi:`10.1103/PhysRevLett.86.2050`, Rev. Lett. 86, 2050 (2001), :doi:`10.1103/PhysRevLett.86.2050`,
:arxiv:`cond-mat/0011174` :arxiv:`cond-mat/0011174`
.. [belardinelli-wang-2007] R. E. Belardinelli, V. D. Pereyra,
"Wang-Landau algorithm: A theoretical analysis of the saturation of
the error", J. Chem. Phys. 127, 184105 (2007),
:doi:`10.1063/1.2803061`, :arxiv:`cond-mat/0702414`
""" """
niter *= self.g.num_vertices() niter *= self.g.num_vertices()
...@@ -1032,12 +1034,19 @@ class BlockState(object): ...@@ -1032,12 +1034,19 @@ class BlockState(object):
["xi_fast", "deg_dl_alt"])) ["xi_fast", "deg_dl_alt"]))
multi_state.state = self._state multi_state.state = self._state
multi_state.f = m_state._f
multi_state.time = m_state._time
multi_state.refine = m_state._refine
multi_state.S_min = m_state._S_min multi_state.S_min = m_state._S_min
multi_state.S_max = m_state._S_max multi_state.S_max = m_state._S_max
multi_state.hist = m_state._hist multi_state.hist = m_state._hist
multi_state.dens = m_state._density multi_state.dens = m_state._density
S, nmoves = self._multicanonical_sweep_dispatch(multi_state) S, nmoves, f, time = \
self._multicanonical_sweep_dispatch(multi_state)
m_state._f = f
m_state._time = time
if _bm_test(): if _bm_test():
assert self._check_clabel(), "invalid clabel after sweep" assert self._check_clabel(), "invalid clabel after sweep"
...@@ -1154,7 +1163,7 @@ class BlockState(object): ...@@ -1154,7 +1163,7 @@ class BlockState(object):
assert nB == B, "wrong number of groups after shrink: %d (should be %d)" % (nB, B) assert nB == B, "wrong number of groups after shrink: %d (should be %d)" % (nB, B)
return state return state
def collect_edge_marginals(self, p=None): def collect_edge_marginals(self, p=None, update=1.):
r"""Collect the edge marginal histogram, which counts the number of times r"""Collect the edge marginal histogram, which counts the number of times
the endpoints of each node have been assigned to a given block pair. the endpoints of each node have been assigned to a given block pair.
...@@ -1168,7 +1177,10 @@ class BlockState(object): ...@@ -1168,7 +1177,10 @@ class BlockState(object):
membership counts. Each vector entry corresponds to ``b[i] + B * membership counts. Each vector entry corresponds to ``b[i] + B *
b[j]``, where ``b`` is the block membership and ``i = min(source(e), b[j]``, where ``b`` is the block membership and ``i = min(source(e),
target(e))`` and ``j = max(source(e), target(e))``. If not provided, an target(e))`` and ``j = max(source(e), target(e))``. If not provided, an
empty histogram will be created. empty histogram will be created
update : float (optional, default: ``1.``)
Each call increases the current count by the amount given by this
parameter.
Returns Returns
------- -------
...@@ -1199,15 +1211,16 @@ class BlockState(object): ...@@ -1199,15 +1211,16 @@ class BlockState(object):
""" """
if p is None: if p is None:
p = self.g.new_ep("vector<int>") p = self.g.new_ep("vector<double>")
libinference.edge_marginals(self.g._Graph__graph, libinference.edge_marginals(self.g._Graph__graph,
self.B, self.B,
_prop("v", self.g, self.b), _prop("v", self.g, self.b),
_prop("e", self.g, p)) _prop("e", self.g, p),
update)
return p return p
def collect_vertex_marginals(self, p=None): def collect_vertex_marginals(self, p=None, update=1.):
r"""Collect the vertex marginal histogram, which counts the number of times a r"""Collect the vertex marginal histogram, which counts the number of times a
node was assigned to a given block. node was assigned to a given block.
...@@ -1219,6 +1232,9 @@ class BlockState(object): ...@@ -1219,6 +1232,9 @@ class BlockState(object):
p : :class:`~graph_tool.PropertyMap` (optional, default: ``None``) p : :class:`~graph_tool.PropertyMap` (optional, default: ``None``)
Vertex property map with vector-type values, storing the previous block Vertex property map with vector-type values, storing the previous block
membership counts. If not provided, an empty histogram will be created. membership counts. If not provided, an empty histogram will be created.
update : float (optional, default: ``1.``)
Each call increases the current count by the amount given by this
parameter.
Returns Returns
------- -------
...@@ -1262,11 +1278,12 @@ class BlockState(object): ...@@ -1262,11 +1278,12 @@ class BlockState(object):
""" """
if p is None: if p is None:
p = self.g.new_vp("vector<int>") p = self.g.new_vp("vector<double>")
libinference.vertex_marginals(self.g._Graph__graph, libinference.vertex_marginals(self.g._Graph__graph,
_prop("v", self.g, self.b), _prop("v", self.g, self.b),
_prop("v", self.g, p)) _prop("v", self.g, p),
update)
return p return p
def draw(self, **kwargs): def draw(self, **kwargs):
......
...@@ -122,6 +122,21 @@ class EMBlockState(object): ...@@ -122,6 +122,21 @@ class EMBlockState(object):
self.prs[r, s] = self.N * m[r, s] / (init_state.wr[r] * init_state.wr[s]) self.prs[r, s] = self.N * m[r, s] / (init_state.wr[r] * init_state.wr[s])
self.prs[s, r] = self.prs[r, s] self.prs[s, r] = self.prs[r, s]
def __getstate__(self):
state = [self.g, self.B, self.vm, self.em_s, self.em_t, self.wr,
self.prs]
return state
def __setstate__(self, state):
conv_pickle_state(state)
g, B, vm, em_s, em_t, wr, prs = state
self.__init__(g, B)
g.copy_property(vm, self.vm)
g.copy_property(em_s, self.em_s)
g.copy_property(em_t, self.em_t)
self.wr[:] = wr
self.prs[:,:] = prs
def get_vertex_marginals(self): def get_vertex_marginals(self):
"""Return the vertex marginals.""" """Return the vertex marginals."""
return self.vm return self.vm
......
...@@ -301,7 +301,6 @@ class LayeredBlockState(OverlapBlockState, BlockState): ...@@ -301,7 +301,6 @@ class LayeredBlockState(OverlapBlockState, BlockState):
def __setstate__(self, state): def __setstate__(self, state):
conv_pickle_state(state) conv_pickle_state(state)
self.__init__(**state) self.__init__(**state)
return state
def __copy__(self): def __copy__(self):
return self.copy() return self.copy()
......
...@@ -381,6 +381,8 @@ class MulticanonicalState(object): ...@@ -381,6 +381,8 @@ class MulticanonicalState(object):
Parameters Parameters
---------- ----------
g : :class:`~graph_tool.Graph`
Graph to be modelled.
S_min : ``float`` S_min : ``float``
Minimum energy. Minimum energy.
S_max : ``float`` S_max : ``float``
...@@ -389,26 +391,34 @@ class MulticanonicalState(object): ...@@ -389,26 +391,34 @@ class MulticanonicalState(object):
Number of bins. Number of bins.
""" """
def __init__(self, S_min, S_max, nbins=1000): def __init__(self, g, S_min, S_max, nbins=1000):
self._g = g
self._N = g.num_vertices()
self._S_min = S_min self._S_min = S_min
self._S_max = S_max self._S_max = S_max
self._density = Vector_double() self._density = Vector_double()
self._density.resize(nbins) self._density.resize(nbins)
self._hist = Vector_size_t() self._hist = Vector_size_t()
self._hist.resize(nbins) self._hist.resize(nbins)
self._perm_hist = numpy.zeros(nbins, dtype="int")
self._f = None
self._time = 0
self._refine = False
def __getstate__(self): def __getstate__(self):
state = [self._S_min, self._S_max, state = [self._g, self._S_min, self._S_max,
numpy.array(self._density.get_array()), numpy.array(self._density.a), numpy.array(self._hist.a),
numpy.array(self._hist.get_array())] numpy.array(self._perm_hist), self._f, self._time,
self._refine]
return state return state
def __setstate__(self, state): def __setstate__(self, state):
S_min, S_max, density, hist = state g, S_min, S_max, density, hist, phist, self._f, self._time, \
self.__init__(S_min, S_max, len(hist)) self._refine = state
self._density.get_array()[:] = density self.__init__(g, S_min, S_max, len(hist))
self._hist.get_array()[:] = hist self._density.a[:] = density
return state self._hist.a[:] = hist
self._perm_hist[:] = phist
def get_energies(self): def get_energies(self):
"Get energy bounds." "Get energy bounds."
...@@ -416,7 +426,7 @@ class MulticanonicalState(object): ...@@ -416,7 +426,7 @@ class MulticanonicalState(object):
def get_allowed_energies(self): def get_allowed_energies(self):
"Get allowed energy bounds." "Get allowed energy bounds."
h = self._hist.get_array() h = self._hist.a
Ss = self.get_range() Ss = self.get_range()
Ss = Ss[h > 0] Ss = Ss[h > 0]
return Ss[0], Ss[-1] return Ss[0], Ss[-1]
...@@ -425,63 +435,66 @@ class MulticanonicalState(object): ...@@ -425,63 +435,66 @@ class MulticanonicalState(object):
"Get energy range." "Get energy range."
return numpy.linspace(self._S_min, self._S_max, len(self._hist)) return numpy.linspace(self._S_min, self._S_max, len(self._hist))
def get_density(self): def get_density(self, B=None):
"""Get density of states, normalized so that the **integral** over the energy """Get density of states, normalized so that total sum is :math:`B^N`, where
range is unity.""" :math:`B` is the number of groups, and :math:`N` is the number of
r = numpy.array(self._density.get_array()) nodes. If not supplied :math:`B=N` is assumed.
"""
r = numpy.array(self._density.a)