#!/usr/bin/perl 
#
# square-waves-decompose.pl : compose a sine wave out of square waves.
#
# The results of this program can be plotted using the gnuplot command:
#
#     plot "gnuplot.dat" , "sinplot.dat"
#
# Written by Shlomi Fish, 2008.
#
# Licensed under the MIT X11 license:
# http://www.opensource.org/licenses/mit-license.php )
# 
# Requires:
# * perl-5.8.x (may work with older versions)
# * The List::Util module.
# * The PDL module: http://pdl.perl.org/
#
# Version: 0.1.0

use strict;
use warnings;

use POSIX ();
use PDL;
use List::Util ();

use constant PI => (4 * atan2(1, 1));

sub square_wave
{
    my ($len, $height, $x_s) = @_;

    return [map {(POSIX::floor($_*$len)%2 == 0) ? $height : -$height } @$x_s];
}

sub primes
{
    my $n = shift;

    my @ret = (2);
    my $next = 3;
    MAIN:
    while (@ret != $n)
    {
        foreach (@ret)
        {
            if ($next % $_ == 0)
            {
                next MAIN;
            }
        }
    }
}

my $num = 100;
my @points = (map { $_ / $num } (-$num .. $num));

my $good = pdl([map { sin($_ * PI()) } @points]);

# my @lengths = ();

my $len = 1;

my @waves;

my $energy = ($good*$good)->average();
while ($energy > 0.0002)
{
    my $base = pdl(square_wave($len, 1, \@points));

    # The co-efficient to multiply
    # If we want min ||y-> - m*x->|| then we need to have
    # m = Sum[xy]/Sum[x^2]
    my $m = ($good * $base)->sum() / ($base*$base)->sum();
    push @waves, ([$len, $m]);
    $good -= $base*$m;
}
continue
{
    $energy = ($good*$good)->average();
    print "$energy\n";
    $len++;
}

open my $plot, ">", "gnuplot.dat";
my $x_num = 10_000;
foreach my $x_int (-$x_num .. $x_num)
{
    my $x = $x_int / $x_num;
    printf {$plot} ("%.10f %.10f\n", $x,
        List::Util::sum(
            map { @{square_wave(@$_, [$x])} } @waves
        ));
}
close($plot);

if (! -e "sinplot.dat")
{
open my $sin_plot, ">", "sinplot.dat";
foreach my $x_int (-10_000 .. 10_000)
{
    my $x = $x_int / 10_000;
    printf {$sin_plot} ("%.10f %.10f\n" , $x, sin($x*PI()));
}
close($sin_plot);
}
