Index   Main   Namespaces   Classes   Hierarchy   Annotated   Files   Compound   Global   Pages  

GoSafetyUtil.cpp

Go to the documentation of this file.
00001 //----------------------------------------------------------------------------
00002 /** @file GoSafetyUtil.cpp
00003     See GoSafetyUtil.h. */
00004 //----------------------------------------------------------------------------
00005 
00006 #include "SgSystem.h"
00007 #include "GoSafetyUtil.h"
00008 
00009 #include <math.h>
00010 #include "GoBlock.h"
00011 #include "GoBoard.h"
00012 #include "GoBoardUtil.h"
00013 #include "GoEyeUtil.h"
00014 #include "GoModBoard.h"
00015 #include "GoRegion.h"
00016 #include "GoRegionBoard.h"
00017 #include "GoSafetySolver.h"
00018 #include "SgBoardColor.h"
00019 #include "SgBWSet.h"
00020 #include "SgVector.h"
00021 #include "SgPointSet.h"
00022 #include "SgPointSetUtil.h"
00023 #include "SgWrite.h"
00024 
00025 //----------------------------------------------------------------------------
00026 
00027 namespace {
00028 const bool DEBUG_SAFETY = false;
00029 const bool DEBUG_MIGHT_MAKE_LIFE = false;
00030 const bool DEBUG_EXTENDED_MIGHT_MAKE_LIFE = false;
00031 
00032 /** Players can fill half the outside liberties of safe stones.
00033     Can round up for ToPlay(), must round down for opponent. */
00034 void AddLibertiesAsMoves(const GoBoard& bd,
00035                                  const SgBWSet& safe,
00036                                  SgBWArray<int>& nuSafe)
00037 {
00038     // add half the adjacent empty points to safe points.
00039     // @todo: is that safe even for seki???
00040     // are there seki where a lot of of dame needs to stay 
00041     // unoccupied?
00042     for (SgBWIterator it; it; ++it)
00043     {
00044         const SgPointSet adj = safe[*it].BorderNoClip() & bd.AllEmpty();
00045         SG_ASSERT(adj.Disjoint(safe.Both()));
00046         int nu = adj.Size();
00047         if (bd.ToPlay() == *it) // can round up for first player
00048             ++nu;
00049         nuSafe[*it] += nu / 2;
00050     }
00051     // todo: add reachable blocks.
00052     // todo: recursive reachability, as in safety solver.
00053 }
00054 
00055 /** Check if one player has enough safe points to win */
00056 inline SgEmptyBlackWhite CheckWinner(
00057                         const SgBWArray<int>& winThreshold, 
00058                         const SgBWArray<int>& nuSafe)
00059 {
00060     for (SgBWIterator it; it; ++it)
00061         if (nuSafe[*it] >= winThreshold[*it])
00062             return *it;
00063     return SG_EMPTY;
00064 }
00065 
00066 /** find 2 libs which would connect block to safe.
00067     if found, update libs and safe to indicate that the block is safe now:
00068     add block to safe, add block libs to libs, remove the two libs. */
00069 bool Find2Connections(const GoBoard& bd, SgPoint block, SgPointSet* libs,
00070                     SgPointSet* usedLibs, SgPointSet* safe)
00071 {
00072     SG_ASSERT(libs->Disjoint(*usedLibs));
00073 
00074     int nuLibs(0);
00075     SgVector<SgPoint> blockLibs;
00076     for (GoBoard::LibertyIterator it(bd, block); it; ++it)
00077     {
00078         if (libs->Contains(*it))
00079         {
00080             blockLibs.PushBack(*it);
00081             ++nuLibs;
00082             if (nuLibs >= 2)
00083                 break;
00084         }
00085     }
00086     if (nuLibs >= 2) // found it!
00087     {
00088         for (GoBoard::StoneIterator it(bd, block); it; ++it)
00089             safe->Include(*it);
00090         for (GoBoard::LibertyIterator it(bd, block); it; ++it)
00091             libs->Include(*it);
00092         for (SgVectorIterator<SgPoint> it(blockLibs); it; ++it)
00093             usedLibs->Include(*it);
00094         *libs -= *usedLibs; // can never re-use such a lib.
00095     }
00096 
00097     return nuLibs >= 2;
00098 }
00099 
00100 /** Find connections for unsafe boundary and interior stones,
00101     and interior empty points.
00102 
00103     Can omit maxNuOmissions (0 or 1) empty points from checking.
00104     maxNuOmissions = 1 if testing whether opponent can make 2 eyes here,
00105     0 otherwise. Returns bool whether connections were found. */
00106 bool Find2ConnectionsForAll(const GoBoard& bd, const SgPointSet& pts,
00107                 const SgPointSet& inSafe, SgBlackWhite color,
00108                 int maxNuOmissions = 0)
00109 {
00110     if (DEBUG_SAFETY)
00111         SgDebug() << "Find2ConnectionsForAll " << pts
00112                   << "safe points - input: " << inSafe;
00113     SgPointSet safe(inSafe);
00114     SgVector<SgPoint> unsafe;
00115     const int size = bd.Size();
00116     GoSafetyUtil::ReduceToAnchors(bd, pts.Border(size) - safe, &unsafe);
00117     // AR sort by # empty nbs in pts
00118 
00119     if (DEBUG_SAFETY)
00120         SgDebug() << SgWritePointList(unsafe, "unsafe anchors: ");
00121 
00122     SgPointSet libs = pts & bd.AllEmpty() & safe.Border(size);
00123     SgPointSet interior = pts - libs;
00124     interior -= bd.All(SgOppBW(color)); // remove opp. stones.
00125     SgVector<SgPoint> unsafeInterior;
00126     GoSafetyUtil::ReduceToAnchors(bd, interior & bd.All(color),
00127                                   &unsafeInterior);
00128     unsafe.Concat(&unsafeInterior);
00129 
00130     SgPointSet usedLibs;
00131     bool change = true;
00132     while (change && unsafe.NonEmpty() && libs.MinSetSize(2))
00133     {
00134         SgVector<SgPoint> newSafe;
00135         for (SgVectorIterator<SgPoint> it(unsafe); it; ++it)
00136             if (Find2Connections(bd, *it, &libs, &usedLibs, &safe))
00137             {
00138                 newSafe.PushBack(*it);
00139             }
00140 
00141         unsafe.Exclude(newSafe);
00142         change = newSafe.NonEmpty();
00143     }
00144 
00145     if (unsafe.NonEmpty()) // could not connect everything.
00146     {
00147         if (DEBUG_SAFETY)
00148             SgDebug()
00149                 << SgWritePointList(unsafe, "could not connect unsafe: ");
00150         return false;
00151     }
00152     // now we know all blocks are safe. try to prove territory safe, too.
00153     // AR if terr. safety fails, still declare blocks safe, but put
00154     // miai strategy commitment on region.
00155 
00156     interior = (pts & bd.AllEmpty()) - safe.Border(size);
00157     // new safe set after Find2Connections.
00158 
00159     // try to prove opp. can't live inside.
00160     if (maxNuOmissions == 1)
00161     {
00162         SgBlackWhite opp(SgOppBW(color));
00163         if (! GoSafetyUtil::MightMakeLife(bd, interior, safe, opp))
00164             /* */ return true; /* */
00165     }
00166 
00167     // now try to find miai-paths to remaining interior empty points
00168     // AR shortcut failure if maxNuOmissions = 0 and opp has eye:
00169     // then this will never find anything.
00170 
00171     for (SgSetIterator it(interior); it; ++it)
00172     {
00173         if (! GoSafetyUtil::Find2Libs(*it, &libs))
00174         {
00175             if (--maxNuOmissions < 0)
00176                 return false;
00177         }
00178     }
00179 
00180     return true;
00181 }
00182 
00183 SgEmptyBlackWhite GetWinner(const GoBoard& bd, 
00184                             const SgBWSet& safe, 
00185                             float komi)
00186 {
00187     
00188     const float EPSILON = 0.1f; // avoid judging draws as wins with integer komi
00189     const int nuPoints = bd.Size() * bd.Size();
00190     int winThresholdBlack =
00191         int(ceil((float(nuPoints) + komi + EPSILON) / 2.f));
00192     int winThresholdWhite =
00193         int(ceil((float(nuPoints) - komi + EPSILON) / 2.f));
00194     const SgBWArray<int> winThreshold(winThresholdBlack, winThresholdWhite);
00195 
00196     SgBWArray<int> nuSafe;
00197     for (SgBWIterator it; it; ++it)
00198         nuSafe[*it] = safe[*it].Size();
00199 
00200     SgEmptyBlackWhite winner = CheckWinner(winThreshold, nuSafe);
00201     if (winner != SG_EMPTY)
00202         return winner;
00203     
00204     AddLibertiesAsMoves(bd, safe, nuSafe);
00205     winner = CheckWinner(winThreshold, nuSafe);
00206     if (winner != SG_EMPTY)
00207         return winner;
00208 
00209     // @todo: check for draw and return draw value.
00210     if (nuSafe[SG_BLACK] + nuSafe[SG_WHITE] == nuPoints)
00211         SgDebug() << "draw: B = " << nuSafe[SG_BLACK] 
00212                       << ", W = " << nuSafe[SG_WHITE]
00213                       << std::endl;
00214         
00215     return SG_EMPTY;
00216 }
00217 
00218 void TestLiberty(SgPoint lib, const SgPointSet& libs,
00219                  SgVector<SgPoint>* foundLibs,
00220                  int* nuLibs)
00221 {
00222     if (libs.Contains(lib))
00223     {
00224         foundLibs->PushBack(lib);
00225         ++(*nuLibs);
00226     }
00227 }
00228 
00229 /** write part and total and rounded percentage of part in total */
00230 void WriteSafeTotal(std::ostream& stream, std::string text,
00231                     int partCount, int totalCount)
00232 {
00233     stream << partCount << " / " << totalCount
00234            << " (" 
00235            << (partCount * 100 + totalCount / 2) / totalCount
00236            << "%) " << text << '\n';
00237 }
00238 
00239 } // namespace
00240 
00241 //----------------------------------------------------------------------------
00242 
00243 void GoSafetyUtil::AddToSafe(const GoBoard& board, const SgPointSet& pts,
00244                              SgBlackWhite color, SgBWSet* safe,
00245                              const char* reason, int depth, bool addBoundary)
00246 {
00247     SG_UNUSED(reason);
00248     SG_UNUSED(depth);
00249 
00250     (*safe)[color] |= pts;
00251     safe->AssertDisjoint();
00252     SgPointSet empty = board.AllEmpty();
00253 
00254     const int size = board.Size();
00255     if (addBoundary)
00256     {
00257         SgPointSet dep(pts.Border(size) - empty); // pts can be zone (open!)
00258         GoBoardUtil::ExpandToBlocks(board, dep);
00259         SG_ASSERT(dep.SubsetOf(board.All(color)));
00260         (*safe)[color] |= dep;
00261         safe->AssertDisjoint();
00262     }
00263 
00264     if (DEBUG_SAFETY)
00265         SgDebug() << "AddToSafe " << reason
00266                   << " depth = " << depth << " points = "
00267                   << SgWritePointSetID(pts) << '\n';
00268 }
00269 
00270 bool GoSafetyUtil::ExtendedMightMakeLife(const GoBoard& board,
00271                                          GoRegionBoard* regions,
00272                                          const SgPointSet& area,
00273                                          const SgPointSet& safe,
00274                                          SgBlackWhite color)
00275 {
00276     const GoRegion* nakadeRegion = 0;
00277 
00278     if (DEBUG_EXTENDED_MIGHT_MAKE_LIFE)
00279         SgDebug() << "ExtendedMightMakeLife for " << SgBW(color)
00280                   << " area " << area << '\n';
00281 
00282     // Check if region is a nakade shape that fills all potential eye space
00283     for (SgVectorIteratorOf<GoRegion> it(regions->AllRegions(color));
00284          it; ++it)
00285     {
00286         if (  area.SupersetOf((*it)->Points())
00287            && area.SupersetOf((*it)->BlocksPoints())
00288            )
00289         {
00290             if (DEBUG_EXTENDED_MIGHT_MAKE_LIFE)
00291             {
00292                 SgDebug() << "contains region ";
00293                 (*it)->WriteID(SgDebug());
00294                 SgDebug() << '\n';
00295             }
00296 
00297             if (! (*it)->ComputedFlag(GO_REGION_COMPUTED_NAKADE))
00298                 (*it)->DoComputeFlag(GO_REGION_COMPUTED_NAKADE); 
00299                 // sets MaxPotEyes()
00300             if ((*it)->MaxPotEyes() > 1)
00301                 return true;
00302             else if (nakadeRegion == 0)
00303                 nakadeRegion = *it;
00304             else // at least 2 regions - might make eyes
00305                 return true;
00306         }
00307     }
00308 
00309     if (DEBUG_EXTENDED_MIGHT_MAKE_LIFE)
00310         SgDebug() << "case 2\n";
00311     SgPointSet rest = area;
00312     if (nakadeRegion == 0) // classical case. Call previous function
00313         return GoSafetyUtil::MightMakeLife(board, area, safe, color);
00314     else
00315     {
00316         if (DEBUG_EXTENDED_MIGHT_MAKE_LIFE)
00317             SgDebug() << "ExtendedMightMakeLife for " << area
00318                       << ": inside opp region " 
00319                       << *nakadeRegion << '\n';
00320         if (nakadeRegion->MaxPotEyes() <= 1)
00321         // what if 0 eyes??? Can allow 1 eye elsewhere?
00322         {
00323             rest -= nakadeRegion->Points();
00324             rest -= nakadeRegion->BlocksPoints();
00325         }
00326     }
00327 
00328     const int size = board.Size();
00329     rest -= safe.Border(size);
00330     rest -= board.All(color);
00331 
00332     if (DEBUG_EXTENDED_MIGHT_MAKE_LIFE)
00333         SgDebug() << "rest = " << rest << "\n";
00334     for (SgSetIterator it(rest); it; ++it)
00335     {
00336         SgPoint p(*it);
00337         if (GoEyeUtil::CanBecomeSinglePointEye(board, p, safe))
00338             return true;
00339     }
00340 
00341     return false;
00342 }
00343 
00344 SgPointSet GoSafetyUtil::FindDamePoints(const GoBoard& bd,
00345                                         const SgPointSet& empty,
00346                                         const SgBWSet& safe)
00347 {
00348     SgPointSet dame, unsurroundable;
00349     FindDameAndUnsurroundablePoints(bd, empty, safe, &dame, &unsurroundable);
00350     return dame;
00351 }
00352 
00353 void GoSafetyUtil::FindDameAndUnsurroundablePoints(const GoBoard& bd,
00354                                                    const SgPointSet& empty,
00355                                                    const SgBWSet& safe,
00356                                                    SgPointSet* dame,
00357                                                    SgPointSet* unsurroundable)
00358 {
00359     SG_ASSERT(dame->IsEmpty());
00360     SG_ASSERT(unsurroundable->IsEmpty());
00361     const int size = bd.Size();
00362     *unsurroundable =   safe[SG_BLACK].Border(size)
00363                       & safe[SG_WHITE].Border(size)
00364                       & empty;
00365     SgPointSet maybeDame(*unsurroundable);
00366     SgBWSet unsafe; // must exclude these
00367     unsafe[SG_BLACK] = bd.All(SG_BLACK) - safe[SG_BLACK];
00368     unsafe[SG_WHITE] = bd.All(SG_WHITE) - safe[SG_WHITE];
00369     maybeDame -= unsafe[SG_BLACK].Border(size);
00370     maybeDame -= unsafe[SG_WHITE].Border(size);
00371     for (SgSetIterator it(maybeDame); it; ++it)
00372     {
00373         SgPoint p(*it);
00374         bool isDame = true;
00375         for (SgNb4Iterator it(p); it; ++it)
00376         {
00377             SgPoint nb(*it);
00378             if (empty[nb] && ! unsurroundable->Contains(nb))
00379             {
00380             // can use unsurroundable instead of smaller set maybeDame
00381                 isDame = false;
00382                 break;
00383             }
00384         }
00385         if (isDame)
00386             dame->Include(p);
00387     }
00388 }
00389 
00390 bool GoSafetyUtil::MightMakeLife(const GoBoard& board,
00391                                  const SgPointSet& area,
00392                                  const SgPointSet& safe, SgBlackWhite color)
00393 {
00394     const int size = board.Size();
00395     SgPointSet eyePts = (area - safe.Border(size)) - board.All(color);
00396     if (eyePts.MaxSetSize(1))
00397         return false;
00398 
00399     if (DEBUG_MIGHT_MAKE_LIFE)
00400         SgDebug() << "GoSafetyUtil::MightMakeLife\n";
00401 
00402     SgPoint eye(SG_NULLPOINT), adjToEye(SG_NULLPOINT);
00403     for (SgSetIterator it(eyePts); it; ++it)
00404     {
00405         const SgPoint p(*it);
00406         if (GoEyeUtil::CanBecomeSinglePointEye(board, p, safe))
00407         {
00408             if (eye == SG_NULLPOINT)
00409             {
00410                 eye = p;
00411                 if (DEBUG_MIGHT_MAKE_LIFE)
00412                     SgDebug() << "eye = " << SgWritePoint(eye) << "\n";
00413             }
00414             else if (  adjToEye == SG_NULLPOINT
00415                     && SgPointUtil::AreAdjacent(eye, p)
00416                     )
00417                 adjToEye = p;
00418             else
00419             {
00420                 if (DEBUG_MIGHT_MAKE_LIFE)
00421                     SgDebug() << "second eye = " << SgWritePoint(p) << "\n";
00422                /* */ return true; /* */
00423             }
00424         }
00425     }
00426 
00427     return false;
00428 }
00429 
00430 bool GoSafetyUtil::Find2Libs(SgPoint p, SgPointSet* libs)
00431 {
00432     int nuLibs = 0;
00433     SgVector<SgPoint> foundLibs;
00434     TestLiberty(p + SG_NS, *libs, &foundLibs, &nuLibs);
00435     TestLiberty(p + SG_WE, *libs, &foundLibs, &nuLibs);
00436     if (nuLibs < 2)
00437     {
00438         TestLiberty(p - SG_NS, *libs, &foundLibs, &nuLibs);
00439         if (nuLibs < 2)
00440             TestLiberty(p - SG_WE, *libs, &foundLibs, &nuLibs);
00441     }
00442     if (nuLibs >= 2)
00443     {
00444         SG_ASSERT(nuLibs == 2 && foundLibs.IsLength(2));
00445         libs->Exclude(foundLibs.Front());
00446         libs->Exclude(foundLibs.Back());
00447     }
00448 
00449     return nuLibs >= 2;
00450 }
00451 
00452 bool GoSafetyUtil::Find2BestLibs(SgPoint p, const SgPointSet& libs,
00453                                  SgPointSet interior, SgMiaiPair* miaiPair)
00454 {
00455     int nuLibs = 0;
00456     SgVector<SgPoint> allLibs; // liberties of point p
00457 
00458     TestLiberty(p + SG_NS, libs, &allLibs, &nuLibs);
00459     TestLiberty(p + SG_WE, libs, &allLibs, &nuLibs);
00460     TestLiberty(p - SG_NS, libs, &allLibs, &nuLibs);
00461     TestLiberty(p - SG_WE, libs, &allLibs, &nuLibs);
00462 
00463     if (allLibs.MaxLength(1))
00464         return false;
00465     else if (allLibs.IsLength(2))
00466     {
00467         SG_ASSERT(nuLibs == 2 && allLibs.IsLength(2));
00468         miaiPair->first = allLibs[0];
00469         miaiPair->second = allLibs[1];
00470         /* */ return true; /* */
00471     }
00472     else
00473     {
00474         SgVector<SgPoint> shared, not_shared;
00475         SgPointSet others = interior;
00476         others.Exclude(p);
00477 
00478         for (SgVectorIterator<SgPoint> it(allLibs); it; ++it)
00479         {
00480             bool share = false;
00481             for (SgSetIterator it2(others); it2; ++it2)
00482             {
00483                 if (SgPointUtil::AreAdjacent(*it, *it2))
00484                 {
00485                     share = true;
00486                     break;
00487                 }
00488             }
00489             if (share)
00490                 shared.PushBack(*it);
00491                 // this lib is shared with other interior points
00492             else
00493                 not_shared.PushBack(*it);
00494         }
00495         // if has >= 2 not_shared libs, then try to find 2 original libs (not
00496         // new-created libs (because the new one might be ip points)
00497         if (not_shared.MinLength(2))
00498         {
00499             miaiPair->first = not_shared[0];
00500             miaiPair->second = not_shared[1];
00501         }
00502         // if only 1 not_shared lib, use this first, then another shared lib
00503         else if (not_shared.IsLength(1))
00504         {
00505             miaiPair->first = not_shared[0];
00506             miaiPair->second = shared[0];
00507         }
00508         // zero not_shared libs, then use two shared libs
00509         else
00510         {
00511             miaiPair->first = shared[0];
00512             miaiPair->second = shared[1];
00513         }
00514         return true;
00515     }
00516 }
00517 
00518 bool GoSafetyUtil::ExtendedIsTerritory(const GoBoard& board,
00519                                        GoRegionBoard* regions,
00520                                        const SgPointSet& pts,
00521                                        const SgPointSet& safe,
00522                                        SgBlackWhite color)
00523 {
00524     SG_ASSERT(! pts.Overlaps(safe));
00525     const int size = board.Size();
00526     SgPointSet boundary(pts.Border(size));
00527     if (boundary.SubsetOf(safe))
00528     {
00529         SgBlackWhite opp = SgOppBW(color);
00530         if (! ExtendedMightMakeLife(board, regions, pts, safe, opp))
00531             /* */ return true; /* */
00532     }
00533 
00534     return IsTerritory(board, pts, safe, color);
00535 }
00536                         
00537 SgEmptyBlackWhite GoSafetyUtil::GetWinner(const GoBoard& constBd)
00538 {
00539     GoModBoard modBoard(constBd); 
00540     // todo: safety solvers should take const board.
00541     GoBoard& bd = modBoard.Board();
00542     GoRegionBoard regionAttachment(bd);
00543     GoSafetySolver solver(bd, &regionAttachment);
00544     SgBWSet safe;
00545     solver.FindSafePoints(&safe);
00546     const float komi = bd.Rules().Komi().ToFloat();
00547     return ::GetWinner(bd, safe, komi);
00548 }
00549 
00550 bool GoSafetyUtil::IsTerritory(const GoBoard& board, const SgPointSet& pts,
00551                                const SgPointSet& safe, SgBlackWhite color)
00552 {
00553     SG_ASSERT(! pts.Overlaps(safe));
00554     const int size = board.Size();
00555     SgPointSet boundary(pts.Border(size));
00556     if (boundary.SubsetOf(safe))
00557     {
00558         SgBlackWhite opp = SgOppBW(color);
00559         if (! GoSafetyUtil::MightMakeLife(board, pts, safe, opp))
00560             /* */ return true; /* */
00561     }
00562 
00563     if (  boundary.SubsetOf(board.All(color))
00564        && Find2ConnectionsForAll(board, pts, safe, color, 1)
00565        )
00566        /* */ return true; /* */
00567     return false;
00568 }
00569 
00570 void GoSafetyUtil::ReduceToAnchors(const GoBoard& board,
00571                                    const SgPointSet& stones,
00572                                    SgVector<SgPoint>* anchors)
00573 {
00574     SG_ASSERT(anchors->IsEmpty());
00575     for (SgSetIterator it(stones); it; ++it)
00576     {
00577         SG_ASSERT(board.Occupied(*it));
00578         anchors->Insert(board.Anchor(*it));
00579     }
00580 }
00581 
00582 void GoSafetyUtil::WriteStatistics(const std::string& heading,
00583                       const GoRegionBoard* regions,
00584                       const SgBWSet* safe)
00585 {    
00586     const SgPointSet allSafe = safe->Both();
00587     int totalRegions = 0;
00588     int safeRegions = 0;
00589     int totalBlocks = 0;
00590     int safeBlocks = 0;  
00591 
00592     for (SgBWIterator it; it; ++it)
00593     {
00594         SgBlackWhite color(*it);
00595         for (SgVectorIteratorOf<GoRegion> it(regions->AllRegions(color));
00596                                            it; ++it)
00597         {
00598             ++totalRegions;
00599             if ((*it)->Points().SubsetOf(allSafe))
00600                 ++safeRegions;
00601         }
00602         for (SgVectorIteratorOf<GoBlock> it(regions->AllBlocks(color));
00603                                         it; ++it)
00604         {
00605             ++totalBlocks;
00606             if (allSafe.Overlaps((*it)->Stones()))
00607                 ++safeBlocks;
00608         }
00609     }
00610     
00611     const int bdSize = regions->Board().Size();
00612     SgDebug() << heading << "\n";
00613     WriteSafeTotal(SgDebug(), "points", allSafe.Size(), bdSize * bdSize);
00614     WriteSafeTotal(SgDebug(), "regions", safeRegions, totalRegions);
00615     WriteSafeTotal(SgDebug(), "blocks", safeBlocks, totalBlocks);
00616 }
00617 
00618 
00619 //----------------------------------------------------------------------------


Sun Mar 13 2011 Doxygen 1.7.1