1377 lines
43 KiB
C
1377 lines
43 KiB
C
/* plfit.c
|
|
*
|
|
* Copyright (C) 2010-2011 Tamas Nepusz
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or (at
|
|
* your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <float.h>
|
|
#include <math.h>
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "gss.h"
|
|
#include "lbfgs.h"
|
|
#include "plfit.h"
|
|
#include "kolmogorov.h"
|
|
#include "hzeta.h"
|
|
|
|
/* #define PLFIT_DEBUG */
|
|
|
|
#define DATA_POINTS_CHECK \
|
|
if (n <= 0) { \
|
|
PLFIT_ERROR("no data points", PLFIT_EINVAL); \
|
|
}
|
|
|
|
#define XMIN_CHECK_ZERO \
|
|
if (xmin <= 0) { \
|
|
PLFIT_ERROR("xmin must be greater than zero", PLFIT_EINVAL); \
|
|
}
|
|
#define XMIN_CHECK_ONE \
|
|
if (xmin < 1) { \
|
|
PLFIT_ERROR("xmin must be at least 1", PLFIT_EINVAL); \
|
|
}
|
|
|
|
static int plfit_i_resample_continuous(const double* xs_head, size_t num_smaller,
|
|
size_t n, double alpha, double xmin, size_t num_samples, plfit_mt_rng_t* rng,
|
|
double* result);
|
|
static int plfit_i_resample_discrete(const double* xs_head, size_t num_smaller,
|
|
size_t n, double alpha, double xmin, size_t num_samples, plfit_mt_rng_t* rng,
|
|
double* result);
|
|
|
|
static int double_comparator(const void *a, const void *b) {
|
|
const double *da = (const double*)a;
|
|
const double *db = (const double*)b;
|
|
return (*da > *db) - (*da < *db);
|
|
}
|
|
|
|
static int plfit_i_copy_and_sort(const double* xs, size_t n, double** result) {
|
|
*result = (double*)malloc(sizeof(double) * n);
|
|
if (*result == NULL) {
|
|
PLFIT_ERROR("cannot create sorted copy of input data", PLFIT_ENOMEM);
|
|
}
|
|
|
|
memcpy(*result, xs, sizeof(double) * n);
|
|
qsort(*result, n, sizeof(double), double_comparator);
|
|
|
|
return PLFIT_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Given an unsorted array of doubles, counts how many elements there are that
|
|
* are smaller than a given value.
|
|
*
|
|
* \param begin pointer to the beginning of the array
|
|
* \param end pointer to the first element after the end of the array
|
|
* \param xmin the threshold value
|
|
*
|
|
* \return the nubmer of elements in the array that are smaller than the given
|
|
* value.
|
|
*/
|
|
static size_t count_smaller(const double* begin, const double* end, double xmin) {
|
|
const double* p;
|
|
size_t counter = 0;
|
|
|
|
for (p = begin; p < end; p++) {
|
|
if (*p < xmin) {
|
|
counter++;
|
|
}
|
|
}
|
|
|
|
return counter;
|
|
}
|
|
|
|
/**
|
|
* Given an unsorted array of doubles, return another array that contains the
|
|
* elements that are smaller than a given value
|
|
*
|
|
* \param begin pointer to the beginning of the array
|
|
* \param end pointer to the first element after the end of the array
|
|
* \param xmin the threshold value
|
|
* \param result_length if not \c NULL, the number of unique elements in the
|
|
* given array is returned here
|
|
*
|
|
* \return pointer to the head of the new array or 0 if there is not enough
|
|
* memory
|
|
*/
|
|
static double* extract_smaller(const double* begin, const double* end, double xmin,
|
|
size_t* result_length) {
|
|
size_t counter = count_smaller(begin, end, xmin);
|
|
double *p, *result;
|
|
|
|
result = calloc(counter > 0 ? counter : 1, sizeof(double));
|
|
if (result == NULL)
|
|
return NULL;
|
|
|
|
for (p = result; begin < end; begin++) {
|
|
if (*begin < xmin) {
|
|
*p = *begin;
|
|
p++;
|
|
}
|
|
}
|
|
|
|
if (result_length) {
|
|
*result_length = counter;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Given a sorted array of doubles, return another array that contains pointers
|
|
* into the array for the start of each block of identical elements.
|
|
*
|
|
* \param begin pointer to the beginning of the array
|
|
* \param end pointer to the first element after the end of the array
|
|
* \param result_length if not \c NULL, the number of unique elements in the
|
|
* given array is returned here. It is left unchanged if
|
|
* the function returns with an error.
|
|
*
|
|
* \return pointer to the head of the new array or NULL if there is not enough
|
|
* memory
|
|
*/
|
|
static double** unique_element_pointers(double* begin, double* end, size_t* result_length) {
|
|
double* ptr = begin;
|
|
double** result;
|
|
double prev_x;
|
|
size_t num_elts = 15;
|
|
size_t used_elts = 0;
|
|
|
|
/* Special case: empty array */
|
|
if (begin == end) {
|
|
result = calloc(1, sizeof(double*));
|
|
if (result != NULL) {
|
|
result[0] = 0;
|
|
if (result_length != 0) {
|
|
*result_length = 0;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/* Allocate initial result array, including the guard element */
|
|
result = calloc(num_elts+1, sizeof(double*));
|
|
if (result == NULL)
|
|
return NULL;
|
|
|
|
prev_x = *begin;
|
|
result[used_elts++] = begin;
|
|
|
|
/* Process the input array */
|
|
for (ptr = begin+1; ptr < end; ptr++) {
|
|
if (*ptr == prev_x)
|
|
continue;
|
|
|
|
/* New block found */
|
|
if (used_elts >= num_elts) {
|
|
/* Array full; allocate a new chunk */
|
|
double** tmp;
|
|
num_elts = num_elts*2 + 1;
|
|
tmp = realloc(result, sizeof(double*) * (num_elts+1));
|
|
if (tmp == NULL) {
|
|
free(result);
|
|
return NULL;
|
|
}
|
|
result = tmp;
|
|
}
|
|
|
|
/* Store the new element */
|
|
result[used_elts++] = ptr;
|
|
prev_x = *ptr;
|
|
}
|
|
|
|
/* Calculate the result length */
|
|
if (result_length != 0) {
|
|
*result_length = used_elts;
|
|
}
|
|
|
|
/* Add the guard entry to the end of the result */
|
|
result[used_elts++] = 0;
|
|
|
|
return result;
|
|
}
|
|
|
|
static void plfit_i_perform_finite_size_correction(plfit_result_t* result, size_t n) {
|
|
result->alpha = result->alpha * (n-1) / n + 1.0 / n;
|
|
}
|
|
|
|
/********** Continuous power law distribution fitting **********/
|
|
|
|
static void plfit_i_logsum_less_than_continuous(const double* begin, const double* end,
|
|
double xmin, double* result, size_t* m) {
|
|
double logsum = 0.0;
|
|
size_t count = 0;
|
|
|
|
for (; begin != end; begin++) {
|
|
if (*begin >= xmin) {
|
|
count++;
|
|
logsum += log(*begin / xmin);
|
|
}
|
|
}
|
|
|
|
*m = count;
|
|
*result = logsum;
|
|
}
|
|
|
|
static double plfit_i_logsum_continuous(const double* begin, const double* end, double xmin) {
|
|
double logsum = 0.0;
|
|
for (; begin != end; begin++)
|
|
logsum += log(*begin / xmin);
|
|
return logsum;
|
|
}
|
|
|
|
static int plfit_i_estimate_alpha_continuous(const double* xs, size_t n,
|
|
double xmin, double* alpha) {
|
|
double result;
|
|
size_t m;
|
|
|
|
XMIN_CHECK_ZERO;
|
|
|
|
plfit_i_logsum_less_than_continuous(xs, xs+n, xmin, &result, &m);
|
|
|
|
if (m == 0) {
|
|
PLFIT_ERROR("no data point was larger than xmin", PLFIT_EINVAL);
|
|
}
|
|
|
|
*alpha = 1 + m / result;
|
|
|
|
return PLFIT_SUCCESS;
|
|
}
|
|
|
|
static int plfit_i_estimate_alpha_continuous_sorted(const double* xs, size_t n,
|
|
double xmin, double* alpha) {
|
|
const double* end = xs+n;
|
|
|
|
XMIN_CHECK_ZERO;
|
|
|
|
for (; xs != end && *xs < xmin; xs++);
|
|
if (xs == end) {
|
|
PLFIT_ERROR("no data point was larger than xmin", PLFIT_EINVAL);
|
|
}
|
|
|
|
*alpha = 1 + (end-xs) / plfit_i_logsum_continuous(xs, end, xmin);
|
|
|
|
return PLFIT_SUCCESS;
|
|
}
|
|
|
|
static int plfit_i_ks_test_continuous(const double* xs, const double* xs_end,
|
|
const double alpha, const double xmin, double* D) {
|
|
/* Assumption: xs is sorted and cut off at xmin so the first element is
|
|
* always larger than or equal to xmin. */
|
|
double result = 0, n;
|
|
int m = 0;
|
|
|
|
n = xs_end - xs;
|
|
|
|
while (xs < xs_end) {
|
|
double d = fabs(1-pow(xmin / *xs, alpha-1) - m / n);
|
|
|
|
if (d > result)
|
|
result = d;
|
|
|
|
xs++; m++;
|
|
}
|
|
|
|
*D = result;
|
|
|
|
return PLFIT_SUCCESS;
|
|
}
|
|
|
|
static int plfit_i_calculate_p_value_continuous(const double* xs, size_t n,
|
|
const plfit_continuous_options_t *options, plfit_bool_t xmin_fixed,
|
|
plfit_result_t *result) {
|
|
long int num_trials;
|
|
long int successes = 0;
|
|
double *xs_head;
|
|
size_t num_smaller;
|
|
plfit_continuous_options_t options_no_p_value = *options;
|
|
int retval = PLFIT_SUCCESS;
|
|
|
|
if (options->p_value_method == PLFIT_P_VALUE_SKIP) {
|
|
result->p = NAN;
|
|
return PLFIT_SUCCESS;
|
|
}
|
|
|
|
if (options->p_value_method == PLFIT_P_VALUE_APPROXIMATE) {
|
|
num_smaller = count_smaller(xs, xs + n, result->xmin);
|
|
result->p = plfit_ks_test_one_sample_p(result->D, n - num_smaller);
|
|
return PLFIT_SUCCESS;
|
|
}
|
|
|
|
options_no_p_value.p_value_method = PLFIT_P_VALUE_SKIP;
|
|
num_trials = (long int)(0.25 / options->p_value_precision / options->p_value_precision);
|
|
if (num_trials <= 0) {
|
|
PLFIT_ERROR("invalid p-value precision", PLFIT_EINVAL);
|
|
}
|
|
|
|
/* Extract the head of xs that contains elements smaller than xmin */
|
|
xs_head = extract_smaller(xs, xs+n, result->xmin, &num_smaller);
|
|
if (xs_head == 0)
|
|
PLFIT_ERROR("cannot calculate exact p-value", PLFIT_ENOMEM);
|
|
|
|
#ifdef _OPENMP
|
|
#pragma omp parallel
|
|
#endif
|
|
{
|
|
/* Parallel section starts here. If we are compiling using OpenMP, each
|
|
* thread will use its own RNG that is seeded from the master RNG. If
|
|
* we are compiling without OpenMP, there is only one thread and it uses
|
|
* the master RNG. This section must be critical to ensure that only one
|
|
* thread is using the master RNG at the same time. */
|
|
#ifdef _OPENMP
|
|
plfit_mt_rng_t private_rng;
|
|
#endif
|
|
plfit_mt_rng_t *p_rng;
|
|
double *ys;
|
|
long int i;
|
|
plfit_result_t result_synthetic;
|
|
|
|
#ifdef _OPENMP
|
|
#pragma omp critical
|
|
{
|
|
p_rng = &private_rng;
|
|
plfit_mt_init_from_rng(p_rng, options->rng);
|
|
}
|
|
#else
|
|
p_rng = options->rng;
|
|
#endif
|
|
|
|
/* Allocate memory to sample into */
|
|
ys = calloc(n > 0 ? n : 1, sizeof(double));
|
|
if (ys == 0) {
|
|
retval = PLFIT_ENOMEM;
|
|
} else {
|
|
/* The main for loop starts here. */
|
|
#ifdef _OPENMP
|
|
#pragma omp for reduction(+:successes)
|
|
#endif
|
|
for (i = 0; i < num_trials; i++) {
|
|
plfit_i_resample_continuous(xs_head, num_smaller, n, result->alpha,
|
|
result->xmin, n, p_rng, ys);
|
|
if (xmin_fixed) {
|
|
plfit_estimate_alpha_continuous(ys, n, result->xmin,
|
|
&options_no_p_value, &result_synthetic);
|
|
} else {
|
|
plfit_continuous(ys, n, &options_no_p_value, &result_synthetic);
|
|
}
|
|
if (result_synthetic.D > result->D)
|
|
successes++;
|
|
}
|
|
free(ys);
|
|
}
|
|
|
|
/* End of parallelized part */
|
|
}
|
|
|
|
free(xs_head);
|
|
|
|
if (retval == PLFIT_SUCCESS) {
|
|
result->p = successes / ((double)num_trials);
|
|
} else {
|
|
PLFIT_ERROR("cannot calculate exact p-value", retval);
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
int plfit_log_likelihood_continuous(const double* xs, size_t n, double alpha,
|
|
double xmin, double* L) {
|
|
double logsum, c;
|
|
size_t m;
|
|
|
|
if (alpha <= 1) {
|
|
PLFIT_ERROR("alpha must be greater than one", PLFIT_EINVAL);
|
|
}
|
|
XMIN_CHECK_ZERO;
|
|
|
|
c = (alpha - 1) / xmin;
|
|
plfit_i_logsum_less_than_continuous(xs, xs+n, xmin, &logsum, &m);
|
|
*L = -alpha * logsum + log(c) * m;
|
|
|
|
return PLFIT_SUCCESS;
|
|
}
|
|
|
|
int plfit_estimate_alpha_continuous_sorted(const double* xs, size_t n, double xmin,
|
|
const plfit_continuous_options_t* options, plfit_result_t *result) {
|
|
const double *begin, *end;
|
|
|
|
if (!options)
|
|
options = &plfit_continuous_default_options;
|
|
|
|
begin = xs;
|
|
end = xs + n;
|
|
while (begin < end && *begin < xmin)
|
|
begin++;
|
|
|
|
PLFIT_CHECK(plfit_i_estimate_alpha_continuous_sorted(begin, end-begin,
|
|
xmin, &result->alpha));
|
|
PLFIT_CHECK(plfit_i_ks_test_continuous(begin, end, result->alpha,
|
|
xmin, &result->D));
|
|
|
|
if (options->finite_size_correction)
|
|
plfit_i_perform_finite_size_correction(result, end-begin);
|
|
result->xmin = xmin;
|
|
|
|
PLFIT_CHECK(plfit_log_likelihood_continuous(begin, end-begin, result->alpha,
|
|
result->xmin, &result->L));
|
|
PLFIT_CHECK(plfit_i_calculate_p_value_continuous(xs, n, options, 1, result));
|
|
|
|
return PLFIT_SUCCESS;
|
|
}
|
|
|
|
int plfit_estimate_alpha_continuous(const double* xs, size_t n, double xmin,
|
|
const plfit_continuous_options_t* options, plfit_result_t *result) {
|
|
double *xs_copy;
|
|
|
|
if (!options)
|
|
options = &plfit_continuous_default_options;
|
|
|
|
PLFIT_CHECK(plfit_i_copy_and_sort(xs, n, &xs_copy));
|
|
PLFIT_CHECK(plfit_estimate_alpha_continuous_sorted(xs_copy, n, xmin,
|
|
options, result));
|
|
free(xs_copy);
|
|
|
|
return PLFIT_SUCCESS;
|
|
}
|
|
|
|
typedef struct {
|
|
double *begin; /**< Pointer to the beginning of the array holding the data */
|
|
double *end; /**< Pointer to after the end of the array holding the data */
|
|
double **probes; /**< Pointers to the elements of the array that will be probed */
|
|
size_t num_probes; /**< Number of probes */
|
|
plfit_result_t last; /**< Result of the last evaluation */
|
|
} plfit_continuous_xmin_opt_data_t;
|
|
|
|
static double plfit_i_continuous_xmin_opt_evaluate(void* instance, double x) {
|
|
plfit_continuous_xmin_opt_data_t* data = (plfit_continuous_xmin_opt_data_t*)instance;
|
|
double* begin = data->probes[(long int)x];
|
|
|
|
data->last.xmin = *begin;
|
|
|
|
#ifdef PLFIT_DEBUG
|
|
printf("Trying with probes[%ld] = %.4f\n", (long int)x, *begin);
|
|
#endif
|
|
|
|
plfit_i_estimate_alpha_continuous_sorted(begin, data->end-begin, *begin,
|
|
&data->last.alpha);
|
|
plfit_i_ks_test_continuous(begin, data->end, data->last.alpha, *begin,
|
|
&data->last.D);
|
|
|
|
return data->last.D;
|
|
}
|
|
|
|
static int plfit_i_continuous_xmin_opt_progress(void* instance, double x, double fx,
|
|
double min, double fmin, double left, double right, int k) {
|
|
#ifdef PLFIT_DEBUG
|
|
printf("Iteration #%d: [%.4f; %.4f), x=%.4f, fx=%.4f, min=%.4f, fmin=%.4f\n",
|
|
k, left, right, x, fx, min, fmin);
|
|
#endif
|
|
|
|
/* Continue only if `left' and `right' point to different integers */
|
|
return (int)left == (int)right;
|
|
}
|
|
|
|
static int plfit_i_continuous_xmin_opt_linear_scan(
|
|
plfit_continuous_xmin_opt_data_t* opt_data, plfit_result_t* best_result,
|
|
size_t* best_n) {
|
|
/* this must be signed because OpenMP with Windows MSVC needs signed for
|
|
* loop index variables. ssize_t will not work because that is a POSIX
|
|
* extension */
|
|
ptrdiff_t i = 0; /* initialize to work around incorrect warning issued by Clang 9.0 */
|
|
plfit_result_t global_best_result;
|
|
size_t global_best_n;
|
|
|
|
/* Prepare some variables */
|
|
global_best_n = 0;
|
|
global_best_result.D = DBL_MAX;
|
|
global_best_result.xmin = 0;
|
|
global_best_result.alpha = 0;
|
|
|
|
/* Due to the OpenMP parallelization, we do things as follows. Each
|
|
* OpenMP thread will search for the best D-score on its own and store
|
|
* the result in a private local_best_result variable. The end of the
|
|
* parallel block contains a critical section that threads will enter
|
|
* one by one and compare their private local_best_result with a
|
|
* global_best that is shared among the threads.
|
|
*/
|
|
#ifdef _OPENMP
|
|
#pragma omp parallel shared(global_best_result, global_best_n) private(i) firstprivate(opt_data)
|
|
#endif
|
|
{
|
|
/* These variables are private since they are declared within the
|
|
* parallel block */
|
|
plfit_result_t local_best_result;
|
|
plfit_continuous_xmin_opt_data_t local_opt_data = *opt_data;
|
|
size_t local_best_n;
|
|
|
|
/* Initialize the local_best_result and local_best_n variables */
|
|
local_best_n = 0;
|
|
local_best_result.D = DBL_MAX;
|
|
local_best_result.xmin = 0;
|
|
local_best_result.alpha = 0;
|
|
local_best_result.p = NAN;
|
|
local_best_result.L = NAN;
|
|
|
|
/* The range of the for loop below is divided among the threads.
|
|
* nowait means that there will be no implicit barrier at the end
|
|
* of the loop so threads that get there earlier can enter the
|
|
* critical section without waiting for the others */
|
|
#ifdef _OPENMP
|
|
#pragma omp for nowait schedule(dynamic,10)
|
|
#endif
|
|
for (i = 0; i < local_opt_data.num_probes-1; i++) {
|
|
plfit_i_continuous_xmin_opt_evaluate(&local_opt_data, i);
|
|
if (local_opt_data.last.D < local_best_result.D) {
|
|
#ifdef PLFIT_DEBUG
|
|
printf("Found new local best at %g with D=%g\n",
|
|
local_opt_data.last.xmin, local_opt_data.last.D);
|
|
#endif
|
|
local_best_result = local_opt_data.last;
|
|
local_best_n = local_opt_data.end - local_opt_data.probes[i];
|
|
}
|
|
}
|
|
|
|
/* Critical section that finds the global best result from the
|
|
* local ones collected by each thread */
|
|
#ifdef _OPENMP
|
|
#pragma omp critical
|
|
#endif
|
|
if (local_best_result.D < global_best_result.D) {
|
|
global_best_result = local_best_result;
|
|
global_best_n = local_best_n;
|
|
#ifdef PLFIT_DEBUG
|
|
printf("Found new global best at %g with D=%g\n", global_best_result.xmin,
|
|
global_best_result.D);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
*best_result = global_best_result;
|
|
*best_n = global_best_n;
|
|
|
|
#ifdef PLFIT_DEBUG
|
|
printf("Returning global best: %g\n", best_result->xmin);
|
|
#endif
|
|
|
|
return PLFIT_SUCCESS;
|
|
}
|
|
|
|
int plfit_continuous(const double* xs, size_t n, const plfit_continuous_options_t* options,
|
|
plfit_result_t* result) {
|
|
gss_parameter_t gss_param;
|
|
plfit_continuous_xmin_opt_data_t opt_data;
|
|
plfit_result_t best_result = {
|
|
/* alpha = */ NAN,
|
|
/* xmin = */ NAN,
|
|
/* L = */ NAN,
|
|
/* D = */ NAN,
|
|
/* p = */ NAN
|
|
};
|
|
|
|
int success;
|
|
size_t i, best_n, num_uniques = 0;
|
|
double x, *px, **uniques, **strata;
|
|
int error_code, retval = PLFIT_SUCCESS;
|
|
|
|
DATA_POINTS_CHECK;
|
|
|
|
/* Set up pointers that we will allocate */
|
|
opt_data.begin = NULL;
|
|
uniques = NULL;
|
|
strata = NULL;
|
|
|
|
/* Sane defaults */
|
|
best_n = n;
|
|
if (!options)
|
|
options = &plfit_continuous_default_options;
|
|
|
|
/* Make a copy of xs and sort it */
|
|
PLFIT_CHECK(plfit_i_copy_and_sort(xs, n, &opt_data.begin));
|
|
opt_data.end = opt_data.begin + n;
|
|
|
|
/* Create an array containing pointers to the unique elements of the input. From
|
|
* each block of unique elements, we add the pointer to the first one. */
|
|
uniques = unique_element_pointers(opt_data.begin, opt_data.end, &num_uniques);
|
|
if (uniques == NULL) {
|
|
free(opt_data.begin);
|
|
PLFIT_ERROR("cannot fit continuous power-law", PLFIT_ENOMEM);
|
|
}
|
|
|
|
/* We will now determine the best xmin that yields the lowest D-score. The
|
|
* 'success' variable will denote whether the search procedure we tried was
|
|
* successful. If it is false after having exhausted all options, we fall
|
|
* back to a linear search. */
|
|
success = 0;
|
|
switch (options->xmin_method) {
|
|
case PLFIT_GSS_OR_LINEAR:
|
|
/* Try golden section search first. */
|
|
if (num_uniques > 5) {
|
|
opt_data.probes = uniques;
|
|
opt_data.num_probes = num_uniques;
|
|
gss_parameter_init(&gss_param);
|
|
success = (gss(0, opt_data.num_probes-5, &x, 0,
|
|
plfit_i_continuous_xmin_opt_evaluate,
|
|
plfit_i_continuous_xmin_opt_progress, &opt_data, &gss_param) == 0);
|
|
if (success) {
|
|
px = opt_data.probes[(int)x];
|
|
best_n = opt_data.end-px+1;
|
|
best_result = opt_data.last;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case PLFIT_STRATIFIED_SAMPLING:
|
|
if (num_uniques >= 50) {
|
|
/* Try stratified sampling to narrow down the interval where the minimum
|
|
* is likely to reside. We check 10% of the unique items, distributed
|
|
* evenly, find the one with the lowest D-score, and then check the
|
|
* area around it more thoroughly. */
|
|
const size_t subdivision_length = 10;
|
|
size_t num_strata = num_uniques / subdivision_length;
|
|
|
|
strata = calloc(num_strata, sizeof(double*));
|
|
if (strata == NULL) {
|
|
free(uniques);
|
|
free(opt_data.begin);
|
|
PLFIT_ERROR("cannot fit continuous power-law", PLFIT_ENOMEM);
|
|
}
|
|
|
|
for (i = 0; i < num_strata; i++) {
|
|
strata[i] = uniques[i * subdivision_length];
|
|
}
|
|
|
|
opt_data.probes = strata;
|
|
opt_data.num_probes = num_strata;
|
|
error_code = plfit_i_continuous_xmin_opt_linear_scan(&opt_data, &best_result, &best_n);
|
|
if (error_code != PLFIT_SUCCESS) {
|
|
retval = error_code;
|
|
goto cleanup;
|
|
}
|
|
|
|
opt_data.num_probes = 0;
|
|
for (i = 0; i < num_strata; i++) {
|
|
if (*strata[i] == best_result.xmin) {
|
|
/* Okay, scan more thoroughly from strata[i-1] to strata[i+1],
|
|
* which is from uniques[(i-1)*subdivision_length] to
|
|
* uniques[(i+1)*subdivision_length */
|
|
opt_data.probes = uniques + (i > 0 ? (i-1)*subdivision_length : 0);
|
|
opt_data.num_probes = 0;
|
|
if (i != 0)
|
|
opt_data.num_probes += subdivision_length;
|
|
if (i != num_strata-1)
|
|
opt_data.num_probes += subdivision_length;
|
|
break;
|
|
}
|
|
}
|
|
|
|
free(strata); strata = NULL;
|
|
|
|
if (opt_data.num_probes > 0) {
|
|
/* Do a strict linear scan in the subrange determined above */
|
|
error_code = plfit_i_continuous_xmin_opt_linear_scan(
|
|
&opt_data, &best_result, &best_n
|
|
);
|
|
if (error_code) {
|
|
retval = error_code;
|
|
goto cleanup;
|
|
}
|
|
success = 1;
|
|
} else {
|
|
/* This should not happen, but we handle it anyway */
|
|
success = 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
/* Just use the linear search */
|
|
break;
|
|
}
|
|
|
|
if (!success) {
|
|
/* More advanced search methods failed or were skipped; try linear search */
|
|
opt_data.probes = uniques;
|
|
opt_data.num_probes = num_uniques;
|
|
error_code = plfit_i_continuous_xmin_opt_linear_scan(&opt_data, &best_result, &best_n);
|
|
if (error_code) {
|
|
retval = error_code;
|
|
goto cleanup;
|
|
}
|
|
success = 1;
|
|
}
|
|
|
|
/* Get rid of the uniques array, we don't need it any more */
|
|
free(uniques); uniques = NULL;
|
|
|
|
/* Sort out the result */
|
|
*result = best_result;
|
|
if (options->finite_size_correction)
|
|
plfit_i_perform_finite_size_correction(result, best_n);
|
|
|
|
error_code = plfit_log_likelihood_continuous(
|
|
opt_data.begin + n - best_n, best_n, result->alpha, result->xmin,
|
|
&result->L
|
|
);
|
|
if (error_code) {
|
|
retval = error_code;
|
|
goto cleanup;
|
|
}
|
|
|
|
error_code = plfit_i_calculate_p_value_continuous(opt_data.begin, n, options, 0, result);
|
|
if (error_code) {
|
|
retval = error_code;
|
|
goto cleanup;
|
|
}
|
|
|
|
cleanup:
|
|
/* It is safe to call free() on NULL */
|
|
free(strata);
|
|
free(uniques);
|
|
free(opt_data.begin);
|
|
|
|
return retval;
|
|
}
|
|
|
|
/********** Discrete power law distribution fitting **********/
|
|
|
|
typedef struct {
|
|
size_t m;
|
|
double logsum;
|
|
double xmin;
|
|
} plfit_i_estimate_alpha_discrete_data_t;
|
|
|
|
static double plfit_i_logsum_discrete(const double* begin, const double* end, double xmin) {
|
|
double logsum = 0.0;
|
|
for (; begin != end; begin++)
|
|
logsum += log(*begin);
|
|
return logsum;
|
|
}
|
|
|
|
static void plfit_i_logsum_less_than_discrete(const double* begin, const double* end, double xmin,
|
|
double* logsum, size_t* m) {
|
|
double result = 0.0;
|
|
size_t count = 0;
|
|
|
|
for (; begin != end; begin++) {
|
|
if (*begin < xmin)
|
|
continue;
|
|
|
|
result += log(*begin);
|
|
count++;
|
|
}
|
|
|
|
*logsum = result;
|
|
*m = count;
|
|
}
|
|
|
|
static lbfgsfloatval_t plfit_i_estimate_alpha_discrete_lbfgs_evaluate(
|
|
void* instance, const lbfgsfloatval_t* x,
|
|
lbfgsfloatval_t* g, const int n,
|
|
const lbfgsfloatval_t step) {
|
|
const plfit_i_estimate_alpha_discrete_data_t* data;
|
|
lbfgsfloatval_t result;
|
|
double dx = step;
|
|
double huge = 1e10; /* pseudo-infinity; apparently DBL_MAX does not work */
|
|
double lnhzeta_x=NAN;
|
|
double lnhzeta_deriv_x=NAN;
|
|
|
|
data = (plfit_i_estimate_alpha_discrete_data_t*)instance;
|
|
|
|
#ifdef PLFIT_DEBUG
|
|
printf("- Evaluating at %.4f (step = %.4f, xmin = %.4f)\n", *x, step, data->xmin);
|
|
#endif
|
|
|
|
if (isnan(*x)) {
|
|
g[0] = huge;
|
|
return huge;
|
|
}
|
|
|
|
/* Find the delta X value to estimate the gradient */
|
|
if (dx > 0.001 || dx == 0)
|
|
dx = 0.001;
|
|
else if (dx < -0.001)
|
|
dx = -0.001;
|
|
|
|
/* Is x[0] in its valid range? */
|
|
if (x[0] <= 1.0) {
|
|
/* The Hurwitz zeta function is infinite in this case */
|
|
g[0] = (dx > 0) ? -huge : huge;
|
|
return huge;
|
|
}
|
|
if (x[0] + dx <= 1.0) {
|
|
g[0] = huge;
|
|
result = x[0] * data->logsum + data->m * hsl_sf_lnhzeta(x[0], data->xmin);
|
|
} else {
|
|
hsl_sf_lnhzeta_deriv_tuple(x[0], data->xmin, &lnhzeta_x, &lnhzeta_deriv_x);
|
|
g[0] = data->logsum + data->m * lnhzeta_deriv_x;
|
|
result = x[0] * data->logsum + data->m * lnhzeta_x;
|
|
}
|
|
|
|
#ifdef PLFIT_DEBUG
|
|
printf(" - Gradient: %.4f\n", g[0]);
|
|
printf(" - Result: %.4f\n", result);
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
|
|
static int plfit_i_estimate_alpha_discrete_lbfgs_progress(void* instance,
|
|
const lbfgsfloatval_t* x, const lbfgsfloatval_t* g,
|
|
const lbfgsfloatval_t fx, const lbfgsfloatval_t xnorm,
|
|
const lbfgsfloatval_t gnorm, const lbfgsfloatval_t step,
|
|
int n, int k, int ls) {
|
|
return 0;
|
|
}
|
|
|
|
static int plfit_i_estimate_alpha_discrete_linear_scan(const double* xs, size_t n,
|
|
double xmin, double* alpha, const plfit_discrete_options_t* options,
|
|
plfit_bool_t sorted) {
|
|
double curr_alpha, best_alpha, L, L_max;
|
|
double logsum;
|
|
size_t m;
|
|
|
|
XMIN_CHECK_ONE;
|
|
if (options->alpha.min <= 1.0) {
|
|
PLFIT_ERROR("alpha.min must be greater than 1.0", PLFIT_EINVAL);
|
|
}
|
|
if (options->alpha.max < options->alpha.min) {
|
|
PLFIT_ERROR("alpha.max must be greater than alpha.min", PLFIT_EINVAL);
|
|
}
|
|
if (options->alpha.step <= 0) {
|
|
PLFIT_ERROR("alpha.step must be positive", PLFIT_EINVAL);
|
|
}
|
|
|
|
if (sorted) {
|
|
logsum = plfit_i_logsum_discrete(xs, xs+n, xmin);
|
|
m = n;
|
|
} else {
|
|
plfit_i_logsum_less_than_discrete(xs, xs+n, xmin, &logsum, &m);
|
|
}
|
|
|
|
best_alpha = options->alpha.min; L_max = -DBL_MAX;
|
|
for (curr_alpha = options->alpha.min; curr_alpha <= options->alpha.max;
|
|
curr_alpha += options->alpha.step) {
|
|
L = -curr_alpha * logsum - m * hsl_sf_lnhzeta(curr_alpha, xmin);
|
|
if (L > L_max) {
|
|
L_max = L;
|
|
best_alpha = curr_alpha;
|
|
}
|
|
}
|
|
|
|
*alpha = best_alpha;
|
|
|
|
return PLFIT_SUCCESS;
|
|
}
|
|
|
|
static int plfit_i_estimate_alpha_discrete_lbfgs(const double* xs, size_t n, double xmin,
|
|
double* alpha, const plfit_discrete_options_t* options, plfit_bool_t sorted) {
|
|
lbfgs_parameter_t param;
|
|
lbfgsfloatval_t* variables;
|
|
plfit_i_estimate_alpha_discrete_data_t data;
|
|
int ret;
|
|
|
|
XMIN_CHECK_ONE;
|
|
|
|
/* Initialize algorithm parameters */
|
|
lbfgs_parameter_init(¶m);
|
|
param.max_iterations = 0; /* proceed until infinity */
|
|
|
|
/* Set up context for optimization */
|
|
data.xmin = xmin;
|
|
if (sorted) {
|
|
data.logsum = plfit_i_logsum_discrete(xs, xs+n, xmin);
|
|
data.m = n;
|
|
} else {
|
|
plfit_i_logsum_less_than_discrete(xs, xs+n, xmin, &data.logsum, &data.m);
|
|
}
|
|
|
|
/* Allocate space for the single alpha variable */
|
|
variables = lbfgs_malloc(1);
|
|
variables[0] = 3.0; /* initial guess */
|
|
|
|
/* Optimization */
|
|
ret = lbfgs(1, variables, /* ptr_fx = */ 0,
|
|
plfit_i_estimate_alpha_discrete_lbfgs_evaluate,
|
|
plfit_i_estimate_alpha_discrete_lbfgs_progress,
|
|
&data, ¶m);
|
|
|
|
if (ret < 0 &&
|
|
ret != LBFGSERR_ROUNDING_ERROR &&
|
|
ret != LBFGSERR_MAXIMUMLINESEARCH &&
|
|
ret != LBFGSERR_MINIMUMSTEP &&
|
|
ret != LBFGSERR_CANCELED) {
|
|
char buf[4096];
|
|
snprintf(buf, 4096, "L-BFGS optimization signaled an error (error code = %d)", ret);
|
|
lbfgs_free(variables);
|
|
PLFIT_ERROR(buf, PLFIT_FAILURE);
|
|
}
|
|
*alpha = variables[0];
|
|
|
|
/* Deallocate the variable array */
|
|
lbfgs_free(variables);
|
|
|
|
return PLFIT_SUCCESS;
|
|
}
|
|
|
|
static int plfit_i_estimate_alpha_discrete_fast(const double* xs, size_t n, double xmin,
|
|
double* alpha, const plfit_discrete_options_t* options, plfit_bool_t sorted) {
|
|
plfit_continuous_options_t cont_options;
|
|
|
|
if (!options)
|
|
options = &plfit_discrete_default_options;
|
|
|
|
plfit_continuous_options_init(&cont_options);
|
|
cont_options.finite_size_correction = options->finite_size_correction;
|
|
|
|
XMIN_CHECK_ONE;
|
|
|
|
if (sorted) {
|
|
return plfit_i_estimate_alpha_continuous_sorted(xs, n, xmin-0.5, alpha);
|
|
} else {
|
|
return plfit_i_estimate_alpha_continuous(xs, n, xmin-0.5, alpha);
|
|
}
|
|
}
|
|
|
|
static int plfit_i_estimate_alpha_discrete(const double* xs, size_t n, double xmin,
|
|
double* alpha, const plfit_discrete_options_t* options,
|
|
plfit_bool_t sorted) {
|
|
switch (options->alpha_method) {
|
|
case PLFIT_LBFGS:
|
|
PLFIT_CHECK(plfit_i_estimate_alpha_discrete_lbfgs(xs, n, xmin, alpha,
|
|
options, sorted));
|
|
break;
|
|
|
|
case PLFIT_LINEAR_SCAN:
|
|
PLFIT_CHECK(plfit_i_estimate_alpha_discrete_linear_scan(xs, n, xmin,
|
|
alpha, options, sorted));
|
|
break;
|
|
|
|
case PLFIT_PRETEND_CONTINUOUS:
|
|
PLFIT_CHECK(plfit_i_estimate_alpha_discrete_fast(xs, n, xmin,
|
|
alpha, options, sorted));
|
|
break;
|
|
|
|
default:
|
|
PLFIT_ERROR("unknown optimization method specified", PLFIT_EINVAL);
|
|
}
|
|
|
|
return PLFIT_SUCCESS;
|
|
}
|
|
|
|
static int plfit_i_ks_test_discrete(const double* xs, const double* xs_end, const double alpha,
|
|
const double xmin, double* D) {
|
|
/* Assumption: xs is sorted and cut off at xmin so the first element is
|
|
* always larger than or equal to xmin. */
|
|
double result = 0, n, lnhzeta, x;
|
|
int m = 0;
|
|
|
|
n = xs_end - xs;
|
|
lnhzeta = hsl_sf_lnhzeta(alpha, xmin);
|
|
|
|
while (xs < xs_end) {
|
|
double d;
|
|
|
|
x = *xs;
|
|
|
|
/* Re the next line: this used to be the following:
|
|
*
|
|
* fabs( 1 - hzeta(alpha, x) / hzeta(alpha, xmin) - m / n)
|
|
*
|
|
* However, using the Hurwitz zeta directly sometimes yields
|
|
* underflows (see Github pull request #17 and related issues).
|
|
* hzeta(alpha, x) / hzeta(alpha, xmin) can be replaced with
|
|
* exp(lnhzeta(alpha, x) - lnhzeta(alpha, xmin)), but then
|
|
* we have 1 - exp(something), which is better to calculate
|
|
* with a dedicated expm1() function.
|
|
*/
|
|
d = fabs( expm1( hsl_sf_lnhzeta(alpha, x) - lnhzeta ) + m / n);
|
|
|
|
if (d > result)
|
|
result = d;
|
|
|
|
do {
|
|
xs++; m++;
|
|
} while (xs < xs_end && *xs == x);
|
|
}
|
|
|
|
*D = result;
|
|
|
|
return PLFIT_SUCCESS;
|
|
}
|
|
|
|
static int plfit_i_calculate_p_value_discrete(const double* xs, size_t n,
|
|
const plfit_discrete_options_t* options, plfit_bool_t xmin_fixed,
|
|
plfit_result_t *result) {
|
|
long int num_trials;
|
|
long int successes = 0;
|
|
double *xs_head;
|
|
size_t num_smaller;
|
|
plfit_discrete_options_t options_no_p_value = *options;
|
|
int retval = PLFIT_SUCCESS;
|
|
|
|
if (options->p_value_method == PLFIT_P_VALUE_SKIP) {
|
|
/* skipping p-value calculation */
|
|
result->p = NAN;
|
|
return PLFIT_SUCCESS;
|
|
}
|
|
|
|
if (options->p_value_method == PLFIT_P_VALUE_APPROXIMATE) {
|
|
/* p-value approximation; most likely an upper bound */
|
|
num_smaller = count_smaller(xs, xs + n, result->xmin);
|
|
result->p = plfit_ks_test_one_sample_p(result->D, n - num_smaller);
|
|
return PLFIT_SUCCESS;
|
|
}
|
|
|
|
options_no_p_value.p_value_method = PLFIT_P_VALUE_SKIP;
|
|
num_trials = (long int)(0.25 / options->p_value_precision / options->p_value_precision);
|
|
if (num_trials <= 0) {
|
|
PLFIT_ERROR("invalid p-value precision", PLFIT_EINVAL);
|
|
}
|
|
|
|
/* Extract the head of xs that contains elements smaller than xmin */
|
|
xs_head = extract_smaller(xs, xs+n, result->xmin, &num_smaller);
|
|
if (xs_head == 0)
|
|
PLFIT_ERROR("cannot calculate exact p-value", PLFIT_ENOMEM);
|
|
|
|
#ifdef _OPENMP
|
|
#pragma omp parallel
|
|
#endif
|
|
{
|
|
/* Parallel section starts here. If we are compiling using OpenMP, each
|
|
* thread will use its own RNG that is seeded from the master RNG. If
|
|
* we are compiling without OpenMP, there is only one thread and it uses
|
|
* the master RNG. This section must be critical to ensure that only one
|
|
* thread is using the master RNG at the same time. */
|
|
#ifdef _OPENMP
|
|
plfit_mt_rng_t private_rng;
|
|
#endif
|
|
plfit_mt_rng_t *p_rng;
|
|
double *ys;
|
|
long int i;
|
|
plfit_result_t result_synthetic;
|
|
|
|
#ifdef _OPENMP
|
|
#pragma omp critical
|
|
{
|
|
p_rng = &private_rng;
|
|
plfit_mt_init_from_rng(p_rng, options->rng);
|
|
}
|
|
#else
|
|
p_rng = options->rng;
|
|
#endif
|
|
|
|
/* Allocate memory to sample into */
|
|
ys = calloc(n > 0 ? n : 1, sizeof(double));
|
|
if (ys == NULL) {
|
|
retval = PLFIT_ENOMEM;
|
|
} else {
|
|
/* The main for loop starts here. */
|
|
#ifdef _OPENMP
|
|
#pragma omp for reduction(+:successes)
|
|
#endif
|
|
for (i = 0; i < num_trials; i++) {
|
|
plfit_i_resample_discrete(xs_head, num_smaller, n, result->alpha,
|
|
result->xmin, n, p_rng, ys);
|
|
if (xmin_fixed) {
|
|
plfit_estimate_alpha_discrete(ys, n, result->xmin,
|
|
&options_no_p_value, &result_synthetic);
|
|
} else {
|
|
plfit_discrete(ys, n, &options_no_p_value, &result_synthetic);
|
|
}
|
|
if (result_synthetic.D > result->D)
|
|
successes++;
|
|
}
|
|
|
|
free(ys);
|
|
}
|
|
|
|
/* End of parallelized part */
|
|
}
|
|
|
|
free(xs_head);
|
|
|
|
if (retval == PLFIT_SUCCESS) {
|
|
result->p = successes / ((double)num_trials);
|
|
} else {
|
|
PLFIT_ERROR("cannot calculate exact p-value", retval);
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
int plfit_log_likelihood_discrete(const double* xs, size_t n, double alpha, double xmin, double* L) {
|
|
double result;
|
|
size_t m;
|
|
|
|
if (alpha <= 1) {
|
|
PLFIT_ERROR("alpha must be greater than one", PLFIT_EINVAL);
|
|
}
|
|
XMIN_CHECK_ONE;
|
|
|
|
plfit_i_logsum_less_than_discrete(xs, xs+n, xmin, &result, &m);
|
|
result = - alpha * result - m * hsl_sf_lnhzeta(alpha, xmin);
|
|
|
|
*L = result;
|
|
|
|
return PLFIT_SUCCESS;
|
|
}
|
|
|
|
int plfit_estimate_alpha_discrete(const double* xs, size_t n, double xmin,
|
|
const plfit_discrete_options_t* options, plfit_result_t *result) {
|
|
double *xs_copy, *begin, *end;
|
|
|
|
if (!options)
|
|
options = &plfit_discrete_default_options;
|
|
|
|
/* Check the validity of the input parameters */
|
|
DATA_POINTS_CHECK;
|
|
if (options->alpha_method == PLFIT_LINEAR_SCAN) {
|
|
if (options->alpha.min <= 1.0) {
|
|
PLFIT_ERROR("alpha.min must be greater than 1.0", PLFIT_EINVAL);
|
|
}
|
|
if (options->alpha.max < options->alpha.min) {
|
|
PLFIT_ERROR("alpha.max must be greater than alpha.min", PLFIT_EINVAL);
|
|
}
|
|
if (options->alpha.step <= 0) {
|
|
PLFIT_ERROR("alpha.step must be positive", PLFIT_EINVAL);
|
|
}
|
|
}
|
|
|
|
PLFIT_CHECK(plfit_i_copy_and_sort(xs, n, &xs_copy));
|
|
|
|
begin = xs_copy; end = xs_copy + n;
|
|
while (begin < end && *begin < xmin)
|
|
begin++;
|
|
|
|
PLFIT_CHECK(plfit_i_estimate_alpha_discrete(begin, end-begin, xmin, &result->alpha,
|
|
options, /* sorted = */ 1));
|
|
PLFIT_CHECK(plfit_i_ks_test_discrete(begin, end, result->alpha, xmin, &result->D));
|
|
|
|
result->xmin = xmin;
|
|
if (options->finite_size_correction)
|
|
plfit_i_perform_finite_size_correction(result, end-begin);
|
|
|
|
PLFIT_CHECK(plfit_log_likelihood_discrete(begin, end-begin, result->alpha,
|
|
result->xmin, &result->L));
|
|
PLFIT_CHECK(plfit_i_calculate_p_value_discrete(xs, n, options, 1, result));
|
|
|
|
free(xs_copy);
|
|
|
|
return PLFIT_SUCCESS;
|
|
}
|
|
|
|
int plfit_discrete(const double* xs, size_t n, const plfit_discrete_options_t* options,
|
|
plfit_result_t* result) {
|
|
double curr_D, curr_alpha;
|
|
plfit_result_t best_result;
|
|
double *xs_copy, *px, *end, *end_xmin, prev_x;
|
|
size_t best_n;
|
|
size_t m;
|
|
|
|
if (!options)
|
|
options = &plfit_discrete_default_options;
|
|
|
|
/* Check the validity of the input parameters */
|
|
DATA_POINTS_CHECK;
|
|
if (options->alpha_method == PLFIT_LINEAR_SCAN) {
|
|
if (options->alpha.min <= 1.0) {
|
|
PLFIT_ERROR("alpha.min must be greater than 1.0", PLFIT_EINVAL);
|
|
}
|
|
if (options->alpha.max < options->alpha.min) {
|
|
PLFIT_ERROR("alpha.max must be greater than alpha.min", PLFIT_EINVAL);
|
|
}
|
|
if (options->alpha.step <= 0) {
|
|
PLFIT_ERROR("alpha.step must be positive", PLFIT_EINVAL);
|
|
}
|
|
}
|
|
|
|
PLFIT_CHECK(plfit_i_copy_and_sort(xs, n, &xs_copy));
|
|
|
|
best_result.D = DBL_MAX;
|
|
best_result.xmin = 1;
|
|
best_result.alpha = 1;
|
|
best_n = 0;
|
|
|
|
/* Skip initial values from xs_copy until we get to a positive element or
|
|
* until we reach the end of the array */
|
|
px = xs_copy; end = px + n; end_xmin = end - 1;
|
|
while (px < end && *px < 1) {
|
|
px++;
|
|
}
|
|
|
|
/* Make sure there are at least three distinct values if possible */
|
|
m = px - xs_copy;
|
|
prev_x = *end_xmin;
|
|
while (end_xmin > px && *end_xmin == prev_x) {
|
|
end_xmin--;
|
|
}
|
|
prev_x = *end_xmin;
|
|
while (end_xmin > px && *end_xmin == prev_x) {
|
|
end_xmin--;
|
|
}
|
|
|
|
prev_x = 0;
|
|
end_xmin++;
|
|
while (px < end_xmin) {
|
|
while (px < end_xmin && *px == prev_x) {
|
|
px++; m++;
|
|
}
|
|
|
|
PLFIT_CHECK(
|
|
plfit_i_estimate_alpha_discrete(
|
|
px, n-m, *px, &curr_alpha, options, /* sorted = */ 1
|
|
)
|
|
);
|
|
PLFIT_CHECK(
|
|
plfit_i_ks_test_discrete(px, end, curr_alpha, *px, &curr_D)
|
|
);
|
|
|
|
if (curr_D < best_result.D) {
|
|
best_result.alpha = curr_alpha;
|
|
best_result.xmin = *px;
|
|
best_result.D = curr_D;
|
|
best_n = n-m;
|
|
}
|
|
|
|
prev_x = *px;
|
|
px++; m++;
|
|
}
|
|
|
|
*result = best_result;
|
|
if (options->finite_size_correction)
|
|
plfit_i_perform_finite_size_correction(result, best_n);
|
|
|
|
PLFIT_CHECK(plfit_log_likelihood_discrete(xs_copy + n - best_n, best_n,
|
|
result->alpha, result->xmin, &result->L));
|
|
PLFIT_CHECK(plfit_i_calculate_p_value_discrete(xs_copy, n, options, 0, result));
|
|
|
|
free(xs_copy);
|
|
|
|
return PLFIT_SUCCESS;
|
|
}
|
|
|
|
/***** resampling routines to generate synthetic replicates ****/
|
|
|
|
static int plfit_i_resample_continuous(const double* xs_head, size_t num_smaller,
|
|
size_t n, double alpha, double xmin, size_t num_samples, plfit_mt_rng_t* rng,
|
|
double* result)
|
|
{
|
|
size_t num_orig_samples, i;
|
|
|
|
/* Calculate how many samples have to be drawn from xs_head */
|
|
num_orig_samples = (size_t) plfit_rbinom(num_samples, num_smaller / (double)n, rng);
|
|
|
|
/* Draw the samples from xs_head */
|
|
for (i = 0; i < num_orig_samples; i++, result++) {
|
|
*result = xs_head[(size_t)plfit_runif(0, num_smaller, rng)];
|
|
}
|
|
|
|
/* Draw the remaining samples from the fitted distribution */
|
|
PLFIT_CHECK(plfit_rpareto_array(xmin, alpha-1, num_samples-num_orig_samples, rng,
|
|
result));
|
|
|
|
return PLFIT_SUCCESS;
|
|
}
|
|
|
|
int plfit_resample_continuous(const double* xs, size_t n, double alpha, double xmin,
|
|
size_t num_samples, plfit_mt_rng_t* rng, double* result) {
|
|
double *xs_head;
|
|
size_t num_smaller = 0;
|
|
int retval;
|
|
|
|
/* Extract the head of xs that contains elements smaller than xmin */
|
|
xs_head = extract_smaller(xs, xs+n, xmin, &num_smaller);
|
|
if (xs_head == 0)
|
|
PLFIT_ERROR("cannot resample continuous dataset", PLFIT_ENOMEM);
|
|
|
|
retval = plfit_i_resample_continuous(xs_head, num_smaller, n, alpha, xmin,
|
|
num_samples, rng, result);
|
|
|
|
/* Free xs_head; we don't need it any more */
|
|
free(xs_head);
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int plfit_i_resample_discrete(const double* xs_head, size_t num_smaller, size_t n,
|
|
double alpha, double xmin, size_t num_samples, plfit_mt_rng_t* rng,
|
|
double* result)
|
|
{
|
|
size_t num_orig_samples, i;
|
|
|
|
/* Calculate how many samples have to be drawn from xs_head */
|
|
num_orig_samples = (size_t) plfit_rbinom(num_samples, num_smaller / (double)n, rng);
|
|
|
|
/* Draw the samples from xs_head */
|
|
for (i = 0; i < num_orig_samples; i++, result++) {
|
|
*result = xs_head[(size_t)plfit_runif(0, num_smaller, rng)];
|
|
}
|
|
|
|
/* Draw the remaining samples from the fitted distribution */
|
|
PLFIT_CHECK(plfit_rzeta_array((long int)xmin, alpha,
|
|
num_samples-num_orig_samples, rng, result));
|
|
|
|
return PLFIT_SUCCESS;
|
|
}
|
|
|
|
int plfit_resample_discrete(const double* xs, size_t n, double alpha, double xmin,
|
|
size_t num_samples, plfit_mt_rng_t* rng, double* result) {
|
|
double *xs_head;
|
|
size_t num_smaller = 0;
|
|
int retval;
|
|
|
|
/* Extract the head of xs that contains elements smaller than xmin */
|
|
xs_head = extract_smaller(xs, xs+n, xmin, &num_smaller);
|
|
if (xs_head == 0)
|
|
PLFIT_ERROR("cannot resample discrete dataset", PLFIT_ENOMEM);
|
|
|
|
retval = plfit_i_resample_discrete(xs_head, num_smaller, n, alpha, xmin,
|
|
num_samples, rng, result);
|
|
|
|
/* Free xs_head; we don't need it any more */
|
|
free(xs_head);
|
|
|
|
return retval;
|
|
}
|
|
|
|
/******** calculating the p-value of a fitted model only *******/
|
|
|
|
int plfit_calculate_p_value_continuous(const double* xs, size_t n,
|
|
const plfit_continuous_options_t* options, plfit_bool_t xmin_fixed,
|
|
plfit_result_t *result) {
|
|
double* xs_copy;
|
|
|
|
PLFIT_CHECK(plfit_i_copy_and_sort(xs, n, &xs_copy));
|
|
PLFIT_CHECK(plfit_i_calculate_p_value_continuous(xs_copy, n, options,
|
|
xmin_fixed, result));
|
|
free(xs_copy);
|
|
|
|
return PLFIT_SUCCESS;
|
|
}
|
|
|
|
int plfit_calculate_p_value_discrete(const double* xs, size_t n,
|
|
const plfit_discrete_options_t* options, plfit_bool_t xmin_fixed,
|
|
plfit_result_t *result) {
|
|
double* xs_copy;
|
|
|
|
PLFIT_CHECK(plfit_i_copy_and_sort(xs, n, &xs_copy));
|
|
PLFIT_CHECK(plfit_i_calculate_p_value_discrete(xs_copy, n, options,
|
|
xmin_fixed, result));
|
|
free(xs_copy);
|
|
|
|
return PLFIT_SUCCESS;
|
|
}
|