polysolve 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
polysolve/__init__.py ADDED
@@ -0,0 +1,451 @@
1
+ import math
2
+ import numpy as np
3
+ from dataclasses import dataclass
4
+ from typing import List, Optional
5
+ import warnings
6
+
7
+ # Attempt to import CuPy for CUDA acceleration.
8
+ # If CuPy is not installed, the CUDA functionality will not be available.
9
+ try:
10
+ import cupy
11
+ _CUPY_AVAILABLE = True
12
+ except ImportError:
13
+ _CUPY_AVAILABLE = False
14
+
15
+ # The CUDA kernel for the fitness function
16
+ _FITNESS_KERNEL = """
17
+ extern "C" __global__ void fitness_kernel(
18
+ const long long* coefficients,
19
+ int num_coefficients,
20
+ const double* x_vals,
21
+ double* ranks,
22
+ int size,
23
+ double y_val)
24
+ {
25
+ int idx = threadIdx.x + blockIdx.x * blockDim.x;
26
+ if (idx < size)
27
+ {
28
+ double ans = 0;
29
+ int lrgst_expo = num_coefficients - 1;
30
+ for (int i = 0; i < num_coefficients; ++i)
31
+ {
32
+ ans += coefficients[i] * pow(x_vals[idx], (double)(lrgst_expo - i));
33
+ }
34
+
35
+ ans -= y_val;
36
+ ranks[idx] = (ans == 0) ? 1.7976931348623157e+308 : fabs(1.0 / ans);
37
+ }
38
+ }
39
+ """
40
+
41
+ @dataclass
42
+ class GA_Options:
43
+ """
44
+ Configuration options for the genetic algorithm used to find function roots.
45
+
46
+ Attributes:
47
+ min_range (float): The minimum value for the initial random solutions.
48
+ max_range (float): The maximum value for the initial random solutions.
49
+ num_of_generations (int): The number of iterations the algorithm will run.
50
+ sample_size (int): The number of top solutions to keep and return.
51
+ data_size (int): The total number of solutions generated in each generation.
52
+ mutation_percentage (float): The amount by which top solutions are mutated each generation.
53
+ """
54
+ min_range: float = -100.0
55
+ max_range: float = 100.0
56
+ num_of_generations: int = 10
57
+ sample_size: int = 1000
58
+ data_size: int = 100000
59
+ mutation_percentage: float = 0.01
60
+
61
+ class Function:
62
+ """
63
+ Represents an exponential function (polynomial) of the form:
64
+ c_0*x^n + c_1*x^(n-1) + ... + c_n
65
+ """
66
+ def __init__(self, largest_exponent: int):
67
+ """
68
+ Initializes a function with its highest degree.
69
+
70
+ Args:
71
+ largest_exponent (int): The largest exponent (n) in the function.
72
+ """
73
+ if not isinstance(largest_exponent, int) or largest_exponent < 0:
74
+ raise ValueError("largest_exponent must be a non-negative integer.")
75
+ self._largest_exponent = largest_exponent
76
+ self.coefficients: Optional[np.ndarray] = None
77
+ self._initialized = False
78
+
79
+ def set_coeffs(self, coefficients: List[int]):
80
+ """
81
+ Sets the coefficients of the polynomial.
82
+
83
+ Args:
84
+ coefficients (List[int]): A list of integer coefficients. The list size
85
+ must be largest_exponent + 1.
86
+
87
+ Raises:
88
+ ValueError: If the input is invalid.
89
+ """
90
+ expected_size = self._largest_exponent + 1
91
+ if len(coefficients) != expected_size:
92
+ raise ValueError(
93
+ f"Function with exponent {self._largest_exponent} requires {expected_size} coefficients, "
94
+ f"but {len(coefficients)} were given."
95
+ )
96
+ if coefficients[0] == 0 and self._largest_exponent > 0:
97
+ raise ValueError("The first constant (for the largest exponent) cannot be 0.")
98
+
99
+ self.coefficients = np.array(coefficients, dtype=np.int64)
100
+ self._initialized = True
101
+
102
+ def _check_initialized(self):
103
+ """Raises a RuntimeError if the function coefficients have not been set."""
104
+ if not self._initialized:
105
+ raise RuntimeError("Function is not fully initialized. Call .set_coeffs() first.")
106
+
107
+ @property
108
+ def largest_exponent(self) -> int:
109
+ """Returns the largest exponent of the function."""
110
+ return self._largest_exponent
111
+
112
+ def solve_y(self, x_val: float) -> float:
113
+ """
114
+ Solves for y given an x value. (i.e., evaluates the polynomial at x).
115
+
116
+ Args:
117
+ x_val (float): The x-value to evaluate.
118
+
119
+ Returns:
120
+ float: The resulting y-value.
121
+ """
122
+ self._check_initialized()
123
+ return np.polyval(self.coefficients, x_val)
124
+
125
+ def differential(self) -> 'Function':
126
+ """
127
+ Calculates the derivative of the function.
128
+
129
+ Returns:
130
+ Function: A new Function object representing the derivative.
131
+ """
132
+ self._check_initialized()
133
+ if self._largest_exponent == 0:
134
+ raise ValueError("Cannot differentiate a constant (Function of degree 0).")
135
+
136
+ derivative_coefficients = np.polyder(self.coefficients)
137
+
138
+ diff_func = Function(self._largest_exponent - 1)
139
+ diff_func.set_coeffs(derivative_coefficients.tolist())
140
+ return diff_func
141
+
142
+ def get_real_roots(self, options: GA_Options = GA_Options(), use_cuda: bool = False) -> np.ndarray:
143
+ """
144
+ Uses a genetic algorithm to find the approximate real roots of the function (where y=0).
145
+
146
+ Args:
147
+ options (GA_Options): Configuration for the genetic algorithm.
148
+ use_cuda (bool): If True, attempts to use CUDA for acceleration.
149
+
150
+ Returns:
151
+ np.ndarray: An array of approximate root values.
152
+ """
153
+ self._check_initialized()
154
+ return self.solve_x(0.0, options, use_cuda)
155
+
156
+ def solve_x(self, y_val: float, options: GA_Options = GA_Options(), use_cuda: bool = False) -> np.ndarray:
157
+ """
158
+ Uses a genetic algorithm to find x-values for a given y-value.
159
+
160
+ Args:
161
+ y_val (float): The target y-value.
162
+ options (GA_Options): Configuration for the genetic algorithm.
163
+ use_cuda (bool): If True, attempts to use CUDA for acceleration.
164
+
165
+ Returns:
166
+ np.ndarray: An array of approximate x-values.
167
+ """
168
+ self._check_initialized()
169
+ if use_cuda and _CUPY_AVAILABLE:
170
+ return self._solve_x_cuda(y_val, options)
171
+ else:
172
+ if use_cuda:
173
+ warnings.warn(
174
+ "use_cuda=True was specified, but CuPy is not installed. "
175
+ "Falling back to NumPy (CPU). For GPU acceleration, "
176
+ "install with 'pip install polysolve[cuda]'.",
177
+ UserWarning
178
+ )
179
+
180
+ return self._solve_x_numpy(y_val, options)
181
+
182
+ def _solve_x_numpy(self, y_val: float, options: GA_Options) -> np.ndarray:
183
+ """Genetic algorithm implementation using NumPy (CPU)."""
184
+ # Create initial random solutions
185
+ solutions = np.random.uniform(options.min_range, options.max_range, options.data_size)
186
+
187
+ for _ in range(options.num_of_generations):
188
+ # Calculate fitness for all solutions (vectorized)
189
+ y_calculated = np.polyval(self.coefficients, solutions)
190
+ error = y_calculated - y_val
191
+
192
+ ranks = np.where(error == 0, np.finfo(float).max, np.abs(1.0 / error))
193
+
194
+ # Sort solutions by fitness (descending)
195
+ sorted_indices = np.argsort(-ranks)
196
+ solutions = solutions[sorted_indices]
197
+
198
+ # Keep only the top solutions
199
+ top_solutions = solutions[:options.sample_size]
200
+
201
+ # For the next generation, start with the mutated top solutions
202
+ # and fill the rest with new random values.
203
+ mutation_factors = np.random.uniform(
204
+ 1 - options.mutation_percentage,
205
+ 1 + options.mutation_percentage,
206
+ options.sample_size
207
+ )
208
+ mutated_solutions = top_solutions * mutation_factors
209
+
210
+ new_random_solutions = np.random.uniform(
211
+ options.min_range, options.max_range, options.data_size - options.sample_size
212
+ )
213
+
214
+ solutions = np.concatenate([mutated_solutions, new_random_solutions])
215
+
216
+ # Final sort of the best solutions from the last generation
217
+ final_solutions = np.sort(solutions[:options.sample_size])
218
+ return final_solutions
219
+
220
+ def _solve_x_cuda(self, y_val: float, options: GA_Options) -> np.ndarray:
221
+ """Genetic algorithm implementation using CuPy (GPU/CUDA)."""
222
+ # Load the raw CUDA kernel
223
+ fitness_gpu = cupy.RawKernel(_FITNESS_KERNEL, 'fitness_kernel')
224
+
225
+ # Move coefficients to GPU
226
+ d_coefficients = cupy.array(self.coefficients, dtype=cupy.int64)
227
+
228
+ # Create initial random solutions on the GPU
229
+ d_solutions = cupy.random.uniform(
230
+ options.min_range, options.max_range, options.data_size, dtype=cupy.float64
231
+ )
232
+ d_ranks = cupy.empty(options.data_size, dtype=cupy.float64)
233
+
234
+ # Configure kernel launch parameters
235
+ threads_per_block = 512
236
+ blocks_per_grid = (options.data_size + threads_per_block - 1) // threads_per_block
237
+
238
+ for i in range(options.num_of_generations):
239
+ # Run the fitness kernel on the GPU
240
+ fitness_gpu(
241
+ (blocks_per_grid,), (threads_per_block,),
242
+ (d_coefficients, d_coefficients.size, d_solutions, d_ranks, d_solutions.size, y_val)
243
+ )
244
+
245
+ # Sort solutions by rank on the GPU
246
+ sorted_indices = cupy.argsort(-d_ranks)
247
+ d_solutions = d_solutions[sorted_indices]
248
+
249
+ if i + 1 == options.num_of_generations:
250
+ break
251
+
252
+ # Get top solutions
253
+ d_top_solutions = d_solutions[:options.sample_size]
254
+
255
+ # Mutate top solutions on the GPU
256
+ mutation_factors = cupy.random.uniform(
257
+ 1 - options.mutation_percentage, 1 + options.mutation_percentage, options.sample_size
258
+ )
259
+ d_mutated = d_top_solutions * mutation_factors
260
+
261
+ # Create new random solutions for the rest
262
+ d_new_random = cupy.random.uniform(
263
+ options.min_range, options.max_range, options.data_size - options.sample_size
264
+ )
265
+
266
+ d_solutions = cupy.concatenate([d_mutated, d_new_random])
267
+
268
+ # Get the final sample, sort it, and copy back to CPU
269
+ final_solutions_gpu = cupy.sort(d_solutions[:options.sample_size])
270
+ return final_solutions_gpu.get()
271
+
272
+
273
+ def __str__(self) -> str:
274
+ """Returns a human-readable string representation of the function."""
275
+ self._check_initialized()
276
+ parts = []
277
+ for i, c in enumerate(self.coefficients):
278
+ if c == 0:
279
+ continue
280
+
281
+ power = self._largest_exponent - i
282
+
283
+ # Coefficient part
284
+ if c == 1 and power != 0:
285
+ coeff = ""
286
+ elif c == -1 and power != 0:
287
+ coeff = "-"
288
+ else:
289
+ coeff = str(c)
290
+
291
+ # Variable part
292
+ if power == 0:
293
+ var = ""
294
+ elif power == 1:
295
+ var = "x"
296
+ else:
297
+ var = f"x^{power}"
298
+
299
+ # Add sign for non-leading terms
300
+ sign = ""
301
+ if i > 0:
302
+ sign = " + " if c > 0 else " - "
303
+ coeff = str(abs(c))
304
+ if abs(c) == 1 and power != 0:
305
+ coeff = "" # Don't show 1 for non-constant terms
306
+
307
+ parts.append(f"{sign}{coeff}{var}")
308
+
309
+ # Join parts and clean up
310
+ result = "".join(parts)
311
+ if result.startswith(" + "):
312
+ result = result[3:]
313
+ return result if result else "0"
314
+
315
+ def __repr__(self) -> str:
316
+ return f"Function(str='{self}')"
317
+
318
+ def __add__(self, other: 'Function') -> 'Function':
319
+ """Adds two Function objects."""
320
+ self._check_initialized()
321
+ other._check_initialized()
322
+
323
+ new_coefficients = np.polyadd(self.coefficients, other.coefficients)
324
+
325
+ result_func = Function(len(new_coefficients) - 1)
326
+ result_func.set_coeffs(new_coefficients.tolist())
327
+ return result_func
328
+
329
+ def __sub__(self, other: 'Function') -> 'Function':
330
+ """Subtracts another Function object from this one."""
331
+ self._check_initialized()
332
+ other._check_initialized()
333
+
334
+ new_coefficients = np.polysub(self.coefficients, other.coefficients)
335
+
336
+ result_func = Function(len(new_coefficients) - 1)
337
+ result_func.set_coeffs(new_coefficients.tolist())
338
+ return result_func
339
+
340
+ def __mul__(self, scalar: int) -> 'Function':
341
+ """Multiplies the function by a scalar constant."""
342
+ self._check_initialized()
343
+ if not isinstance(scalar, (int, float)):
344
+ return NotImplemented
345
+ if scalar == 0:
346
+ raise ValueError("Cannot multiply a function by 0.")
347
+
348
+ new_coefficients = self.coefficients * scalar
349
+
350
+ result_func = Function(self._largest_exponent)
351
+ result_func.set_coeffs(new_coefficients.tolist())
352
+ return result_func
353
+
354
+ def __rmul__(self, scalar: int) -> 'Function':
355
+ """Handles scalar multiplication from the right (e.g., 3 * func)."""
356
+ return self.__mul__(scalar)
357
+
358
+ def __imul__(self, scalar: int) -> 'Function':
359
+ """Performs in-place multiplication by a scalar (func *= 3)."""
360
+ self._check_initialized()
361
+ if not isinstance(scalar, (int, float)):
362
+ return NotImplemented
363
+ if scalar == 0:
364
+ raise ValueError("Cannot multiply a function by 0.")
365
+
366
+ self.coefficients *= scalar
367
+ return self
368
+
369
+
370
+ def quadratic_solve(f: Function) -> Optional[List[float]]:
371
+ """
372
+ Calculates the real roots of a quadratic function using the quadratic formula.
373
+
374
+ Args:
375
+ f (Function): A Function object of degree 2.
376
+
377
+ Returns:
378
+ Optional[List[float]]: A list containing the two real roots, or None if there are no real roots.
379
+ """
380
+ f._check_initialized()
381
+ if f.largest_exponent != 2:
382
+ raise ValueError("Input function must be quadratic (degree 2).")
383
+
384
+ a, b, c = f.coefficients
385
+
386
+ discriminant = (b**2) - (4*a*c)
387
+
388
+ if discriminant < 0:
389
+ return None # No real roots
390
+
391
+ sqrt_discriminant = math.sqrt(discriminant)
392
+ root1 = (-b + sqrt_discriminant) / (2 * a)
393
+ root2 = (-b - sqrt_discriminant) / (2 * a)
394
+
395
+ return [root1, root2]
396
+
397
+ # Example Usage
398
+ if __name__ == '__main__':
399
+ print("--- Demonstrating Functionality ---")
400
+
401
+ # Create a quadratic function: 2x^2 - 3x - 5
402
+ f1 = Function(2)
403
+ f1.set_coeffs([2, -3, -5])
404
+ print(f"Function f1: {f1}")
405
+
406
+ # Solve for y
407
+ y = f1.solve_y(5)
408
+ print(f"Value of f1 at x=5 is: {y}") # Expected: 2*(25) - 3*(5) - 5 = 50 - 15 - 5 = 30
409
+
410
+ # Find the derivative: 4x - 3
411
+ df1 = f1.differential()
412
+ print(f"Derivative of f1: {df1}")
413
+
414
+ # --- Root Finding ---
415
+ # 1. Analytical solution for quadratic
416
+ roots_analytic = quadratic_solve(f1)
417
+ print(f"Analytic roots of f1: {roots_analytic}") # Expected: -1, 2.5
418
+
419
+ # 2. Genetic algorithm solution
420
+ ga_opts = GA_Options(num_of_generations=20, data_size=50000, sample_size=10)
421
+ print("\nFinding roots with Genetic Algorithm (CPU)...")
422
+ roots_ga_cpu = f1.get_real_roots(ga_opts)
423
+ print(f"Approximate roots from GA (CPU): {roots_ga_cpu}")
424
+ print("(Note: GA provides approximations around the true roots)")
425
+
426
+ # 3. CUDA accelerated genetic algorithm
427
+ if _CUPY_AVAILABLE:
428
+ print("\nFinding roots with Genetic Algorithm (CUDA)...")
429
+ # Since this PC has an RTX 4060 Ti, we can use the CUDA version.
430
+ roots_ga_gpu = f1.get_real_roots(ga_opts, use_cuda=True)
431
+ print(f"Approximate roots from GA (GPU): {roots_ga_gpu}")
432
+ else:
433
+ print("\nSkipping CUDA example: CuPy library not found or no compatible GPU.")
434
+
435
+ # --- Function Arithmetic ---
436
+ print("\n--- Function Arithmetic ---")
437
+ f2 = Function(1)
438
+ f2.set_coeffs([1, 10]) # x + 10
439
+ print(f"Function f2: {f2}")
440
+
441
+ # Addition: (2x^2 - 3x - 5) + (x + 10) = 2x^2 - 2x + 5
442
+ f_add = f1 + f2
443
+ print(f"f1 + f2 = {f_add}")
444
+
445
+ # Subtraction: (2x^2 - 3x - 5) - (x + 10) = 2x^2 - 4x - 15
446
+ f_sub = f1 - f2
447
+ print(f"f1 - f2 = {f_sub}")
448
+
449
+ # Multiplication: (x + 10) * 3 = 3x + 30
450
+ f_mul = f2 * 3
451
+ print(f"f2 * 3 = {f_mul}")
@@ -0,0 +1,147 @@
1
+ Metadata-Version: 2.4
2
+ Name: polysolve
3
+ Version: 0.1.0
4
+ Summary: A Python library for representing, manipulating, and solving exponential functions using analytical methods and genetic algorithms, with optional CUDA acceleration.
5
+ Author-email: Jonathan Rampersad <jonathan@jono-rams.work>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 jono
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
11
+ associated documentation files (the "Software"), to deal in the Software without restriction, including
12
+ without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
14
+ following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all copies or substantial
17
+ portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
20
+ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
21
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
22
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
23
+ USE OR OTHER DEALINGS IN THE SOFTWARE.
24
+
25
+ Project-URL: Homepage, https://gitea.jono-rams.work/jono/PolySolve
26
+ Project-URL: Bug Tracker, https://gitea.jono-rams.work/jono/PolySolve/issues
27
+ Keywords: math,polynomial,genetic algorithm,cuda,equation solver
28
+ Classifier: Development Status :: 4 - Beta
29
+ Classifier: Intended Audience :: Developers
30
+ Classifier: Intended Audience :: Science/Research
31
+ Classifier: License :: OSI Approved :: MIT License
32
+ Classifier: Operating System :: OS Independent
33
+ Classifier: Programming Language :: Python :: 3
34
+ Classifier: Programming Language :: Python :: 3.8
35
+ Classifier: Programming Language :: Python :: 3.9
36
+ Classifier: Programming Language :: Python :: 3.10
37
+ Classifier: Programming Language :: Python :: 3.11
38
+ Classifier: Programming Language :: Python :: 3.12
39
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
40
+ Requires-Python: >=3.8
41
+ Description-Content-Type: text/markdown
42
+ License-File: LICENSE
43
+ Requires-Dist: numpy>=1.21
44
+ Provides-Extra: cuda12
45
+ Requires-Dist: cupy-cuda12x; extra == "cuda12"
46
+ Provides-Extra: dev
47
+ Requires-Dist: pytest; extra == "dev"
48
+ Dynamic: license-file
49
+
50
+ # polysolve
51
+
52
+ [![PyPI version](https://img.shields.io/pypi/v/polysolve.svg)](https://pypi.org/project/polysolve/)
53
+ [![PyPI pyversions](https://img.shields.io/pypi/pyversions/polysolve.svg)](https://pypi.org/project/polysolve/)
54
+
55
+ A Python library for representing, manipulating, and solving polynomial equations using a high-performance genetic algorithm, with optional CUDA/GPU acceleration.
56
+
57
+ ---
58
+
59
+ ## Key Features
60
+
61
+ * **Create and Manipulate Polynomials**: Easily define polynomials of any degree and perform arithmetic operations like addition, subtraction, and scaling.
62
+ * **Genetic Algorithm Solver**: Find approximate real roots for complex polynomials where analytical solutions are difficult or impossible.
63
+ * **CUDA Accelerated**: Leverage NVIDIA GPUs for a massive performance boost when finding roots in large solution spaces.
64
+ * **Analytical Solvers**: Includes standard, exact solvers for simple cases (e.g., `quadratic_solve`).
65
+ * **Simple API**: Designed to be intuitive and easy to integrate into any project.
66
+
67
+ ---
68
+
69
+ ## Installation
70
+
71
+ Install the base package from PyPI:
72
+
73
+ ```bash
74
+ pip install polysolve
75
+ ```
76
+
77
+ ### CUDA Acceleration
78
+
79
+ To enable GPU acceleration, install the extra that matches your installed NVIDIA CUDA Toolkit version. This provides a significant speedup for the genetic algorithm.
80
+
81
+ **For CUDA 12.x users:**
82
+ ```bash
83
+ pip install polysolve[cuda12]
84
+ ```
85
+
86
+ ---
87
+
88
+ ## Quick Start
89
+
90
+ Here is a simple example of how to define a quadratic function, find its properties, and solve for its roots.
91
+
92
+ ```python
93
+ from polysolve import Function, GA_Options, quadratic_solve
94
+
95
+ # 1. Define the function f(x) = 2x^2 - 3x - 5
96
+ f1 = Function(largest_exponent=2)
97
+ f1.set_constants([2, -3, -5])
98
+
99
+ print(f"Function f1: {f1}")
100
+ # > Function f1: 2x^2 - 3x - 5
101
+
102
+ # 2. Solve for y at a given x
103
+ y_val = f1.solve_y(5)
104
+ print(f"Value of f1 at x=5 is: {y_val}")
105
+ # > Value of f1 at x=5 is: 30.0
106
+
107
+ # 3. Get the derivative: 4x - 3
108
+ df1 = f1.differential()
109
+ print(f"Derivative of f1: {df1}")
110
+ # > Derivative of f1: 4x - 3
111
+
112
+ # 4. Find roots analytically using the quadratic formula
113
+ # This is exact and fast for degree-2 polynomials.
114
+ roots_analytic = quadratic_solve(f1)
115
+ print(f"Analytic roots: {sorted(roots_analytic)}")
116
+ # > Analytic roots: [-1.0, 2.5]
117
+
118
+ # 5. Find roots with the genetic algorithm (CPU)
119
+ # This can solve polynomials of any degree.
120
+ ga_opts = GA_Options(num_of_generations=20)
121
+ roots_ga = f1.get_real_roots(ga_opts, use_cuda=False)
122
+ print(f"Approximate roots from GA: {roots_ga[:2]}")
123
+ # > Approximate roots from GA: [-1.000..., 2.500...]
124
+
125
+ # If you installed a CUDA extra, you can run it on the GPU:
126
+ # roots_ga_gpu = f1.get_real_roots(ga_opts, use_cuda=True)
127
+ # print(f"Approximate roots from GA (GPU): {roots_ga_gpu[:2]}")
128
+
129
+ ```
130
+
131
+ ---
132
+
133
+ ## Development & Testing Environment
134
+
135
+ This project is automatically tested against a specific set of dependencies to ensure stability. Our Continuous Integration (CI) pipeline runs on an environment using **CUDA 12.5** on **Ubuntu 24.04**.
136
+
137
+ While the code may work on other configurations, all contributions must pass the automated tests in our reference environment. For detailed information on how to replicate the testing environment, please see our [**Contributing Guide**](CONTRIBUTING.md).
138
+
139
+ ## Contributing
140
+
141
+ Contributions are welcome! Whether it's a bug report, a feature request, or a pull request, please feel free to get involved.
142
+
143
+ Please read our `CONTRIBUTING.md` file for details on our code of conduct and the process for submitting pull requests.
144
+
145
+ ## License
146
+
147
+ This project is licensed under the MIT License - see the `LICENSE` file for details.
@@ -0,0 +1,6 @@
1
+ polysolve/__init__.py,sha256=Pi3xCcq6HkiPTXU-smJvcfkPHKKnO0b-kfHbUH30bKM,16204
2
+ polysolve-0.1.0.dist-info/licenses/LICENSE,sha256=wIfm5eVLhSQ0GEB33VxX-KfXMpt6atKNH90nEJyULfU,1070
3
+ polysolve-0.1.0.dist-info/METADATA,sha256=JtrU1UJpHlMBXSzzV7GtPv9XQiXTPbfivxlR8tyqgNw,6151
4
+ polysolve-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
5
+ polysolve-0.1.0.dist-info/top_level.txt,sha256=eUFd1Z67SvnzOu3SBpdhLWPogRrNmoluRsVwUlWnlbg,10
6
+ polysolve-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,18 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 jono
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
6
+ associated documentation files (the "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
9
+ following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all copies or substantial
12
+ portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
15
+ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
16
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
18
+ USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1 @@
1
+ polysolve