sub true ($ )
{
my ($self) = @_;
- return $self->invert->false;
+ # We cache 'true' so that simplify() can use the value if it's available.
+ return $self->{'true'} if defined $self->{'true'};
+ my $res = $self->invert->false;
+ $self->{'true'} = $res;
+ return $res;
}
=item C<$str = $set-E<gt>string>
return $res;
}
+=item C<$simp = $set->simplify>
+
+Find prime implicants and return a simplified C<ConditionalSet>.
+
+=cut
+
+sub _simplify ($) # Based on Quine-McCluskey's algorithm.
+{
+ my ($self) = @_;
+
+ # If we know this ConditionalSet is always true, we have nothing to do.
+ # Use the cached value if true if available. Never call true()
+ # as this would call invert() which can be slow.
+ return new Automake::ConditionalSet TRUE
+ if $self->{'hash'}{&TRUE} || $self->{'true'};
+
+ my $nvars = 0;
+ my %var_rank;
+ my @rank_var;
+
+ # Initialization.
+ # Translate and-terms into bit string pairs: [$true, $false].
+ #
+ # Each variable is given a bit position in the strings.
+ #
+ # The first string in the pair tells wether a variable is
+ # uncomplemented in the term.
+ # The second string tells whether a variable is complemented.
+ # If a variable does not appear in the term, then its
+ # corresponding bit is unset in both strings.
+
+ # Order the resulting bit string pairs by the number of
+ # variables involved:
+ # @{$subcubes[2]} is the list of string pairs involving two variables.
+ # (Level 0 is used for "TRUE".)
+ my @subcubes;
+ for my $and_conds ($self->conds)
+ {
+ my $true = 0; # Bit string for uncomplemented variables.
+ my $false = 0; # Bit string for complemented variables.
+
+ my @conds = $and_conds->conds;
+ for my $cond (@conds)
+ {
+ # Which variable is this condition about?
+ confess "can't parse `$cond'"
+ unless $cond =~ /^(.*_)(FALSE|TRUE)$/;
+
+ # Get the variabe's rank, or assign it a new one.
+ my $rank = $var_rank{$1};
+ if (! defined $rank)
+ {
+ $rank = $nvars++;
+
+ # FIXME: simplify() cannot work with more that 31 variables.
+ # We need a bitset implementation to allow more variables.
+ # For now we just return the input, as is, not simplified.
+ return $self if $rank >= 31;
+
+ $var_rank{$1} = $rank;
+ $rank_var[$rank] = $1;
+ }
+
+ # Fire the relevant bit in the strings.
+ if ($2 eq 'FALSE')
+ {
+ $false |= 1 << $rank;
+ }
+ else
+ {
+ $true |= 1 << $rank;
+ }
+ }
+
+ # Register this term.
+ push @{$subcubes[1 + $#conds]}, [$true, $false];
+ }
+
+ # Real work. Let's combine terms.
+
+ # Process terms in diminishing size order. Those
+ # involving the maximum number of variables first.
+ for (my $m = $#subcubes; $m > 0; --$m)
+ {
+ my $m_subcubes = $#{$subcubes[$m]};
+
+ # Consider all terms with $m variables.
+ for (my $j = 0; $j <= $m_subcubes; ++$j)
+ {
+ my $tj = $subcubes[$m][$j];
+ my $jtrue = $tj->[0];
+ my $jfalse = $tj->[1];
+
+ # Compare them with all other terms with $m variables.
+ COMBINATION:
+ for (my $k = $j + 1; $k <= $m_subcubes; ++$k)
+ {
+ my $tk = $subcubes[$m][$k];
+ my $ktrue = $tk->[0];
+ my $kfalse = $tk->[1];
+
+ # Two terms can combine if they differ only by one variable
+ # (i.e., a bit here), which is complemented in one term
+ # and uncomplemented in the other.
+ my $true = $jtrue ^ $ktrue;
+ my $false = $jfalse ^ $kfalse;
+ next COMBINATION if $true != $false;
+ # There should be exactly one bit set.
+ # (`$true & ($true - 1)' unsets the rightmost 1 bit in $true.)
+ next COMBINATION if $true == 0 || $true & ($true - 1);
+
+ # At this point we know we can combine the two terms.
+
+ # Mark these two terms as "combined", so they will be
+ # deleted after we have processed all other combinations.
+ $tj->[2] = 1;
+ $tk->[2] = 1;
+
+ # Actually combine the two terms.
+ my $ctrue = $jtrue & $ktrue;
+ my $cfalse = $jfalse & $kfalse;
+
+ # Don't add the combined term if it already exists.
+ DUP_SEARCH:
+ for my $c (@{$subcubes[$m - 1]})
+ {
+ next DUP_SEARCH if $ctrue != $c->[0];
+ next COMBINATION if $cfalse == $c->[1];
+ }
+ push @{$subcubes[$m - 1]}, [$ctrue, $cfalse];
+ }
+ }
+
+ # Delete all covered terms.
+ for (my $j = 0; $j <= $m_subcubes; ++$j)
+ {
+ delete $subcubes[$m][$j] if $subcubes[$m][$j][2];
+ }
+ }
+
+ # Finally merge bit strings back into a Automake::ConditionalSet.
+
+ # If level 0 has been filled, we've found `TRUE'. No need to translate
+ # anything.
+ return new Automake::ConditionalSet TRUE if $#{$subcubes[0]} >= 0;
+
+ # Otherwise, translate uncombined terms in other levels.
+
+ my @or_conds = ();
+ # Process terms in diminishing size order. Those
+ # involving the maximum number of variables first.
+ for (my $m = 1; $m <= $#subcubes; ++$m)
+ {
+ my $m_subcubes = $#{$subcubes[$m]};
+ # Consider all terms with $m variables.
+ for (my $j = 0; $j <= $m_subcubes; ++$j)
+ {
+ my $tj = $subcubes[$m][$j];
+ next unless $tj; # Skip deleted terms.
+ my $jtrue = $tj->[0];
+ my $jfalse = $tj->[1];
+
+ # Filter-out implied terms.
+ #
+ # An and-term at level N might cover and-terms at level M>N.
+ # We need to mark all these covered terms so that they are
+ # not output in the result formula.
+ #
+ # If $tj was generated by combining two terms at level N+1,
+ # there two terms are already marked. However there might be
+ # implied terms deeper.
+ #
+ # For instance consider this input: "A_TRUE | A_TRUE C_FALSE".
+ #
+ # This can also occur with and-term generated by the
+ # combining algorith. E.g., consider
+ # "A_TRUE B_TRUE" | "A_TRUE B_FALSE" | "A_TRUE C_FALSE D_FALSE"
+ # - at level 3 we can't combine "A_TRUE C_FALSE D_FALSE"
+ # - at level 2 we can combine "A_TRUE B_TRUE" | "A_TRUE B_FALSE"
+ # into "A_TRUE
+ # - at level 1 we an't combine "A_TRUE"
+ # so without more simplification we would output
+ # "A_TRUE | A_TRUE C_FALSE D_FALSE"
+ #
+ # So let's filter-out and-terms which are implied by other
+ # and-terms. An and-term $tk is implied by an and-term $tj if $k
+ # involves more variables than $tj (i.e., N>M) and if
+ # all variables occurring in $tk also occur in A in the
+ # same state (complemented or uncomplemented.)
+ for (my $n = $m + 1; $n <= $#subcubes; ++$n)
+ {
+ my $n_subcubes = $#{$subcubes[$n]};
+ for (my $k = 0; $k <= $n_subcubes; ++$k)
+ {
+ my $tk = $subcubes[$n][$k];
+ next unless $tk; # Skip deleted terms.
+ my $ktrue = $tk->[0];
+ my $kfalse = $tk->[1];
+
+ next unless $ktrue == ($ktrue | $jtrue);
+ next unless $kfalse == ($kfalse | $jfalse);
+
+ delete $subcubes[$n][$k];
+ }
+ }
+
+ # Translate $tj.
+ my @and_conds = ();
+ my $rank = 0;
+ while ($jtrue > 0)
+ {
+ if ($jtrue & 1)
+ {
+ push @and_conds, $rank_var[$rank] . 'TRUE';
+ }
+ $jtrue >>= 1;
+ ++$rank;
+ }
+ $rank = 0;
+ while ($jfalse > 0)
+ {
+ if ($jfalse & 1)
+ {
+ push @and_conds, $rank_var[$rank] . 'FALSE';
+ }
+ $jfalse >>= 1;
+ ++$rank;
+ }
+
+ push @or_conds, new Automake::Conditional @and_conds if @and_conds;
+ }
+ }
+
+ return new Automake::ConditionalSet @or_conds;
+}
+
+sub simplify ($)
+{
+ my ($self) = @_;
+ return $self->{'simplify'} if defined $self->{'simplify'};
+ my $res = $self->_simplify ;
+ $self->{'simplify'} = $res;
+ return $res;
+}
+
=head1 SEE ALSO
L<Automake::Conditional>.
my $per = $set->permutations;
if ($per != $res)
{
- print $per->string . ' != ' . $res->string . "\n";
+ print " (P) " . $per->string . ' != ' . $res->string . "\n";
return 1;
}
}
my $inv = $set->invert;
if ($inv != $res)
{
- print $inv->string . ' != ' . $res->string . "\n";
+ print " (I) " . $inv->string . ' != ' . $res->string . "\n";
return 1;
}
}
return 0;
}
-exit (test_basics || test_permutations || test_invert);
+sub test_simplify ()
+{
+ my @tests = ([[["FOO_TRUE", "BAR_FALSE", "BAZ_FALSE"],
+ ["FOO_TRUE", "BAR_FALSE", "BAZ_TRUE"]],
+ [["FOO_TRUE", "BAR_FALSE"]]],
+
+ [[["FOO_TRUE", "BAR_FALSE", "BAZ_FALSE"],
+ ["FOO_TRUE", "BAR_FALSE", "BAZ_TRUE"],
+ ["FOO_TRUE", "BAR_TRUE"]],
+ [["FOO_TRUE"]]],
+
+ [[["FOO_TRUE", "BAR_FALSE", "BAZ_FALSE"],
+ ["FOO_TRUE", "BAR_FALSE", "BAZ_TRUE"],
+ ["FOO_TRUE", "BAR_TRUE"],
+ ["FOO_FALSE"]],
+ [["TRUE"]]],
+
+ [[["FOO_TRUE", "BAR_FALSE", "BAZ_FALSE"],
+ ["FOO_TRUE", "BAR_FALSE", "BAZ_TRUE"],
+ ["BAR_TRUE", "BAZ_TRUE"],
+ ["BAR_FALSE", "BAZ_TRUE"]],
+ [["BAZ_TRUE"], ["FOO_TRUE", "BAR_FALSE"]]],
+
+ [[["FOO_TRUE", "BAR_FALSE", "BAZ_FALSE"],
+ ["FOO_TRUE", "BAR_FALSE", "BAZ_TRUE"],
+ ["BAR_TRUE", "BAZ_TRUE"],
+ ["BAR_FALSE", "BAZ_TRUE"],
+ ["FOO_FALSE"]],
+ # Note that this could be further simplified to
+ # [["FOO_FALSE"], ["BAZ_TRUE"], ["BAR_FALSE"]]
+ # but simplify isn't able to detect this.
+ [["FOO_FALSE"], ["BAZ_TRUE"], ["BAR_FALSE", "FOO_TRUE"]]],
+
+ [[["B_TRUE"],
+ ["A_FALSE", "B_TRUE"]],
+ [["B_TRUE"]]],
+
+ [[["B_TRUE"],
+ ["A_FALSE", "B_FALSE", "C_TRUE"],
+ ["A_FALSE", "B_FALSE", "C_FALSE"]],
+ # Note that this could be further simplified to
+ # [["A_FALSE"], ["B_TRUE"]]
+ # but simplify isn't able to detect this.
+ [["A_FALSE", "B_FALSE"], ["B_TRUE"]]],
+
+ [[["B_TRUE"],
+ ["A_FALSE", "B_FALSE", "C_TRUE"],
+ ["A_FALSE", "B_FALSE", "C_FALSE"],
+ ["A_TRUE", "B_FALSE"]],
+ [["TRUE"]]],
+
+ [[["A_TRUE", "B_TRUE"],
+ ["A_TRUE", "B_FALSE"],
+ ["A_TRUE", "C_FALSE", "D_FALSE"]],
+ [["A_TRUE"]]],
+
+ [[["A_FALSE", "B_FALSE", "C_FALSE", "D_TRUE", "E_FALSE"],
+ ["A_FALSE", "B_FALSE", "C_TRUE", "D_TRUE", "E_TRUE"],
+ ["A_FALSE", "B_TRUE", "C_TRUE", "D_FALSE", "E_TRUE"],
+ ["A_FALSE", "B_TRUE", "C_FALSE", "D_FALSE", "E_FALSE"],
+ ["A_TRUE", "B_TRUE", "C_FALSE", "D_FALSE", "E_FALSE"],
+ ["A_TRUE", "B_TRUE", "C_TRUE", "D_FALSE", "E_TRUE"],
+ ["A_TRUE", "B_FALSE", "C_TRUE", "D_TRUE", "E_TRUE"],
+ ["A_TRUE", "B_FALSE", "C_FALSE", "D_TRUE", "E_FALSE"]],
+ [ ["B_FALSE", "C_FALSE", "D_TRUE", "E_FALSE"],
+ ["B_FALSE", "C_TRUE", "D_TRUE", "E_TRUE"],
+ ["B_TRUE", "C_TRUE", "D_FALSE", "E_TRUE"],
+ ["B_TRUE", "C_FALSE", "D_FALSE", "E_FALSE"]]],
+
+ [[["A_FALSE", "B_FALSE", "C_FALSE", "D_TRUE", "E_FALSE"],
+ ["A_FALSE", "B_FALSE", "C_TRUE", "D_TRUE", "E_TRUE"],
+ ["A_FALSE", "B_TRUE", "C_TRUE", "D_FALSE", "E_TRUE"],
+ ["A_FALSE", "B_TRUE", "C_FALSE", "D_FALSE", "E_FALSE"],
+ ["A_TRUE", "B_TRUE", "C_FALSE", "D_FALSE", "E_FALSE"],
+ ["A_TRUE", "B_TRUE", "C_TRUE", "D_FALSE", "E_TRUE"],
+ ["A_TRUE", "B_FALSE", "C_TRUE", "D_TRUE", "E_TRUE"],
+ ["A_TRUE", "B_FALSE", "C_FALSE", "D_TRUE", "E_FALSE"],
+ ["A_FALSE", "B_FALSE", "C_FALSE", "D_FALSE", "E_FALSE"],
+ ["A_FALSE", "B_FALSE", "C_TRUE", "D_FALSE", "E_TRUE"],
+ ["A_FALSE", "B_TRUE", "C_TRUE", "D_TRUE", "E_TRUE"],
+ ["A_FALSE", "B_TRUE", "C_FALSE", "D_TRUE", "E_FALSE"],
+ ["A_TRUE", "B_TRUE", "C_FALSE", "D_TRUE", "E_FALSE"],
+ ["A_TRUE", "B_TRUE", "C_TRUE", "D_TRUE", "E_TRUE"],
+ ["A_TRUE", "B_FALSE", "C_TRUE", "D_FALSE", "E_TRUE"],
+ ["A_TRUE", "B_FALSE", "C_FALSE", "D_FALSE", "E_FALSE"]],
+ [["C_FALSE", "E_FALSE"],
+ ["C_TRUE", "E_TRUE"]]],
+
+ [[["A_FALSE"],
+ ["A_TRUE", "B_FALSE"],
+ ["A_TRUE", "B_TRUE", "C_FALSE"],
+ ["A_TRUE", "B_TRUE", "C_TRUE", "D_FALSE"],
+ ["A_TRUE", "B_TRUE", "C_TRUE", "D_TRUE", "E_FALSE"],
+ ["A_TRUE", "B_TRUE", "C_TRUE", "D_TRUE", "E_TRUE", "F_FALSE"],
+ ["A_TRUE", "B_TRUE", "C_TRUE", "D_TRUE", "E_TRUE"]],
+ [["TRUE"]]],
+
+ # Simplify should work with up to 31 variables.
+ [[["V01_TRUE", "V02_TRUE", "V03_TRUE", "V04_TRUE", "V05_TRUE",
+ "V06_TRUE", "V07_TRUE", "V08_TRUE", "V09_TRUE", "V10_TRUE",
+ "V11_TRUE", "V12_TRUE", "V13_TRUE", "V14_TRUE", "V15_TRUE",
+ "V16_TRUE", "V17_TRUE", "V18_TRUE", "V19_TRUE", "V20_TRUE",
+ "V21_TRUE", "V22_TRUE", "V23_TRUE", "V24_TRUE", "V25_TRUE",
+ "V26_TRUE", "V27_TRUE", "V28_TRUE", "V29_TRUE", "V30_TRUE",
+ "V31_TRUE"],
+ ["V01_TRUE", "V02_TRUE", "V03_TRUE", "V04_TRUE", "V05_TRUE",
+ "V06_TRUE", "V07_TRUE", "V08_TRUE", "V09_TRUE", "V10_TRUE",
+ "V11_TRUE", "V12_TRUE", "V13_TRUE", "V14_TRUE", "V15_TRUE",
+ "V16_TRUE", "V17_TRUE", "V18_TRUE", "V19_TRUE", "V20_TRUE",
+ "V21_TRUE", "V22_TRUE", "V23_TRUE", "V24_TRUE", "V25_TRUE",
+ "V26_TRUE", "V27_TRUE", "V28_TRUE", "V29_TRUE", "V30_TRUE",
+ "V31_FALSE"],
+ ["V01_FALSE","V02_TRUE", "V03_TRUE", "V04_TRUE", "V05_TRUE",
+ "V06_TRUE", "V07_TRUE", "V08_TRUE", "V09_TRUE", "V10_TRUE",
+ "V11_TRUE", "V12_TRUE", "V13_TRUE", "V14_TRUE", "V15_TRUE",
+ "V16_TRUE", "V17_TRUE", "V18_TRUE", "V19_TRUE", "V20_TRUE",
+ "V21_TRUE", "V22_TRUE", "V23_TRUE", "V24_TRUE", "V25_TRUE",
+ "V26_TRUE", "V27_TRUE", "V28_TRUE", "V29_TRUE", "V30_TRUE",
+ "V31_TRUE"],
+ ["V01_FALSE","V02_TRUE", "V03_TRUE", "V04_TRUE", "V05_TRUE",
+ "V06_TRUE", "V07_TRUE", "V08_TRUE", "V09_TRUE", "V10_TRUE",
+ "V11_TRUE", "V12_TRUE", "V13_TRUE", "V14_TRUE", "V15_TRUE",
+ "V16_TRUE", "V17_TRUE", "V18_TRUE", "V19_TRUE", "V20_TRUE",
+ "V21_TRUE", "V22_TRUE", "V23_TRUE", "V24_TRUE", "V25_TRUE",
+ "V26_TRUE", "V27_TRUE", "V28_TRUE", "V29_TRUE", "V30_TRUE",
+ "V31_FALSE"]],
+ [[ "V02_TRUE", "V03_TRUE", "V04_TRUE", "V05_TRUE",
+ "V06_TRUE", "V07_TRUE", "V08_TRUE", "V09_TRUE", "V10_TRUE",
+ "V11_TRUE", "V12_TRUE", "V13_TRUE", "V14_TRUE", "V15_TRUE",
+ "V16_TRUE", "V17_TRUE", "V18_TRUE", "V19_TRUE", "V20_TRUE",
+ "V21_TRUE", "V22_TRUE", "V23_TRUE", "V24_TRUE", "V25_TRUE",
+ "V26_TRUE", "V27_TRUE", "V28_TRUE", "V29_TRUE", "V30_TRUE"
+ ]]]);
+
+ for my $t (@tests)
+ {
+ my $set = build_set @{$t->[0]};
+ my $res = build_set @{$t->[1]};
+
+ # Make sure simplify() yields the expected result.
+ my $sim = $set->simplify;
+ if ($sim != $res)
+ {
+ print " (S1) " . $set->string . "\n\t"
+ . $sim->string . ' != ' . $res->string . "\n";
+ return 1;
+ }
+
+ # Make sure simplify() is idempotent.
+ my $sim2 = $sim->simplify;
+ if ($sim2 != $sim)
+ {
+ print " (S2) " . $sim->string . "\n\t"
+ . $sim2->string . ' != ' . $sim->string . "\n";
+ return 1;
+ }
+
+ # Also exercize invert() while we are at it.
+
+ # FIXME: Don't run invert() with too much conditionals, this is too slow.
+ next if $#{$t->[0][0]} > 8;
+
+ my $inv1 = $set->invert->simplify;
+ my $inv2 = $sim->invert->simplify;
+ if ($inv1 != $inv2)
+ {
+ print " (S3) " . $set->string . ", " . $sim->string . "\n\t"
+ . $inv1->string . ' != ' . $inv2->string . "\n";
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+exit (test_basics || test_permutations || test_invert || test_simplify);
### Setup "GNU" style for perl-mode and cperl-mode.
## Local Variables: