polysolve 0.7.0__tar.gz → 0.8.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: polysolve
3
- Version: 0.7.0
3
+ Version: 0.8.0
4
4
  Summary: A Python library for representing, manipulating, and solving exponential functions using analytical methods and genetic algorithms, with optional CUDA acceleration.
5
5
  Author-email: Jonathan Rampersad <jonathan@jono-rams.work>
6
6
  License: MIT License
@@ -33,17 +33,17 @@ Classifier: Intended Audience :: Science/Research
33
33
  Classifier: License :: OSI Approved :: MIT License
34
34
  Classifier: Operating System :: OS Independent
35
35
  Classifier: Programming Language :: Python :: 3
36
- Classifier: Programming Language :: Python :: 3.8
37
- Classifier: Programming Language :: Python :: 3.9
38
36
  Classifier: Programming Language :: Python :: 3.10
39
37
  Classifier: Programming Language :: Python :: 3.11
40
38
  Classifier: Programming Language :: Python :: 3.12
39
+ Classifier: Programming Language :: Python :: 3.13
40
+ Classifier: Programming Language :: Python :: 3.14
41
41
  Classifier: Topic :: Scientific/Engineering :: Mathematics
42
- Requires-Python: >=3.8
42
+ Requires-Python: >=3.10
43
43
  Description-Content-Type: text/markdown
44
44
  License-File: LICENSE
45
- Requires-Dist: numpy>=1.21
46
- Requires-Dist: numba
45
+ Requires-Dist: numpy>=2.0
46
+ Requires-Dist: numba>=0.60
47
47
  Provides-Extra: cuda12
48
48
  Requires-Dist: cupy-cuda12x; extra == "cuda12"
49
49
  Provides-Extra: dev
@@ -103,6 +103,8 @@ from polysolve import Function, GA_Options
103
103
  # Coefficients can be integers or floats.
104
104
  f1 = Function(largest_exponent=2)
105
105
  f1.set_coeffs([2, -3, -5])
106
+ f2 = Function(largest_exponent=2)
107
+ f2.set_coeffs([3, -4, -5])
106
108
 
107
109
  print(f"Function f1: {f1}")
108
110
  # > Function f1: 2x^2 - 3x - 5
@@ -122,20 +124,30 @@ ddf1 = f1.nth_derivative(2)
122
124
  print(f"2nd Derivative of f1: {ddf1}")
123
125
  # > Derivative of f1: 4
124
126
 
125
- # 5. Find roots analytically using the quadratic formula
127
+ # 5. Find the integral: x^3 - 2x^2 - 5x + 0
128
+ igl = f2.integral()
129
+ print(f"Integral of f2: {igl}")
130
+ # > integral of f2: x^3 - 2x^2 - 5x + 0
131
+
132
+ # 6. Find the integral with custom constant of integration: x^3 - 2x^2 - 5x + 2
133
+ igl = f2.integral(constant_of_integration=2)
134
+ print(f"Integral of f2: {igl}")
135
+ # > integral of f2: x^3 - 2x^2 - 5x + 2
136
+
137
+ # 7. Find roots analytically using the quadratic formula
126
138
  # This is exact and fast for degree-2 polynomials.
127
139
  roots_analytic = f1.quadratic_solve()
128
140
  print(f"Analytic roots: {sorted(roots_analytic)}")
129
141
  # > Analytic roots: [-1.0, 2.5]
130
142
 
131
- # 6. Find REAL roots with the genetic algorithm (Numba CPU)
143
+ # 8. Find REAL roots with the genetic algorithm (Numba CPU)
132
144
  # This is the default, JIT-compiled CPU solver.
133
145
  ga_opts = GA_Options(num_of_generations=20)
134
146
  roots_ga = f1.get_real_roots(ga_opts, use_cuda=False)
135
147
  print(f"Approximate real roots: {roots_ga[:2]}")
136
148
  # > Approximate real roots: [-1.000..., 2.500...]
137
149
 
138
- # 7. Find ALL roots (Real + Complex)
150
+ # 9. Find ALL roots (Real + Complex)
139
151
  # Use get_roots() to search the complex plane.
140
152
  f_complex = Function(2, [1, 0, 1]) # x^2 + 1
141
153
  roots_all = f_complex.get_roots(ga_opts)
@@ -51,6 +51,8 @@ from polysolve import Function, GA_Options
51
51
  # Coefficients can be integers or floats.
52
52
  f1 = Function(largest_exponent=2)
53
53
  f1.set_coeffs([2, -3, -5])
54
+ f2 = Function(largest_exponent=2)
55
+ f2.set_coeffs([3, -4, -5])
54
56
 
55
57
  print(f"Function f1: {f1}")
56
58
  # > Function f1: 2x^2 - 3x - 5
@@ -70,20 +72,30 @@ ddf1 = f1.nth_derivative(2)
70
72
  print(f"2nd Derivative of f1: {ddf1}")
71
73
  # > Derivative of f1: 4
72
74
 
73
- # 5. Find roots analytically using the quadratic formula
75
+ # 5. Find the integral: x^3 - 2x^2 - 5x + 0
76
+ igl = f2.integral()
77
+ print(f"Integral of f2: {igl}")
78
+ # > integral of f2: x^3 - 2x^2 - 5x + 0
79
+
80
+ # 6. Find the integral with custom constant of integration: x^3 - 2x^2 - 5x + 2
81
+ igl = f2.integral(constant_of_integration=2)
82
+ print(f"Integral of f2: {igl}")
83
+ # > integral of f2: x^3 - 2x^2 - 5x + 2
84
+
85
+ # 7. Find roots analytically using the quadratic formula
74
86
  # This is exact and fast for degree-2 polynomials.
75
87
  roots_analytic = f1.quadratic_solve()
76
88
  print(f"Analytic roots: {sorted(roots_analytic)}")
77
89
  # > Analytic roots: [-1.0, 2.5]
78
90
 
79
- # 6. Find REAL roots with the genetic algorithm (Numba CPU)
91
+ # 8. Find REAL roots with the genetic algorithm (Numba CPU)
80
92
  # This is the default, JIT-compiled CPU solver.
81
93
  ga_opts = GA_Options(num_of_generations=20)
82
94
  roots_ga = f1.get_real_roots(ga_opts, use_cuda=False)
83
95
  print(f"Approximate real roots: {roots_ga[:2]}")
84
96
  # > Approximate real roots: [-1.000..., 2.500...]
85
97
 
86
- # 7. Find ALL roots (Real + Complex)
98
+ # 9. Find ALL roots (Real + Complex)
87
99
  # Use get_roots() to search the complex plane.
88
100
  f_complex = Function(2, [1, 0, 1]) # x^2 + 1
89
101
  roots_all = f_complex.get_roots(ga_opts)
@@ -5,14 +5,14 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  # --- Core Metadata ---
7
7
  name = "polysolve"
8
- version = "0.7.0"
8
+ version = "0.8.0"
9
9
  authors = [
10
10
  { name="Jonathan Rampersad", email="jonathan@jono-rams.work" },
11
11
  ]
12
12
  description = "A Python library for representing, manipulating, and solving exponential functions using analytical methods and genetic algorithms, with optional CUDA acceleration."
13
13
  readme = "README.md"
14
- requires-python = ">=3.8"
15
- license = { file="LICENSE" }
14
+ requires-python = ">=3.10"
15
+ license = { file = "LICENSE" }
16
16
  keywords = ["math", "polynomial", "genetic algorithm", "cuda", "equation solver"]
17
17
 
18
18
  # --- Classifiers ---
@@ -23,18 +23,18 @@ classifiers = [
23
23
  "License :: OSI Approved :: MIT License",
24
24
  "Operating System :: OS Independent",
25
25
  "Programming Language :: Python :: 3",
26
- "Programming Language :: Python :: 3.8",
27
- "Programming Language :: Python :: 3.9",
28
26
  "Programming Language :: Python :: 3.10",
29
27
  "Programming Language :: Python :: 3.11",
30
28
  "Programming Language :: Python :: 3.12",
29
+ "Programming Language :: Python :: 3.13",
30
+ "Programming Language :: Python :: 3.14",
31
31
  "Topic :: Scientific/Engineering :: Mathematics",
32
32
  ]
33
33
 
34
34
  # --- Dependencies ---
35
35
  dependencies = [
36
- "numpy>=1.21",
37
- "numba"
36
+ "numpy>=2.0",
37
+ "numba>=0.60"
38
38
  ]
39
39
 
40
40
  # --- Optional Dependencies (Extras) ---
@@ -45,5 +45,5 @@ dev = ["pytest"]
45
45
  [project.urls]
46
46
  Homepage = "https://polysolve.jono-rams.work"
47
47
  Documentation = "https://polysolve.jono-rams.work/docs"
48
- Repository = "https://github.com/jono-rams/PolySolve"
48
+ Repository = "https://github.com/jono-rams/PolySolve"
49
49
  "Bug Tracker" = "https://github.com/jono-rams/PolySolve/issues"
@@ -1,6 +1,7 @@
1
1
  import math
2
2
  import cmath
3
3
  import numpy as np
4
+ from numpy.polynomial import Polynomial
4
5
  import numba
5
6
  from dataclasses import dataclass
6
7
  from typing import List, Optional, Union
@@ -383,6 +384,9 @@ class Function:
383
384
  largest_exponent (int): The largest exponent (n) in the function.
384
385
  """
385
386
  self._largest_exponent = largest_exponent
387
+ self.poly_obj = None
388
+ self._initialized = False
389
+
386
390
  if coefficients is not None:
387
391
  self.set_coeffs(coefficients)
388
392
  # Verify user provided exponent matches if they provided both
@@ -390,7 +394,6 @@ class Function:
390
394
  raise ValueError("Provided largest_exponent does not match coefficient list length.")
391
395
  elif largest_exponent is not None:
392
396
  self.coefficients = None
393
- self._initialized = False
394
397
  else:
395
398
  raise ValueError("Must provide either coefficients or largest_exponent.")
396
399
 
@@ -425,6 +428,9 @@ class Function:
425
428
  target_dtype = np.float64
426
429
 
427
430
  self.coefficients = np.array(coefficients, dtype=target_dtype)
431
+ reversed_coeffs = coefficients[::-1]
432
+ self.poly_obj = Polynomial(reversed_coeffs)
433
+
428
434
  self._largest_exponent = len(coefficients) - 1
429
435
  self._initialized = True
430
436
 
@@ -454,7 +460,7 @@ class Function:
454
460
  float: The resulting y-value.
455
461
  """
456
462
  self._check_initialized()
457
- return np.polyval(self.coefficients, x_val)
463
+ return self.poly_obj(x_val)
458
464
 
459
465
  def differential(self) -> 'Function':
460
466
  """
@@ -489,10 +495,11 @@ class Function:
489
495
  diff_func.set_coeffs([0])
490
496
  return diff_func
491
497
 
492
- derivative_coefficients = np.polyder(self.coefficients)
498
+ deriv_poly = self.poly_obj.deriv(1)
499
+ new_coeffs = deriv_poly.coef[::-1].tolist()
493
500
 
494
501
  diff_func = Function(self._largest_exponent - 1)
495
- diff_func.set_coeffs(derivative_coefficients.tolist())
502
+ diff_func.set_coeffs(new_coeffs)
496
503
  return diff_func
497
504
 
498
505
 
@@ -512,18 +519,30 @@ class Function:
512
519
  raise ValueError("Derivative order 'n' must be a positive integer.")
513
520
 
514
521
  if n > self.largest_exponent:
515
- function = Function(0)
516
- function.set_coeffs([0])
517
- return function
522
+ diff_func = Function(0)
523
+ diff_func.set_coeffs([0])
524
+ return diff_func
518
525
 
519
- if n == 1:
520
- return self.derivative()
526
+ deriv_poly = self.poly_obj.deriv(n)
527
+ new_coeffs = deriv_poly.coef[::-1].tolist()
521
528
 
522
- function = self
523
- for _ in range(n):
524
- function = function.derivative()
529
+ new_degree = self._largest_exponent - n
530
+ diff_func = Function(new_degree)
531
+ diff_func.set_coeffs(new_coeffs)
532
+ return diff_func
533
+
525
534
 
526
- return function
535
+ def integral(self, constant_of_integration: float = 0.0) -> 'Function':
536
+ """Calculates the indefinite integral (antiderivative)."""
537
+ self._check_initialized()
538
+
539
+ # Integration naturally increases the degree by 1
540
+ integ_poly = self.poly_obj.integ(m=1, k=[constant_of_integration])
541
+ new_coeffs = integ_poly.coef[::-1].tolist()
542
+
543
+ integral_func = Function(self._largest_exponent + 1)
544
+ integral_func.set_coeffs(new_coeffs)
545
+ return integral_func
527
546
 
528
547
 
529
548
  def get_real_roots(self, options: Optional[GA_Options] = None, use_cuda: bool = False) -> np.ndarray:
@@ -1139,49 +1158,10 @@ class Function:
1139
1158
 
1140
1159
  def __str__(self) -> str:
1141
1160
  """Returns a human-readable string representation of the function."""
1142
- self._check_initialized()
1143
- parts = []
1144
- for i, c in enumerate(self.coefficients):
1145
- if c == 0:
1146
- continue
1147
-
1148
- power = self._largest_exponent - i
1149
-
1150
- # Coefficient part
1151
- coeff_val = c
1152
- if c == int(c):
1153
- coeff_val = int(c)
1154
-
1155
- if coeff_val == 1 and power != 0:
1156
- coeff = ""
1157
- elif coeff_val == -1 and power != 0:
1158
- coeff = "-"
1159
- else:
1160
- coeff = str(coeff_val)
1161
-
1162
- # Variable part
1163
- if power == 0:
1164
- var = ""
1165
- elif power == 1:
1166
- var = "x"
1167
- else:
1168
- var = f"x^{power}"
1161
+ if not self._initialized:
1162
+ return "Uninitialized Function"
1169
1163
 
1170
- # Add sign for non-leading terms
1171
- sign = ""
1172
- if i > 0:
1173
- sign = " + " if c > 0 else " - "
1174
- coeff = str(abs(coeff_val))
1175
- if abs(c) == 1 and power != 0:
1176
- coeff = "" # Don't show 1 for non-constant terms
1177
-
1178
- parts.append(f"{sign}{coeff}{var}")
1179
-
1180
- # Join parts and clean up
1181
- result = "".join(parts)
1182
- if result.startswith(" + "):
1183
- result = result[3:]
1184
- return result if result else "0"
1164
+ return str(self.poly_obj)
1185
1165
 
1186
1166
  def __repr__(self) -> str:
1187
1167
  return f"Function(str='{self}')"
@@ -1191,11 +1171,12 @@ class Function:
1191
1171
  self._check_initialized()
1192
1172
  other._check_initialized()
1193
1173
 
1194
- new_coefficients = np.polyadd(self.coefficients, other.coefficients)
1195
- new_coefficients = self._strip_leading_zeros(new_coefficients)
1174
+ # The Polynomial objects handle degree alignment and zero-trimming natively
1175
+ new_poly = self.poly_obj + other.poly_obj
1176
+ new_coeffs = new_poly.coef[::-1].tolist()
1196
1177
 
1197
- result_func = Function(len(new_coefficients) - 1)
1198
- result_func.set_coeffs(new_coefficients.tolist())
1178
+ result_func = Function(len(new_coeffs) - 1)
1179
+ result_func.set_coeffs(new_coeffs)
1199
1180
  return result_func
1200
1181
 
1201
1182
  def _strip_leading_zeros(self, coeffs: np.ndarray) -> np.ndarray:
@@ -1209,11 +1190,11 @@ class Function:
1209
1190
  self._check_initialized()
1210
1191
  other._check_initialized()
1211
1192
 
1212
- new_coefficients = np.polysub(self.coefficients, other.coefficients)
1213
- new_coefficients = self._strip_leading_zeros(new_coefficients)
1193
+ new_poly = self.poly_obj - other.poly_obj
1194
+ new_coeffs = new_poly.coef[::-1].tolist()
1214
1195
 
1215
- result_func = Function(len(new_coefficients) - 1)
1216
- result_func.set_coeffs(new_coefficients.tolist())
1196
+ result_func = Function(len(new_coeffs) - 1)
1197
+ result_func.set_coeffs(new_coeffs)
1217
1198
  return result_func
1218
1199
 
1219
1200
  def _multiply_by_scalar(self, scalar: Union[int, float, complex]) -> 'Function':
@@ -1237,15 +1218,11 @@ class Function:
1237
1218
  self._check_initialized()
1238
1219
  other._check_initialized()
1239
1220
 
1240
- # np.polymul performs convolution of coefficients to multiply polynomials
1241
- new_coefficients = np.polymul(self.coefficients, other.coefficients)
1242
- new_coefficients = self._strip_leading_zeros(new_coefficients)
1243
-
1244
- # The degree of the resulting polynomial is derived from the new coefficients
1245
- new_degree = len(new_coefficients) - 1
1221
+ new_poly = self.poly_obj * other.poly_obj
1222
+ new_coeffs = new_poly.coef[::-1].tolist()
1246
1223
 
1247
- result_func = Function(new_degree)
1248
- result_func.set_coeffs(new_coefficients.tolist())
1224
+ result_func = Function(len(new_coeffs) - 1)
1225
+ result_func.set_coeffs(new_coeffs)
1249
1226
  return result_func
1250
1227
 
1251
1228
  def __mul__(self, other: Union['Function', int, float, complex]) -> 'Function':
@@ -1269,16 +1246,18 @@ class Function:
1269
1246
 
1270
1247
  if isinstance(other, (int, float, complex)):
1271
1248
  if other == 0:
1272
- self.coefficients = np.array([0], dtype=self.coefficients.dtype)
1273
- self._largest_exponent = 0
1249
+ self.set_coeffs([0])
1274
1250
  else:
1275
- self.coefficients *= other
1251
+ # Update the underlying poly_obj, then sync the legacy array
1252
+ self.poly_obj *= other
1253
+ self.coefficients = self.poly_obj.coef[::-1]
1254
+ self._largest_exponent = len(self.coefficients) - 1
1276
1255
 
1277
1256
  elif isinstance(other, self.__class__):
1278
1257
  other._check_initialized()
1279
- self.coefficients = np.polymul(self.coefficients, other.coefficients)
1258
+ self.poly_obj *= other.poly_obj
1259
+ self.coefficients = self.poly_obj.coef[::-1]
1280
1260
  self._largest_exponent = len(self.coefficients) - 1
1281
-
1282
1261
  else:
1283
1262
  return NotImplemented
1284
1263
 
@@ -1289,20 +1268,20 @@ class Function:
1289
1268
  Checks if two Function objects are equal by comparing
1290
1269
  their coefficients.
1291
1270
  """
1292
- # Check if the 'other' object is even a Function
1293
1271
  if not isinstance(other, Function):
1294
1272
  return NotImplemented
1295
1273
 
1296
- # Ensure both are initialized before trying to access .coefficients
1297
1274
  if not self._initialized or not other._initialized:
1298
1275
  return False
1299
1276
 
1300
- c1 = self._strip_leading_zeros(self.coefficients)
1301
- c2 = self._strip_leading_zeros(other.coefficients)
1277
+ # Extract the auto-trimmed arrays from the modern API
1278
+ c1 = self.poly_obj.coef
1279
+ c2 = other.poly_obj.coef
1302
1280
 
1303
1281
  if c1.shape != c2.shape:
1304
1282
  return False
1305
1283
 
1284
+ # Retain np.allclose to protect against floating-point inaccuracies
1306
1285
  return np.allclose(c1, c2)
1307
1286
 
1308
1287
 
@@ -1320,10 +1299,9 @@ class Function:
1320
1299
  a, b, c = self.coefficients
1321
1300
 
1322
1301
  discriminant = (b**2) - (4*a*c)
1323
-
1324
1302
  sqrt_discriminant = cmath.sqrt(discriminant)
1325
1303
 
1326
- if b >= 0:
1304
+ if b.real >= 0:
1327
1305
  sign_b = 1
1328
1306
  else:
1329
1307
  sign_b = -1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: polysolve
3
- Version: 0.7.0
3
+ Version: 0.8.0
4
4
  Summary: A Python library for representing, manipulating, and solving exponential functions using analytical methods and genetic algorithms, with optional CUDA acceleration.
5
5
  Author-email: Jonathan Rampersad <jonathan@jono-rams.work>
6
6
  License: MIT License
@@ -33,17 +33,17 @@ Classifier: Intended Audience :: Science/Research
33
33
  Classifier: License :: OSI Approved :: MIT License
34
34
  Classifier: Operating System :: OS Independent
35
35
  Classifier: Programming Language :: Python :: 3
36
- Classifier: Programming Language :: Python :: 3.8
37
- Classifier: Programming Language :: Python :: 3.9
38
36
  Classifier: Programming Language :: Python :: 3.10
39
37
  Classifier: Programming Language :: Python :: 3.11
40
38
  Classifier: Programming Language :: Python :: 3.12
39
+ Classifier: Programming Language :: Python :: 3.13
40
+ Classifier: Programming Language :: Python :: 3.14
41
41
  Classifier: Topic :: Scientific/Engineering :: Mathematics
42
- Requires-Python: >=3.8
42
+ Requires-Python: >=3.10
43
43
  Description-Content-Type: text/markdown
44
44
  License-File: LICENSE
45
- Requires-Dist: numpy>=1.21
46
- Requires-Dist: numba
45
+ Requires-Dist: numpy>=2.0
46
+ Requires-Dist: numba>=0.60
47
47
  Provides-Extra: cuda12
48
48
  Requires-Dist: cupy-cuda12x; extra == "cuda12"
49
49
  Provides-Extra: dev
@@ -103,6 +103,8 @@ from polysolve import Function, GA_Options
103
103
  # Coefficients can be integers or floats.
104
104
  f1 = Function(largest_exponent=2)
105
105
  f1.set_coeffs([2, -3, -5])
106
+ f2 = Function(largest_exponent=2)
107
+ f2.set_coeffs([3, -4, -5])
106
108
 
107
109
  print(f"Function f1: {f1}")
108
110
  # > Function f1: 2x^2 - 3x - 5
@@ -122,20 +124,30 @@ ddf1 = f1.nth_derivative(2)
122
124
  print(f"2nd Derivative of f1: {ddf1}")
123
125
  # > Derivative of f1: 4
124
126
 
125
- # 5. Find roots analytically using the quadratic formula
127
+ # 5. Find the integral: x^3 - 2x^2 - 5x + 0
128
+ igl = f2.integral()
129
+ print(f"Integral of f2: {igl}")
130
+ # > integral of f2: x^3 - 2x^2 - 5x + 0
131
+
132
+ # 6. Find the integral with custom constant of integration: x^3 - 2x^2 - 5x + 2
133
+ igl = f2.integral(constant_of_integration=2)
134
+ print(f"Integral of f2: {igl}")
135
+ # > integral of f2: x^3 - 2x^2 - 5x + 2
136
+
137
+ # 7. Find roots analytically using the quadratic formula
126
138
  # This is exact and fast for degree-2 polynomials.
127
139
  roots_analytic = f1.quadratic_solve()
128
140
  print(f"Analytic roots: {sorted(roots_analytic)}")
129
141
  # > Analytic roots: [-1.0, 2.5]
130
142
 
131
- # 6. Find REAL roots with the genetic algorithm (Numba CPU)
143
+ # 8. Find REAL roots with the genetic algorithm (Numba CPU)
132
144
  # This is the default, JIT-compiled CPU solver.
133
145
  ga_opts = GA_Options(num_of_generations=20)
134
146
  roots_ga = f1.get_real_roots(ga_opts, use_cuda=False)
135
147
  print(f"Approximate real roots: {roots_ga[:2]}")
136
148
  # > Approximate real roots: [-1.000..., 2.500...]
137
149
 
138
- # 7. Find ALL roots (Real + Complex)
150
+ # 9. Find ALL roots (Real + Complex)
139
151
  # Use get_roots() to search the complex plane.
140
152
  f_complex = Function(2, [1, 0, 1]) # x^2 + 1
141
153
  roots_all = f_complex.get_roots(ga_opts)
@@ -1,5 +1,5 @@
1
- numpy>=1.21
2
- numba
1
+ numpy>=2.0
2
+ numba>=0.60
3
3
 
4
4
  [cuda12]
5
5
  cupy-cuda12x
@@ -18,6 +18,13 @@ def quadratic_func() -> Function:
18
18
  f.set_coeffs([2, -3, -5])
19
19
  return f
20
20
 
21
+ @pytest.fixture
22
+ def int_func() -> Function:
23
+ """Provides a function for easy integral calculation."""
24
+ f = Function(largest_exponent=2)
25
+ f.set_coeffs([3, -4, -5])
26
+ return f
27
+
21
28
  @pytest.fixture
22
29
  def linear_func() -> Function:
23
30
  """Provides a standard linear function: x + 10."""
@@ -70,6 +77,20 @@ def test_nth_derivative(quadratic_func):
70
77
  # The derivative of 2x^2 - 3x - 5 is 4x - 3
71
78
  assert np.array_equal(derivative.coefficients, [4])
72
79
 
80
+ def test_integral(int_func):
81
+ """Tests the calculation of the function's integral."""
82
+ integral = int_func.integral()
83
+ assert integral.largest_exponent == 3
84
+ # The integral of 2x^2 -3x - 5 is x^3 - 2x^2 - 5x
85
+ assert np.array_equal(integral.coefficients, [1, -2, -5, 0])
86
+
87
+ def test_integral_custom_constant(int_func):
88
+ """Tests the calculation of the function's integral with custom constant of integration."""
89
+ integral = int_func.integral(constant_of_integration=42)
90
+ assert integral.largest_exponent == 3
91
+ # The integral of 2x^2 -3x - 5 is x^3 - 2x^2 - 5x
92
+ assert np.array_equal(integral.coefficients, [1, -2, -5, 42])
93
+
73
94
  def test_quadratic_solve(quadratic_func):
74
95
  """Tests the analytical quadratic solver for exact roots."""
75
96
  roots = quadratic_func.quadratic_solve()
@@ -139,7 +160,7 @@ def test_get_real_roots_numpy(quadratic_func):
139
160
  Tests that the NumPy-based genetic algorithm approximates the roots correctly.
140
161
  """
141
162
  # Using more generations for higher accuracy in testing
142
- ga_opts = GA_Options(num_of_generations=50, data_size=200000, selection_percentile=0.66, root_precision=3)
163
+ ga_opts = GA_Options(num_of_generations=100, data_size=500000, selection_percentile=0.66, root_precision=2)
143
164
 
144
165
  roots = quadratic_func.get_real_roots(ga_opts, use_cuda=False)
145
166
 
@@ -158,7 +179,7 @@ def test_get_real_roots_cuda(quadratic_func):
158
179
  It will be skipped automatically if CuPy is not available.
159
180
  """
160
181
 
161
- ga_opts = GA_Options(num_of_generations=50, data_size=200000, selection_percentile=0.66, root_precision=3)
182
+ ga_opts = GA_Options(num_of_generations=100, data_size=500000, selection_percentile=0.66, root_precision=2)
162
183
 
163
184
  roots = quadratic_func.get_real_roots(ga_opts, use_cuda=True)
164
185
 
@@ -172,7 +193,7 @@ def test_get_roots_numpy(complex_func):
172
193
  Tests that the NumPy-based genetic algorithm approximates the roots correctly.
173
194
  """
174
195
  # Using more generations for higher accuracy in testing
175
- ga_opts = GA_Options(num_of_generations=50, data_size=200000, selection_percentile=0.66, root_precision=3)
196
+ ga_opts = GA_Options(num_of_generations=100, data_size=500000, selection_percentile=0.66, root_precision=2)
176
197
 
177
198
  roots = complex_func.get_roots(ga_opts, use_cuda=False)
178
199
 
@@ -191,7 +212,7 @@ def test_get_roots_cuda(complex_func):
191
212
  It will be skipped automatically if CuPy is not available.
192
213
  """
193
214
 
194
- ga_opts = GA_Options(num_of_generations=50, data_size=200000, selection_percentile=0.66, root_precision=3)
215
+ ga_opts = GA_Options(num_of_generations=100, data_size=500000, selection_percentile=0.66, root_precision=2)
195
216
 
196
217
  roots = complex_func.get_roots(ga_opts, use_cuda=True)
197
218
 
File without changes
File without changes