mirror of
				https://github.com/KevinMidboe/linguist.git
				synced 2025-10-29 17:50:22 +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
 |