Alphis/count.c
2025-09-21 10:32:28 +02:00

294 lines
8.4 KiB
C

/* SPDX-License-Identifier: GPL-2.0-only */
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include <gmp.h>
#include <time.h>
#define square( x ) ( x * x )
// If endlessly looping, this is how many loops are made between allocing for a
// bigger array
#define DIMENSION_PER_ALLOC 4096
// Define this for debugging.
//#define DEBUG
// Define this for showing time for each dimension.
// Not useful for small inputs.
#define TIME
// Which clock to use for measuring time used to calculate.
// CLOCK_PROCESS_CPUTIME_ID to counts CPU time of the current process.
// This is preferred as this makes optimisation easier
#define CLOCK CLOCK_MONOTONIC
// Returns 1 if the root of length2 is larger than the length of the vector.
// Returns 0 if they're equal.
// Returns -1 if the root of length2 is shorter than the length of the vector.
static int vector_length2_cmp(
const int length2,
const int *const vector,
const int dimension
){
int vector_length2 = 0;
for( int i=0; i < dimension && vector[i] != 0; i++ ){
vector_length2 += square( vector[i] );
if( vector_length2 > length2 ) return -1;
}
if( vector_length2 == length2 )
return 0;
else
return 1;
}
// Amount of unique orderings given a sorted array, including flipping of signs.
// A table of factorial values is used to not need to compute the same stuff
// over and over again.
static void unique_array_orders(
mpz_t rop,
const int *const intptr,
const int length,
mpz_t *const factorial_table
){
// rop = 1
mpz_set_ui( rop, 1 );
int n = 1;
int zeros = 0;
// Only one order foe all 0
if( intptr[0] == 0 ) return;
for( int i = 1; i < length; i++ ){
if( intptr[i-1] == intptr[i] ){
n++;
}else{
// rop = rop * n!
mpz_mul( rop, rop, factorial_table[n] );
n = 1;
if( intptr[i] == 0 ){
zeros = length - i;
n = zeros;
break;
}
}
}
if( n != 1 ){
// rop = rop * n!
mpz_mul( rop, rop, factorial_table[n] );
}
// rop = 2^( length - zeros ) * ( length! / rop )
mpz_divexact( rop, factorial_table[length], rop );
if(zeros != length)
mpz_mul_2exp( rop, rop, length - zeros );
}
#ifdef TIME
static void print_elapsed_time(
const struct timespec t_start,
const struct timespec t_finish
){
const int nsec_elapsed =
( t_finish.tv_nsec - t_start.tv_nsec )
+ ( t_finish.tv_nsec < t_start.tv_nsec ) * 1000000000;
printf( "Time elapsed: " );
if( nsec_elapsed < 1000000 ){
printf( "%7.3fus\n", nsec_elapsed / 1000.0 );
return;
}
int seconds_elapsed =
( t_finish.tv_sec - t_start.tv_sec )
- ( t_finish.tv_nsec < t_start.tv_nsec );
if( seconds_elapsed < 1 ){
printf( "%7.3fms\n",
nsec_elapsed / 1000000.0 );
return;
}
int msec_elapsed = nsec_elapsed / 1000000;
if( seconds_elapsed < 60 ){
printf( "%2d.%03ds\n", seconds_elapsed, msec_elapsed );
return;
}
int minutes_elapsed = seconds_elapsed / 60;
seconds_elapsed = seconds_elapsed % 60;
if( minutes_elapsed < 60 ){
printf( "%2dm %2d.%03ds\n", minutes_elapsed, seconds_elapsed, msec_elapsed );
return;
}
int hours_elapsed = minutes_elapsed / 60;
minutes_elapsed = minutes_elapsed % 60;
printf( "%2dh %2dm %2ds\n", hours_elapsed, minutes_elapsed, seconds_elapsed );
// Not bothering making more than this,
// though I wonder how big the output would get
}
#endif /* TIME */
// Less slow algorithm
int main( const int argc, const char *argv[] ){
if( argc > 4 || argc < 2 ){
fprintf( stderr,
"Usage: \n\t%s radius [dimension] [dimension_end]\n"
"Radius is needed, dimension is optional.\n"
"If no dimension given, this program will loop starting from 1 dimension and\n"
" increment the dimension each loop until the program is termnated.\n"
"If dimension_end is given, it loops from dimension to dimension_end.\n"
,argv[0]
);
return -1;
}
const bool loop = ( argc == 2 );
const int radius = atoi( argv[1] );
if( radius < 1 ){
fprintf( stderr, "Please input a positive number for radius\n" );
return -1;
}
if( radius > INT16_MAX ){
fprintf( stderr,
"Please input a number for radius that's less than %d \n", INT16_MAX
);
return -1;
}
const int r2 = square( radius );
int dimension = loop ? 1 : atoi( argv[2] );
if( dimension < 0 ){
fprintf( stderr, "Please input a positive number for dimension\n" );
return -1;
}
int dimension_end = argc < 4 ? dimension : atoi( argv[3] );
if( dimension_end < dimension ){
fprintf( stderr,
"Please bigger number for dimension_end than for dimension\n"
);
return -1;
}
// Cover the edge cases because the rest of the code assumes dimension > 1
if( dimension == 0 ){
printf( "0: 1\n" );
if( dimension_end == 0 && loop == 0 ) return 0;
dimension = 1;
}
if( dimension == 1 ){
printf( "1: %d\n", 2 * radius + 1 );
if( dimension_end == 1 && loop == 0 ) return 0;
dimension = 2;
}
#ifdef TIME
// Get start time
struct timespec t_start;
clock_gettime( CLOCK, &t_start );
#endif
// Init mpz_t variables
mpz_t count, z;
mpz_init( count );
mpz_init( z );
// Allocate arrays
int dimension_allocated = loop ? DIMENSION_PER_ALLOC : dimension_end + 1;
int *vector = calloc( dimension_allocated, sizeof(int) );
mpz_t *factorial_table = calloc( dimension_allocated + 1, sizeof(mpz_t) );
// Generate initial lookup table
mpz_init_set_ui( factorial_table[0], 1 );
mpz_init_set_ui( factorial_table[1], 1 );
printf( "Generating factorial lookup table " );
if( dimension < 2 * r2 + 1 ){ // Generate full lookup table
printf( "until %d\n", dimension - 1 );
for( int i = 2; i < dimension; i++ ){
mpz_init( factorial_table[i] );
mpz_mul_ui( factorial_table[i], factorial_table[i-1], i );
}
}else{ // Generate sparse lookup table
printf( "until %d ", r2 );
// Generate the first part of the table
for( int i = 2; i <= r2; i++ ){
mpz_init( factorial_table[i] );
mpz_mul_ui( factorial_table[i], factorial_table[i-1], i );
}
printf( "and from %d to %d\n",
dimension - r2, dimension - 1 );
// Generate the second part of the table
mpz_init( factorial_table[ dimension - r2 - 1 ] );
mpz_fac_ui( factorial_table[ dimension - r2 - 1 ], dimension - r2 - 1 );
for( int i = dimension - r2; i < dimension; i++ ){
mpz_init( factorial_table[i] );
mpz_mul_ui( factorial_table[i], factorial_table[i-1], i );
}
}
#ifdef TIME
// Get start time
struct timespec t_finish;
clock_gettime( CLOCK, &t_finish );
print_elapsed_time( t_start, t_finish );
#endif
// Actually start doing stuff
do{
#ifdef TIME
// Get start time
clock_gettime( CLOCK, &t_start );
#endif
// Check if enough allocated
if( dimension == dimension_allocated ){
dimension_allocated += DIMENSION_PER_ALLOC;
factorial_table = realloc( factorial_table,
( dimension_allocated + 1 ) * sizeof(mpz_t) );
free( vector );
vector = calloc( dimension_allocated, sizeof(int) );
}
// Initialise count to 0
mpz_set_ui( count, 0 );
// Generate lookup table entry for this dimension
mpz_init( factorial_table[ dimension ] );
mpz_mul_ui( factorial_table[ dimension ],
factorial_table[ dimension - 1 ],
dimension );
// Clear old lookup table entry if unused
if( dimension > r2 * 2 + 1 )
mpz_clear( factorial_table[ dimension - r2 - 1 ] );
// Calculate stuff
for( int i=0; i<dimension; i++ ) vector[i] = 0;
for( bool is_counting = true; is_counting;){
const int cmp = vector_length2_cmp( r2, vector, dimension );
if( cmp >=0 ){
unique_array_orders( z, vector, dimension, factorial_table );
mpz_add( count, count, z );
};
#ifdef DEBUG
for( int i=0; i<dimension; i++ ) printf( "%2d, ", vector[i] );
gmp_printf( "cmp = %2d, count = %Zd\n", cmp, count );
#endif
int max_in_vector = 0;
for( int i=0;; i++ ){
if( i+1 < dimension ){
if( cmp > 0 ){
if( vector[i] < radius ){
vector[i]++;
break;
} else if( vector[i+1] < radius ){
vector[i+1]++;
for(int j=i;j>=0;j--)vector[j]=vector[i+1];
break;
}
}else{
if( vector[i] > max_in_vector ) max_in_vector = vector[i];
if( max_in_vector > vector[i+1] + 1 ){
vector[i+1]++;
for(int j=i;j>=0;j--)vector[j]=vector[i+1];
break;
}
}
}else{
is_counting = false;
break;
}
}
}
#ifdef TIME
// Get elapsed time
clock_gettime( CLOCK, &t_finish );
#endif
// Print stuff
gmp_printf( "%d: %Zd\n", dimension, count );
#ifdef TIME
print_elapsed_time( t_start, t_finish );
#endif
dimension++;
}while( loop || dimension <= dimension_end );
return 0;
}