Benchmarks
The charts on the homepage demonstrate PolySolve‘s stability and performance against NumPy. The full Python script used to generate these results is provided below.
This script was designed to test two key metrics:
- Accuracy: It measures the Mean Absolute Error (MAE) between the known roots of a generated polynomial and the roots found by each library.
- Performance: It measures the wall-clock time taken to solve polynomials of increasing degree.
Benchmark Script
import numpy as npimport pandas as pdimport timeimport polysolve# Check for CUDA availabilitytry:import cupy_CUPY_AVAILABLE = Trueprint("CuPy found. Running 3-way benchmark (NumPy, PolySolve CPU, PolySolve GPU)...")except ImportError:_CUPY_AVAILABLE = Falseprint("CuPy not found. Running 2-way benchmark (NumPy, PolySolve CPU)...")def calculate_mae(coeffs, roots):"""Calculates the mean absolute error by plugging roots back into the polynomial."""if roots.size == 0:return 0.0errors = np.abs(np.polyval(coeffs, roots.astype(complex)))return np.mean(errors)def get_real_roots(roots):"""Filters a numpy array for real roots (imaginary part < 1e-10)."""return roots[np.abs(np.imag(roots)) < 1e-10].realdef run_suite():"""Runs the full 3-way benchmark suite for time and accuracy."""degrees = [2, 3, 5, 10, 20, 40, 60, 80, 100]repeat_count = 10results = []print("Starting benchmark suite...")print(f"Degrees: {degrees}")print(f"Repeat Count: {repeat_count}")ga_options = polysolve.GA_Options(num_of_generations=150,data_size=1000000,mutation_strength=0.005,elite_ratio=0.1,crossover_ratio=0.5,mutation_ratio=0.4,root_precision=15)for deg in degrees:print(f"Testing Degree: {deg} (x{repeat_count} loops)")np_times, ps_cpu_times, ps_gpu_times = [], [], []np_maes, ps_cpu_maes, ps_gpu_maes = [], [], []for i in range(repeat_count):# Generate one polynomial for all methodscoeffs = np.random.rand(deg + 1)ps_poly = polysolve.Function(deg)ps_poly.set_coeffs(coeffs)# --- 1. Test NumPy (CPU) ---start_np = time.perf_counter()np_all_roots = np.roots(coeffs)np_times.append(time.perf_counter() - start_np)np_real_roots = get_real_roots(np_all_roots)np_maes.append(calculate_mae(coeffs, np_real_roots))# --- 2. Test PolySolve (CPU) ---start_ps_cpu = time.perf_counter()ps_cpu_real_roots = ps_poly.get_real_roots(options=ga_options, use_cuda=False)ps_cpu_times.append(time.perf_counter() - start_ps_cpu)ps_cpu_maes.append(calculate_mae(coeffs, ps_cpu_real_roots))# --- 3. Test PolySolve (GPU) ---if _CUPY_AVAILABLE:start_ps_gpu = time.perf_counter()ps_gpu_real_roots = ps_poly.get_real_roots(options=ga_options, use_cuda=True)ps_gpu_times.append(time.perf_counter() - start_ps_gpu)ps_gpu_maes.append(calculate_mae(coeffs, ps_gpu_real_roots))else:ps_gpu_times.append(np.nan)ps_gpu_maes.append(np.nan)# Calculate averages for this degreeavg_np_time = np.mean(np_times)avg_ps_cpu_time = np.mean(ps_cpu_times)avg_ps_gpu_time = np.mean(ps_gpu_times)avg_np_mae = np.mean(np_maes)avg_ps_cpu_mae = np.mean(ps_cpu_maes)avg_ps_gpu_mae = np.mean(ps_gpu_maes)# Print resultsprint(f" NumPy (CPU): Time: {avg_np_time:.6f}s | MAE: {avg_np_mae:.2e}")print(f" PolySolve (CPU): Time: {avg_ps_cpu_time:.6f}s | MAE: {avg_ps_cpu_mae:.2e}")if _CUPY_AVAILABLE:print(f" PolySolve (GPU): Time: {avg_ps_gpu_time:.6f}s | MAE: {avg_ps_gpu_mae:.2e}")results.append({"Degree": deg,"NumPy (CPU) Time": avg_np_time,"PolySolve (CPU) Time": avg_ps_cpu_time,"PolySolve (GPU) Time": avg_ps_gpu_time,"NumPy (CPU) MAE": avg_np_mae,"PolySolve (CPU) MAE": avg_ps_cpu_mae,"PolySolve (GPU) MAE": avg_ps_gpu_mae})df = pd.DataFrame(results)df.to_csv("benchmark_results.csv", index=False)print("--- Benchmark Complete ---")print(df)print("Results saved to benchmark_results.csv")if __name__ == "__main__":run_suite()
