mirror of
https://github.com/KevinMidboe/linguist.git
synced 2025-10-29 09:40:21 +00:00
147 lines
3.8 KiB
Raku
147 lines
3.8 KiB
Raku
use v6;
|
|
|
|
class Math::Model;
|
|
|
|
use Math::RungeKutta;
|
|
# TODO: only load when needed
|
|
use SVG;
|
|
use SVG::Plot;
|
|
|
|
has %.derivatives;
|
|
has %.variables;
|
|
has %.initials;
|
|
has @.captures is rw;
|
|
|
|
has %!inv = %!derivatives.invert;
|
|
# in Math::Model all variables are accessible by name
|
|
# in contrast Math::RungeKutta uses vectors, so we need
|
|
# to define an (arbitrary) ordering
|
|
# @!deriv-names holds the names of the derivatives in a fixed
|
|
# order, sod @!deriv-names[$number] turns the number into a name
|
|
# %!deriv-keying{$name} translates a name into the corresponding index
|
|
has @!deriv-names = %!inv.keys;
|
|
has %!deriv-keying = @!deriv-names Z=> 0..Inf;
|
|
|
|
# snapshot of all variables in the current model
|
|
has %!current-values;
|
|
|
|
has %.results;
|
|
has @.time;
|
|
|
|
has $.numeric-error is rw = 0.0001;
|
|
|
|
my sub param-names(&c) {
|
|
&c.signature.params».name».substr(1).grep({ $_ ne '_'});
|
|
}
|
|
|
|
method !params-for(&c) {
|
|
param-names(&c).map( {; $_ => %!current-values{$_} } ).hash;
|
|
}
|
|
|
|
method topo-sort(*@vars) {
|
|
my %seen;
|
|
my @order;
|
|
sub topo(*@a) {
|
|
for @a {
|
|
next if %!inv.exists($_) || %seen{$_} || $_ eq 'time';
|
|
die "Undeclared variable '$_' used in model"
|
|
unless %.variables.exists($_);
|
|
topo(param-names(%.variables{$_}));
|
|
@order.push: $_;
|
|
%seen{$_}++;
|
|
}
|
|
}
|
|
topo(@vars);
|
|
# say @order.perl;
|
|
@order;
|
|
}
|
|
|
|
|
|
method integrate(:$from = 0, :$to = 10, :$min-resolution = ($to - $from) / 20, :$verbose) {
|
|
for %.derivatives -> $d {
|
|
die "There must be a variable defined for each derivative, missing for '$d.key()'"
|
|
unless %.variables.exists($d.key) || %!inv.exists($d.key);
|
|
die "There must be an initial value defined for each derivative target, missing for '$d.value()'"
|
|
unless %.initials.exists($d.value);
|
|
}
|
|
|
|
%!current-values = %.initials;
|
|
%!current-values<time> = $from;
|
|
|
|
my @vars-topo = self.topo-sort(%.variables.keys);
|
|
sub update-current-values($time, @values) {
|
|
%!current-values<time> = $time;
|
|
%!current-values{@!deriv-names} = @values;
|
|
for @vars-topo {
|
|
my $c = %.variables{$_};
|
|
%!current-values{$_} = $c.(|self!params-for($c));
|
|
}
|
|
}
|
|
|
|
my @initial = %.initials{@!deriv-names};
|
|
|
|
sub derivatives($time, @values) {
|
|
update-current-values($time, @values);
|
|
my @r;
|
|
for %!inv{@!deriv-names} {
|
|
my $v = %.variables{$_};
|
|
@r.push: $v.defined
|
|
?? $v(|self!params-for($v))
|
|
!! %!current-values{$_};
|
|
}
|
|
@r;
|
|
}
|
|
|
|
@!time = ();
|
|
for @.captures {
|
|
%!results{$_} = [];
|
|
}
|
|
|
|
sub record($time, @values) {
|
|
update-current-values($time, @values);
|
|
@!time.push: $time;
|
|
say $time if $verbose;
|
|
|
|
for @.captures {
|
|
%!results{$_}.push: %!current-values{$_};;
|
|
}
|
|
}
|
|
|
|
record($from, %.initials{@!deriv-names});
|
|
|
|
adaptive-rk-integrate(
|
|
:$from,
|
|
:$to,
|
|
:@initial,
|
|
:derivative(&derivatives),
|
|
:max-stepsize($min-resolution),
|
|
:do(&record),
|
|
:epsilon($.numeric-error),
|
|
);
|
|
%!results;
|
|
}
|
|
|
|
method render-svg(
|
|
$filename,
|
|
:$x-axis = 'time',
|
|
:$width = 800,
|
|
:$height = 600,
|
|
:$title = 'Model output') {
|
|
my $f = open $filename, :w
|
|
or die "Can't open file '$filename' for writing: $!";
|
|
my @values = map { %!results{$_} }, @.captures.grep({ $_ ne $x-axis});
|
|
my @x = $x-axis eq 'time' ?? @!time !! %!results{$x-axis}.flat;
|
|
my $svg = SVG::Plot.new(
|
|
:$width,
|
|
:$height,
|
|
:@x,
|
|
:@values,
|
|
:$title,
|
|
).plot(:xy-lines);
|
|
$f.say(SVG.serialize($svg));
|
|
$f.close;
|
|
say "Wrote ouput to '$filename'";
|
|
}
|
|
|
|
# vim: ft=perl6
|