Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/albertodev01/equations
An equation solving library written in Dart.
https://github.com/albertodev01/equations
dart
Last synced: 11 days ago
JSON representation
An equation solving library written in Dart.
- Host: GitHub
- URL: https://github.com/albertodev01/equations
- Owner: albertodev01
- License: mit
- Created: 2020-07-11T12:36:02.000Z (over 4 years ago)
- Default Branch: master
- Last Pushed: 2024-08-11T13:37:19.000Z (3 months ago)
- Last Synced: 2024-10-12T18:14:00.290Z (26 days ago)
- Topics: dart
- Language: Dart
- Homepage: https://pub.dev/packages/equations
- Size: 20.7 MB
- Stars: 60
- Watchers: 3
- Forks: 8
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
An equation-solving library written purely in Dart.
---
The `equations` package is used to solve numerical analysis problems with ease. It's purely written in Dart, meaning it has no platform-specific dependencies and doesn't require Flutter to work. You can use `equations`, for example, in a Dart CLI project or a Flutter cross-platform application. Here's a summary of what you can do with this package:
- solve polynomial equations with `Algebraic` types;
- solve nonlinear equations with `Nonlinear` types;
- solve linear systems of equations with `SystemSolver` types;
- evaluate integrals with `NumericalIntegration` types;
- interpolate data points with `Interpolation ` types.In addition, you can also find utilities to work with:
- Real and complex matrices, using the `Matrix` types;
- Complex number, using the `Complex` type;
- Fractions, using the `Fraction` and `MixedFraction` types.This package has a type-safe API and requires Dart 3.0 (or higher versions). There is a demo project created with Flutter that shows an example of how this library could be used to develop a numerical analysis application :rocket:
Equation Solver - Flutter web demo
The source code of the Flutter application can be found at `example/flutter_example/`. Visit the official [pub.dev documentation](https://pub.dev/documentation/equations/latest/) for details about methods signatures and inline documentation.
---
# Algebraic (Polynomial equations)
Use one of the following classes to find the roots of a polynomial equation (also known as "algebraic equation"). You can use both complex numbers and fractions as coefficients.
| Solver name | Equation | Params field |
|:-----------:|:-------------------------------------------------------------------------:|:-----------------:|
| `Constant` | f(x) = a | a ∈ C |
| `Linear` | f(x) = ax + b | a, b ∈ C |
| `Quadratic` | f(x) = ax2 + bx + c | a, b, c ∈ C |
| `Cubic` | f(x) = ax3 + bx2 + cx + d | a, b, c, d ∈ C |
| `Quartic` | f(x) = ax4 + bx3 + cx2 + dx + e | a, b, c, d, e ∈ C |
| `DurandKerner` | Any polynomial P(xi) where xi are coefficients | xi ∈ C |There's a formula for polynomials up to the fourth degree, as explained by the [Galois theory](https://en.wikipedia.org/wiki/Galois_theory). Roots of polynomials whose degree is 5 or higher must be found using DurandKerner's method (or any other root-finding algorithm). For this reason, we suggest the following approach:
- use `Linear` to find the roots of a polynomial whose degree is 1;
- use `Quadratic` to find the roots of a polynomial whose degree is 2;
- use `Cubic` to find the roots of a polynomial whose degree is 3;
- use `Quartic` to find the roots of a polynomial whose degree is 4;
- use `DurandKerner` to find the roots of a polynomial whose degree is 5 or higher.Since `DurandKerner` works with any polynomial, you could also use it (for example) to solve a cubic equation. However, `DurandKerner` internally uses loops, derivatives, and other mechanics to approximate the actual roots. When the degree is 4 or lower, prefer working with `Quartic`, `Cubic`, `Quadratic` or `Linear` because they use direct formulas to find the roots (and thus they're more precise). Here's an example of how to find the roots of a cubic:
```dart
// f(x) = (2-3i)x^3 + 6/5ix^2 - (-5+i)x - (9+6i)
final equation = Cubic(
a: Complex(2, -3),
b: Complex.fromImaginaryFraction(Fraction(6, 5)),
c: Complex(5, -1),
d: Complex(-9, -6)
);final degree = equation.degree; // 3
final isReal = equation.isRealEquation; // false
final discr = equation.discriminant(); // -31299.688 + 27460.192i// f(x) = (2 - 3i)x^3 + 1.2ix^2 + (5 - 1i)x + (-9 - 6i)
print('$equation');// f(x) = (2 - 3i)x^3 + 6/5ix^2 + (5 - 1i)x + (-9 - 6i)
print(equation.toStringWithFractions());/*
* Prints the roots:
*
* x1 = 0.348906207844 - 1.734303423032i
* x2 = -1.083892638909 + 0.961044482775
* x3 = 1.011909507988 + 0.588643555642
* */
for (final root in equation.solutions()) {
print(root);
}
```Alternatively, you could have used `DurandKerner` to solve the same equation:
```dart
// f(x) = (2-3i)x^3 + 6/5ix^2 - (-5+i)x - (9+6i)
final equation = DurandKerner(
coefficients: [
Complex(2, -3),
Complex.fromImaginaryFraction(Fraction(6, 5)),
Complex(5, -1),
Complex(-9, -6),
]
);/*
* Prints the roots of the equation:
*
* x1 = 1.0119095 + 0.5886435
* x2 = 0.3489062 - 1.7343034i
* x3 = -1.0838926 + 0.9610444
* */
for (final root in equation.solutions()) {
print(root);
}
```As we've already pointed out, both ways are equivalent. However, `DurandKerner` is computationally slower than `Cubic` and doesn't always guarantee to converge to the correct roots. Use `DurandKerner` only when the degree of your polynomial is greater or equal to 5.
```dart
final quadratic = Algebraic.from(const [
Complex(2, -3),
Complex.i(),
Complex(1, 6)
]);final quartic = Algebraic.fromReal(const [
1, -2, 3, -4, 5
]);
```The factory constructor `Algebraic.from()` automatically returns the best type of `Algebraic` according to the number of parameters you've passed.
# Nonlinear equations
Use one of the following classes, representing a root-finding algorithm, to find a root of an equation. Only real numbers are allowed. This package supports the following root-finding methods:
| Solver name | Params field |
|:------------:|:-----------------:|
| `Bisection` | a, b ∈ R |
| `Chords` | a, b ∈ R |
| `Netwon` | x0 ∈ R |
| `Secant` | a, b ∈ R |
| `Steffensen` | x0 ∈ R |
| `Brent` | a, b ∈ R |
| `RegulaFalsi`| a, b ∈ R |Expressions are parsed using [petitparser](https://pub.dev/packages/petitparser/): a fast, stable and well-tested grammar parser. Here's a simple example of how you can find the roots of an equation using Newton's method:
```dart
final newton = Newton("2*x+cos(x)", -1, maxSteps: 5);final steps = newton.maxSteps; // 5
final tol = newton.tolerance; // 1.0e-10
final fx = newton.function; // 2*x+cos(x)
final guess = newton.x0; // -1final solutions = newton.solve();
final convergence = solutions.convergence.round(); // 2
final solutions = solutions.efficiency.round(); // 1/*
* The getter `solutions.guesses` returns the list of values computed by the algorithm
*
* -0.4862880170389824
* -0.45041860473199363
* -0.45018362150211116
* -0.4501836112948736
* -0.45018361129487355
*/
final List guesses = solutions.guesses;
```Certain algorithms don't always guarantee to converge to the correct root so carefully read the documentation before choosing the method.
# Systems of equations
Use one of the following classes to solve systems of linear equations. Only real coefficients are allowed (so `double` is ok, but `Complex` isn't) and you must define `N` equations in `N` variables (so **square** matrices only are allowed). This package supports the following algorithms:
| Solver name | Iterative method |
|:---------------------:|:------------------:|
| `CholeskySolver` | :x: |
| `GaussianElimination` | :x: |
| `GaussSeidelSolver` | :heavy_check_mark: |
| `JacobiSolver` | :heavy_check_mark: |
| `LUSolver` | :x: |
| `SORSolver` | :heavy_check_mark: |These solvers are used to find the `x` in the `Ax = b` equation. Methods require, at least, the system matrix `A` and the known values vector `b`. Iterative methods may require additional parameters such as an initial guess or a particular configuration value. For example:
```dart
// Solve a system using LU decomposition
final luSolver = LUSolver(
equations: const [
[7, -2, 1],
[14, -7, -3],
[-7, 11, 18]
],
constants: const [12, 17, 5]
);final solutions = luSolver.solve(); // [-1, 4, 3]
final determinant = luSolver.determinant(); // -84.0
```If you just want to work with matrices (for operations, decompositions, eigenvalues, etc...) you can consider using either `RealMatrix` (to work with `double`) or `ComplexMatrix` (to work with `Complex`). Both are subclasses of `Matrix` so they have the same public API:
```dart
final matrixA = RealMatrix.fromData(
columns: 2,
rows: 2,
data: const [
[2, 6],
[-5, 0]
]
);final matrixB = RealMatrix.fromData(
columns: 2,
rows: 2,
data: const [
[-4, 1],
[7, -3],
]
);final sum = matrixA + matrixB;
final sub = matrixA - matrixB;
final mul = matrixA * matrixB;
final div = matrixA / matrixB;final lu = matrixA.luDecomposition();
final cholesky = matrixA.choleskyDecomposition();
final cholesky = matrixA.choleskyDecomposition();
final qr = matrixA.qrDecomposition();
final svd = matrixA.singleValueDecomposition();final det = matrixA.determinant();
final rank = matrixA.rank();final eigenvalues = matrixA.eigenvalues();
final characPolynomial = matrixA.characteristicPolynomial();
```You can use `toString()` to print the matrix contents. The `toStringAugmented()` method prints the augmented matrix (the matrix + one extra column with the known values vector). For example:
```dart
final lu = LUSolver(
equations: const [
[7, -2, 1],
[14, -7, -3],
[-7, 11, 18]
],
constants: const [12, 17, 5]
);/*
* Output with 'toString':
*
* [7.0, -2.0, 1.0]
* [14.0, -7.0, -3.0]
* [-7.0, 11.0, 18.0]
*/
print("$lu");/*
* Output with 'toStringAugmented':
*
* [7.0, -2.0, 1.0 | 12.0]
* [14.0, -7.0, -3.0 | 17.0]
* [-7.0, 11.0, 18.0 | 5.0]
*/
print("${lu.toStringAugmented()}");
```The `ComplexMatrix` has the same API and the same usage as `RealMatrix` with the only difference that it works with complex numbers rather then real numbers.
# Numerical integration
The "**numerical integration**" term refers to a group of algorithms for calculating the numerical value of a definite integral. The function must be continuous within the integration bounds. This package currently supports the following algorithms:
- `MidpointRule`
- `SimpsonRule`
- `TrapezoidalRule`
- `AdaptiveQuadrature`Other than the integration bounds (called `lowerBound` and `lowerBound`), some classes may also have an optional `intervals` parameter. It already has a good default value but of course you can change it! For example:
```dart
const simpson = SimpsonRule(
function: 'sin(x)*e^x',
lowerBound: 2,
upperBound: 4,
);// Calculating the value of...
//
// ∫ sin(x) * e^x dx
//
// ... between 2 and 4.
final results = simpson.integrate();// Prints '-7.713'
print('${results.result.toStringAsFixed(3)}');// Prints '32'
print('${results.guesses.length}');
```Midpoint, trapezoidal and Simpson methods have the `intervals` parameter while the adaptive quadrature method doesn't (because it doesn't need it). The `integrate()` function returns an `IntegralResults` which simply is a wrapper for 2 values:
1. `result`: the value of the definite integral evaluated within `lowerBound` and `lowerBound`,
2. `guesses`: the various intermediate values that brought to the final result.# Interpolation
This package can also perform linear, polynomial or spline interpolations using the `Interpolation` types. You just need to give a few points in the constructor and then use `compute(double x)` to interpolate the value. The package currently supports the following algorithms:
- `LinearInterpolation`
- `PolynomialInterpolation`
- `NewtonInterpolation`
- `SplineInterpolation`You'll always find the `compute(double x)` method in any `Interpolation` type, but some classes may have additional methods that others haven't. For example:
```dart
const newton = NewtonInterpolation(
nodes: [
InterpolationNode(x: 45, y: 0.7071),
InterpolationNode(x: 50, y: 0.7660),
InterpolationNode(x: 55, y: 0.8192),
InterpolationNode(x: 60, y: 0.8660),
],
);// Prints 0.788
final y = newton.compute(52);
print(y.toStringAsFixed(3));// Prints the following:
//
// [0.7071, 0.05890000000000006, -0.005700000000000038, -0.0007000000000000339]
// [0.766, 0.053200000000000025, -0.006400000000000072, 0.0]
// [0.8192, 0.04679999999999995, 0.0, 0.0]
// [0.866, 0.0, 0.0, 0.0]
print('\n${newton.forwardDifferenceTable()}');
```Since the Newton interpolation algorithm internally builds the "divided differences table", the API exposes two methods (`forwardDifferenceTable()` and `backwardDifferenceTable()`) to print those tables. Of course, you won't find `forwardDifferenceTable()` in other interpolation types because they just don't use it. By default, `NewtonInterpolation` uses the forward difference method but if you want the backward one, just pass `forwardDifference: false` in the constructor.
```dart
const polynomial = PolynomialInterpolation(
nodes: [
InterpolationNode(x: 0, y: -1),
InterpolationNode(x: 1, y: 1),
InterpolationNode(x: 4, y: 1),
],
);// Prints -4.54
final y = polynomial.compute(-1.15);
print(y.toStringAsFixed(2));// Prints -0.5x^2 + 2.5x + -1
print('\n${polynomial.buildPolynomial()}');
```This is another example with a different interpolation strategy. The `buildPolynomial()` method returns the interpolation polynomial (as an `Algebraic` type) internally used to interpolate `x`.
# Expressions parsing
You can use the `ExpressionParser` type to either parse numerical expressions or evaluate mathematical functions with the `x` variable:
```dart
const parser = ExpressionParser();print(parser.evaluate('5*3-4')); // 11
print(parser.evaluate('sqrt(49)+10')); // 17
print(parser.evaluate('pi')); // 3.1415926535print(parser.evaluateOn('6*x + 4', 3)); // 22
print(parser.evaluateOn('sqrt(x) - 3', 81)); // 6
```This type is internally used by the library to evaluate nonlinear functions.