scidart 0.0.1-dev.2

Dart native js
Flutter Android iOS web

Cross-platform scientific library for Dart.

SciDart #

SciDart is a experimental cross-platform scientific library for Dart.

Goals #

The main goal of SciDart is run where Dart can run, in other words, run on Flutter, Dart CLI, Dart web, etc.

Motivation #

Some time ago I tried make a guitar tuner (frequency estimator) with Flutter and I faced with the problem: Dart dind't have a unified scientific library. So, I tried implement something to help me and the community with this problem.

Link to the Pub repository: https://pub.dev/packages/scidart

Instalation #

You can follow instruction in the Pub web site: https://pub.dev/packages/scidart#-installing-tab-

Dart REPL for prototyping quickly #

Dart don't have REPL by default but I found this amazing post from Andreas Kirsch and He implemented a experimental Dart REPL. But the current library don't support Dart SDK > 2.0.0. So, I decided to leave this work pending for now.

SciDart structure #

  • IO: File manipulation libraries;
    • csv: CSV file manipulation;
    • txt: text file manipulation;
  • NumDart: Numerical computation libraries;
    • array_base: array manipulation;
    • calculus: calculus base;
    • constants: scientific constants;
    • fastmath: optimized math operations;
    • geometric: geometric related operations;
    • interpolation: discrete function interpolation;
    • linalg: linear algebra algorithms: matrix, decomposition, etc;
    • numbers: numeric base that are missing in Dart by default: Complex, double truncation, etc;
    • polynomial: polynomial estimation, regression, etc;
    • random: random data generation;
    • spaces: vector linear spaces generators: arrange, ones, linspace, etc;
    • statistic: statistic computation;
    • time: execution time measurement;
  • SciDart: Scientific computation libraries;
    • fftpack: fourier transformer tools;
      • dbfft
      • fft
      • fftfreq
      • rfft
    • signal: signal manipulation tools;
      • convolution
      • fir
      • lfilter
      • peaks
      • windows
    • special: special functions;
      • bessel

Examples #

NumDart basics #

Arrays

import 'package:scidart/numdart.dart';

// 1D array creation
var a = Array([1.0, 2.0, 3.0]);

print(a); // print vector

// 2D array creation
var mA = Array2d.empty();
var line = Array([1.0, 2.0, 3.0]);

mA.add(line);
mA.add(line);
mA.add(line);

print(mA); // print matrix

// 3D array creation
var book = Array3d.empty();

var page = Array2d.empty();
page.add(Array([1.0, 2.0, 3.0]));
page.add(Array([1.0, 2.0, 3.0]));
page.add(Array([1.0, 2.0, 3.0]));

book.add(page);
book.add(page);
book.add(page);

print(book);

// Create a array of complex numbers
var aComplex = ArrayComplex([
  Complex(real: 2.0, imaginary: 2.0),
  Complex(real: 2.0, imaginary: 2.0),
  Complex(real: 2.0, imaginary: 2.0)
]);

print(aComplex);

Numeric spaces

import 'package:scidart/numdart.dart';

var ar = arange(step: 0, stop: 100); // generate a array 0 to 99
print(ar);

var lin =
    linspace(0, 10, num: 40); // generate a array 0 to 10 with 40 elements
print(lin);

var on =
    ones(10); // generate a array with length 10 where all elements are 1
print(on);

var zer =
    zeros(5); // generate a array with length 5 where all elements are 0
print(zer);

// generate a 1Hz sine wave with sample frequency (fs) of 128Hz
var N = 50.0;
var fs = 128;
var n = linspace(0, N, num: (N * fs).toInt(), endpoint: false);
var f1 = 1; // 1Hz
var sg = arraySin(arrayMultiplyToScalar(n, 2 * pi * f1));
print(sg);

Random data generation

import 'package:scidart/numdart.dart';

var r =
    randomArray(10); // generate a array with 10 random elements 0 to 0.99
print(r);

var r2d = randomArray2d(3, 3); // generate a 2D 3x3 with random elements
print(r2d);

var rAc =
    randomArrayComplex(10); // generate ArrayComplex with 10 random elements
print(rAc);

Array operations

import 'package:scidart/numdart.dart';

var a = Array([1.0, 2.0, 3.0]);
var b = Array([1.0, 2.0, 3.0]);
var aComplex = ArrayComplex([
  Complex(real: 2.0, imaginary: 2.0),
  Complex(real: 2.0, imaginary: 2.0),
  Complex(real: 2.0, imaginary: 2.0)
]);

print(a + b);
print(a - b);
print(a * b);
print(a / b);
print(arrayConcat(a, b)); // array concatenation
print(arrayMax(a));
print(arrayMin(a));
print(arrayDivisionToScalar(a, 2));
print(a.getRangeArray(0, 2));
print(arraySum(a));
print(arrayToComplexArray(a));
print(arrayPadStart(a, 2)); // add zeros at start of array
print(arraySin(a)); // compute sin for all elements of array

var c = Array([1.12121212, 2.12121212, 3.12121212]);
print(arrayTruncateEachElement(c, 4,
    returnNewArray: true)); // truncate all values

print(a == c);
print(arrayPow(a, 2));
print(arrayReshapeToMatrix(Array([1.0, 2.0, 3.0, 4.0]), 2));

// Complex array operations
print(arrayComplexAbs(aComplex));
print(arrayComplexConjugate(aComplex));
print(arrayComplexSum(aComplex, aComplex));
print(arrayComplexPadStart(aComplex, 2));

Matrix operations

import 'package:scidart/numdart.dart';

var mA = Array2d.empty();
var line = Array([1.0, 2.0, 3.0]);

mA.add(line);
mA.add(line);
mA.add(line);

var mB = Array2d([
  Array([1.0, 2.0, 3.0]),
  Array([4.0, 5.0, 6.0]),
  Array([7.0, 8.0, 10.0])
]);
var mC = Array2d([
  Array([1]),
  Array([2]),
  Array([3])
]);

print(matrixDeterminant(mA)); // find matrix determinant
print(matrixRank(mA)); // matrix rank
print(matrixTranspose(mA)); // matrix transposition
print(matrixInverse(mC)); // matrix inverse
print(matrixDot(mA, mC)); // Matrix product
print(matrixQR(mA).Q()); // Matrix Q of QR decomposition
print(matrixSolve(mB, mC)); // solve a linear system

var mD = Array2d([
  Array([1.121212121212]),
  Array([2.121212121212]),
  Array([3.121212121212])
]);
array2dTruncateEachElement(mD, 4);
print(mD);

print(mA + mB);
print(mA - mB);
print(mA * mB);
print(mA / mB);
print(mA == mB);

print(matrixNormOne(mA));
print(matrixNormTwo(mA));

Complex operations

import 'package:scidart/numdart.dart';

var a = Complex(real: 1.121212, imaginary: 2.22222);
var b = Complex.ri(4.0, 8.0);

print(a + b);
print(a - b);
print(a * b);
print(a / b);

print(complexAbs(a));
print(complexConjugate(a));
print(complexCos(a));
print(complexDivideScalar(a, 2));
print(complexMultiplyScalar(a, 2));
print(complexTruncate(a, 2));

Calculus operations

import 'package:scidart/numdart.dart';

var y = Array([-1, -1, -1, -1, 1, 1, 1, 1]); // function y points
var x = arange(stop: 8); // function x points

// array integration
print(trapzArray(y, x: x)); // integration with trapezoidal rule
print(simpsArray(y, x: x)); // integration with Simpson's rule

// cumulative integration using trapezoidal rule, useful for signals
print(cumIntegration(y, x: x));

// function integration
var f = (x) => pow(x, 2); // function definition: y = x^2
var a = 0.0; // start integration interval
var b = 10.0; // end integration interval
var n = 10; // number of point between a and b
print(trapzFunction(a, b, n, f)); // integration using trapezoidal rule
print(simpsFunction(a, b, n, f)); // integration using Simpson's rule

// array differentiation
print(differentiateArray(y, x)); // return a array with the differentiation

// function differentiation
var px = 10.0; // point where the function will be differentiated
print(differentiateFunction(
    px, f)); // return the differentiation at point x=10.0

Interpolation

import 'package:scidart/numdart.dart';

// genrate a x range -3 to 3
var x = linspace(-3, 3, num: 30);
// generate parabolic equation: y = 5 - 0.555*x^2
var y = Array.fixed(x.length, initialValue: 5) -
    arrayMultiplyToScalar(arrayPow(x, 2), 0.555);
// estimate the inter-sample points vx and vy around the maximum point
// of y function
var xMax = arrayArgMax(y);
var yMax = arrayMax(y);

print("Array points:");
print("${xMax}, ${yMax}");

print("Estimated points:");
print(parabolic(y, xMax));

Polynomial

import 'package:scidart/numdart.dart';

// some point to estimate a polynomial regression
var y = Array([0, 1, 4, 3, 2, 5, 0]); // y axis points
var x = Array([0, 1, 2, 3, 4, 5, 6]); // x axis points
var degree = 10; // suggested polynomial degree

var p = PolyFit(x, y, degree); // polynomial regression
print(p); // print polynomial

// print the current polynomial degree, the function reduces the degree
// to get the ideal degree rank for the actual points
print(p.polyDegree());

print(p.coefficients()); // polynomial coefficients

print(p.predict(3.5)); // estimate a y value for the point x = 3.5

Statistic

import 'package:scidart/numdart.dart';

var a = randomArray(100); // generate a random array
print(a); // print array
print(mean(a)); // mean of array
print(median(a)); // median of array
print(mode(a)); // mode of array
print(standardDeviation(a)); // standard deviation of array
print(variance(a)); // variance of array

Time

import 'package:scidart/numdart.dart';

// the random function to measure the computation time
var func = () {
  var x = arange(stop: 99);
  var y = randomArray(99);
  var py = PolyFit(x, y, 10);
  print(py);
  print(py.predict(55.5));
};
// the time measure the computation time if func and return
var dur = timeit(func);
print(dur); // print the computation time

SciDart basics #

fftpack

import 'package:scidart/numdart.dart'; import 'package:scidart/scidart.dart';

// generate the signal for test
// 1Hz sine wave
var N = 50.0;
var fs = 128.0;
var n = linspace(0, N, num: (N * fs).toInt(), endpoint: false);
var f1 = 1.0; // 1Hz
var sw = arraySin(arrayMultiplyToScalar(n, 2 * pi * f1));

// return the fft of the signal
var swF = fft(arrayToComplexArray(sw));
print(swF);

// return the real fft, in other words,
// only the components with of positive frequencies
var swFr = rfft(sw);
print(swFr);

// return the frequency scale of FFT in Hz
var swFScale = fftFreq(swF.length, d: 1 / fs);
print(swFScale);

// return only the real frequencies of FFT, useful to use with rFFT
swFScale = fftFreq(swF.length, d: 1 / fs, realFrequenciesOnly: true);
print(swFScale);

print(ifft(swF)); // inverse FFT
print(rifft(swFr)); // inverse FFT of a real FFT

print(dbfft(sw, fs)); // Return the frequency and fft in DB scale

signal

import 'package:scidart/numdart.dart';
import 'package:scidart/scidart.dart';

// sample signals for test
var x = arrayConcat(zeros(8), ones(8));
var y = arrayConcat(ones(8), zeros(8));

//-------- convolution -----------//
// simple numeric convolution
print('convolution');
print(convolution(x, y));

// compute the convolution using FFT, that will be more fast only if:
// x.length + y.length is equal power of 2;
print(convolution(x, y, fast: true));

// compute the convolution for complex signals using FFT
print(convolutionComplex(arrayToComplexArray(x), arrayToComplexArray(y)));

// compute the circular convolution for the complex signals
print(convolutionCircularComplex(
    arrayToComplexArray(x), arrayToComplexArray(y)));

//-------- FIR filter -----------//
// generate the test signal for filter
// a sum of sine waves with 1Hz and 10Hz, 50 samples and
// sample frequency (fs) 128Hz
var N = 50.0;
var fs = 128;
var n = linspace(0, N, num: (N * fs).toInt(), endpoint: false);
var f1 = 1; // 1Hz
var sg1 = arraySin(arrayMultiplyToScalar(n, 2 * pi * f1));
var f2 = 10; // 10Hz
var sg2 = arraySin(arrayMultiplyToScalar(n, 2 * pi * f2));
var sg = sg1 + sg2;

// design a FIR filter low pass with cut frequency at 1Hz, the objective is
// remove the 10Hz sine wave signal
var nyq = 0.5 * fs; // nyquist frequency
var fc = 1; // cut frequency 1Hz
var normal_fc = fc / nyq; // frequency normalization for digital filters
var numtaps = 30; // attenuation of the filter after cut frequency

// generation of the filter coefficients
var b = firwin(numtaps, Array([normal_fc]));
print('FIR');
print(b);

//-------- Digital filter application -----------//
print('digital filter application');
// Apply the filter on the signal using lfilter function
// lfilter uses direct form II transposed, for FIR filter
// the a coefficient is 1.0
var sgFiltered = lfilter(b, Array([1.0]), sg);

print(sg); // show the original signal
print(sgFiltered); // show the filtered signal

//-------- peaks -----------//
print('peaks');
var pk = findPeaks(sgFiltered);
print(pk[0]);// print the indexes of the peaks found in the signal
print(pk[1]);// print the values of the peaks found in the signal

//-------- window functions -----------//
// generate a backmanharris window with x length
print('window functions');
var wBh = blackmanharris(x.length);
print(x * wBh); // apply the windows on the signal

// the windows available can be get with getWindow
print(getWindow('hann', x.length)); // hann window

// for kaiser window, we need pass beta (double value)
print(getWindow(['kaiser', 2.0], x.length));

special

import 'package:scidart/numdart.dart';
import 'package:scidart/scidart.dart';

// modified Bessel function of order 0
print(besselI0(10));

// modified Bessel function of order 0 for a Array
var a = randomArray(10);
print(arrayBesselI0(a));

Complete examples #

Avoid spectral leakage

import 'package:scidart/numdart.dart';
import 'package:scidart/scidart.dart';

// generate the signals for test
// 1Hz sine wave with incomplete with result a spectral leakage
// in the frequency domain
var N = 20.0;
var fs = 128.0;
var n = linspace(0, N, num: (N * fs).toInt(), endpoint: false);
var f1 = 1.0; // 1Hz
var sg = arraySin(arrayMultiplyToScalar(n, 2 * pi * f1));
sg = sg.getRangeArray(0, sg.length - 201);
sg = arrayConcat(sg, zeros(200));

// reduce spectral leakage with a window function
var sgWindowed = blackmanharris(sg.length) * sg;

print(dbfft(sg, fs)); // fft of original signal
print(dbfft(sgWindowed, fs)); // fft of windowed signal

Frequency estimator

import 'package:scidart/numdart.dart';
import 'package:scidart/scidart.dart';

// generate the signals for test
// 1Hz sine wave
var N = 50.0;
var fs = 128.0;
var n = linspace(0, N, num: (N * fs).toInt(), endpoint: false);
var f1 = 1.0; // 1Hz
var sg1 = arraySin(arrayMultiplyToScalar(n, 2 * pi * f1));

var fEstimated = freqFromFft(sg1, fs);

print('The original and estimated frequency need be very close each other');
print('Original frequency: ${f1}');
print('Estimated frequency: ${fEstimated}');

// both are equal if truncated
expect(truncate(f1, 4), truncate(fEstimated, 4));

double freqFromFft(Array sig, double fs) {
  // Estimate frequency from peak of FFT

  // Compute Fourier transform of windowed signal
  // Avoid spectral leakage: https://en.wikipedia.org/wiki/Spectral_leakage
  var windowed = sig * blackmanharris(sig.length);
  var f = rfft(windowed);

  var fAbs = arrayComplexAbs(f);

  // Find the peak and interpolate to get a more accurate peak
  var i = arrayArgMax(fAbs); // Just use this for less-accurate, naive version

  // Parabolic approximation is necessary to get the exactly frequency of a discrete signal
  // since the frequency can be in some point between the samples.
  var true_i = parabolic(arrayLog(fAbs), i)[0];

  // Convert to equivalent frequency
  return fs * true_i / windowed.length;
}

References #

Below, the list of the main references of SciDart:

How to contribute #

I recommend see the Todo List and choose a task or choose and solve a problem with SciDart and implement the missing parts.

The references values for all function was obtained with SciPy. The contributions need use SciPy as reference too.

Every contribution needs have tests, documentation and examples, otherwise, the pull request will be blocked.

Todo list #

Project documentation #

  • create a logo
  • create a official web site
  • adjust the documentation to be more friendly for the users
  • write tutorials

Benchmarks #

  • construct codes for benchmark tests
  • made benchmark for Android devices
  • made benchmark for iOS devices
  • made benchmark on Web
  • made benchmark on Linux
  • made benchmark on MacOS
  • made benchmark on MS Windows

Code implementations #

  • complex numbers algebra (ok)
  • polyfit (ok but need more tests)
  • documentation with examples(ok)
  • function to time the execution of a process (ok)
  • circular convolution (ok)
  • convolution (ok)
  • random (ok)
  • write, read txt (ok, but work only in PC)
  • write, read csv (ok, but work only in PC)
  • separate fast math (ok)
  • test fast math (ok)
  • refactor array(ok), array2d(ok), arrayComplex(ok), Complex(ok) class to be more functional
  • write matrix operations tests (ok)
  • implement determinant calculation (ok)
  • integration and derivation (ok)
  • filter FIR (ok)
  • dbConverters (ok)
  • write some examples: simples uses, frequency estimator (ok)
  • write readme (ok)
  • write other examples: step counter, FM modulation, AM modulation;
  • filter IIR
  • generate a plot in memory (delegated to https://stackoverflow.com/questions/43703802/flutter-draw-graphs-on-the-screen)
  • logSpace (https://docs.scipy.org/doc/numpy-1.15.0/reference/generated/numpy.logspace.html)
  • write a REPL to interact with the lib (Need support Dart SDK > 2 : https://medium.com/dartlang/dart-repl-poc-f327e3769b6f)
  • publish package
  • show to the community
12
likes
80
pub points
76%
popularity

Cross-platform scientific library for Dart.

Repository (GitHub)
View/report issues

Uploader

angelopolotto@gmail.com

License

BSD (LICENSE)

Dependencies

path

More

Packages that depend on scidart