### ----------------------------------------------------
### -- Math::Libgsl::Matrix
### -- Licenses: Artistic-2.0
### -- Authors: Fernando Santagata
### -- File: t/03-vector.rakutest
### ----------------------------------------------------
#!/usr/bin/env raku

use Test;
use lib 'lib';
use Math::Libgsl::Vector;
use Math::Libgsl::Vector::Complex64 ();
use Math::Libgsl::Constants;

subtest 'vector allocation' => {
  my Math::Libgsl::Vector $v1 .= new(:size(10));
  isa-ok $v1, Math::Libgsl::Vector, 'new using pair';
  ok $v1.vector.size == 10, 'size directly';
  ok $v1.size == 10, 'size through method';
  my Math::Libgsl::Vector $v2 .= new(10);
  isa-ok $v2, Math::Libgsl::Vector, 'new using single value';
};

subtest 'vector set & get' => {
  my Math::Libgsl::Vector $v1 .= new(:size(10));
  ok $v1.get(0) == 0, 'get element';
  dies-ok { $v1.get(10) }, 'get element 11 of a 10-element vector dies';
  ok $v1[0] == 0, 'get element using postcircumfix';
  dies-ok { $v1[10] }, 'get element 11 of a 10-element vector using postcircumfix dies';
  lives-ok { $v1.set(0, 10) }, 'element set lives';
  ok $v1.get(0) == 10e0, 'element set';
  dies-ok { $v1.set(10) }, 'set element 11 of a 10-element vector dies';
  lives-ok { $v1[1] = 12 }, 'set element using postcircumfix lives';
  ok $v1.get(1) == 12e0, 'element set using postcircumfix lives';
  is-deeply $v1[^3], (10e0, 12e0, 0e0), 'get range of elements';
  lives-ok { $v1.setall(1) }, 'set all elements';
  is-deeply $v1[^10], (1e0 xx 10), 'all elements set';
  lives-ok { $v1.zero }, 'set all elements to zero';
  is-deeply $v1[^10], (0e0 xx 10), 'all elements zeroed';
  lives-ok { $v1.basis(1) }, 'basis';
  is-deeply $v1[^10], (0e0, 1e0, 0e0 xx 8).flat, 'basis vector';
  dies-ok { $v1.basis(10) }, 'basis 11 of a 10-element vector dies';
};

subtest 'vector IO' => {
  my Math::Libgsl::Vector $v1 .= new(:size(10));
  $v1.setall(1);
  my $filename = 't/vector.dat';
  LEAVE { with $filename.IO { .unlink if .e } }
  lives-ok { $v1.write($filename) }, 'can write a vector to file';
  lives-ok { $v1.read($filename) }, 'can read a vector from file';
  lives-ok { $v1.printf($filename, "%3.1lf") }, 'can print a vector to file';
  is-deeply $filename.IO.lines, '1.0' xx 10, 'vector file';
};

subtest 'views' => {
  my Math::Libgsl::Vector $v1 .= new(:size(10));
  $v1.setall(1);
  my Math::Libgsl::Vector $vv1;
  my Math::Libgsl::Vector::View $vv .= new;
  lives-ok { $vv1 = $vv.subvector($v1, 0, 3) }, 'create subvector';
  $vv1.setall(12);
  is-deeply $v1[^10], (12e0 xx 3, 1e0 xx 7).flat, 'modify vector using subvector';
  dies-ok { $vv1 = $vv.subvector($v1, 10, 2) }, 'dies if subvector offset past end than vector';
  dies-ok { $vv1 = $vv.subvector($v1, 0, 11) }, 'dies if subvector longer than vector';
  $v1.setall(1);
  my Math::Libgsl::Vector $vv2;
  lives-ok { $vv2 = $vv.subvector-stride($v1, 0, 2, 3) }, 'create subvector with stride';
  $vv2.setall(12);
  is-deeply $v1[^10], ((12e0, 1e0) xx 3, 1e0 xx 4).flat, 'modify vector using subvector with stride';
  my Math::Libgsl::Vector::Complex64 $cv .= new(:size(10));
  isa-ok $cv, Math::Libgsl::Vector::Complex64, 'new complex vector';
  lives-ok { $cv.setall(1+2i) }, 'set all elements to complex values';
  is-deeply $cv[^10], (1+2i xx 10), 'all complex elements set';
  my Math::Libgsl::Vector $vv5;
  my Math::Libgsl::Vector::Complex64::View $cvv .= new;
  lives-ok { $vv5 = $cvv.real($cv) }, 'create real view from complex vector';
  is-deeply $vv5[^10], (1e0 xx 10).flat, 'verify real view';
  my Math::Libgsl::Vector $vv6;
  lives-ok { $vv6 = $cvv.imag($cv) }, 'create imag view from complex vector';
  is-deeply $vv6[^10], (2e0 xx 10).flat, 'verify imag view';
  my @a = 1 xx 10;
  my Math::Libgsl::Vector $vv3;
  my $array = prepvec(@a);
  lives-ok { $vv3 = $vv.array($array) }, 'create array using vector view';
  is-deeply $vv3[^10], (1e0 xx 10).flat, 'verify array using vector view';
  $vv3[0] = 2;
  ok $array[0] == 2, 'changing view results in a changed array';
  $array[0] = 1e0;
  my Math::Libgsl::Vector $vv4;
  lives-ok { $vv4 = $vv.array-stride($array, 2) }, 'create array with stride using vector view';
  is-deeply $vv4[^5], (1e0 xx 5).flat, 'verify array with stride using vector view';
  lives-ok { $array = num64-prepvec(@a) }, 'num64-prepvec alias works';
  my $list := 1 xx 10;
  my Math::Libgsl::Vector $v;
  array-vec(-> $vec { $v = $vec; $v[1] = 2 }, $list);
  is-deeply $v[^10], (1e0, 2e0, |(1e0 xx 8)), 'list view as a closure';
  array-vec(sub ($vec) { $v = $vec; $v[0] = 2 }, $list);
  is-deeply $v[^10], (2e0, |(1e0 xx 9)), 'list view as an anomymous sub';
  my @array = 1 xx 10;
  array-vec(-> $vec { $v = $vec; $v[1] = 2 }, @array);
  is-deeply $v[^10], (1e0, 2e0, |(1e0 xx 8)), 'array view as a closure';
  num64-array-vec(sub ($vec) { $v = $vec; $v[0] = 2 }, @array);
  is-deeply $v[^10], (2e0, |(1e0 xx 9)), 'array view as an anomymous sub';
  array-vec(sub ($vec) { $v = $vec; $v[0] = 2 }, @array);
  is-deeply $v[^10], (2e0, |(1e0 xx 9)), 'alias array view as an anomymous sub';
  array-stride-vec(-> $vec { $v = $vec; $v[0] = 2 }, 2, @array);
  is-deeply $v[^5], (2e0, |(1e0 xx 4)), 'array view with stride as a closure';
};

subtest 'vector copy' => {
  my Math::Libgsl::Vector $v1 .= new(:size(10));
  $v1.set($_, $_.Num) for ^10;
  my Math::Libgsl::Vector $v2 .= new(:size(10));
  lives-ok { $v2.copy($v1) }, 'can copy';
  is-deeply $v2[^10], (0..9).flat».Num, 'copy';
  my Math::Libgsl::Vector $v3 .= new(:size(11));
  dies-ok { $v3.copy($v1) }, "can't copy different sized vectors";
  $v2.zero;
  lives-ok { $v1.swap($v2) }, 'can swap';
  is-deeply $v1[^10], (0e0 xx 10), 'swap';
  dies-ok { $v3.swap($v1) }, "can't swap different sized vectors";
};

subtest 'element exchange' => {
  my Math::Libgsl::Vector $v1 .= new(:size(10));
  $v1.set($_, $_.Num) for ^10;
  lives-ok { $v1.swap-elems(0, 1) }, 'can exchange elements';
  is-deeply $v1[^10], (1e0, 0e0, 2e0..9e0).flat, 'swap elements';
  dies-ok { $v1.swap-elems(0, 10) }, "can't swap elements out of range";
  $v1.set($_, $_.Num) for ^10;
  lives-ok { $v1.reverse }, 'can reverse elements';
  is-deeply $v1[^10], (9e0…0e0).flat, 'reverse elements';
};

subtest 'vector operations' => {
  my Math::Libgsl::Vector $v1 .= new(:size(10));
  $v1.setall(4);
  my Math::Libgsl::Vector $v2 .= new(:size(10));
  $v2.setall(2);
  my Math::Libgsl::Vector $v3 .= new(:size(11));
  lives-ok { $v1.add($v2) }, 'can add';
  is-deeply $v1[^10], (6e0 xx 10), 'add';
  dies-ok { $v1.add($v3) }, "can't add vectors of different size";
  lives-ok { $v1.sub($v2) }, 'can sub';
  is-deeply $v1[^10], (4e0 xx 10), 'sub';
  dies-ok { $v1.sub($v3) }, "can't sub vectors of different size";
  lives-ok { $v1.mul($v2) }, 'can mul';
  is-deeply $v1[^10], (8e0 xx 10), 'mul';
  dies-ok { $v1.mul($v3) }, "can't mul vectors of different size";
  lives-ok { $v1.div($v2) }, 'can div';
  is-deeply $v1[^10], (4e0 xx 10), 'div';
  dies-ok { $v1.div($v3) }, "can't div vectors of different size";
  lives-ok { $v1.scale(2e0) }, 'can scale';
  is-deeply $v1[^10], (8e0 xx 10), 'scale';
  lives-ok { $v1.add-constant(2e0) }, 'can add a constant';
  is-deeply $v1[^10], (10e0 xx 10), 'add-constant';
  if $gsl-version > v2.6 {
    ok $v1.sum == 100, 'sum';
    lives-ok { $v1.axpby(2e0, 3e0, $v2) }, 'can compute αx + βy';
    is-deeply $v2[^10], (26e0 xx 10), 'axpby';
  }
};

subtest 'vector min & max' => {
  my Math::Libgsl::Vector $v1 .= new(:size(10));
  $v1.set($_, $_.Num) for ^10;
  ok $v1.max == 9e0, 'max element';
  ok $v1.min == 0e0, 'min element';
  is-deeply $v1.minmax, (0e0, 9e0), 'min & max elements';
  ok $v1.max-index == 9, 'index of max element';
  ok $v1.min-index == 0, 'index of min element';
  is-deeply $v1.minmax-index, (0, 9), 'index of min & max elements';
};

subtest 'vector properties' => {
  my Math::Libgsl::Vector $v1 .= new(:size(10));
  ok $v1.is-null, 'all elements of v are 0';
  $v1.setall(2e0);
  ok $v1.is-pos, 'all elements of v are >0';
  $v1.set(0, 0e0);
  nok $v1.is-pos, 'fails if not all elements of v are >0';
  $v1.setall(-2e0);
  ok $v1.is-neg, 'all elements of v are <0';
  my Math::Libgsl::Vector $v2 .= new(:size(10));
  $v2.setall(-2e0);
  ok $v1.is-equal($v2), 'two vectors are equal';
};

subtest 'vector to list' => {
  my Math::Libgsl::Vector $v .= new(:size(10));
  $v[$_] = $_ for ^10;
  is-deeply $v.list, (^10)».Num,       'list method coerces to a List';
  is-deeply $v[*],   (^10)».Num,       'AT-POS method coerces to a List';
  my @a = $v[*];
  is-deeply @a,      (^10)».Num.Array, 'assign a vector to a Raku array';
};

subtest 'gist & put' => {
  my Math::Libgsl::Vector $v .= new(:size(200));
  $v[$_] = $_ for ^200;
  ok $v.gist eq '(' ~ (^100).join(' ') ~ ' ...)', 'gist returns the first 100 elements';

  sub capture_stdout(Callable $code) {
    my $result;
    my $*OUT = class {
      method print(*@args) {
        $result ~= @args.join;
      }
      method flush {}
    }
    $code.();
    $result;
  }

  my $out = capture_stdout({ $v.put });
  ok $out eq (^200).join(' ') ~ "\n", 'method put';
  $out = capture_stdout({ put $v });
  ok $out eq (^200).join(' ') ~ "\n", 'sub put';
}

done-testing;


