Algorithm-GaussianElimination-GF2

 view release on metacpan or  search on metacpan

lib/Algorithm/GaussianElimination/GF2.pm  view on Meta::CPAN

package Algorithm::GaussianElimination::GF2;

our $VERSION = '0.02';

use strict;
use warnings;

sub new {
    my $class = shift;
    my $self = { eqs => [] };
    bless $self, $class;
}

sub _add_equation {
    my ($self, $eq) = @_;
    push @{$self->{eqs}}, $eq;
}

sub new_equation {
    my $self = shift;
    my $eq = Algorithm::GaussianElimination::GF2::Equation->_new(@_);
    $self->_add_equation($eq);
    $eq;
}

*add_equation = \&new_equation;

sub _first_1 {
    pos($_[0]) = 0;
    $_[0] =~ /[^\0]/g or return length($_[0]) * 8;
    my $end = pos($_[0]) * 8 - 1;
    for my $i (($end - 7) .. $end) {
        return $i if vec($_[0], $i, 1);
    }
}

sub dump {
    my $self = shift;
    my $eqs = $self->{eqs};
    my $len = 0;
    for (@$eqs) {
        $len = $_->[2] if $_->[2] > $len;
    }
    printf "GF(2) system of %d equations and %d variables\n", scalar(@$eqs), $len;
    for (@$eqs) {
        $_->[2] = $len;
        $_->dump;
    }
    print "\n";
}

sub solve {
    my $self = shift;
    my $eqs = $self->{eqs};
    my $len = 0;
    for my $eq (@$eqs) {
        $len = $eq->[2] if $eq->[2] > $len;
    }
    my @v;
    for my $eq (@$eqs) {
        push @v, $eq->[0];
        vec($v[-1], $len, 1) = $eq->[1];
    }

    for my $i (0..$#v) {
        my $v = $v[$i];
        my $ix = _first_1($v);
        if ($ix < $len) {
            for my $j (($i + 1)..$#v) {
                $v[$j] ^= $v if vec($v[$j], $ix, 1);
            }
        }
        elsif (vec($v, $len, 1)) {
            # inconsistent!
            return
        }
    }

    my @sol;
    $sol[$len] = 1;
    for my $v (reverse @v) {
        my $ix = _first_1($v);
        if ($ix < $len) {
            my $sol = 0;
            for my $i (($ix + 1) .. $len) {
                $sol ^= vec($v, $i, 1) if $sol[$i];
            }
            $sol[$ix] = $sol;
        }
    }

    my @free;
    for my $i (0 .. $len - 1) {
        unless (defined $sol[$i]) {
            push @free, $i;
            $sol[$i] = 0;
        }
    }
    pop @sol;

    return \@sol unless wantarray;

    my @base0;
    for my $free (@free) {
        my @sol0;
        $sol0[$_] = 0 for @free;
        $sol0[$free] = 1;
        for my $v (reverse @v) {
            my $ix = _first_1($v);
            if ($ix < $len) {
                my $sol = 0;
                for my $i (($ix + 1) .. ($len - 1)) {
                    $sol ^= vec($v, $i, 1) if $sol0[$i];
                }
                $sol0[$ix] = $sol;
            }
        }
        push @base0, \@sol0;
    }
    return \@sol, @base0;
}

package Algorithm::GaussianElimination::GF2::Equation;

sub _new {
    my $class = shift;
    my $self = ['', 0, 0];
    bless $self, $class;
    if (@_) {
        $self->[1] = (pop @_ ? 1 : 0);
        for my $ix (0..$#_) {
            vec($self->[0], $ix, 1) = $_[$ix]
        }
        $self->[2] = @_;
    }
    $self
}

sub a {
    my ($self, $ix, $v) = @_;
    if (defined $v) {
        $self->[2] = $ix + 1 if $self->[2] <= $ix;
        return vec($self->[0], $ix, 1) = $v;
    }
    return vec($self->[0], $ix, 1);
}

sub as {
    my $self = shift;
    map { vec($self->[0], $_, 1) } 0..($self->[2] - 1);
}

sub b {
    my ($self, $v) = @_;
    if (defined $v) {
        return $self->[1] = ($v ? 1 : 0);
    }
    return $self->[1];
}

sub len { shift->[2] }

sub dump {
    my $self = shift;
    my $last = $self->[2] - 1;
    my @a = map vec($self->[0], $_, 1), 0.. $last;
    print "@a | $self->[1]\n";
}

sub test_solution {
    my $self = shift;
    my $v = $self->[0];
    my $len = $self->[2];
    my $b = 0;
    for my $ix (0..$#_) {
        $b ^= vec($v, $ix, 1) if $_[$ix];
    }
    return ($b == $self->[1]);
}

sub clone {
    my $self = shift;
    my @self = @$self;
    bless \@self, ref $self;
}

1;
__END__

=head1 NAME

Algorithm::GaussianElimination::GF2 - Solve linear systems of equations on GF(2)

=head1 SYNOPSIS

  use Algorithm::GaussianElimination::GF2;

  my $age = Algorithm::GaussianElimination::GF2->new;
  $age->new_equation(1, 0, 0, 1 => 1);
  $age->new_equation(0, 0, 1, 1 => 0);
  my ($sol, @base0) = $age->solve;

  # or you can also create the equations setting elements at given
  # positions:

  my $age = Algorithm::GaussianElimination::GF2->new;
  my $eq1 = $age->new_equation;
  $eq1->a(0, 1);
  $eq1->a(3, 1);
  $eq1->b(1);
  my $eq2 = $age->new_equation;
  $eq2->a(2, 1);
  $eq2->a(3, 1);
  $eq2->b(0);
  my ($sol, @base0) = $age->solve;


=head1 DESCRIPTION

This module implements a variation of the Gaussian Elimination
algorithm that allows to solve systems of linear equations over GF(2).

=head2 Algorithm::GaussianElimination::GF2 methods

Those are the interesting methods:

=over 4

=item $age = Algorithm::GaussianElimination::GF2->new;

=item $eq = $age->new_equation(@a, $b)

=item $eq = $age->new_equation()

Creates and adds a new equation to the algorithm.

The returned value is a reference to the equation object that can be
used to change the equation coeficients before calling the C<solve>
method.

=item ($sol, @base0) = $age->solve

=item $sol = $age->solve



( run in 1.417 second using v1.01-cache-2.11-cpan-d7f47b0818f )