Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
65d714a
Create build_and_test.sh
LucasBoTang Sep 11, 2025
1bd5f39
Create .gitignore
LucasBoTang Sep 11, 2025
8d15355
Update .gitignore
LucasBoTang Sep 11, 2025
1292069
Update: config
LucasBoTang Sep 16, 2025
8110ca0
Init interface
LucasBoTang Sep 17, 2025
6b36f3f
New test: LP model
LucasBoTang Sep 17, 2025
1aaba96
Merge branch 'dev' into main
LucasBoTang Sep 18, 2025
417510b
Merge pull request #1 from LucasBoTang/main
LucasBoTang Sep 18, 2025
082d122
New feat: interface
LucasBoTang Sep 18, 2025
e60c6d5
New feat: CSC/COO→CSR for interface
LucasBoTang Sep 18, 2025
c393294
Create build_and_test.sh
LucasBoTang Sep 11, 2025
59e3fa5
Create .gitignore
LucasBoTang Sep 11, 2025
e0cd2ee
Update .gitignore
LucasBoTang Sep 11, 2025
5314f12
Update: config
LucasBoTang Sep 16, 2025
961ac6f
Init interface
LucasBoTang Sep 17, 2025
927a70f
New test: LP model
LucasBoTang Sep 17, 2025
06172b2
New feat: interface
LucasBoTang Sep 18, 2025
2093d0d
New feat: CSC/COO→CSR for interface
LucasBoTang Sep 18, 2025
3509994
Merge branch 'dev' of https://github.com/LucasBoTang/cuPDLPx into dev
LucasBoTang Sep 18, 2025
ea5667f
Bug fixed: output interface
LucasBoTang Sep 18, 2025
78fedf5
Document C API and example for LP solving
LucasBoTang Sep 19, 2025
c8ba888
Warning fixed: .c -> .cu for interface
LucasBoTang Sep 19, 2025
43a27a0
Merge branch 'dev' of https://github.com/LucasBoTang/cuPDLPx into dev
LucasBoTang Sep 19, 2025
ce03fd9
Update: .cu -> .c for interface
LucasBoTang Sep 19, 2025
7766045
Clean code: remove the repeating declaration
LucasBoTang Sep 20, 2025
7b3474a
Update: `make_problem_from_matrix` -> `create_lp_problem`
LucasBoTang Sep 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

# Prerequisites
*.d

Expand Down Expand Up @@ -58,3 +57,4 @@ dkms.conf
# ignored files
build/*
test/*
/.vscode
73 changes: 71 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ NVCCFLAGS = -I. -I$(CUDA_HOME)/include -O3 -g -gencode arch=compute_90,code=sm_9
# LDFLAGS for the linker
LDFLAGS = -L$(CONDA_PREFIX)/lib -L$(CUDA_HOME)/lib64 -lcudart -lcusparse -lcublas -lz -lm

# Source discovery (exclude the debug main)
C_SOURCES = $(filter-out $(SRC_DIR)/cupdlpx.c, $(wildcard $(SRC_DIR)/*.c))
CU_SOURCES = $(wildcard $(SRC_DIR)/*.cu)

Expand All @@ -25,22 +26,41 @@ OBJECTS = $(C_OBJECTS) $(CU_OBJECTS)

TARGET_LIB = $(BUILD_DIR)/libcupdlpx.a

# Debug executable (optional)
DEBUG_SRC = $(SRC_DIR)/cupdlpx.c
DEBUG_EXEC = $(BUILD_DIR)/cupdlpx

.PHONY: all clean build
# Tests auto-discovery
TEST_DIR := ./test
TEST_BUILD_DIR := $(BUILD_DIR)/tests

TEST_CU_SOURCES := $(wildcard $(TEST_DIR)/*.cu)
TEST_C_SOURCES := $(wildcard $(TEST_DIR)/*.c)

# Each test source becomes an executable at build/tests/<basename>
TEST_EXEC_CU := $(patsubst $(TEST_DIR)/%.cu,$(TEST_BUILD_DIR)/%,$(TEST_CU_SOURCES))
TEST_EXEC_C := $(patsubst $(TEST_DIR)/%.c,$(TEST_BUILD_DIR)/%,$(TEST_C_SOURCES))

# Phony targets
.PHONY: all clean build tests test run-tests run-test clean-tests

# Default: build the static library
all: $(TARGET_LIB)

# Archive all objects into the static library
$(TARGET_LIB): $(OBJECTS)
@echo "Archiving objects into $(TARGET_LIB)..."
@mkdir -p $(BUILD_DIR)
@ar rcs $@ $^

# Build the debug executable (links the library with cupdlpx.c main)
build: $(DEBUG_EXEC)

$(DEBUG_EXEC): $(DEBUG_SRC) $(TARGET_LIB)
@echo "Building debug executable..."
@$(LINKER) $(NVCCFLAGS) $(DEBUG_SRC) -o $(DEBUG_EXEC) $(TARGET_LIB) $(LDFLAGS)

# Pattern rules for objects
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
@mkdir -p $(BUILD_DIR)
@echo "Compiling $< -> $@..."
Expand All @@ -51,6 +71,55 @@ $(BUILD_DIR)/%.o: $(SRC_DIR)/%.cu
@echo "Compiling $< -> $@..."
@$(NVCC) $(NVCCFLAGS) -c $< -o $@

# Build all tests discovered under test/
test: tests
tests: $(TARGET_LIB) $(TEST_EXEC_CU) $(TEST_EXEC_C)
@echo "All tests built under $(TEST_BUILD_DIR)/"

# Run all tests one by one
run-tests: tests
@echo "Running all tests..."
@set -e; \
for t in $(TEST_EXEC_CU) $(TEST_EXEC_C); do \
echo "=== $$t ==="; \
"$$t" || exit $$?; \
echo; \
done

# Run a single test by basename: make run-test name=<basename>
run-test: tests
@if [ -z "$(name)" ]; then \
echo "Usage: make run-test name=<basename-of-test-file>"; exit 2; \
fi
@if [ -x "$(TEST_BUILD_DIR)/$(name)" ]; then \
echo "=== $(TEST_BUILD_DIR)/$(name) ==="; \
"$(TEST_BUILD_DIR)/$(name)"; \
else \
echo "Executable not found: $(TEST_BUILD_DIR)/$(name)"; \
echo "Did you 'make tests' and is there a test source named '$(TEST_DIR)/$(name).c(u)'?"; \
exit 1; \
fi

# Build rule for CUDA tests: compile and link directly with nvcc
$(TEST_BUILD_DIR)/%: $(TEST_DIR)/%.cu $(TARGET_LIB)
@mkdir -p $(TEST_BUILD_DIR)
@echo "Building CUDA test $< -> $@..."
@$(LINKER) $(NVCCFLAGS) -I$(SRC_DIR) $< -o $@ $(TARGET_LIB) $(LDFLAGS)

# Build rule for C tests: compile with gcc, link with nvcc to get CUDA libs
$(TEST_BUILD_DIR)/%: $(TEST_DIR)/%.c $(TARGET_LIB)
@mkdir -p $(TEST_BUILD_DIR)
@echo "Building C test $< -> $@..."
@$(CC) $(CFLAGS) -I$(SRC_DIR) -c $< -o $(TEST_BUILD_DIR)/$*.o
@$(LINKER) $(NVCCFLAGS) $(TEST_BUILD_DIR)/$*.o -o $@ $(TARGET_LIB) $(LDFLAGS)


# Cleaning
clean-tests:
@echo "Cleaning test executables..."
@rm -rf $(TEST_BUILD_DIR)

clean:
@echo "Cleaning up..."
@rm -rf $(BUILD_DIR) $(TARGET_LIB) $(DEBUG_EXEC)
@rm -rf $(BUILD_DIR) $(TARGET_LIB) $(DEBUG_EXEC)
@rm -rf $(TEST_BUILD_DIR)
90 changes: 90 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,96 @@ The solver generates three text files in the specified <output_directory>. The f
- `INSTANCE_primal_solution.txt`: The primal solution vector.
- `INSTANCE_dual_solution.txt`: The dual solution vector.

### C API Example

Besides the command-line interface, cuPDLPx also provides a C API (interface.c) for directly solving LPs in memory. This is useful when integrating cuPDLPx into other C/C++ projects or when generating problems programmatically.

#### Functions and Parameters

The C API involves two main functions:

```c
lp_problem_t* create_lp_problem(
const matrix_desc_t* A_desc, // constraint matrix A
const double* objective_c, // objective vector c (length n)
const double* objective_constant, // scalar objective offset
const double* var_lb, // variable lower bounds (length n)
const double* var_ub, // variable upper bounds (length n)
const double* con_lb, // constraint lower bounds (length m)
const double* con_ub // constraint upper bounds (length m)
);

cupdlpx_result_t* solve_lp_problem(
const lp_problem_t* prob,
const pdhg_parameters_t* params // NULL → use default parameters
);
```

`create_lp_problem` parameters:
- `A_desc`: Matrix descriptor. Supports `matrix_dense`, `matrix_csr`, `matrix_csc`, `matrix_coo`.
- `objective_c`: Objective vector. If NULL, defaults to all zeros.
- `objective_constant`: Scalar constant term added to the objective value. If NULL, defaults to 0.0.
- `var_lb`: Variable lower bounds. If NULL, defaults to all 0.0.
- `var_ub`: Variable upper bounds. If NULL, defaults to all +INFINITY.
- `con_lb`: Constraint lower bounds. If NULL, defaults to all -INFINITY.
- `con_ub`: Constraint upper bounds. If NULL, defaults to all +INFINITY.

`solve_lp_problem` parameters:
- `prob`: An LP problem built with `create_LP_problem`.
- `params`: Solver parameters. If `NULL`, the solver will use default parameters.

#### Example: Solving a Small LP
```c
#include "cupdlpx/interface.h"
#include <math.h>
#include <stdio.h>

int main() {
int m = 3; // number of constraints
int n = 2; // number of variables

// Dense matrix A
double A[3][2] = {
{1.0, 2.0},
{0.0, 1.0},
{3.0, 2.0}
};

// Describe A
matrix_desc_t A_desc;
A_desc.m = m; A_desc.n = n;
A_desc.fmt = matrix_dense;
A_desc.zero_tolerance = 0.0;
A_desc.data.dense.A = &A[0][0];

// Objective coefficients
double c[2] = {1.0, 1.0};

// Constraint bounds: l <= A x <= u
double l[3] = {5.0, -INFINITY, -INFINITY};
double u[3] = {5.0, 2.0, 8.0};

// Build the problem
lp_problem_t* prob = create_lp_problem(
&A_desc, c, NULL, NULL, NULL, l, u);

// Solve (NULL → use default parameters)
cupdlpx_result_t* res = solve_lp_problem(prob, NULL);

printf("Termination reason: %d\n", res->termination_reason);
printf("Primal objective: %.6f\n", res->primal_objective_value);
printf("Dual objective: %.6f\n", res->dual_objective_value);
for (int j = 0; j < res->num_variables; ++j) {
printf("x[%d] = %.6f\n", j, res->primal_solution[j]);
}

lp_problem_free(prob);
cupdlpx_result_free(res);

return 0;
}
```

## Reference
If you use cuPDLPx or the ideas in your work, please cite the source below.

Expand Down
37 changes: 37 additions & 0 deletions build_and_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/bin/bash
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really want to upload this file to GitHub? This is already the test/test.sh file. Maybe merge these two?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two tests are not the same: one runs from bash and reads an MPS file, while the other directly passes in coefficients. They exercise different code paths (file I/O parsing vs. API interface), so both are valuable to keep.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

# ============================
# cuPDLPx build & test script
# ============================

# 1. request interactive GPU node
#echo ">>> Requesting interactive GPU node..."
#srun --pty --partition=ou_sloan_gpu --cpus-per-task=1 --mem=16G --gres=gpu:1 --time=00:15:00 bash

# 2. load CUDA module
echo ">>> Loading CUDA module..."
module purge
module load cuda/12.4 || { echo "CUDA 12.4 not found, try another version"; exit 1; }

# . show environment info
which nvcc
nvcc --version
echo "CUDA_HOME = $CUDA_HOME"

# 4. build
echo ">>> Cleaning and building..."
make clean
make build

# 5. download test instance if not present
TESTDIR=$HOME/src/cuPDLPx/test
mkdir -p $TESTDIR
if [ ! -f $TESTDIR/2club200v15p5scn.mps.gz ]; then
echo ">>> Downloading test instance..."
wget -P $TESTDIR https://miplib.zib.de/WebData/instances/2club200v15p5scn.mps.gz
fi

# 6. run test
echo ">>> Running smoke test..."
./build/cupdlpx $TESTDIR/2club200v15p5scn.mps.gz $TESTDIR

echo ">>> Done! Results saved in $TESTDIR"
25 changes: 0 additions & 25 deletions cupdlpx/cupdlpx.c
Original file line number Diff line number Diff line change
Expand Up @@ -130,31 +130,6 @@ void save_solver_summary(const cupdlpx_result_t *result, const char *output_dir,
free(file_path);
}

void set_default_parameters(pdhg_parameters_t *params)
{
params->l_inf_ruiz_iterations = 10;
params->has_pock_chambolle_alpha = true;
params->pock_chambolle_alpha = 1.0;
params->bound_objective_rescaling = true;
params->verbose = false;
params->termination_evaluation_frequency = 200;
params->reflection_coefficient = 1.0;

params->termination_criteria.eps_optimal_relative = 1e-4;
params->termination_criteria.eps_feasible_relative = 1e-4;
params->termination_criteria.eps_infeasible = 1e-10;
params->termination_criteria.time_sec_limit = 3600.0;
params->termination_criteria.iteration_limit = INT32_MAX;

params->restart_params.artificial_restart_threshold = 0.36;
params->restart_params.sufficient_reduction_for_restart = 0.2;
params->restart_params.necessary_reduction_for_restart = 0.5;
params->restart_params.k_p = 0.99;
params->restart_params.k_i = 0.01;
params->restart_params.k_d = 0.0;
params->restart_params.i_smooth = 0.3;
}

void print_usage(const char *prog_name)
{
fprintf(stderr, "Usage: %s [OPTIONS] <mps_file> <output_dir>\n\n", prog_name);
Expand Down
Loading