Net-OnlineCode

 view release on metacpan or  search on metacpan

lib/Net/OnlineCode/GraphDecoder.pm  view on Meta::CPAN

sub is_check {
  my ($self, $i) = @_;
  return ($i >= $self->{coblocks});
}




# the decoding algorithm is divided into two steps. The first adds a
# new check block to the graph, while the second resolves the graph to
# discover newly solvable auxiliary or message blocks.

# Decoder object creates a check block and we store it in the graph
# here

sub add_check_block {
  my $self = shift;
  my $nodelist = shift;
  my $mblocks = $self->{mblocks};

  unless (ref($nodelist) eq "ARRAY") {
    croak ref($self) . "->add_check_block: nodelist should be a listref!\n";
  }

  my $node = $self->{nodes}++;

  # set up new array elements
  $self->mark_as_solved($node);

  # Bones version handles edges and xor list in one list.
  # The constructor also tests whether elements are solved/unsolved
  my $bone = Net::OnlineCode::Bones->new($self, $node, $nodelist);
  die "add_check_block: failed to create bone\n" unless ref($bone);

  $self->{unknowns}->[$node-$mblocks] = $bone->unknowns;
  $self->{top}->     [$node-$mblocks] = $bone;

  print "Set unknowns count for check block $node to " .
    ($bone->unknowns) . " \n" if DEBUG;

  if (DEBUG) {
    print "New check block $node: " . ($bone->pp) . "\n";
  }

  # Make reciprocal links
  my ($min, $max) = $bone->unknowns_range;
  for ($min .. $max) {
    $self->{bottom}->[$bone->[$_]]->{$node} = $bone;
  }

  # mark node as pending resolution
  push @{$self->{unresolved}}, $node;

  # return index of newly created node
  return $node;

}

# Graph resolution. Resolution of the graph has a "downward" part
# (resolve()) where nodes with one unsolved edge solve a message or
# aux block, and an upward part (cascade()) that works up from a
# newly-solved node.

# helper function
sub apply_solution {

  my ($self, $node, $bone);



}

# Work up from a newly-solved block, potentially doing up-propagation
# rule
sub cascade {
  my ($self,$node) = @_;

  my $mblocks  = $self->{mblocks};
  my $ablocks  = $self->{ablocks};
  my $coblocks = $mblocks + $ablocks;
  my $pending  = $self->{unresolved};

  my @upper = keys %{$self->{bottom}->[$node]};

  if (DEBUG) {
    if (@upper) {
      print "Solved node $node cascades to nodes " . (join " ", @upper)
	. "\n\n";
    } else {
      print "Solved node $node has no cascade\n\n";
    }
  }

  # update the count of unsolved edges and maybe solve aux blocks
  for my $to (@upper) {
    print "Decrementing unknowns count for block $to\n" if DEBUG;
    ($self->{unknowns}->[$to - $mblocks])--;
    
  }
  push @$pending, @upper;
  
}

sub resolve {

  my ($self, @junk) = @_;

  if (ASSERT and scalar(@junk)) {
    die "resolve doesn't take arguments\n";
  }

  my $pending = $self->{unresolved};
  unless (@$pending) {
    return ($self->{done});
  }

  my $start_node = $pending->[0];
  if (ASSERT and $start_node < $self->{mblocks}) {
    croak ref($self) . "->resolve: start node '$start_node' is a message block!\n";
  }

  my @newly_solved = ();
  my $mblocks  = $self->{mblocks};
  my $ablocks  = $self->{ablocks};
  my $coblocks = $self->{coblocks};

  unless ($self->{unsolved_count}) {
    $self->{done}=1;
    return (1);
  }

  while (@$pending) {

    my ($from, $to, $min, $max) = (shift @$pending);

    my $bone     = $self->{top}     ->[$from - $mblocks];
    my $unknowns = $self->{unknowns}->[$from - $mblocks];
    my $solved   = $self->{solution}->[$from];

    next unless ref($bone);

    if (DEBUG) {
      print "\nStarting resolve at $from: " . $bone->pp .
	" ($unknowns unknowns)\n";
    }
    if (DEBUG) {
      my ($type, $status) = ("check", "unsolved");
      $type   = "auxiliary" if $from < $coblocks;
      $status = "solved"    if $solved;
      print "Resolving from $status $type node $from\n";
    }

    # I'm going back to the old way of doing up-propagation since the
    # new way messes with single-stepping

    if ($unknowns == 0) {

      next unless $from < $coblocks and !$solved;

      if (DEBUG) {
	print "Solving aux block $from based on aux rule\n";
      }

      die "Aux rule: didn't have one unknown\n"
	if ($from != $bone->known_unsolved($from));

      $self->{solution}->[$from] = $bone;

      # delete all edges that point up to us
      ($min,$max) = $bone->knowns_range;
      for ($min .. $max) {
	my $lower = $bone->[$_];
	die "Tried to delete non-existent up edge\n" if ASSERT and
	  !exists($self->{bottom}->[$lower]->{$from});
	delete $self->{bottom}->[$lower]->{$from};
      }

      $self->{top}->[$from - $mblocks] = undef;
      
      push @newly_solved, $bone;
      cascade($self, $from);

    } elsif ($unknowns == 1) {

      # Propagation rule matched (one unknown down edge)

      # resolve() only solves a node if the upper node itself is solved.
      # cascade() will handle the case of solving an unsolved aux block
      # by solving its last unsolved message block (upward propagation)


      if ($from < $coblocks and !$solved) {
	print "Skipping down propagation rule on unsolved aux\n" if DEBUG;
	next;
      }

      # pull out the unknown node
      if ($solved) {
	$to = $bone->unknown_unsolved($self);
      }

      if (DEBUG) {
	my ($type, $status) = ("auxiliary", "an unsolved");
	$type   = "message" if $to < $mblocks;
	$status = "a solved"  if $self->{solution}->[$to];
	print "To node $to is $status $type node.\n";
      }

      # delete reciprocal links for all known edges
      ($min, $max) = $bone->knowns_range;
      for ($min .. $max) {
	my $lower = $bone->[$_];
	# next if $lower == $from;
	# die "Tried to delete non-existent up edge $lower->$from\n" 
	#   if ASSERT and !exists($self->{bottom}->[$lower]->{$from});
	delete $self->{bottom}->[$lower]->{$from};
      }

      # mark child node as solved
      print "Marking block $to as solved\n" if DEBUG;
      $self->{solution}->[$to] = $bone;
      push @newly_solved, $bone;

      if ($to < $mblocks) {
	print "Solved message block $to completely\n" if DEBUG;
	unless (--($self->{unsolved_count})) {
	  $self->{done} = 1;
	  # comment out next two lines to continue decoding just in
	  # case there's a bug later
	  @$pending = ();
	  last;                 # finish searching
	}
      } else {
	print "Solved auxiliary block $to completely\n" if DEBUG;
	push @$pending, $to;
      }
      cascade($self, $to);
    } else {
      next;			# go to next pending
    }

    return ($self->{done}, @newly_solved) if STEPPING;

  }

  return ($self->{done}, @newly_solved);


}


1;

__END__



( run in 2.154 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )