hyperbolic_graph_generator/
hg_math.rs

1
2/*
3 * Hyperbolic Graph Generator
4 *
5 * Rodrigo Aldecoa, Northeastern University
6 * raldecoa@neu.edu
7 *
8 * Copyright (C) 2014 The Regents of the University of California.
9 *
10 * This file is part of the Hyperbolic Graph Generator.
11 *
12 * The Hyperbolic Graph Generator is free software: you can redistribute
13 * it and/or modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, either version 3 of the
15 * License, or  (at your option) any later version.
16 *
17 * The Hyperbolic Graph Generator is distributed in the hope that it will
18 * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
19 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with the Hyperbolic Graph Generator.
24 * If not, see <http://www.gnu.org/licenses/>.
25 *
26 */
27
28use std::process;
29use std::f64;
30
31use crate::hg_debug::*;
32use crate::hg_formats::*;
33
34
35fn rho(alpha: f64, rr: f64, r: f64) -> f64 {
36  return alpha * (alpha * (r - rr)).exp();
37}
38
39fn hg_heaviside(zeta: f64, rr : f64, r1: f64, r2: f64, theta: f64) -> f64 {
40  let x = (1.0 / zeta)
41    * ((zeta * r1).cosh() * (zeta * r2).cosh()
42    - (zeta * r1).sinh() * (zeta * r2).sinh() * theta.cos()).acosh();
43  if x >= rr {
44    return 0.0;
45  } else {
46    return 1.0;
47  }
48}
49
50fn hg_fermi_dirac_std(beta: f64, zeta: f64, rr: f64, r1: f64, r2: f64, theta: f64) -> f64 {
51  let x = (1.0 / zeta)
52    * ((zeta * r1).cosh() * (zeta * r2).cosh()
53      -(zeta * r1).sinh() * (zeta * r2).sinh() * theta.cos()).acosh();
54  
55  return 1.0 / (1.0 + (beta * zeta / 2.0 * (x - rr)).exp());
56}
57
58fn hg_fermi_dirac_scm(beta: f64, rr: f64, r1: f64, r2: f64) -> f64 {
59  return 1.0 / (1.0 + ((beta / 2.0) * (r1 + r2 - rr)).exp());
60}
61
62fn hg_integral_heaviside(x: &[f64], _dim: usize, fp: &HgFParams) -> f64 {
63  return 1.0 / HG_PI
64    * rho(fp.alpha, fp.rr, x[0])
65    * rho(fp.alpha, fp.rr, x[1])
66    * hg_heaviside(fp.zeta, fp.rr, x[0], x[1], x[2]);
67}
68
69fn hg_integral_standard(x: &[f64], _dim: usize, fp: &HgFParams) -> f64 {
70  return 1.0 / HG_PI 
71    * rho(fp.alpha, fp.rr, x[0])
72    * rho(fp.alpha, fp.rr, x[1])
73    * hg_fermi_dirac_std(fp.beta, fp.zeta, fp.rr, x[0], x[1], x[2]);
74}
75
76fn hg_integral_scm(x: &[f64], _dim: usize, fp: &HgFParams) -> f64 {
77  return rho(fp.alpha, fp.rr, x[0])
78    * rho(fp.alpha, fp.rr, x[1])
79    * hg_fermi_dirac_scm(fp.eta, fp.rr, x[0], x[1]);
80}
81
82pub fn hg_get_rr(pg: &HgParametersType, p: &HgAlgorithmParametersType) -> f64 {
83  let calls = 100000; // number of integral iterations
84  let mut xl = [0.0f64; 3];
85  let mut xu = [0.0f64; 3];
86
87  // hyperbolic_rgg and hyperbolic_standard integrals are 3D
88  // soft_configuration_model only 2 dimensions
89  let (dim, mut params) = match pg.gtype {
90    HgGraphType::SoftConfigurationModel => {
91      (2, HgFParams::new(0.0, p.alpha, -1.0, p.eta, -1.0))
92    },
93    HgGraphType::HyperbolicStandard => {
94      xu[2] = HG_PI;
95      (3, HgFParams::new(0.0, p.alpha, pg.zeta_eta, -1.0, 1.0 / pg.temperature))
96    },
97    _ => {
98      xu[2] = HG_PI;
99      (3, HgFParams::new(0.0, p.alpha, pg.zeta_eta, -1.0, -1.0))
100    }
101  };
102
103  let cb = match pg.gtype {
104    HgGraphType::SoftConfigurationModel => hg_integral_scm,
105    HgGraphType::HyperbolicStandard => hg_integral_standard,
106    _ => hg_integral_heaviside
107  };
108
109  rgsl::RngType::env_setup();
110  let t : rgsl::RngType = rgsl::rng::default();
111  let mut r = rgsl::Rng::new(&t).unwrap();
112  let mut s = rgsl::MiserMonteCarlo::new(dim).unwrap();
113
114  let n = pg.expected_n as f64;
115  let k_bar = pg.expected_degree as f64;
116  
117  let eps = 0.01f64; // maximum error for the avg degree
118  let mut low = 0f64;
119  let e = if params.beta < 1.0 { 2.5 } else { 2.0 };
120  let mut high = n.ln().powf(e).max(50.0);
121  let mut res = 0.0;
122  let mut mid = 0.0;
123
124  for _ in 0..5000 {
125    // set midpoint
126    mid = (high + low) / 2.0;
127    xu[0] = mid;
128    xu[1] = mid;
129    params.rr = mid; // R = mid
130
131    // integrate
132    let (_res, _err) = s.integrate(dim, |k| { cb(k, dim, &params) }, &mut xl, &mut xu, calls, &mut r).unwrap();
133    res = _res;
134
135    if res.is_nan() {
136      mid *= 1.00001; 
137    } else {
138      // bisection
139      if (n * res) < k_bar {
140       high = mid;
141      } else {
142       low = mid;
143      }
144    }
145
146    hg_debug!("{} - {} - {}", it, n * res, params.rr);
147    if ((n * res - k_bar).abs() <= eps && !res.is_nan()) || (high <= f64::MIN_POSITIVE) {
148      break;
149    }
150  }
151
152  if res.is_nan() || ((n * res - k_bar).abs() > eps) || (high < f64::MIN_POSITIVE) {
153    eprintln!("Network cannot be generated. Try different parameters.");
154    process::exit(1);
155  }
156
157  return mid;
158}
159
160// Given that |z|>1, we need some transformations
161fn hypergeometric_f(_a: f64, mut b: f64, _c: f64, z: f64) -> f64 {
162  if b == 1.0 {
163    return -1.0 * (1.0 - z).ln() / z;
164  } else {
165    let w = 1.0 / (1.0 - z);
166    
167    // If b is an integer, the function diverges
168    // We just add a small epsilon to make it work
169    if ((b as i64) as f64) == b {
170      let eps = 0.000001f64;
171      b += eps;
172    }
173
174    let f = w * b / (b - 1.0) * rgsl::hypergeometric::hyperg_2F1(1.0, 1.0, 2.0 - b, w)
175      + b * HG_PI * (1.0 - w).powf(-b) * w.powf(b) * (b * HG_PI).sin().powf(-1.0);
176
177    return f;
178  }
179}
180
181pub fn hg_get_lambda(params: &HgParametersType, _p: &HgAlgorithmParametersType) -> f64 {
182  let beta = 1.0 / params.temperature;
183  let n = params.expected_n as f64;
184  let k_bar = params.expected_degree as f64;
185
186  let eps = 0.001; // maximum error for the avg degree
187  let mut low = 1.0;
188  let mut high = rgsl::DBL_MAX;
189  let mut mid;
190
191  loop {
192    // set midpoint
193    mid = (high + low) / 2.0;
194
195    let res = hypergeometric_f(1.0, 1.0 / beta, 1.0 + 1.0 / beta, -mid);
196
197    if (n * res) < k_bar {
198      high = mid;
199    } else {
200      low = mid;
201    }
202
203    //cout << n*res << " - " << mid << endl;
204    if (n * res - k_bar).abs() <= eps && !res.is_nan() {
205      break;
206    }
207  }
208  return mid;
209}