<commit_before>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="LogarithmicAxis.cs" company="OxyPlot">
//   Copyright (c) 2014 OxyPlot contributors
// </copyright>
// <summary>
//   Represents an axis with logarithmic scale.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace OxyPlot.Axes
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;

    /// <summary>
    /// Represents an axis with logarithmic scale.
    /// </summary>
    /// <remarks>See http://en.wikipedia.org/wiki/Logarithmic_scale.</remarks>
    public class LogarithmicAxis : Axis
    {
        /// <summary>
        /// Initializes a new instance of the <see cref = "LogarithmicAxis" /> class.
        /// </summary>
        public LogarithmicAxis()
        {
            this.PowerPadding = true;
            this.Base = 10;
            this.FilterMinValue = 0;
        }

        /// <summary>
        /// Gets or sets the logarithmic base (normally 10).
        /// </summary>
        /// <value>The logarithmic base.</value>
        /// <remarks>See http://en.wikipedia.org/wiki/Logarithm.</remarks>
        public double Base { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether the ActualMaximum and ActualMinimum values should be padded to the nearest power of the Base.
        /// </summary>
        public bool PowerPadding { get; set; }

        /// <summary>
        /// Gets or sets the actual logarithmic maximum value of the axis.
        /// </summary>
        protected double LogActualMaximum { get; set; }

        /// <summary>
        /// Gets or sets the actual logarithmic minimum value of the axis.
        /// </summary>
        protected double LogActualMinimum { get; set; }

        /// <summary>
        /// Gets the coordinates used to draw ticks and tick labels (numbers or category names).
        /// </summary>
        /// <param name="majorLabelValues">The major label values.</param>
        /// <param name="majorTickValues">The major tick values.</param>
        /// <param name="minorTickValues">The minor tick values.</param>
        public override void GetTickValues(out IList<double> majorLabelValues, out IList<double> majorTickValues, out IList<double> minorTickValues)
        {
            // For easier readability, the nomenclature of this function and all related functions assumes a base of 10, and therefore uses the
            // term "decade". However, the code supports all other bases as well.
            var logBandwidth = Math.Abs(this.LogActualMaximum - this.LogActualMinimum);
            var axisBandwidth = Math.Abs(this.IsVertical() ? this.ScreenMax.Y - this.ScreenMin.Y : this.ScreenMax.X - this.ScreenMin.X);

            var desiredNumberOfTicks = axisBandwidth / this.IntervalLength;
            var ticksPerDecade = desiredNumberOfTicks / logBandwidth;
            var logDesiredStepSize = 1.0 / Convert.ToInt32(ticksPerDecade);

            var intBase = Convert.ToInt32(this.Base);

            if (ticksPerDecade < 0.75)
            {   // Major Ticks every few decades (increase in powers of 2), up to eight minor tick subdivisions
                var decadesPerMajorTick = (int)Math.Pow(2, Math.Ceiling(Math.Log(1 / ticksPerDecade, 2)));
                majorTickValues = this.DecadeTicks(decadesPerMajorTick);
                minorTickValues = this.DecadeTicks(Math.Ceiling(decadesPerMajorTick / 8.0));
            }
            else if (Math.Abs(this.Base - intBase) > 1e-10)
            {   // fractional Base, best guess: naively subdivide decades
                majorTickValues = this.DecadeTicks(logDesiredStepSize);
                minorTickValues = this.DecadeTicks(0.5 * logDesiredStepSize);
            }
            else if (ticksPerDecade < 2)
            {   // Major Ticks at every decade, Minor Ticks at fractions (not for fractional base)
                majorTickValues = this.DecadeTicks();
                minorTickValues = this.SubdividedDecadeTicks();
            }
            else if (ticksPerDecade > this.Base * 1.5)
            {   // Fall back to linearly distributed tick values
                base.GetTickValues(out majorLabelValues, out majorTickValues, out minorTickValues);
            }
            else
            {
                // use subdivided decades as major candidates
                var logMajorCandidates = this.LogSubdividedDecadeTicks(false);

                if (logMajorCandidates.Count < 2)
                {   // this should usually not be the case, but if for some reason we should happen to have too few candidates, fall back to linear ticks
                    base.GetTickValues(out majorLabelValues, out majorTickValues, out minorTickValues);
                    return;
                }

                // check for large candidate intervals; if there are any, subdivide with minor ticks
                var logMinorCandidates = this.LogCalculateMinorCandidates(logMajorCandidates, logDesiredStepSize);

                // use all minor tick candidates that are in the axis range
                minorTickValues = this.PowList(logMinorCandidates, true);

                // find suitable candidates for every desired major step
                majorTickValues = this.AlignTicksToCandidates(logMajorCandidates, logDesiredStepSize);
            }

            majorLabelValues = majorTickValues;
        }

        /// <summary>
        /// Determines whether the axis is used for X/Y values.
        /// </summary>
        /// <returns><c>true</c> if it is an XY axis; otherwise, <c>false</c> .</returns>
        public override bool IsXyAxis()
        {
            return true;
        }

        /// <summary>
        /// Determines whether the axis is logarithmic.
        /// </summary>
        /// <returns><c>true</c> if it is a logarithmic axis; otherwise, <c>false</c> .</returns>
        public override bool IsLogarithmic()
        {
            return true;
        }

        /// <summary>
        /// Pans the specified axis.
        /// </summary>
        /// <param name="ppt">The previous point (screen coordinates).</param>
        /// <param name="cpt">The current point (screen coordinates).</param>
        public override void Pan(ScreenPoint ppt, ScreenPoint cpt)
        {
            if (!this.IsPanEnabled)
            {
                return;
            }

            var isHorizontal = this.IsHorizontal();

            var x0 = this.InverseTransform(isHorizontal ? ppt.X : ppt.Y);
            var x1 = this.InverseTransform(isHorizontal ? cpt.X : cpt.Y);

            if (Math.Abs(x1) < double.Epsilon)
            {
                return;
            }

            var oldMinimum = this.ActualMinimum;
            var oldMaximum = this.ActualMaximum;

            var dx = x0 / x1;

            var newMinimum = this.ActualMinimum * dx;
            var newMaximum = this.ActualMaximum * dx;
            if (newMinimum < this.AbsoluteMinimum)
            {
                newMinimum = this.AbsoluteMinimum;
                newMaximum = newMinimum * this.ActualMaximum / this.ActualMinimum;
            }

            if (newMaximum > this.AbsoluteMaximum)
            {
                newMaximum = this.AbsoluteMaximum;
                newMinimum = newMaximum * this.ActualMinimum / this.ActualMaximum;
            }

            this.ViewMinimum = newMinimum;
            this.ViewMaximum = newMaximum;

            var deltaMinimum = this.ActualMinimum - oldMinimum;
            var deltaMaximum = this.ActualMaximum - oldMaximum;

            this.OnAxisChanged(new AxisChangedEventArgs(AxisChangeTypes.Pan, deltaMinimum, deltaMaximum));
        }

        /// <summary>
        /// Inverse transforms the specified screen coordinate. This method can only be used with non-polar coordinate systems.
        /// </summary>
        /// <param name="sx">The screen coordinate.</param>
        /// <returns>The value.</returns>
        public override double InverseTransform(double sx)
        {
            // Inline the <see cref="PostInverseTransform" /> method here.
            return Math.Exp((sx / this.Scale) + this.Offset);
        }

        /// <summary>
        /// Transforms the specified coordinate to screen coordinates.
        /// </summary>
        /// <param name="x">The value.</param>
        /// <returns>The transformed value (screen coordinate).</returns>
        public override double Transform(double x)
        {
            if (x <= 0)
            {
                return -1;
            }

            // Inline the <see cref="PreTransform" /> method here.
            return (Math.Log(x) - this.Offset) * this.Scale;
        }

        /// <summary>
        /// Zooms the axis at the specified coordinate.
        /// </summary>
        /// <param name="factor">The zoom factor.</param>
        /// <param name="x">The coordinate to zoom at.</param>
        public override void ZoomAt(double factor, double x)
        {
            if (!this.IsZoomEnabled)
            {
                return;
            }

            var oldMinimum = this.ActualMinimum;
            var oldMaximum = this.ActualMaximum;

            var px = this.PreTransform(x);
            var dx0 = this.PreTransform(this.ActualMinimum) - px;
            var dx1 = this.PreTransform(this.ActualMaximum) - px;
            var newViewMinimum = this.PostInverseTransform((dx0 / factor) + px);
            var newViewMaximum = this.PostInverseTransform((dx1 / factor) + px);

            var newMinimum = Math.Max(newViewMinimum, this.AbsoluteMinimum);
            var newMaximum = Math.Min(newViewMaximum, this.AbsoluteMaximum);

            this.ViewMinimum = newMinimum;
            this.ViewMaximum = newMaximum;
            this.UpdateActualMaxMin();

            var deltaMinimum = this.ActualMinimum - oldMinimum;
            var deltaMaximum = this.ActualMaximum - oldMaximum;

            this.OnAxisChanged(new AxisChangedEventArgs(AxisChangeTypes.Zoom, deltaMinimum, deltaMaximum));
        }

        /// <summary>
        /// Raises all elements of a List to the power of <c>this.Base</c>.
        /// </summary>
        /// <param name="logInput">The input values.</param>
        /// <param name="clip">If true, discards all values that are not in the axis range.</param>
        /// <returns>A new IList containing the resulting values.</returns>
        internal IList<double> PowList(IList<double> logInput, bool clip = false)
        {
            return
                logInput.Where(item => !clip || !(item < this.LogActualMinimum))
                    .TakeWhile(item => !clip || !(item > this.LogActualMaximum))
                    .Select(item => Math.Pow(this.Base, item))
                    .ToList();
        }

        /// <summary>
        /// Applies the logarithm with <c>this.Base</c> to all elements of a List.
        /// </summary>
        /// <param name="input">The input values.</param>
        /// <param name="clip">If true, discards all values that are not in the axis range.</param>
        /// <returns>A new IList containing the resulting values.</returns>
        internal IList<double> LogList(IList<double> input, bool clip = false)
        {
            return
                input.Where(item => !clip || !(item < this.ActualMinimum))
                    .TakeWhile(item => !clip || !(item > this.ActualMaximum))
                    .Select(item => Math.Log(item, this.Base))
                    .ToList();
        }

        /// <summary>
        /// Calculates ticks of the decades in the axis range with a specified step size.
        /// </summary>
        /// <param name="step">The step size.</param>
        /// <returns>A new IList containing the decade ticks.</returns>
        internal IList<double> DecadeTicks(double step = 1)
        {
            return this.PowList(this.LogDecadeTicks(step));
        }

        /// <summary>
        /// Calculates logarithmic ticks of the decades in the axis range with a specified step size.
        /// </summary>
        /// <param name="step">The step size.</param>
        /// <returns>A new IList containing the logarithmic decade ticks.</returns>
        internal IList<double> LogDecadeTicks(double step = 1)
        {
            var ret = new List<double>();

            var last = double.NaN;
            for (var exponent = Math.Ceiling(this.LogActualMinimum); exponent <= this.LogActualMaximum; exponent += step)
            {
                if (exponent <= last)
                    break;
                last = exponent;
                if (exponent >= this.LogActualMinimum)
                    ret.Add(exponent);
            }

            return ret;
        }

        /// <summary>
        /// Calculates logarithmic ticks of all decades in the axis range and their subdivisions.
        /// </summary>
        /// <param name="clip">If true (default), the lowest and highest decade are clipped to the axis range.</param>
        /// <returns>A new IList containing the logarithmic decade ticks.</returns>
        internal IList<double> LogSubdividedDecadeTicks(bool clip = true)
        {
            return this.LogList(this.SubdividedDecadeTicks(clip));
        }

        /// <summary>
        /// Calculates ticks of all decades in the axis range and their subdivisions.
        /// </summary>
        /// <param name="clip">If true (default), the lowest and highest decade are clipped to the axis range.</param>
        /// <returns>A new IList containing the decade ticks.</returns>
        internal IList<double> SubdividedDecadeTicks(bool clip = true)
        {
            var ret = new List<double>();
            for (var exponent = (int)Math.Floor(this.LogActualMinimum);; exponent++)
            {
                if (exponent > this.LogActualMaximum)
                {
                    break;
                }

                var currentDecade = Math.Pow(this.Base, exponent);
                for (var mantissa = 1; mantissa < this.Base; mantissa++)<commit_msg>generate reasonable logarithmic ticks only<commit_after>﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="LogarithmicAxis.cs" company="OxyPlot">
//   Copyright (c) 2014 OxyPlot contributors
// </copyright>
// <summary>
//   Represents an axis with logarithmic scale.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace OxyPlot.Axes
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;

    /// <summary>
    /// Represents an axis with logarithmic scale.
    /// </summary>
    /// <remarks>See http://en.wikipedia.org/wiki/Logarithmic_scale.</remarks>
    public class LogarithmicAxis : Axis
    {
        /// <summary>
        /// Initializes a new instance of the <see cref = "LogarithmicAxis" /> class.
        /// </summary>
        public LogarithmicAxis()
        {
            this.PowerPadding = true;
            this.Base = 10;
            this.FilterMinValue = 0;
        }

        /// <summary>
        /// Gets or sets the logarithmic base (normally 10).
        /// </summary>
        /// <value>The logarithmic base.</value>
        /// <remarks>See http://en.wikipedia.org/wiki/Logarithm.</remarks>
        public double Base { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether the ActualMaximum and ActualMinimum values should be padded to the nearest power of the Base.
        /// </summary>
        public bool PowerPadding { get; set; }

        /// <summary>
        /// Gets or sets the actual logarithmic maximum value of the axis.
        /// </summary>
        protected double LogActualMaximum { get; set; }

        /// <summary>
        /// Gets or sets the actual logarithmic minimum value of the axis.
        /// </summary>
        protected double LogActualMinimum { get; set; }

        /// <summary>
        /// Gets the coordinates used to draw ticks and tick labels (numbers or category names).
        /// </summary>
        /// <param name="majorLabelValues">The major label values.</param>
        /// <param name="majorTickValues">The major tick values.</param>
        /// <param name="minorTickValues">The minor tick values.</param>
        public override void GetTickValues(out IList<double> majorLabelValues, out IList<double> majorTickValues, out IList<double> minorTickValues)
        {
            // For easier readability, the nomenclature of this function and all related functions assumes a base of 10, and therefore uses the
            // term "decade". However, the code supports all other bases as well.
            var logBandwidth = Math.Abs(this.LogActualMaximum - this.LogActualMinimum);
            var axisBandwidth = Math.Abs(this.IsVertical() ? this.ScreenMax.Y - this.ScreenMin.Y : this.ScreenMax.X - this.ScreenMin.X);

            var desiredNumberOfTicks = axisBandwidth / this.IntervalLength;
            var ticksPerDecade = desiredNumberOfTicks / logBandwidth;
            var logDesiredStepSize = 1.0 / Convert.ToInt32(ticksPerDecade);

            var intBase = Convert.ToInt32(this.Base);

            if (ticksPerDecade < 0.75)
            {   // Major Ticks every few decades (increase in powers of 2), up to eight minor tick subdivisions
                var decadesPerMajorTick = (int)Math.Pow(2, Math.Ceiling(Math.Log(1 / ticksPerDecade, 2)));
                majorTickValues = this.DecadeTicks(decadesPerMajorTick);
                minorTickValues = this.DecadeTicks(Math.Ceiling(decadesPerMajorTick / 8.0));
            }
            else if (Math.Abs(this.Base - intBase) > 1e-10)
            {   // fractional Base, best guess: naively subdivide decades
                majorTickValues = this.DecadeTicks(logDesiredStepSize);
                minorTickValues = this.DecadeTicks(0.5 * logDesiredStepSize);
            }
            else if (ticksPerDecade < 2)
            {   // Major Ticks at every decade, Minor Ticks at fractions (not for fractional base)
                majorTickValues = this.DecadeTicks();
                minorTickValues = this.SubdividedDecadeTicks();
            }
            else if (ticksPerDecade > this.Base * 1.5)
            {   // Fall back to linearly distributed tick values
                base.GetTickValues(out majorLabelValues, out majorTickValues, out minorTickValues);
            }
            else
            {
                // use subdivided decades as major candidates
                var logMajorCandidates = this.LogSubdividedDecadeTicks(false);

                if (logMajorCandidates.Count < 2)
                {   // this should usually not be the case, but if for some reason we should happen to have too few candidates, fall back to linear ticks
                    base.GetTickValues(out majorLabelValues, out majorTickValues, out minorTickValues);
                    return;
                }

                // check for large candidate intervals; if there are any, subdivide with minor ticks
                var logMinorCandidates = this.LogCalculateMinorCandidates(logMajorCandidates, logDesiredStepSize);

                // use all minor tick candidates that are in the axis range
                minorTickValues = this.PowList(logMinorCandidates, true);

                // find suitable candidates for every desired major step
                majorTickValues = this.AlignTicksToCandidates(logMajorCandidates, logDesiredStepSize);
            }

            majorLabelValues = majorTickValues;
        }

        /// <summary>
        /// Determines whether the axis is used for X/Y values.
        /// </summary>
        /// <returns><c>true</c> if it is an XY axis; otherwise, <c>false</c> .</returns>
        public override bool IsXyAxis()
        {
            return true;
        }

        /// <summary>
        /// Determines whether the axis is logarithmic.
        /// </summary>
        /// <returns><c>true</c> if it is a logarithmic axis; otherwise, <c>false</c> .</returns>
        public override bool IsLogarithmic()
        {
            return true;
        }

        /// <summary>
        /// Pans the specified axis.
        /// </summary>
        /// <param name="ppt">The previous point (screen coordinates).</param>
        /// <param name="cpt">The current point (screen coordinates).</param>
        public override void Pan(ScreenPoint ppt, ScreenPoint cpt)
        {
            if (!this.IsPanEnabled)
            {
                return;
            }

            var isHorizontal = this.IsHorizontal();

            var x0 = this.InverseTransform(isHorizontal ? ppt.X : ppt.Y);
            var x1 = this.InverseTransform(isHorizontal ? cpt.X : cpt.Y);

            if (Math.Abs(x1) < double.Epsilon)
            {
                return;
            }

            var oldMinimum = this.ActualMinimum;
            var oldMaximum = this.ActualMaximum;

            var dx = x0 / x1;

            var newMinimum = this.ActualMinimum * dx;
            var newMaximum = this.ActualMaximum * dx;
            if (newMinimum < this.AbsoluteMinimum)
            {
                newMinimum = this.AbsoluteMinimum;
                newMaximum = newMinimum * this.ActualMaximum / this.ActualMinimum;
            }

            if (newMaximum > this.AbsoluteMaximum)
            {
                newMaximum = this.AbsoluteMaximum;
                newMinimum = newMaximum * this.ActualMinimum / this.ActualMaximum;
            }

            this.ViewMinimum = newMinimum;
            this.ViewMaximum = newMaximum;

            var deltaMinimum = this.ActualMinimum - oldMinimum;
            var deltaMaximum = this.ActualMaximum - oldMaximum;

            this.OnAxisChanged(new AxisChangedEventArgs(AxisChangeTypes.Pan, deltaMinimum, deltaMaximum));
        }

        /// <summary>
        /// Inverse transforms the specified screen coordinate. This method can only be used with non-polar coordinate systems.
        /// </summary>
        /// <param name="sx">The screen coordinate.</param>
        /// <returns>The value.</returns>
        public override double InverseTransform(double sx)
        {
            // Inline the <see cref="PostInverseTransform" /> method here.
            return Math.Exp((sx / this.Scale) + this.Offset);
        }

        /// <summary>
        /// Transforms the specified coordinate to screen coordinates.
        /// </summary>
        /// <param name="x">The value.</param>
        /// <returns>The transformed value (screen coordinate).</returns>
        public override double Transform(double x)
        {
            if (x <= 0)
            {
                return -1;
            }

            // Inline the <see cref="PreTransform" /> method here.
            return (Math.Log(x) - this.Offset) * this.Scale;
        }

        /// <summary>
        /// Zooms the axis at the specified coordinate.
        /// </summary>
        /// <param name="factor">The zoom factor.</param>
        /// <param name="x">The coordinate to zoom at.</param>
        public override void ZoomAt(double factor, double x)
        {
            if (!this.IsZoomEnabled)
            {
                return;
            }

            var oldMinimum = this.ActualMinimum;
            var oldMaximum = this.ActualMaximum;

            var px = this.PreTransform(x);
            var dx0 = this.PreTransform(this.ActualMinimum) - px;
            var dx1 = this.PreTransform(this.ActualMaximum) - px;
            var newViewMinimum = this.PostInverseTransform((dx0 / factor) + px);
            var newViewMaximum = this.PostInverseTransform((dx1 / factor) + px);

            var newMinimum = Math.Max(newViewMinimum, this.AbsoluteMinimum);
            var newMaximum = Math.Min(newViewMaximum, this.AbsoluteMaximum);

            this.ViewMinimum = newMinimum;
            this.ViewMaximum = newMaximum;
            this.UpdateActualMaxMin();

            var deltaMinimum = this.ActualMinimum - oldMinimum;
            var deltaMaximum = this.ActualMaximum - oldMaximum;

            this.OnAxisChanged(new AxisChangedEventArgs(AxisChangeTypes.Zoom, deltaMinimum, deltaMaximum));
        }

        /// <summary>
        /// Raises all elements of a List to the power of <c>this.Base</c>.
        /// </summary>
        /// <param name="logInput">The input values.</param>
        /// <param name="clip">If true, discards all values that are not in the axis range.</param>
        /// <returns>A new IList containing the resulting values.</returns>
        internal IList<double> PowList(IList<double> logInput, bool clip = false)
        {
            return
                logInput.Where(item => !clip || !(item < this.LogActualMinimum))
                    .TakeWhile(item => !clip || !(item > this.LogActualMaximum))
                    .Select(item => Math.Pow(this.Base, item))
                    .ToList();
        }

        /// <summary>
        /// Applies the logarithm with <c>this.Base</c> to all elements of a List.
        /// </summary>
        /// <param name="input">The input values.</param>
        /// <param name="clip">If true, discards all values that are not in the axis range.</param>
        /// <returns>A new IList containing the resulting values.</returns>
        internal IList<double> LogList(IList<double> input, bool clip = false)
        {
            return
                input.Where(item => !clip || !(item < this.ActualMinimum))
                    .TakeWhile(item => !clip || !(item > this.ActualMaximum))
                    .Select(item => Math.Log(item, this.Base))
                    .ToList();
        }

        /// <summary>
        /// Calculates ticks of the decades in the axis range with a specified step size.
        /// </summary>
        /// <param name="step">The step size.</param>
        /// <returns>A new IList containing the decade ticks.</returns>
        internal IList<double> DecadeTicks(double step = 1)
        {
            return this.PowList(this.LogDecadeTicks(step));
        }

        /// <summary>
        /// Calculates logarithmic ticks of the decades in the axis range with a specified step size.
        /// </summary>
        /// <param name="step">The step size.</param>
        /// <returns>A new IList containing the logarithmic decade ticks.</returns>
        internal IList<double> LogDecadeTicks(double step = 1)
        {
            var ret = new List<double>();
            if (step > 0)
            {
                var last = double.NaN;
                for (var exponent = Math.Ceiling(this.LogActualMinimum); exponent <= this.LogActualMaximum; exponent += step)
                {
                    if (exponent <= last)
                        break;
                    last = exponent;
                    if (exponent >= this.LogActualMinimum)
                        ret.Add(exponent);
                }
            }
            return ret;
        }

        /// <summary>
        /// Calculates logarithmic ticks of all decades in the axis range and their subdivisions.
        /// </summary>
        /// <param name="clip">If true (default), the lowest and highest decade are clipped to the axis range.</param>
        /// <returns>A new IList containing the logarithmic decade ticks.</returns>
        internal IList<double> LogSubdividedDecadeTicks(bool clip = true)
        {
            return this.LogList(this.SubdividedDecadeTicks(clip));
        }

        /// <summary>
        /// Calculates ticks of all decades in the axis range and their subdivisions.
        /// </summary>
        /// <param name="clip">If true (default), the lowest and highest decade are clipped to the axis range.</param>
        /// <returns>A new IList containing the decade ticks.</returns>
        internal IList<double> SubdividedDecadeTicks(bool clip = true)
        {
            var ret = new List<double>();
            for (var exponent = (int)Math.Floor(this.LogActualMinimum);; exponent++)
            {
                if (exponent > this.LogActualMaximum)
                {
                    break;
                }

                var currentDecade = Math.Pow(this.Base, exponent);
                for (var mantissa = 1; mantissa < this.Base; mantissa++)
{'fraction_non_alphanumeric': 0.12100752142732202, 'fraction_numerical': 0.003463354906419451, 'mean_word_length': 1.7139466438811355, 'pattern_counts': {'":': 0, '<': 285, '<?xml version=': 0, '>': 290, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7304895', 'n_tokens_mistral': 7679, 'n_tokens_neox': 7122, 'n_words': 2100}