private static void WriteBox(
 TextWriter writer,
 AliquotChainLink link)
   {
       var node = link.Current;
         var factors = link.Factorisation;
         var driver = link.Factorisation.Driver();
         string color = CalcColor(node, factors);
         if(driver.Length > 0)
         {
       writer.WriteLine("{0} [shape=record,label=\"<f0>{0}|<f1>{1}|<f2>{2}\",color={3}];", node, factors, driver, color);
         }
         else
         {
       writer.WriteLine("{0} [shape=record,label=\"<f0>{0}|<f1>{1}\",color={2}];", node, factors, color);
         }
   }
        public static AliquotDatabase Create(
      IPrimes p, 
      int dbLimit,
      Progress<ProgressEventArgs> progressIndicator = null,
      CancellationToken? maybeCancellationToken = null)
        {
            var creationProperties = new Dictionary<string, string>();
              creationProperties["Create.ChainStartLimit"] = dbLimit.ToString();

              BigInteger upperLimit = BigInteger.Parse("1000000000000000"); // 10^15
              Utilities.LogLine("AliquotDatabase: Create to {0}, Successor Limit {1} ({2} digits)", dbLimit, upperLimit, upperLimit.ToString().Length);
              creationProperties["Create.UpperLimit"] = upperLimit.ToString();

              var links = new Dictionary<BigInteger, AliquotChainLink>();
              DateTime dtStart = DateTime.UtcNow;

              int progress = 0;

              // Set up a parallel run across the collection
              var range = Enumerable.Range(1, dbLimit);
              var parOpts = new ParallelOptions();
              parOpts.CancellationToken = maybeCancellationToken.Value;
              // We *could* set this, but really .NET should be able to figure it out!
              // parOpts.MaxDegreeOfParallelism = System.Environment.ProcessorCount;
              Parallel.ForEach(range,
            (i) =>
              {
            // Build the chain onwards from this number
            BigInteger n = i;
            while (n > 1 && n < upperLimit)
            {
              // Get the new link
              var s = new AliquotChainLink(p, n);
              // Abandon if we would go above the limit
              if (s.Successor > upperLimit) { break; }
              // Synchronize on the links collection since this is in parallel
              lock(links)
              {
            // We exit if we are joining an existing chain
            if (links.ContainsKey(n)) { break; }
            // It's a new link - add it to the database
            links[n] = s;
              }
              // Go to next element in chain
              n = s.Successor;
            }

            // Indicate progress
            int newProgress = (int)(100.0 * i / dbLimit);
            if (newProgress > progress)
            {
              if(maybeCancellationToken.HasValue)
              {
            maybeCancellationToken.Value.ThrowIfCancellationRequested();
              }

              double s = (DateTime.UtcNow - dtStart).TotalSeconds;
              double expected = s * (dbLimit - i) / i;
              ProgressEventArgs.RaiseEvent(progressIndicator, newProgress, "ADB: i {0} Time Used (min) {1:N} Estimated time Left (min) {2:N}".FormatWith(i, s / 60.0, expected / 60.0));
              progress = newProgress;
            }
              }
              );

              creationProperties["Create.FinishTimeUtc"] = DateTime.UtcNow.ToString();
              creationProperties["Create.Seconds"] = (DateTime.UtcNow - dtStart).TotalSeconds.ToString("N2");

              return new AliquotDatabase(links, creationProperties);
        }
        public void Reader(BinaryReader reader)
        {
            Links = new Dictionary<BigInteger, AliquotChainLink>();
              CreationProperties = new Dictionary<string, string>(CreationProperties);

              // format
              string adbFormat = reader.ReadString();
              Utilities.LogLine("ADB format: {0}", adbFormat);
              if (adbFormat != "adb.1")
              {
            throw new InvalidDataException(string.Format("Unexpected ADB Format [{0}]", adbFormat));
              }
              // # properties
              UInt64 numProperties = reader.ReadUInt64();
              for(UInt64 ip = 0; ip != numProperties; ++ip)
              {
            string propertyKey = reader.ReadString();
            string propertyValue = reader.ReadString();
            CreationProperties[propertyKey] = propertyValue;
            Utilities.LogLine("ADB property [{0}]=[{1}]", propertyKey, propertyValue);
              }
              if (!CreationProperties.ContainsKey("Count"))
              {
            throw new InvalidDataException("ADB file did not have 'Count' property");
              }
              UInt64 n = UInt64.Parse(CreationProperties["Count"]);
              // read links
              UInt64 n100 = n / 100;
              UInt64 c = 0;
              DateTime dtStart = DateTime.UtcNow;
              for(UInt64 i = 0; i < n; ++i)
              {
            if (c++ == n100)
            {
              c = 0;

              // Check for cancellation
              ThrowIfCancellationRequested();

              // Raise progress message
              string message = "AliquotDB: Read {0:N0} of {1:N0}".FormatWith(i, n);
              BigInteger b_i = i;
              BigInteger b_n = n;
              BigInteger b_percent = b_i * 100 / b_n;
              int percent = int.Parse(b_percent.ToString());
              ProgressEventArgs.RaiseEvent(myProgressIndicator, percent, message);
            }

            UInt64 current = reader.ReadUInt64();
            UInt64 successor = reader.ReadUInt64();
            var pf = PrimeFactorisation.Create(reader);
            Links[current] = new AliquotChainLink(current, successor, pf);
              }
              DateTime dtEnd = DateTime.UtcNow;
              double sec = (dtEnd - dtStart).TotalSeconds;
              Utilities.LogLine("ADB Read Time: {0:N2} sec", sec);
        }