ApproxMasterEqs

Build Status

1. Installation

For now, the package is not an official Julia package. Therefore, the user should manually add it by running:

import Pkg
Pkg.add("https://github.com/d4v1d-cub/ApproxMasterEqs.jl.git")

2. Description

This is a package to perform the numerical integration of some Approximated Master Equations for the dynamics of binary variables in graphs. It contains two different methods: the Cavity Master Equation (CME) [1] [2] [3] and the Conditional Dynamic Approximation (CDA) [4]. Provided a model and some network representing the interactions, the package estimates the local probabilities of observing a specific configuration of the variables at time $t$.

The two main functions implemented in the package so far are CME_KSAT and CDA_KSAT, with the following arguments

(ratefunc::Function, rargs_cst, rarg_build::Function;
         graph::HGraph=build_empty_graph(), N::Int64=0, K::Int64=0,
         alpha::Union{Float64, Int64}=0.0, seed_g::Int64=rand(1:typemax(Int64)),
         links::Matrix{Int8}=Matrix{Int8}(undef, 0, 0), seed_l::Int64=rand(1:typemax(Int64)), 
         tspan::Vector{Float64}=[0.0, 1.0], p0::Float64=0.5, method=VCABM(), eth::Float64=1e-6,
         cbs_save::CallbackSet=CallbackSet(), dt_s::Float64=0.1, abstol::Float64=1e-6, reltol::Float64=1e-3)

In its first version, the package works for models on hypergraphs where only one configuration in the factor nodes unsatisfies the interaction (K-SAT like). This contains as a particular case a model with pairwise interactions (2-SAT like)

3. How to provide a graph?

The package has its own implementation of hypergraph structures.

mutable struct HGraph
    # This structure holds the hypergraph information
        N::Int64                                   # number of variable nodes
        M::Int64                                   # number of hyperedges
        K::Int64                                   # number of nodes in each hyperedge
        var_2_he::Array{Array{Int64, 1}, 1}        # list of hyperedges for each variable
        he_2_var::Matrix{Int64}                    # list of variables per hyperedge
        degrees::Vector{Int64}                     # list of variable nodes' degrees
        nodes_in::Array{Dict{Int64, Int64}, 1}     # dictionary with key=node_index and val=place_in_he
        #.......
end

that can be initialized with

build_HGraph(var_2_he::Array{Array{Int64, 1}, 1} , he_2_var::Matrix{Int64}, degrees::Vector{Int64})

There are a couple of implemented examples:

build_RR_HGraph(N::Int64, c::Int64, K::Int64, idum::Int64=rand(1:typemax(Int64)))          # Random Regular Graphs
build_ER_HGraph(N::Int64, c::Union{Float64, Int64}, K::Int64, idum::Int64=rand(1:typemax(Int64)))  # Erdös-Rényi

If the user does not provide a graph to the functions CME_KSAT or CDA_KSAT, an empty graph is taken as default. The functions then expect to receive three numbers: $N$, $K$, and $\alpha$. These, together with the optional seed_g (seed for the random generator), are used to create an Erdös-Rényi hypergraph with size $N$, $K$ nodes per hyperedge and mean node connectivity $c=\alpha K$.

The couplings or links for the K-SAT boolean formula can be input via the parameter links::Matrix{Float64}. The indexes are links[he, i] with $he=1, \ldots, M$ and $i=1, \ldots, K$, where $M$ is the number of hyperedges in the graph. If the user does not input any matrix of links, the default is to randomly choose one.

For a neat example see the end of the Section 5

4. How to provide a model?

The information about the interactions goes into the transition rates. These are related to the first three arguments of the functions CME_KSAT and CDA_KSAT:

ratefunc::Function            # function that computes the transition rate
        rargs_cst                     # constant arguments that 'ratefunc' will receive when evaluated
        rarg_build::Function          # function that computes the non-constant arguments to be obtained
                                      # at each integration step and then passed to 'ratefunc'

The structure of ratefunc must fit the following

ratefunc(Ep::Int64, Em::Int64, rateargs...)

where Ep is the local energy when the variable is positive $(\sigma=1)$ and Em is the local energy when the variable is negative $(\sigma=-1)$.

To build the rest of the arguments the user should use a function like

rarg_build(graph::HGraph, st::State_CME, rargs_cst...)
        rarg_build(graph::HGraph, st::State_CDA, rargs_cst...)

The first argument is the graph, with all the associated information (see previous section). The second argument is a struct with the following information:

mutable struct State_CME
    p_cav::Array{Float64, 4}     # cavity conditional probabilities p_cav[hyperedge, site_cond, s_cond, chain]
    probi::Vector{Float64}       # single-site probabilities probi[node]
    pu_cond::Array{Float64, 3}   # cavity conditional probabilities of partially unsatisfied
                                 # hyperedges pu_cond[hyperedge, site_cond, s_cond]
    p_joint_u::Vector{Float64}   # probability of having the hyperedge unsatisfied p_joint_u[hyperedge]
end
mutable struct State_CDA
    p_joint::Matrix{Float64}     # joint probabilities p_joint[hyperedge, chain]
    pu_cond::Array{Float64, 3}   # conditional probabilities of partially unsatisfied
                                 # hyperedges pu_cond[hyperedge, site_cond, s_cond]
    p_joint_u::Vector{Float64}   # probability of having the hyperedge unsatisfied p_joint_u[hyperedge]
end

All the other constant arguments of the transition rates must go into rargs_cst...

The function rarg_build() must return a list of arguments ready to be inserted into the function ratefunc

As an example, let us see the implementation of the Focused Metropolis Search algorithm for K-SAT optimization

# This function computes the energy in a K-SAT formula, where there is only one unsatisfied
# configuration of the variables in a clause
function ener(p_joint_u::Vector{Float64})
    e = 0.0
    for he in eachindex(p_joint_u)
        e += p_joint_u[he]
    end
    return e
end

# This is the rate of FMS algorithm for KSAT
function rate_FMS_KSAT(Ep::Int64, Em::Int64, eta::Float64, avE::Float64, K::Number, N::Int64)
    dE = Em - Ep
    if dE > 0
        return Ep * N / K / avE * eta ^ dE
    else
        return Ep * N / K / avE
    end
end


# Each rate function needs some specific arguments. The general form of these functions 
function build_args_rate_FMS(graph::HGraph, st::State_CME, eta::Float64)
    avE = ener(st.p_joint_u)
    return eta, avE, graph.K, graph.N
end


# Each rate function needs some specific arguments. The general form of these functions 
function build_args_rate_FMS(graph::HGraph, st::State_CDA, eta::Float64)
    avE = ener(st.p_joint_u)
    return eta, avE, graph.K, graph.N
end

5. How to numerically integrate?

A simple example is written in the file "package_dir/test/runtests.jl"

using ApproxMasEq
using Test

N = 10
alpha = 2
K = 3
seed = 1

eta = 1.0
rargs = [eta]

answ_CME = CME_KSAT(rate_FMS_KSAT, rargs, build_args_rate_FMS, N=N, K=K, alpha=alpha, seed_g=seed)
answ_CDA = CDA_KSAT(rate_FMS_KSAT, rargs, build_args_rate_FMS, N=N, K=K, alpha=alpha, seed_g=seed)

Here, a hypergraph with $N=10$ nodes, $K=3$ node per hyperedge and mean connectivity $c=\alpha K = 6$ is randomly built with seed_g=1.

The model is the one in the example of Section 4: Focused Metropolis Search algorithm in K-SAT. The constant argument for the transition rates is eta=1 ($\eta=1$).

Other keyword arguments that can be specified before the numerical integration:

  • p0::Float=0.5 is the probability for generating the initial conditions. Every variable is set to 1 or -1 with $p(1) = 1-p(-1) = p_0$.
  • tspan::Vector{Float64}=[0.0, 1.0] is the time interval for the integration.
  • method=VCABM() is the numerical method for the integration performed with the package OrdinaryDiffEqs. See the full list here.
  • abstol::Float64=1e-6 absolute tolerance for the numerical integration with OrdinaryDiffEqs.
  • reltol::Float64=1e-6 relative tolerance for the numerical integration with OrdinaryDiffEqs.

6. Accessing the results

At this point, the user should be able to run a simple example and collect the output of the functions CME_KSAT or CDA_KSAT. These functions return an object of type SciMLBase.ODESolution (see here) with the solution saved in tspan sampled at intervals of length dts. The latter is a parameter of the functions CME_KSAT and CDA_KSAT (dt_s::Float64=0.1).

To get other quantities the user should use callbacks. This is allowed by the package DiffEqCallbacks. A stopping condition is implemented by default that stops the integration when the energy goes below a given value eth. This is also a parameter of the functions CME_KSAT and CDA_KSAT (eth::Float64=1e-6).

The following shows how to save some quantity at each integration step. This is implemented via a SavingCallback (see here). The general form of a function to be used as a callback is

function save_something(u, t, integrator)
     # compute something
     return #something
end

Thus, the package implements certain pre-defined requests for the numerical integrator.

The user could:

  • get_graph(integrator) returns the graph::HGraph variable
  • reshape_u_to_probs_CME(u::Vector{Float64}, integrator) reshapes the vector $u$ to the probabilities involved in the CME: 'p_cav' and 'probi' (see Section 4).
  • reshape_u_to_probs_CDA(u::Vector{Float64}, integrator) reshapes the vector $u$ to the probability involved in the CDA: 'p_joint' (see Section 4).
  • get_pju_CME(u::Vector{Float64}, integrator) computes the probability of the unsatisfied configuration for each hyperedge in the CME (see Section 4).
  • get_pju_CDA(u::Vector{Float64}, integrator) computes the probability of the unsatisfied configuration for each hyperedge in the CDA (see Section 4).

With this, the user can define a callback that, for example, saves the system's energy at each step of the CDA's integration:

function save_ener_CDA(u, t, integrator)
    p_joint_u = get_pju_CDA(u, integrator)
    e = ener(p_joint_u)
    return e
end

where ener() is simply

function ener(p_joint_u::Vector{Float64})
    e = 0.0
    for he in eachindex(p_joint_u)
        e += p_joint_u[he]
    end
    return e
end

The numerical integration that saves the energy at each step can be performed by running:

using ApproxMasEq
using OrdinaryDiffEq, DiffEqCallbacks, Sundials

N = 100
alpha = 3.5
K = 3

eta = 0.7
rargs = [eta]

t0 = 0.0
tlim = 10.0

saved_eners_CDA = SavedValues(Float64, Float64)
cb_ener_CDA = SavingCallback(save_ener_CDA, saved_eners_CDA)
cbs_save_CDA = CallbackSet(cb_ener_CDA)

answ = CDA_KSAT(rate_FMS_KSAT, rargs, build_args_rate_FMS, N=N, K=K, alpha=alpha, tspan=[t0, tlim], cbs_save=cbs_save_CDA)

where the callback is passed as a parameter. The function receives a CallbackSet, so different callbacks can be passed at once, via the argument cbs_save::CallbackSet=CallbackSet(). In the line cbs_save_CDA = CallbackSet(cb_ener_CDA) the custom callback is cast to an object of type CallbackSet and then put as an argument of CDA_KSAT.

Something analogous can be done for the function CME_KSAT

References

[1] E. Aurell, G. D. Ferraro, E. Domínguez, and R. Mulet. A cavity master equation for the continuous time dynamics of discrete spins models. Physical Review E, 95:052119, 2017.

[2] E. Aurell, E. Domínguez, D. Machado and R. Mulet. Exploring the diluted ferromagnetic p-spin model with a cavity master equation. Physical Review E, 97:050103(R), 2018

[3] E. Aurell, E. Domínguez, D. Machado and R. Mulet. A theory non-equilibrium local search on random satisfaction problems. Physical Review Letters, 123:230602, 2019

[4] D. Machado, R. Mulet and F. Ricci-Tersenghi. Improved mean-field dynamical equations are able to detect the two-step relaxation in glassy dynamics at low temperatures. Journal of Statistical Mechanics: Theory and Experiment, 2023:123301