Linear equetion solver.

This commit is contained in:
Alan Mishchenko 2025-11-01 22:55:14 -07:00
parent 5273eab9f7
commit 800c274cc2
2 changed files with 815 additions and 0 deletions

View File

@ -4,6 +4,7 @@ SRC += src/misc/util/utilBridge.c \
src/misc/util/utilColor.c \
src/misc/util/utilFile.c \
src/misc/util/utilIsop.c \
src/misc/util/utilLinear.c \
src/misc/util/utilNam.c \
src/misc/util/utilPrefix.cpp \
src/misc/util/utilPth.c \

814
src/misc/util/utilLinear.c Normal file
View File

@ -0,0 +1,814 @@
/**CFile****************************************************************
FileName [utilLinear.c]
SystemName [ABC: Logic synthesis and verification system.]
PackageName [Handling counter-examples.]
Synopsis [Handling counter-examples.]
Author [Alan Mishchenko]
Affiliation [UC Berkeley]
Date [Ver. 1.0. Started - June 20, 2005.]
Revision [$Id: utilColor.c,v 1.00 2005/06/20 00:00:00 alanmi Exp $]
***********************************************************************/
//#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "misc/util/abc_global.h"
ABC_NAMESPACE_IMPL_START
////////////////////////////////////////////////////////////////////////
/// DECLARATIONS ///
////////////////////////////////////////////////////////////////////////
#define EPSILON 1e-12
#define VERIFY_TOLERANCE 1e-9
#define INTEGER_TOLERANCE 1e-9
////////////////////////////////////////////////////////////////////////
/// FUNCTION DEFINITIONS ///
////////////////////////////////////////////////////////////////////////
// Reads a full line from the file, allocating memory dynamically.
static char *read_line(FILE *fp) {
size_t capacity = 128;
size_t length = 0;
char *buffer = malloc(capacity);
if (!buffer) {
errno = ENOMEM;
return NULL;
}
int ch;
while ((ch = fgetc(fp)) != EOF) {
if (ch == '\r') {
continue;
}
if (ch == '\n') {
break;
}
if (length + 1 >= capacity) {
size_t new_capacity = capacity * 2;
char *tmp = realloc(buffer, new_capacity);
if (!tmp) {
free(buffer);
errno = ENOMEM;
return NULL;
}
buffer = tmp;
capacity = new_capacity;
}
buffer[length++] = (char)ch;
}
if (length == 0 && ch == EOF) {
free(buffer);
return NULL;
}
buffer[length] = '\0';
return buffer;
}
// Loads the augmented matrix from a text file.
static int load_augmented_matrix(const char *path, double **outMatrix, int *outEqus, int *outVars) {
FILE *fp = fopen(path, "r");
if (!fp) {
perror("fopen");
return 0;
}
double *data = NULL;
size_t capacity = 0;
size_t count = 0;
int row_width = -1;
int rows = 0;
while (1) {
char *line = read_line(fp);
if (!line) {
if (feof(fp)) {
break;
}
fprintf(stderr, "Failed to read line from input file.\n");
free(data);
fclose(fp);
return 0;
}
char *ptr = line;
int columns = 0;
while (*ptr != '\0') {
char *end_ptr;
double value = strtod(ptr, &end_ptr);
if (end_ptr == ptr) {
if (*ptr == '\0') {
break;
}
++ptr;
continue;
}
if (count == capacity) {
size_t new_capacity = capacity == 0 ? 64 : capacity * 2;
double *tmp = realloc(data, new_capacity * sizeof(double));
if (!tmp) {
fprintf(stderr, "Out of memory while loading matrix.\n");
free(data);
free(line);
fclose(fp);
return 0;
}
data = tmp;
capacity = new_capacity;
}
data[count++] = value;
++columns;
ptr = end_ptr;
}
free(line);
if (columns == 0) {
continue;
}
if (row_width == -1) {
row_width = columns;
} else if (columns != row_width) {
fprintf(stderr, "Row %d has %d entries, expected %d.\n", rows + 1, columns, row_width);
free(data);
fclose(fp);
return 0;
}
++rows;
}
fclose(fp);
if (rows == 0) {
fprintf(stderr, "Input file is empty or contains no data rows.\n");
free(data);
return 0;
}
if (row_width <= 1) {
fprintf(stderr, "Each row must contain at least two numbers.\n");
free(data);
return 0;
}
if ((size_t)rows * (size_t)row_width != count) {
fprintf(stderr, "Internal error: unexpected data size.\n");
free(data);
return 0;
}
*outMatrix = data;
*outEqus = rows;
*outVars = row_width - 1;
return 1;
}
// Swaps two rows in-place.
static void swap_rows(double *matrix, int cols, int row_a, int row_b) {
if (row_a == row_b) {
return;
}
for (int c = 0; c < cols; ++c) {
double tmp = matrix[row_a * cols + c];
matrix[row_a * cols + c] = matrix[row_b * cols + c];
matrix[row_b * cols + c] = tmp;
}
}
// Clears values that are numerically close to zero.
static void prune_small_values(double *row, int count) {
for (int i = 0; i < count; ++i) {
if (fabs(row[i]) < EPSILON) {
row[i] = 0.0;
}
}
}
// Computes a solution vector given assignments for the free variables.
static void compute_solution_from_free(
const double *rref,
int nVars,
const int *pivot_cols,
int rank,
const int *free_cols,
int free_count,
const double *free_values,
double *solution_out) {
int row_len = nVars + 1;
for (int i = 0; i < nVars; ++i) {
solution_out[i] = 0.0;
}
for (int i = 0; i < free_count; ++i) {
int col = free_cols[i];
double value = free_values ? free_values[i] : 0.0;
solution_out[col] = value;
}
for (int r = 0; r < rank; ++r) {
int pivot_col = pivot_cols[r];
double value = rref[r * row_len + nVars];
for (int j = 0; j < free_count; ++j) {
int free_col = free_cols[j];
value -= rref[r * row_len + free_col] * solution_out[free_col];
}
solution_out[pivot_col] = value;
}
}
// Verifies that the solution satisfies the original system.
static int verify_solution(
const double *matrix,
int nEqus,
int nVars,
const double *solution,
double tolerance,
double *out_max_residual) {
int row_len = nVars + 1;
double max_residual = 0.0;
int ok = 1;
for (int r = 0; r < nEqus; ++r) {
double lhs = 0.0;
for (int c = 0; c < nVars; ++c) {
lhs += (double)matrix[r * row_len + c] * (double)solution[c];
}
double rhs = matrix[r * row_len + nVars];
double residual = fabs(lhs - rhs);
if ((double)residual > max_residual) {
max_residual = (double)residual;
}
if ((double)residual > tolerance) {
ok = 0;
}
}
if (out_max_residual) {
*out_max_residual = max_residual;
}
return ok;
}
// Checks whether a particular free-variable assignment yields an integer solution.
// Optionally enforces that the first two variables differ once rounded to integers.
static int assign_and_check_integer(
const double *rref,
int nVars,
const int *pivot_cols,
int rank,
const int *free_cols,
int free_count,
const double *free_values,
const double *original_matrix,
int nEqus,
double verify_tolerance,
double *workspace,
double *rounded,
double *out_solution,
int require_nonzero,
int require_distinct_first_two) {
compute_solution_from_free(
rref,
nVars,
pivot_cols,
rank,
free_cols,
free_count,
free_values,
workspace);
for (int i = 0; i < nVars; ++i) {
double candidate = workspace[i];
double nearest = round(candidate);
if (fabs(candidate - nearest) > INTEGER_TOLERANCE) {
return 0;
}
rounded[i] = nearest;
}
int all_zero = 1;
int has_zero_component = 0;
int has_positive_component = 0;
int has_negative_component = 0;
for (int i = 0; i < nVars; ++i) {
if (rounded[i] != 0.0) {
all_zero = 0;
} else {
has_zero_component = 1;
}
if (rounded[i] > 0.0) {
has_positive_component = 1;
} else if (rounded[i] < 0.0) {
has_negative_component = 1;
}
}
if (require_nonzero) {
if (all_zero || has_negative_component || !has_positive_component || !has_zero_component) {
return 0;
}
}
if (require_distinct_first_two && nVars >= 2) {
if (fabs(rounded[0] - rounded[1]) < INTEGER_TOLERANCE) {
return 0;
}
}
if (verify_solution(original_matrix, nEqus, nVars, rounded, verify_tolerance, NULL)) {
memcpy(out_solution, rounded, (size_t)nVars * sizeof(double));
return 1;
}
return 0;
}
// Recursively explores integer assignments for free variables while propagating optional constraints.
static int search_integer_solutions(
int depth,
int free_count,
double *free_values,
const int *candidates,
int candidate_count,
const double *rref,
int nEqus,
int nVars,
const int *pivot_cols,
int rank,
const int *free_cols,
const double *original_matrix,
double verify_tolerance,
double *workspace,
double *rounded,
double *out_solution,
int require_nonzero,
int require_distinct_first_two,
int *explored,
int exploration_limit) {
if (*explored >= exploration_limit) {
return 0;
}
if (depth == free_count) {
(*explored)++;
return assign_and_check_integer(
rref,
nVars,
pivot_cols,
rank,
free_cols,
free_count,
free_values,
original_matrix,
nEqus,
verify_tolerance,
workspace,
rounded,
out_solution,
require_nonzero,
require_distinct_first_two);
}
for (int i = 0; i < candidate_count; ++i) {
free_values[depth] = (double)candidates[i];
if (search_integer_solutions(
depth + 1,
free_count,
free_values,
candidates,
candidate_count,
rref,
nEqus,
nVars,
pivot_cols,
rank,
free_cols,
original_matrix,
verify_tolerance,
workspace,
rounded,
out_solution,
require_nonzero,
require_distinct_first_two,
explored,
exploration_limit)) {
return 1;
}
}
return 0;
}
// Attempts to locate an integer solution when free variables are present.
// Searches a small integer lattice and enforces optional constraints such as distinct first variables.
static int try_integer_solution(
const double *rref,
int nEqus,
int nVars,
const int *pivot_cols,
int rank,
const int *free_cols,
int free_count,
const double *original_matrix,
double verify_tolerance,
double *workspace,
double *rounded,
double *out_solution,
int require_nonzero,
int require_distinct_first_two) {
if (free_count == 0) {
return 0;
}
// Expand search radius to allow moderately sized integer assignments (up to |20|).
const int candidates[] = {
0,
1, -1,
2, -2,
3, -3,
4, -4,
5, -5,
6, -6,
7, -7,
8, -8,
9, -9,
10, -10,
11, -11,
12, -12,
13, -13,
14, -14,
15, -15,
16, -16,
17, -17,
18, -18,
19, -19,
20, -20
};
const int candidate_count = (int)(sizeof(candidates) / sizeof(candidates[0]));
int exploration_limit = 200000;
int explored = 0;
double *free_values = malloc((size_t)free_count * sizeof(double));
if (!free_values) {
return 0;
}
for (int i = 0; i < free_count; ++i) {
free_values[i] = 0.0;
}
if (assign_and_check_integer(
rref,
nVars,
pivot_cols,
rank,
free_cols,
free_count,
free_values,
original_matrix,
nEqus,
verify_tolerance,
workspace,
rounded,
out_solution,
require_nonzero,
require_distinct_first_two)) {
free(free_values);
return 1;
}
int found = search_integer_solutions(
0,
free_count,
free_values,
candidates,
candidate_count,
rref,
nEqus,
nVars,
pivot_cols,
rank,
free_cols,
original_matrix,
verify_tolerance,
workspace,
rounded,
out_solution,
require_nonzero,
require_distinct_first_two,
&explored,
exploration_limit);
free(free_values);
return found;
}
// Gaussian-elimination-based solver that optionally searches for integer solutions.
double *linear_equation_solver(double *pMatrix, int nEqus, int nVars) {
if (!pMatrix || nEqus <= 0 || nVars <= 0) {
fprintf(stderr, "Invalid matrix dimensions provided to solver.\n");
return NULL;
}
int row_len = nVars + 1;
size_t matrix_bytes = (size_t)nEqus * (size_t)row_len * sizeof(double);
double *matrix_copy = NULL;
double *solution = NULL;
double *workspace = NULL;
double *rounded = NULL;
double *result = NULL;
int *pivot_cols = NULL;
int *pivot_column_used = NULL;
int *free_cols = NULL;
matrix_copy = malloc(matrix_bytes);
if (!matrix_copy) {
fprintf(stderr, "Unable to allocate memory for solver workspace.\n");
goto cleanup;
}
memcpy(matrix_copy, pMatrix, matrix_bytes);
pivot_cols = malloc((size_t)nEqus * sizeof(int));
if (!pivot_cols) {
fprintf(stderr, "Unable to allocate memory for pivot tracking.\n");
goto cleanup;
}
for (int i = 0; i < nEqus; ++i) {
pivot_cols[i] = -1;
}
int pivot_row_index = 0;
for (int col = 0; col < nVars && pivot_row_index < nEqus; ++col) {
int best_row = pivot_row_index;
double max_value = fabs(matrix_copy[best_row * row_len + col]);
for (int r = pivot_row_index + 1; r < nEqus; ++r) {
double value = fabs(matrix_copy[r * row_len + col]);
if (value > max_value) {
max_value = value;
best_row = r;
}
}
if (max_value < EPSILON) {
continue;
}
swap_rows(matrix_copy, row_len, pivot_row_index, best_row);
double pivot_value = matrix_copy[pivot_row_index * row_len + col];
for (int c = 0; c < row_len; ++c) {
matrix_copy[pivot_row_index * row_len + c] /= pivot_value;
}
matrix_copy[pivot_row_index * row_len + col] = 1.0;
prune_small_values(&matrix_copy[pivot_row_index * row_len], row_len);
for (int r = 0; r < nEqus; ++r) {
if (r == pivot_row_index) {
continue;
}
double factor = matrix_copy[r * row_len + col];
if (fabs(factor) < EPSILON) {
continue;
}
for (int c = 0; c < row_len; ++c) {
matrix_copy[r * row_len + c] -= factor * matrix_copy[pivot_row_index * row_len + c];
}
matrix_copy[r * row_len + col] = 0.0;
prune_small_values(&matrix_copy[r * row_len], row_len);
}
pivot_cols[pivot_row_index] = col;
++pivot_row_index;
}
int rank = pivot_row_index;
//printf("Rank: %d\n", rank);
for (int r = 0; r < nEqus; ++r) {
int zero_row = 1;
for (int c = 0; c < nVars; ++c) {
if (fabs(matrix_copy[r * row_len + c]) > EPSILON) {
zero_row = 0;
break;
}
}
if (zero_row && fabs(matrix_copy[r * row_len + nVars]) > EPSILON) {
printf("Verification: inconsistent system detected.\n");
goto cleanup;
}
}
pivot_column_used = calloc((size_t)nVars, sizeof(int));
if (!pivot_column_used) {
fprintf(stderr, "Unable to allocate memory for pivot metadata.\n");
goto cleanup;
}
for (int r = 0; r < rank; ++r) {
int col = pivot_cols[r];
if (col >= 0 && col < nVars) {
pivot_column_used[col] = 1;
}
}
int free_count = 0;
for (int c = 0; c < nVars; ++c) {
if (!pivot_column_used[c]) {
++free_count;
}
}
if (free_count > 0) {
free_cols = malloc((size_t)free_count * sizeof(int));
if (!free_cols) {
fprintf(stderr, "Unable to allocate memory for free column list.\n");
goto cleanup;
}
int idx = 0;
for (int c = 0; c < nVars; ++c) {
if (!pivot_column_used[c]) {
free_cols[idx++] = c;
}
}
}
int homogeneous_rhs = 1;
for (int r = 0; r < nEqus; ++r) {
if (fabs(pMatrix[r * row_len + nVars]) > EPSILON) {
homogeneous_rhs = 0;
break;
}
}
// Only demand integer solutions when the system is homogeneous with free variables.
int require_nonzero_integer = homogeneous_rhs && free_count > 0;
// Enforce distinct first two variables whenever multiple variables remain free.
int require_distinct_first_two = (free_count > 0 && nVars >= 2);
solution = malloc((size_t)nVars * sizeof(double));
workspace = malloc((size_t)nVars * sizeof(double));
rounded = malloc((size_t)nVars * sizeof(double));
if (!solution || !workspace || !rounded) {
fprintf(stderr, "Unable to allocate solver buffers.\n");
goto cleanup;
}
if (free_count == 0) {
compute_solution_from_free(
matrix_copy,
nVars,
pivot_cols,
rank,
free_cols,
free_count,
NULL,
solution);
} else {
if (free_count >= 6) {
printf("Warning: large nullspace (%d free variables); integer search radius +/-20 may be expensive.\n", free_count);
}
if (!try_integer_solution(
matrix_copy,
nEqus,
nVars,
pivot_cols,
rank,
free_cols,
free_count,
pMatrix,
VERIFY_TOLERANCE,
workspace,
rounded,
solution,
require_nonzero_integer,
require_distinct_first_two)) {
for (int i = 0; i < free_count; ++i) {
workspace[i] = 0.0;
}
compute_solution_from_free(
matrix_copy,
nVars,
pivot_cols,
rank,
free_cols,
free_count,
workspace,
solution);
if (require_nonzero_integer && require_distinct_first_two) {
printf("Note: integer solution meeting non-negative and distinct-first-two constraints not found; returning floating-point solution.\n");
} else if (require_nonzero_integer) {
printf("Note: non-negative integer solution with mixed zero/positive entries not found; returning floating-point solution.\n");
} else if (require_distinct_first_two) {
printf("Note: integer solution with distinct first two variables not found; returning floating-point solution.\n");
} else {
printf("Note: integer solution not found; returning floating-point solution.\n");
}
}
}
double max_residual = 0.0;
if (!verify_solution(pMatrix, nEqus, nVars, solution, VERIFY_TOLERANCE, &max_residual)) {
printf("Verification: failure (max residual = %.6f)\n", max_residual);
goto cleanup;
}
//printf("Verification: success (max residual = %.6f)\n", max_residual);
result = solution;
solution = NULL;
cleanup:
free(matrix_copy);
free(pivot_cols);
free(pivot_column_used);
free(free_cols);
free(workspace);
free(rounded);
free(solution);
return result;
}
/*
int main(int argc, char **argv) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <matrix_file>\n", argv[0]);
return EXIT_FAILURE;
}
double *matrix = NULL;
int nEqus = 0;
int nVars = 0;
if (!load_augmented_matrix(argv[1], &matrix, &nEqus, &nVars)) {
return EXIT_FAILURE;
}
printf("Equations: %d, Variables: %d\n", nEqus, nVars);
struct timespec start_time;
struct timespec end_time;
if (clock_gettime(CLOCK_MONOTONIC, &start_time) != 0) {
perror("clock_gettime");
free(matrix);
return EXIT_FAILURE;
}
double *solution = linear_equation_solver(matrix, nEqus, nVars);
if (clock_gettime(CLOCK_MONOTONIC, &end_time) != 0) {
perror("clock_gettime");
free(matrix);
free(solution);
return EXIT_FAILURE;
}
if (!solution) {
free(matrix);
return EXIT_FAILURE;
}
double elapsed = (double)(end_time.tv_sec - start_time.tv_sec);
elapsed += (double)(end_time.tv_nsec - start_time.tv_nsec) / 1e9;
printf("Solve time: %.6f seconds\n", elapsed);
for (int i = 0; i < nVars; ++i) {
printf("x%d = %.6f\n", i, solution[i]);
}
free(matrix);
free(solution);
return EXIT_SUCCESS;
}
*/
////////////////////////////////////////////////////////////////////////
/// END OF FILE ///
////////////////////////////////////////////////////////////////////////
ABC_NAMESPACE_IMPL_END