1
0
mirror of https://github.com/TREX-CoE/qmckl.git synced 2024-11-04 05:03:59 +01:00
qmckl/org/qmckl_numprec.org

505 lines
14 KiB
Org Mode
Raw Normal View History

2023-11-30 01:17:18 +01:00
3+TITLE: Numerical precision
2021-04-30 01:26:19 +02:00
#+SETUPFILE: ../tools/theme.setup
#+INCLUDE: ../tools/lib.org
2021-03-30 14:51:23 +02:00
* Headers :noexport:
#+begin_src c :tangle (eval c_test) :noweb yes
#include "qmckl.h"
2021-05-11 16:41:03 +02:00
#include "assert.h"
2021-05-10 10:05:50 +02:00
#ifdef HAVE_CONFIG_H
2021-05-10 10:41:59 +02:00
#include "config.h"
2021-05-09 02:12:38 +02:00
#endif
2021-05-11 16:41:03 +02:00
int main() {
2021-03-30 14:51:23 +02:00
#+end_src
#+begin_src c :tangle (eval h_private_type)
#ifndef QMCKL_NUMPREC_HPT
#define QMCKL_NUMPREC_HPT
2021-05-10 10:05:50 +02:00
#ifdef HAVE_STDINT_H
2021-03-30 14:51:23 +02:00
#include <stdint.h>
2021-05-10 10:05:50 +02:00
#elif HAVE_INTTYPES_H
#include <inttypes.h>
#endif
2021-03-30 14:51:23 +02:00
#+end_src
2021-04-30 01:26:19 +02:00
#+begin_src c :tangle (eval c)
2021-05-10 10:05:50 +02:00
#ifdef HAVE_CONFIG_H
2021-05-10 10:41:59 +02:00
#include "config.h"
2021-05-09 02:12:38 +02:00
#endif
2021-05-10 10:05:50 +02:00
#ifdef HAVE_STDINT_H
2021-03-30 14:51:23 +02:00
#include <stdint.h>
2021-05-10 10:05:50 +02:00
#elif HAVE_INTTYPES_H
#include <inttypes.h>
#endif
2021-03-30 14:51:23 +02:00
#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
2023-06-29 15:56:11 +02:00
#ifdef HAVE_FPE
#define _GNU_SOURCE
#include <fenv.h>
#include <signal.h>
#include <stdio.h>
#include <execinfo.h>
#define MAX_BACKTRACE_SIZE 100
void floatingPointExceptionHandler(int signal) {
void* backtraceArray[MAX_BACKTRACE_SIZE];
int backtraceSize = backtrace(backtraceArray, MAX_BACKTRACE_SIZE);
char** backtraceSymbols = backtrace_symbols(backtraceArray, backtraceSize);
// Print the backtrace
for (int i = 0; i < backtraceSize; ++i) {
printf("[%d] %s\n", i, backtraceSymbols[i]);
}
// Clean up the memory used by backtrace_symbols
free(backtraceSymbols);
exit(EXIT_FAILURE);
}
static void __attribute__ ((constructor))
trapfpe ()
{
/* Enable some exceptions. At startup all exceptions are masked. */
feenableexcept (FE_INVALID|FE_DIVBYZERO|FE_OVERFLOW);
signal(SIGFPE, floatingPointExceptionHandler);
}
#endif
2021-05-11 13:57:23 +02:00
#include "qmckl.h"
2021-03-30 14:51:23 +02:00
#include "qmckl_context_private_type.h"
#+end_src
* Control of the numerical precision
Controlling numerical precision enables optimizations. Here, the
default parameters determining the target numerical precision and
range are defined. Following the IEEE Standard for Floating-Point
Arithmetic (IEEE 754),
2023-11-28 17:00:39 +01:00
/precision/ refers to the number of significand bits (including the
sign bit) and /range/ refers to the number of exponent bits.
2021-03-30 14:51:23 +02:00
#+NAME: table-precision
| ~QMCKL_DEFAULT_PRECISION~ | 53 |
| ~QMCKL_DEFAULT_RANGE~ | 11 |
# We need to force Emacs not to indent the Python code:
# -*- org-src-preserve-indentation: t
#+begin_src python :var table=table-precision :results drawer :exports results
""" This script generates the C and Fortran constants from the org-mode table.
"""
result = [ "#+begin_src c :comments org :tangle (eval h_type)" ]
for (text, code) in table:
text=text.replace("~","")
result += [ f"#define {text:30s} {code:d}" ]
result += [ "#+end_src" ]
result += [ "" ]
result += [ "#+begin_src f90 :comments org :tangle (eval fh_func) :exports none" ]
for (text, code) in table:
text=text.replace("~","")
result += [ f" integer, parameter :: {text:30s} = {code:d}" ]
result += [ "#+end_src" ]
return '\n'.join(result)
#+end_src
#+RESULTS:
:results:
#+begin_src c :comments org :tangle (eval h_type)
#define QMCKL_DEFAULT_PRECISION 53
#define QMCKL_DEFAULT_RANGE 11
#+end_src
#+begin_src f90 :comments org :tangle (eval fh_func) :exports none
integer, parameter :: QMCKL_DEFAULT_PRECISION = 53
integer, parameter :: QMCKL_DEFAULT_RANGE = 11
#+end_src
:end:
#+begin_src c :comments org :tangle (eval h_private_type)
typedef struct qmckl_numprec_struct {
uint32_t precision;
uint32_t range;
} qmckl_numprec_struct;
#+end_src
2021-04-30 01:26:19 +02:00
2021-03-30 14:51:23 +02:00
The following functions set and get the required precision and
range. ~precision~ is an integer between 2 and 53, and ~range~ is an
integer between 2 and 11.
The setter functions functions return a new context as a 64-bit
integer. The getter functions return the value, as a 32-bit
integer. The update functions return ~QMCKL_SUCCESS~ or
~QMCKL_FAILURE~.
* Precision
~qmckl_context_set_numprec_precision~ modifies the parameter for the
numerical precision in the context.
# Header
#+begin_src c :comments org :tangle (eval h_func) :exports none
qmckl_exit_code qmckl_set_numprec_precision(const qmckl_context context, const int precision);
#+end_src
# Source
#+begin_src c :tangle (eval c)
qmckl_exit_code qmckl_set_numprec_precision(const qmckl_context context, const int precision) {
if (qmckl_context_check(context) == QMCKL_NULL_CONTEXT)
return QMCKL_INVALID_CONTEXT;
if (precision < 2) {
return qmckl_failwith(context,
QMCKL_INVALID_ARG_2,
"qmckl_update_numprec_precision",
"precision < 2");
}
if (precision > 53) {
return qmckl_failwith(context,
QMCKL_INVALID_ARG_2,
"qmckl_update_numprec_precision",
"precision > 53");
}
2022-04-05 11:03:38 +02:00
qmckl_context_struct* const ctx = (qmckl_context_struct*) context;
2021-03-30 14:51:23 +02:00
/* This should be always true because the context is valid */
assert (ctx != NULL);
qmckl_lock(context);
{
ctx->numprec.precision = (uint32_t) precision;
}
qmckl_unlock(context);
return QMCKL_SUCCESS;
}
#+end_src
# Fortran interface
#+begin_src f90 :tangle (eval fh_func)
interface
integer (qmckl_exit_code) function qmckl_set_numprec_precision(context, precision) bind(C)
use, intrinsic :: iso_c_binding
import
integer (qmckl_context), intent(in), value :: context
integer (c_int32_t), intent(in), value :: precision
end function qmckl_set_numprec_precision
end interface
#+end_src
~qmckl_get_numprec_precision~ returns the value of the numerical precision in the context.
#+begin_src c :comments org :tangle (eval h_func) :exports none
int32_t qmckl_get_numprec_precision(const qmckl_context context);
#+end_src
# Source
#+begin_src c :tangle (eval c)
int qmckl_get_numprec_precision(const qmckl_context context) {
if (qmckl_context_check(context) == QMCKL_NULL_CONTEXT) {
return qmckl_failwith(context,
QMCKL_INVALID_CONTEXT,
"qmckl_get_numprec_precision",
"");
}
2022-04-05 11:03:38 +02:00
const qmckl_context_struct* const ctx = (qmckl_context_struct*) context;
2021-03-30 14:51:23 +02:00
return ctx->numprec.precision;
}
#+end_src
2021-04-30 01:26:19 +02:00
2021-03-30 14:51:23 +02:00
# Fortran interface
#+begin_src f90 :tangle (eval fh_func)
interface
integer (qmckl_exit_code) function qmckl_get_numprec_precision(context) bind(C)
use, intrinsic :: iso_c_binding
import
integer (qmckl_context), intent(in), value :: context
end function qmckl_get_numprec_precision
end interface
#+end_src
* Range
2021-04-30 01:26:19 +02:00
2021-03-30 14:51:23 +02:00
~qmckl_set_numprec_range~ modifies the parameter for the numerical
range in a given context.
# Header
#+begin_src c :comments org :tangle (eval h_func) :exports none
qmckl_exit_code qmckl_set_numprec_range(const qmckl_context context, const int range);
#+end_src
# Source
#+begin_src c :tangle (eval c)
qmckl_exit_code qmckl_set_numprec_range(const qmckl_context context, const int range) {
if (qmckl_context_check(context) == QMCKL_NULL_CONTEXT)
return QMCKL_INVALID_CONTEXT;
if (range < 2) {
return qmckl_failwith(context,
QMCKL_INVALID_ARG_2,
"qmckl_set_numprec_range",
"range < 2");
}
if (range > 11) {
return qmckl_failwith(context,
QMCKL_INVALID_ARG_2,
"qmckl_set_numprec_range",
"range > 11");
}
2022-04-05 11:03:38 +02:00
qmckl_context_struct* const ctx = (qmckl_context_struct*) context;
2021-03-30 14:51:23 +02:00
/* This should be always true because the context is valid */
assert (ctx != NULL);
qmckl_lock(context);
{
ctx->numprec.range = (uint32_t) range;
}
qmckl_unlock(context);
return QMCKL_SUCCESS;
}
#+end_src
# Fortran interface
#+begin_src f90 :tangle (eval fh_func)
interface
2022-05-02 16:37:15 +02:00
integer (qmckl_exit_code) function qmckl_set_numprec_range(context, range) bind(C)
2021-03-30 14:51:23 +02:00
use, intrinsic :: iso_c_binding
import
integer (qmckl_context), intent(in), value :: context
integer (c_int32_t), intent(in), value :: range
2022-05-02 16:37:15 +02:00
end function qmckl_set_numprec_range
2021-03-30 14:51:23 +02:00
end interface
#+end_src
~qmckl_get_numprec_range~ returns the value of the numerical range in the context.
#+begin_src c :comments org :tangle (eval h_func) :exports none
2022-05-02 16:37:15 +02:00
int32_t qmckl_get_numprec_range(const qmckl_context context);
2021-03-30 14:51:23 +02:00
#+end_src
# Source
#+begin_src c :tangle (eval c)
int qmckl_get_numprec_range(const qmckl_context context) {
if (qmckl_context_check(context) == QMCKL_NULL_CONTEXT) {
return qmckl_failwith(context,
QMCKL_INVALID_CONTEXT,
"qmckl_get_numprec_range",
"");
}
2022-04-05 11:03:38 +02:00
const qmckl_context_struct* const ctx = (qmckl_context_struct*) context;
2021-03-30 14:51:23 +02:00
return ctx->numprec.range;
}
#+end_src
# Fortran interface
#+begin_src f90 :tangle (eval fh_func) :exports none
interface
integer (qmckl_exit_code) function qmckl_get_numprec_range(context) bind(C)
use, intrinsic :: iso_c_binding
import
integer (qmckl_context), intent(in), value :: context
end function qmckl_get_numprec_range
end interface
#+end_src
* Helper functions
** Epsilon
2023-11-30 01:17:18 +01:00
~qmckl_get_numprec_epsilon~ returns $\epsilon = 2^{1-n}$ where ~n~ is the precision.
We need to remove the sign bit from the precision.
2021-03-30 14:51:23 +02:00
#+begin_src c :comments org :tangle (eval h_func) :exports none
2021-03-30 14:51:23 +02:00
double qmckl_get_numprec_epsilon(const qmckl_context context);
#+end_src
2021-03-30 14:51:23 +02:00
# Source
#+begin_src c :tangle (eval c)
2021-03-30 14:51:23 +02:00
double qmckl_get_numprec_epsilon(const qmckl_context context) {
if (qmckl_context_check(context) == QMCKL_NULL_CONTEXT)
return QMCKL_INVALID_CONTEXT;
const qmckl_context_struct* const ctx = (qmckl_context_struct*) context;
const int precision = ctx->numprec.precision;
2023-11-28 17:00:39 +01:00
return 1. / (double) ( ((uint64_t) 1) << (precision-2));
2021-03-30 14:51:23 +02:00
}
#+end_src
2021-03-30 14:51:23 +02:00
# Fortran interface
#+begin_src f90 :tangle (eval fh_func) :exports none
2021-03-30 14:51:23 +02:00
interface
real (c_double) function qmckl_get_numprec_epsilon(context) bind(C)
use, intrinsic :: iso_c_binding
import
integer (qmckl_context), intent(in), value :: context
end function qmckl_get_numprec_epsilon
end interface
#+end_src
** Testing the number of unchanged bits
To test that a given approximation keeps a given number of bits
unchanged, we need a function that returns the number of unchanged
bits in the range, and in the precision.
2023-11-30 01:17:18 +01:00
For this, we first count by how many units in the last place (ulps) two
numbers differ.
#+begin_src c :tangle (eval c)
2023-11-30 01:17:18 +01:00
int64_t countUlpDifference_64(double a, double b) {
union int_or_float {
int64_t i;
double f;
} x, y;
x.f = a;
y.f = b;
2023-11-30 01:17:18 +01:00
// Handle sign bit discontinuity: if the signs are different and either value is not zero
if ((x.i < 0) != (y.i < 0) && (x.f != 0.0) && (y.f != 0.0)) {
// Use the absolute values and add the distance to zero for both numbers
int64_t distanceToZeroForX = x.i < 0 ? INT64_MAX + x.i : INT64_MAX - x.i;
int64_t distanceToZeroForY = y.i < 0 ? INT64_MAX + y.i : INT64_MAX - y.i;
return distanceToZeroForX + distanceToZeroForY;
}
2023-11-30 01:17:18 +01:00
// Calculate the difference in their binary representations
int64_t result = x.i - y.i;
result = result > 0 ? result : -result;
return result;
}
#+end_src
2023-11-30 01:17:18 +01:00
#+begin_src c :comments org :tangle (eval h_func) :exports none
int32_t qmckl_test_precision_64(double a, double b);
int32_t qmckl_test_precision_32(float a, float b);
#+end_src
#+begin_src c :tangle (eval c)
2023-11-30 01:17:18 +01:00
int32_t qmckl_test_precision_64(double a, double b) {
2023-11-30 01:17:18 +01:00
int64_t diff = countUlpDifference_64(a,b);
2023-11-30 01:17:18 +01:00
if (diff == 0) return 53;
2023-11-30 01:17:18 +01:00
int32_t result = 53;
2023-11-30 01:17:18 +01:00
for (int i=0 ; i<53 && diff != 0 ; ++i) {
diff >>= 1;
2023-11-30 01:17:18 +01:00
result--;
}
return result;
}
#+end_src
2023-11-30 01:17:18 +01:00
#+begin_src c :tangle (eval c)
int32_t qmckl_test_precision_32(float a, float b) {
return qmckl_test_precision_64( (double) a, (double) b );
}
#+end_src
#+begin_src f90 :tangle (eval fh_func) :exports none
interface
integer (c_int) function qmckl_test_precision_32(a,b) bind(C)
use, intrinsic :: iso_c_binding
import
real (c_float), intent(in), value :: a, b
end function qmckl_test_precision_32
end interface
interface
integer (c_int) function qmckl_test_precision_64(a,b) bind(C)
use, intrinsic :: iso_c_binding
import
real (c_double), intent(in), value :: a, b
end function qmckl_test_precision_64
end interface
#+end_src
2023-11-30 01:17:18 +01:00
2023-11-28 17:00:39 +01:00
* Approximate functions
2023-11-30 01:17:18 +01:00
2023-11-28 17:00:39 +01:00
** Exponential
Fast exponential function, adapted from Johan Rade's implementation
(https://gist.github.com/jrade/293a73f89dfef51da6522428c857802d). It
is based on Schraudolph's paper:
N. Schraudolph, "A Fast, Compact Approximation of the Exponential Function",
/Neural Computation/ *11*, 853862 (1999).
(available at https://nic.schraudolph.org/pubs/Schraudolph99.pdf)
2023-11-30 01:17:18 +01:00
2023-11-28 17:00:39 +01:00
#+begin_src c :tangle (eval c)
float fastExpf(float x)
{
const float a = 12102203.0;
const float b = 1064986816.0;
x = a * x + b;
2023-11-30 01:17:18 +01:00
2023-11-28 17:00:39 +01:00
const float c = 8388608.0;
const float d = 2139095040.0;
if (x < c || x > d)
x = (x < c) ? 0.0f : d;
2023-11-30 01:17:18 +01:00
2023-11-28 17:00:39 +01:00
uint32_t n = (uint32_t) x;
memcpy(&x, &n, 4);
return x;
}
double fastExp(double x)
{
const double a = 6497320848556798.0;
const double b = 4606985713057410560.0;
x = a * x + b;
const double c = 4503599627370496.0;
const double d = 9218868437227405312.0;
if (x < c || x > d)
x = (x < c) ? 0.0 : d;
2023-11-30 01:17:18 +01:00
2023-11-28 17:00:39 +01:00
uint64_t n = (uint64_t) x;
memcpy(&x, &n, 8);
return x;
}
#+end_src
2021-03-30 14:51:23 +02:00
* End of files :noexport:
#+begin_src c :comments link :tangle (eval h_private_type)
#endif
#+end_src
*** Test
#+begin_src c :comments link :tangle (eval c_test)
2021-05-11 16:41:03 +02:00
return 0;
2021-03-30 14:51:23 +02:00
}
#+end_src