diff --git a/docs/samples/Microsoft.ML.Samples/Dynamic/DataOperations/LoadingSvmLight.cs b/docs/samples/Microsoft.ML.Samples/Dynamic/DataOperations/LoadingSvmLight.cs new file mode 100644 index 0000000000..4e08596529 --- /dev/null +++ b/docs/samples/Microsoft.ML.Samples/Dynamic/DataOperations/LoadingSvmLight.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Microsoft.ML; +using Microsoft.ML.Data; +using Microsoft.ML.Transforms; +using Microsoft.VisualBasic.CompilerServices; +using Tensorflow; + +namespace Samples.Dynamic.DataOperations +{ + public static class LoadingSvmLight + { + // This examples shows all the ways to load data with TextLoader. + public static void Example() + { + // Create a random SVM light format file. + var random = new Random(42); + var dataDirectoryName = "DataDir"; + Directory.CreateDirectory(dataDirectoryName); + var fileName = Path.Combine(dataDirectoryName, $"SVM_Data.csv"); + using (var fs = File.CreateText(fileName)) + { + // Write random lines in SVM light format + for (int line = 0; line < 10; line++) + { + var sb = new StringBuilder(); + if (random.NextDouble() > 0.5) + sb.Append("1 "); + else + sb.Append("-1 "); + if (line % 2 == 0) + sb.Append("cost:1"); + else + sb.Append("cost:2"); + for (int i = 1; i <= 10; i++) + { + if (random.NextDouble() > 0.5) + continue; + sb.Append($"{i}:{random.NextDouble()} "); + } + fs.WriteLine(sb.ToString()); + } + } + + // Create an SvmLightLoader. + var mlContext = new MLContext(); + var file = new MultiFileSource(fileName); + var loader = mlContext.Data.CreateSvmLightLoader(dataSample: file); + + // Load a single file from path. + var svmData = loader.Load(file); + + PrintSchema(svmData); + + // Expected Output: + // Column Label type Single + // Column Weight type Single + // Column GroupId type Key + // Column Comment type String + // Column Features type Vector + + PrintData(svmData); + + // Expected Output: + // 1 1 0 0 0.2625927 0 0 0.7612506 0.2573214 0 0.3809696 0.5174511 + // -1 1 0 0 0 0.7051522 0 0 0.7111546 0.9062127 0 0 + // -1 1 0 0 0 0.535722 0 0 0.1491191 0.05100901 0 0 + // -1 1 0 0.6481459 0.04449836 0 0 0.4203662 0 0 0.01325378 0.2674384 + // -1 1 0 0 0.7978093 0.5134962 0.008952909 0 0.003074009 0.6541431 0.9135142 0 + // -1 1 0 0.3727672 0.4369507 0 0 0.2973725 0 0 0 0.8816807 + // 1 1 0 0.1031429 0.3332489 0 0.1346936 0.5916625 0 0 0 0 + // 1 1 0 0 0 0.3454075 0 0.2197472 0.03848049 0.5923384 0.09373277 0 + // -1 1 0 0.7511514 0 0.0420841 0 0 0.9262196 0 0.545344 0 + // 1 1 0 0.02958358 0.9334617 0 0 0.8833956 0.2947684 0 0 0 + + // If the loader is created without a data sample we need to specify the number of features expected in the file. + loader = mlContext.Data.CreateSvmLightLoader(inputSize: 10); + svmData = loader.Load(file); + + PrintSchema(svmData); + PrintData(svmData); + } + + private static void PrintSchema(IDataView svmData) + { + foreach (var col in svmData.Schema) + Console.WriteLine($"Column {col.Name} type {col.Type}"); + } + + private static void PrintData(IDataView svmData) + { + using (var cursor = svmData.GetRowCursor(svmData.Schema)) + { + var labelGetter = cursor.GetGetter(svmData.Schema["Label"]); + var weightGetter = cursor.GetGetter(svmData.Schema["Weight"]); + var featuresGetter = cursor.GetGetter>(svmData.Schema["Features"]); + + VBuffer features = default; + while (cursor.MoveNext()) + { + float label = default; + labelGetter(ref label); + + float weight = default; + weightGetter(ref weight); + + featuresGetter(ref features); + + Console.WriteLine($"{label} {weight} {string.Join(' ', features.DenseValues())}"); + } + } + } + } +} diff --git a/docs/samples/Microsoft.ML.Samples/Microsoft.ML.Samples.csproj b/docs/samples/Microsoft.ML.Samples/Microsoft.ML.Samples.csproj index 9b66d0a6ae..da47c54d21 100644 --- a/docs/samples/Microsoft.ML.Samples/Microsoft.ML.Samples.csproj +++ b/docs/samples/Microsoft.ML.Samples/Microsoft.ML.Samples.csproj @@ -24,6 +24,7 @@ + diff --git a/src/Microsoft.ML.Transforms/SvmLight/SvmLightLoader.cs b/src/Microsoft.ML.Transforms/SvmLight/SvmLightLoader.cs new file mode 100644 index 0000000000..badc037a6e --- /dev/null +++ b/src/Microsoft.ML.Transforms/SvmLight/SvmLightLoader.cs @@ -0,0 +1,819 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.ML; +using Microsoft.ML.CommandLine; +using Microsoft.ML.Data; +using Microsoft.ML.Data.Conversion; +using Microsoft.ML.Internal.Utilities; +using Microsoft.ML.Runtime; +using Microsoft.ML.Transforms; + +[assembly: LoadableClass(SvmLightLoader.Summary, typeof(ILegacyDataLoader), typeof(SvmLightLoader), typeof(SvmLightLoader.Options), typeof(SignatureDataLoader), + SvmLightLoader.UserName, SvmLightLoader.LoaderSignature, "SvmLight", "svm")] + +[assembly: LoadableClass(SvmLightLoader.Summary, typeof(ILegacyDataLoader), typeof(SvmLightLoader), null, typeof(SignatureLoadDataLoader), + SvmLightLoader.UserName, SvmLightLoader.LoaderSignature)] + +namespace Microsoft.ML.Data +{ + /// + /// This attempts to reads data in a format close to the SVM-light format, the goal being + /// that the majority of SVM-light formatted data should be interpretable by this loader. + /// The loader may also be different than SVM-light's parsing behavior, in the following + /// general ways: + /// + /// 1. As an , vectors are required to have a logical length, + /// and for practical reasons it's helpful if the output of this loader has a fixed + /// length vector type, since few transforms and no basic learners accept features + /// of a variable length vector types. SVM-light had no such concept. + /// 2. The idiom has different behavior w.r.t. parse errors. + /// 3. The SVM-light has some restrictions in its format that are unnatural to attempt + /// to restrict in the concept of this loader. + /// 4. Some common "extensions" of this format that have happened over the years are + /// accomodated where sensible, often supported by specifying some options. + /// + /// The SVM-light format can be summarized here. An SVM-light file can lead with any number + /// of lines starting with '#'. These are discarded. + /// {label} {key}:{value} {key}:{value} ... {key}:{value}[#{comment}] + /// + /// Lines are not whitespace trimmed, though whitespace within the line, prior to the # + /// comment character (if any) are ignored. SVM-light itself uses the standard C "isspace" + /// function, while we respect only space and tab as whitespace. So, the spaces in the + /// line above could be, say, tabs, and there could even be multiple of them in sequence. + /// Unlike the text loader's format, for instance, there is no concept of a "blank" field + /// having any status. + /// + /// Labels are floating point values for regression, and for binary classification being one + /// of {-1, 0, 1/+1}. Negative class examples are -1, positive class examples are +1, and 0 + /// indicates that the label is unknown and should be classified using semi-supervised + /// techniques. + /// + /// The "0 label" semi-supervised capability was rarely used and none of our learners + /// currently do anything like this, though it is possible we may introduce semi-supervised + /// learners in the future. For now this loader just parses this as a single floating point + /// value, period. (Which means, for our binary classifier learners, that 0 and -1 will both + /// be treated identically.) If we were to support this, the natural thing would be to have + /// an option to map 0 to NA, somehow. But practically, variants of the SVM-light format have + /// promulgated to the point where nearly every time 0 is used, it actually refers to a + /// negative example, so we may continue to accept this corruption as "correct" by default. + /// + /// The actual feature vector is specified through a series of key/value pairs. SVM-light + /// requires that the keys be positive, increasing integers, except for three special keys: + /// cost (we interpret as Weight), qid (we interpret as GroupId) and sid (we ignore these, + /// but might present them as a column in the future if any of our learners implement anything + /// resembling slack id). The value for 'cost' is float, 'qid' is a long, and 'sid' is a long + /// that must be positive. If these keys are specified multiple times, the last one wins. + /// + /// SVM-light, if the tail of the value is not interpretable as a number, will ignore the tail. + /// E.g., "5:3.14hello" will be interpreted the same as "5:3.14". I am aware of one real dataset + /// that took advantage of this, and for now I do not support this type of thing. + /// + /// We do not retain the restriction on keys needing to be increasing values in our loader, + /// due to the way we compose our feature vectors, but it will be most efficient if this policy + /// is still followed. If it is followed a sort will not be required. + /// + /// This loader has the special option through the xf option to specify a transform, + /// possibly trainable, to convert the raw text of the key values into the key value. The + /// transform, whatever it is, must in addition to user specified options accept an argument + /// of the form "column=Name" to identify a column to convert. Ideally there would be some + /// other way to specify this other than hacking arguments. The intent of this is to allow + /// things like string keys, a common variant of the format, but one emphatically not allowed + /// by the original format. + /// + public sealed class SvmLightLoader : IDataLoader + { + internal enum FeatureIndices + { + ZeroBased, + OneBased, + Names + } + + internal sealed class Options + { + [Argument(ArgumentType.AtMostOnce, HelpText = "The size of the feature vectors.", ShortName = "size")] + public int InputSize; + + [Argument(ArgumentType.Multiple, HelpText = "Whether the features are indexed by numbers starting at 0, by numbers starting at 1, or by feature names.", ShortName = "indices")] + public FeatureIndices FeatureIndices = FeatureIndices.OneBased; + + [Argument(ArgumentType.AtMostOnce, HelpText = "The number of rows used to train the feature name to index mapping transform. If unspecified, all rows will be used.", ShortName = "numxf")] + public long? NumberOfRows; + } + + /// + /// This class is used as input for the mapping the input as a line of text. + /// It is used by two custom mappers: one that maps the text to a float value indicating whether the line of text is a comment + /// and the other maps the text to an object of type . + /// + private sealed class Input + { +#pragma warning disable 0649 // Disable warnings about unused members. They are used through reflection. + public ReadOnlyMemory Text; +#pragma warning restore 0649 + + public static void MapComment(Input input, CommentIndicator output) + { + // We expand a bit on the SVM-light comment strategy. In SVM-light, a comment line + // must have the # as the first character, and a totally whitespace or empty line + // is considered a parse error. However, for the purpose of detecting comments, + // we detect # after trimming whitespace, and also consider totally blank lines + // "comments" instead of whitespace. + ReadOnlyMemory text = ReadOnlyMemoryUtils.TrimWhiteSpace(input.Text); + if (text.IsEmpty || text.Span[0] == '#') + output.IsComment = float.NaN; + else + output.IsComment = 0; + } + } + + // This class is used in the CustomMappingTransformer that maps a line of input to a float indicating (by the value NaN) + // whether the line is a comment line. + private sealed class CommentIndicator + { + public float IsComment; + } + + /// + /// This class contains the mapper that maps an to an . + /// The mapper parses the label and weight into floats, the group ID into ulong, the comment into a of , + /// the feature values into a vector of floats and the feature indices/names into a vector of of . + /// + private sealed class InputMapper + { + private readonly char[] _seps; + private readonly TryParseMapper _tryFloatParse; + private readonly TryParseMapper _tryLongParse; + + public InputMapper() + { + _seps = new char[] { ' ', '\t' }; + _tryFloatParse = Conversions.Instance.GetTryParseConversion(NumberDataViewType.Single); + _tryLongParse = Conversions.Instance.GetTryParseConversion(NumberDataViewType.Int64); + } + + public void MapInput(Input input, IntermediateInput intermediate) + { + ReadOnlyMemory text = ReadOnlyMemoryUtils.TrimWhiteSpace(input.Text); + + // Handle comments, if any. If no comments are present, the value for that column + // in this row will be empty. + if (!ReadOnlyMemoryUtils.SplitOne(text, '#', out var left, out var right)) + right = ReadOnlyMemory.Empty; + intermediate.Comment = right; + + var ator = ReadOnlyMemoryUtils.Split(left, _seps).GetEnumerator(); + + // Empty lines are filtered in the Input.MapComment step. + var notEmpty = ator.MoveNext(); + Contracts.Assert(notEmpty); + + ReadOnlyMemory token = ator.Current; + + // Parse the label. + if (_tryFloatParse(in token, out intermediate.Label)) + intermediate.Weight = 1; // Default weight is of course 1. + else + { + // Report not parsing out the label? + intermediate.Label = float.NaN; + intermediate.Weight = float.NaN; + } + + // Group IDs are missing by default. + intermediate.GroupId = ulong.MaxValue; + + // SVM-light "special" tokens are the following: + // qid: Basically our group ID, with similar semantics. + // sid: Slack ID. This is kind of only relevant to SVMs. + // cost: Weight. + + var keys = new List>(); + var values = new List(); + float val; + while (ator.MoveNext()) + { + token = ator.Current; + if (!SplitOneRight(token, ':', out left, out right)) + { + // Report that this appears to be a malformed token? For now just silently ignore. + continue; + } + + // Handle the special tokens. + if (ReadOnlyMemoryUtils.EqualsStr("cost", left)) + { + if (_tryFloatParse(in right, out val)) + intermediate.Weight = val; + } + else if (ReadOnlyMemoryUtils.EqualsStr("qid", left)) + { + // SVM-light has a query ID field, and this can be any long. + // That said, I've never seen anyone ever use a negative query + // ID, ever. If they do, I'm going to have long.MinValue map + // into a missing value, non-negative values map into 0 onwards + // as is natural, and in case there are any negative values, + // these will be mapped using a straight cast to ulong (so that + // -1 would map to ulong.MaxValue). + if (_tryLongParse(in right, out long qid)) + { + if (qid >= 0) + intermediate.GroupId = (ulong)qid; + else + intermediate.GroupId = ulong.MaxValue; + } + } + else if (ReadOnlyMemoryUtils.EqualsStr("sid", left)) + { + // We'll pay attention to this insofar that we'll not consider + // it a feature, but right now we have no learners that pay + // attention to so-called "slack IDs" so we'll ignore these for + // right now. + continue; + } + else + { + // No special handling considered these, so treat it as though it is a feature. + if (!_tryFloatParse(in right, out val)) + { + // Report not parsing out the value? For now silently ignore. + continue; + } + keys.Add(left); + values.Add(val); + } + } + intermediate.FeatureKeys = new VBuffer>(keys.Count, keys.ToArray()); + intermediate.FeatureValues = new VBuffer(values.Count, values.ToArray()); + } + + private static bool SplitOneRight(ReadOnlyMemory memory, char separator, out ReadOnlyMemory left, out ReadOnlyMemory right) + { + if (memory.IsEmpty) + { + left = memory; + right = default; + return false; + } + + int index = memory.Span.LastIndexOf(separator); + if (index == -1) + { + left = memory; + right = default; + return false; + } + + left = memory.Slice(0, index); + right = memory.Slice(index + 1, memory.Length - index - 1); + return true; + } + } + + // This class is used by the CustomMappingTransformer that does the initial parsing of the input. + // The features are mapped to two fields: a vector of floats for the feature values, and a vector + // of ReadOnlyMemory for the feature indices/names. + private sealed class IntermediateInput + { + public float Label; + public float Weight; + public VBuffer> FeatureKeys; + public VBuffer FeatureValues; + public ReadOnlyMemory Comment; + [KeyType(ulong.MaxValue - 1)] + public ulong GroupId; + } + + /// + /// This class is used for mapping the vector of of field in an object + /// to a numeric vector of indices. This class is used in case the indices in the input file are numeric. For the case where the input file contains + /// feature names, instead of a , we use a . + /// + private sealed class Indices + { + [KeyType(uint.MaxValue - 1)] + public VBuffer FeatureKeys; + } + + private sealed class IndexParser + { + private readonly uint _offset; + private readonly uint _na; + + public IndexParser(bool zeroBased, ulong featureCount) + { + _offset = zeroBased ? (uint)0 : 1; + _na = (uint)featureCount + 1; + } + + public void ParseIndices(IntermediateInput input, Indices output) + { + var editor = VBufferEditor.Create(ref output.FeatureKeys, input.FeatureKeys.Length); + var inputValues = input.FeatureKeys.GetValues(); + for (int i = 0; i < inputValues.Length; i++) + { + if (Conversions.Instance.TryParse(in inputValues[i], out uint index)) + { + if (index < _offset) + { + throw Contracts.Except("Encountered 0 index while parsing a 1-based dataset"); + } + editor.Values[i] = index - _offset + 1; + } + else + throw Contracts.Except($"Encountered non-parsable index '{inputValues[i]}' while parsing dataset"); + } + output.FeatureKeys = editor.Commit(); + } + } + + /// + /// This class is used by the + /// that maps a vector of indices and a vector of values into a single of values. + /// + private sealed class IntermediateOut + { + public VBuffer FeatureKeys; + public VBuffer FeatureValues; + } + + private sealed class Output + { + public VBuffer Features; + } + + /// + /// This class contains the mapper that maps an an + /// to an . + /// + private sealed class OutputMapper + { + private readonly uint _keyMax; + private readonly BufferBuilder _bldr; + private readonly BitArray _indexUsed; + + public OutputMapper(int keyCount) + { + Contracts.Assert(keyCount > 0); + // Leave as uint, so that comparisons against uint key values do not + // incur any sort of implicit value conversions. + _keyMax = (uint)keyCount; + _bldr = new BufferBuilder(FloatAdder.Instance); + _indexUsed = new BitArray((int)_keyMax); + } + + public void Map(IntermediateOut intermediate, Output output) + { + MapCore(ref intermediate.FeatureKeys, ref intermediate.FeatureValues, output); + } + + private void MapCore(ref VBuffer keys, ref VBuffer values, Output output) + { + Contracts.Check(keys.Length == values.Length, "number of keys does not match number of values."); + + // Both of these inputs should be dense, but still work even if they're not. + VBufferUtils.Densify(ref keys); + VBufferUtils.Densify(ref values); + var keysValues = keys.GetValues(); + var valuesValues = values.GetValues(); + + // The output vector could be sparse, so we use BufferBuilder here. + _bldr.Reset((int)_keyMax, false); + _indexUsed.SetAll(false); + for (int i = 0; i < keys.Length; ++i) + { + var key = keysValues[i]; + if (key == 0 || key > _keyMax) + continue; + if (_indexUsed[(int)key - 1]) + throw Contracts.Except("Duplicate keys found in dataset"); + _bldr.AddFeature((int)key - 1, valuesValues[i]); + _indexUsed[(int)key - 1] = true; + } + _bldr.GetResult(ref output.Features); + } + } + + /// + /// This class creates an from an , that has a single text column + /// called "Text". + /// + private sealed class TextDataView : IDataView + { + public bool CanShuffle => false; + + public DataViewSchema Schema { get; } + + private readonly IHost _host; + private readonly IMultiStreamSource _files; + + public TextDataView(IHostEnvironment env, IMultiStreamSource files) + { + Contracts.CheckValue(env, nameof(env)); + env.CheckValue(files, nameof(files)); + + _host = env.Register("TextDataView"); + _files = files; + + var bldr = new DataViewSchema.Builder(); + bldr.AddColumn("Text", TextDataViewType.Instance); + Schema = bldr.ToSchema(); + } + + public long? GetRowCount() + { + if (_files.Count == 0) + return 0; + return null; + } + + public DataViewRowCursor GetRowCursor(IEnumerable columnsNeeded, Random rand = null) + { + _host.CheckValue(columnsNeeded, nameof(columnsNeeded)); + _host.CheckValueOrNull(rand); + return new Cursor(this, columnsNeeded.Any()); + } + + public DataViewRowCursor[] GetRowCursorSet(IEnumerable columnsNeeded, int n, Random rand = null) + { + _host.CheckValue(columnsNeeded, nameof(columnsNeeded)); + _host.CheckValueOrNull(rand); + return new DataViewRowCursor[] { GetRowCursor(columnsNeeded, rand) }; + } + + private sealed class Cursor : RootCursorBase + { + private readonly TextDataView _parent; + private readonly bool _isActive; + private int _fileIdx; + private TextReader _currReader; + private ReadOnlyMemory _text; + private ValueGetter> _getter; + + public override long Batch => 0; + + public override DataViewSchema Schema => _parent.Schema; + + public Cursor(TextDataView parent, bool isActive) + : base(parent._host) + { + _parent = parent; + _isActive = isActive; + if (_parent._files.Count == 0) + { + // Rather than corrupt MoveNextCore with a bunch of custom logic for + // the empty file case and make that less efficient, be slightly inefficient + // for our zero-row case. + _fileIdx = -1; + _currReader = new StringReader(""); + _currReader.ReadLine(); + // Beyond this point _currReader will return null from ReadLine. + } + else + _currReader = _parent._files.OpenTextReader(_fileIdx); + if (_isActive) + _getter = Getter; + } + + private void Getter(ref ReadOnlyMemory val) + { + Ch.Check(IsGood, "cannot call getter with cursor in its current state"); + Ch.Assert(_isActive); + val = _text; + } + + public override ValueGetter GetGetter(DataViewSchema.Column column) + { + Ch.CheckParam(column.Index == 0, nameof(column)); + Ch.CheckParam(_isActive, nameof(column), "requested column not active"); + ValueGetter getter = _getter as ValueGetter; + if (getter == null) + throw Ch.Except("Invalid TValue: '{0}'", typeof(TValue)); + return getter; + } + + public override ValueGetter GetIdGetter() + { + return + (ref DataViewRowId val) => + { + Ch.Check(IsGood, "Cannot call ID getter in current state"); + val = new DataViewRowId((ulong)Position, 0); + }; + } + + public override bool IsColumnActive(DataViewSchema.Column column) + { + Ch.CheckParam(column.Index == 0, nameof(column)); + return _isActive; + } + + protected override bool MoveNextCore() + { + Ch.AssertValue(_currReader); + Ch.Assert(-1 <= _fileIdx && _fileIdx < _parent._files.Count); + + var count = _parent._files.Count; + for (; ; ) + { + var line = _currReader.ReadLine(); + if (line != null) + { + if (_isActive) + _text = line.AsMemory(); + return true; + } + if (++_fileIdx == count) + return false; + Ch.AssertValue(_parent._files); + _currReader = _parent._files.OpenTextReader(_fileIdx); + } + } + } + } + + private readonly IHost _host; + private readonly ITransformer _keyVectorsToIndexVectors; + private readonly FeatureIndices _indicesKind; + private readonly ulong _featureCount; + private readonly DataViewSchema _outputSchema; + + internal const string Summary = "Loads text in the SVM-light format and close variants."; + internal const string UserName = "SVM-Light Loader"; + + internal const string LoaderSignature = "SvmLightLoader"; + private static VersionInfo GetVersionInfo() + { + return new VersionInfo( + modelSignature: "SVMLTLDR", + verWrittenCur: 0x00010001, // Initial + verReadableCur: 0x00010001, + verWeCanReadBack: 0x00010001, + loaderSignature: LoaderSignature, + loaderAssemblyName: typeof(SvmLightLoader).Assembly.FullName); + } + + internal SvmLightLoader(IHostEnvironment env, Options options = null, IMultiStreamSource dataSample = null) + { + Contracts.CheckValue(env, nameof(env)); + _host = env.Register(LoaderSignature); + if (options == null) + options = new Options(); + _host.CheckUserArg(options.InputSize >= 0, nameof(options.InputSize), "Maximum feature index must be positive, or 0 to infer it from the dataset"); + + _indicesKind = options.FeatureIndices; + if (_indicesKind != FeatureIndices.Names) + { + if (options.InputSize > 0) + _featureCount = (ulong)options.InputSize; + else + { + if (dataSample == null || dataSample.Count == 0) + throw env.Except("If the number of features is not specified, a dataset must be provided to infer it."); + var data = GetData(_host, options.NumberOfRows, dataSample); + _featureCount = InferMax(_host, data) + (ulong)(_indicesKind == FeatureIndices.ZeroBased ? 1 : 0); + } + _host.Assert(_featureCount <= int.MaxValue); + } + else + { + // We need to train a ValueToKeyMappingTransformer. + if (dataSample == null || dataSample.Count == 0) + throw env.Except("To use the text feature names option, a dataset must be provided"); + + var data = GetData(_host, options.NumberOfRows, dataSample); + _keyVectorsToIndexVectors = new ValueToKeyMappingEstimator(_host, nameof(IntermediateInput.FeatureKeys)).Fit(data); + var keyCol = _keyVectorsToIndexVectors.GetOutputSchema(data.Schema).GetColumnOrNull(nameof(Indices.FeatureKeys)); + _host.Assert(keyCol.HasValue); + var keyType = keyCol.Value.Type.GetItemType() as KeyDataViewType; + _host.AssertValue(keyType); + _featureCount = keyType.Count; + } + + _outputSchema = CreateOutputSchema(); + } + + private SvmLightLoader(IHost host, ModelLoadContext ctx) + { + Contracts.AssertValue(host, "host"); + host.AssertValue(ctx); + + _host = host; + + // *** Binary format *** + // int: Whether the indices column type is a key type, integer starting with 0 or integet starting with 1. + // ulong: The number of features. + // submodel: The transformer converting the indices from text to numeric/key. + + _indicesKind = (FeatureIndices)ctx.Reader.ReadInt32(); + _featureCount = ctx.Reader.ReadUInt64(); + + ctx.LoadModelOrNull(_host, out _keyVectorsToIndexVectors, "KeysToIndices"); + } + + internal static SvmLightLoader Create(IHostEnvironment env, ModelLoadContext ctx) + { + Contracts.CheckValue(env, nameof(env)); + IHost h = env.Register(LoaderSignature); + + h.CheckValue(ctx, nameof(ctx)); + ctx.CheckAtModel(GetVersionInfo()); + + return h.Apply("Loading Model", ch => new SvmLightLoader(h, ctx)); + } + + void ICanSaveModel.Save(ModelSaveContext ctx) + { + _host.CheckValue(ctx, nameof(ctx)); + ctx.CheckAtModel(); + ctx.SetVersionInfo(GetVersionInfo()); + + // *** Binary format *** + // int: Whether the indices column type is a key type, integer starting with 0 or integet starting with 1. + // ulong: The number of features. + // submodel: The transformer converting the indices from text to numeric/key. + + ctx.Writer.Write((int)_indicesKind); + ctx.Writer.Write(_featureCount); + + if (_keyVectorsToIndexVectors != null) + ctx.SaveModel(_keyVectorsToIndexVectors, "KeysToIndices"); + } + + private DataViewSchema CreateOutputSchema() + { + var data = GetData(_host, null, new MultiFileSource(null)); + var indexParser = new IndexParser(_indicesKind == FeatureIndices.ZeroBased, _featureCount); + var schemaDef = SchemaDefinition.Create(typeof(Indices)); + schemaDef[nameof(Indices.FeatureKeys)].ColumnType = new KeyDataViewType(typeof(uint), _featureCount); + var keyVectorsToIndexVectors = _keyVectorsToIndexVectors ?? + new CustomMappingTransformer(_host, indexParser.ParseIndices, null); + var schema = keyVectorsToIndexVectors.GetOutputSchema(data.Schema); + return CreateOutputTransformer(_host, (int)_featureCount, + _indicesKind == FeatureIndices.Names, schema).GetOutputSchema(schema); + } + + private static IDataView GetData(IHostEnvironment env, long? numRows, IMultiStreamSource dataSample) + { + IDataView data = new TextDataView(env, dataSample); + + // First stage of the transform, effectively comment out comment lines. Comment lines are those + // whose very first character is a '#'. We add an NAFilter to filter out entire lines that start with '#'. + // REVIEW: When ML.NET supports custom mappings for filters, we can replace this stage with a custom filter. + + var transformer = new CustomMappingTransformer(env, Input.MapComment, null); + data = transformer.Transform(data); + data = new NAFilter(env, data, columns: nameof(CommentIndicator.IsComment)); + + // Second stage of the transform, parse out the features into a text vector of keys/indices and a text vector of values. + // If we are loading the data for training a KeyToValueTransformer, users can specify the number of rows to train on. + var inputMapper = new InputMapper(); + data = new CustomMappingTransformer(env, inputMapper.MapInput, null).Transform(data); + if (numRows.HasValue) + data = SkipTakeFilter.Create(env, new SkipTakeFilter.TakeOptions() { Count = numRows.Value }, data); + return data; + } + + private static uint InferMax(IHostEnvironment env, IDataView view) + { + ulong keyMax = 0; + var parser = Conversions.Instance.GetTryParseConversion(NumberDataViewType.UInt64); + var col = view.Schema.GetColumnOrNull(nameof(IntermediateInput.FeatureKeys)); + env.Assert(col.HasValue); + + using (var ch = env.Start("Infer key transform")) + using (var cursor = view.GetRowCursor(col.Value)) + { + VBuffer> result = default; + var getter = cursor.GetGetter>>(col.Value); + + long count = 0; + long missingCount = 0; + while (cursor.MoveNext()) + { + getter(ref result); + var values = result.GetValues(); + for (int i = 0; i < values.Length; ++i) + { + if (!parser(in values[i], out var val)) + { + missingCount++; + continue; + } + count++; + if (keyMax < val) + keyMax = val; + } + } + if (missingCount > 0) + ch.Warning("{0} of {1} detected keys were missing or unparsable", missingCount, count + missingCount); + if (count == 0) + throw ch.Except("No int parsable keys found during key transform inference"); + ch.Info("Observed max was {0}", keyMax); + + if (keyMax > int.MaxValue) + { + // Similarly for missing values/misparses, warn, but don't error. + ch.Warning("Indices above {0} will be ignored.", int.MaxValue); + keyMax = int.MaxValue; + } + } + return (uint)keyMax; + } + + private static ITransformer CreateOutputTransformer(IHostEnvironment env, int keyCount, bool keyIndices, DataViewSchema inputSchema) + { + // Third stage of the transform, do what amounts to a weighted KeyToVector transform. + // REVIEW: Really the KeyToVector transform should have support for weights on the keys. + // If we add this, replace this stuff with that. + var outputMapper = new OutputMapper(keyCount); + // The size of the output is fixed, so just update the schema definition to reflect that. + var schemaDef = SchemaDefinition.Create(typeof(Output)); + env.Assert(schemaDef.Count == 1); + schemaDef[0].ColumnType = new VectorDataViewType(NumberDataViewType.Single, keyCount); + + ITransformer outputTransformer; + if (keyIndices) + { + var col = inputSchema[nameof(Indices.FeatureKeys)]; + var keyValuesCol = col.Annotations.Schema.GetColumnOrNull(AnnotationUtils.Kinds.KeyValues); + if (keyValuesCol.HasValue) + { + VBuffer> keyValues = default; + col.Annotations.GetValue(AnnotationUtils.Kinds.KeyValues, ref keyValues); + schemaDef[0].AddAnnotation(AnnotationUtils.Kinds.SlotNames, keyValues, keyValuesCol.Value.Type); + } + outputTransformer = new CustomMappingTransformer(env, + outputMapper.Map, null, outputSchemaDefinition: schemaDef); + } + else + { + outputTransformer = new CustomMappingTransformer(env, + outputMapper.Map, null, outputSchemaDefinition: schemaDef); + } + + string[] toKeep = { "Label", "Weight", "GroupId", "Comment", "Features" }; + return outputTransformer.Append(new ColumnSelectingTransformer(env, toKeep, null)); + } + + public DataViewSchema GetOutputSchema() => _outputSchema; + + public IDataView Load(IMultiStreamSource input) + { + _host.CheckValue(input, nameof(input)); + + var data = GetData(_host, null, input); + var indexParser = new IndexParser(_indicesKind == FeatureIndices.ZeroBased, _featureCount); + var keyVectorsToIndexVectors = _keyVectorsToIndexVectors ?? + new CustomMappingTransformer(_host, indexParser.ParseIndices, null); + data = keyVectorsToIndexVectors.Transform(data); + return CreateOutputTransformer(_host, (int)_featureCount, _indicesKind == FeatureIndices.Names, data.Schema).Transform(data); + } + + // These are legacy constructors needed for ComponentCatalog. + internal static ILegacyDataLoader Create(IHostEnvironment env, ModelLoadContext ctx, IMultiStreamSource files) + { + var svmLoader = Create(env, ctx); + return new LegacyLoader(svmLoader, svmLoader.Load(files)); + } + internal static ILegacyDataLoader Create(IHostEnvironment env, Options options, IMultiStreamSource files) + { + var svmLoader = new SvmLightLoader(env, options, files); + return new LegacyLoader(svmLoader, svmLoader.Load(files)); + } + + private sealed class LegacyLoader : ILegacyDataLoader + { + public bool CanShuffle => _view.CanShuffle; + + public DataViewSchema Schema => _view.Schema; + + private readonly IDataView _view; + private readonly SvmLightLoader _loader; + + public LegacyLoader(SvmLightLoader loader, IDataView view) + { + _loader = loader; + _view = view; + } + + public long? GetRowCount() => _view.GetRowCount(); + + public DataViewRowCursor GetRowCursor(IEnumerable columnsNeeded, Random rand = null) => _view.GetRowCursor(columnsNeeded, rand); + + public DataViewRowCursor[] GetRowCursorSet(IEnumerable columnsNeeded, int n, Random rand = null) => _view.GetRowCursorSet(columnsNeeded, n, rand); + + void ICanSaveModel.Save(ModelSaveContext ctx) + { + ((ICanSaveModel)_loader).Save(ctx); + } + } + } +} diff --git a/src/Microsoft.ML.Transforms/SvmLight/SvmLightLoaderSaverCatalog.cs b/src/Microsoft.ML.Transforms/SvmLight/SvmLightLoaderSaverCatalog.cs new file mode 100644 index 0000000000..5d274218a0 --- /dev/null +++ b/src/Microsoft.ML.Transforms/SvmLight/SvmLightLoaderSaverCatalog.cs @@ -0,0 +1,125 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO; +using System.Linq; +using Microsoft.ML.Runtime; + +namespace Microsoft.ML.Data +{ + public static class SvmLightLoaderSaverCatalog + { + /// + /// Creates a loader that loads SVM-light format files. . + /// + /// The catalog. + /// The number of features in the Features column. If 0 is specified, the + /// loader will determine it by looking at the file sample given in . + /// The number of rows from the sample to be used for determining the number of features. + /// If the file contains zero-based indices, this parameter should be set to true. If they are one-based + /// it should be set to false. + /// A data sample to be used for determining the number of features in the Features column. + public static SvmLightLoader CreateSvmLightLoader(this DataOperationsCatalog catalog, + long? numberOfRows = null, + int inputSize = 0, + bool zeroBased = false, + IMultiStreamSource dataSample = null) + => new SvmLightLoader(CatalogUtils.GetEnvironment(catalog), new SvmLightLoader.Options() + { InputSize = inputSize, NumberOfRows = numberOfRows, FeatureIndices = zeroBased ? + SvmLightLoader.FeatureIndices.ZeroBased : SvmLightLoader.FeatureIndices.OneBased }, dataSample); + + /// + /// Creates a loader that loads SVM-light like files, where features are specified by their names. + /// + /// The catalog. + /// The number of rows from the sample to be used for determining the set of feature names. + /// A data sample to be used for determining the set of features names. + public static SvmLightLoader CreateSvmLightLoaderWithFeatureNames(this DataOperationsCatalog catalog, + long? numberOfRows = null, + IMultiStreamSource dataSample = null) + => new SvmLightLoader(CatalogUtils.GetEnvironment(catalog), new SvmLightLoader.Options() + { NumberOfRows = numberOfRows, FeatureIndices = SvmLightLoader.FeatureIndices.Names }, dataSample); + + /// + /// Load a from a text file using . + /// + /// The catalog. + /// The path to the file. + /// The number of features in the Features column. If 0 is specified, the + /// loader will determine it by looking at the file given in . + /// If the file contains zero-based indices, this parameter should be set to true. If they are one-based + /// it should be set to false. + /// The number of rows from the sample to be used for determining the number of features. + public static IDataView LoadFromSvmLightFile(this DataOperationsCatalog catalog, + string path, + long? numberOfRows = null, + int inputSize = 0, + bool zeroBased = false) + { + Contracts.CheckNonEmpty(path, nameof(path)); + + var file = new MultiFileSource(path); + var loader = catalog.CreateSvmLightLoader(numberOfRows, inputSize, zeroBased, file); + return loader.Load(file); + } + + /// + /// Load a from a text file containing features specified by feature names, + /// using . + /// + /// The catalog. + /// The path to the file. + /// The number of rows from the sample to be used for determining the set of feature names. + public static IDataView LoadFromSvmLightFileWithFeatureNames(this DataOperationsCatalog catalog, + string path, + long? numberOfRows = null) + { + Contracts.CheckNonEmpty(path, nameof(path)); + + var file = new MultiFileSource(path); + var loader = catalog.CreateSvmLightLoaderWithFeatureNames(numberOfRows, file); + return loader.Load(file); + } + + /// + /// Save the in SVM-light format. Four columns can be saved: a label and a features column, + /// and optionally a group ID column and an example weight column. + /// + /// The catalog. + /// The data view to save. + /// The stream to write to. + /// Whether to index the features starting at 0 or at 1. + /// If set to true, saves 1 for positive labels, -1 for non-positive labels and 0 for NaN. + /// Otherwise, saves the value of the label in the data view. + /// The name of the column to be saved as the label column. + /// The name of the column to be saved as the features column. + /// The name of the column to be saved as the group ID column. If null, a group ID column + /// will not be saved. + /// The name of the column to be saved as the weight column. If null, a weight column + /// will not be saved. + public static void SaveInSvmLightFormat(this DataOperationsCatalog catalog, + IDataView data, + Stream stream, + bool zeroBasedIndexing = false, + bool binaryLabel = false, + string labelColumnName = DefaultColumnNames.Label, + string featureColumnName = DefaultColumnNames.Features, + string rowGroupColumnName = null, + string exampleWeightColumnName = null) + { + var args = new SvmLightSaver.Arguments() + { + Zero = zeroBasedIndexing, + Binary = binaryLabel, + LabelColumnName = labelColumnName, + FeatureColumnName = featureColumnName, + RowGroupColumnName = rowGroupColumnName, + ExampleWeightColumnName = exampleWeightColumnName + }; + + var saver = new SvmLightSaver(CatalogUtils.GetEnvironment(catalog), args); + saver.SaveData(stream, data, data.Schema.Select(col => col.Index).ToArray()); + } + } +} diff --git a/src/Microsoft.ML.Transforms/SvmLight/SvmLightSaver.cs b/src/Microsoft.ML.Transforms/SvmLight/SvmLightSaver.cs new file mode 100644 index 0000000000..65519d7548 --- /dev/null +++ b/src/Microsoft.ML.Transforms/SvmLight/SvmLightSaver.cs @@ -0,0 +1,177 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.ML; +using Microsoft.ML.CommandLine; +using Microsoft.ML.Data; +using Microsoft.ML.Data.IO; +using Microsoft.ML.Runtime; + +[assembly: LoadableClass(SvmLightSaver.Summary, typeof(SvmLightSaver), typeof(SvmLightSaver.Arguments), typeof(SignatureDataSaver), + "SVM-Light Saver", SvmLightSaver.LoadName, "SvmLight", "Svm")] + +namespace Microsoft.ML.Data +{ + /// + /// The SVM-light saver is a saver class that is capable of saving the label, + /// features, group ID and weight columns of a dataset in SVM-light format. It is a bit + /// idiosyncratic in that unlike and , there is no + /// attempt to save all columns, just those specific columns, with other columns being dropped on + /// the floor. + /// + [BestFriend] + internal sealed class SvmLightSaver : IDataSaver + { + public sealed class Arguments + { + [Argument(ArgumentType.LastOccurenceWins, HelpText = "Write the variant of SVM-light format where feature indices start from 0, not 1", ShortName = "z")] + public bool Zero; + + [Argument(ArgumentType.LastOccurenceWins, HelpText = "Format output labels for a binary classification problem (-1 for negative, 1 for positive)", ShortName = "b")] + public bool Binary; + + [Argument(ArgumentType.AtMostOnce, HelpText = "Column to use for features", ShortName = "feat", SortOrder = 2, Visibility = ArgumentAttribute.VisibilityType.EntryPointsOnly)] + public string FeatureColumnName = DefaultColumnNames.Features; + + [Argument(ArgumentType.AtMostOnce, HelpText = "Column to use for labels", ShortName = "lab", SortOrder = 3, Visibility = ArgumentAttribute.VisibilityType.EntryPointsOnly)] + public string LabelColumnName = DefaultColumnNames.Label; + + [Argument(ArgumentType.AtMostOnce, HelpText = "Column to use for example weight", ShortName = "weight", SortOrder = 4, Visibility = ArgumentAttribute.VisibilityType.EntryPointsOnly)] + public string ExampleWeightColumnName = null; + + [Argument(ArgumentType.AtMostOnce, HelpText = "Column to use for example groupId", ShortName = "groupId", SortOrder = 5, Visibility = ArgumentAttribute.VisibilityType.EntryPointsOnly)] + public string RowGroupColumnName = null; + } + + internal const string LoadName = "SvmLightSaver"; + internal const string Summary = + "Writes Label/Features/Weight/GroupId columns into a data file in SVM-light format. " + + "Label and Features are required, but the others are optional."; + + private readonly IHost _host; + private readonly bool _zero; + private readonly bool _binary; + private readonly string _featureCol; + private readonly string _labelCol; + private readonly string _groupCol; + private readonly string _weightCol; + + public SvmLightSaver(IHostEnvironment env, Arguments args) + { + Contracts.CheckValue(env, nameof(env)); + _host = env.Register(SvmLightSaver.LoadName); + _host.CheckValue(args, nameof(args)); + + _zero = args.Zero; + _binary = args.Binary; + _featureCol = args.FeatureColumnName; + _labelCol = args.LabelColumnName; + _groupCol = args.RowGroupColumnName; + _weightCol = args.ExampleWeightColumnName; + } + + public bool IsColumnSavable(DataViewType type) + { + // REVIEW: The SVM-light saver is a bit peculiar in that it does not + // save all columns, just some columns, and the determination of whether it will + // save a column or not is not dependent only on its type, but rather its name + // and other factors. This will claim to save all columns, but it will just + // ignore a bunch depending not on the type, but on the name. + return true; + } + + public void SaveData(Stream stream, IDataView data, params int[] cols) + { + _host.CheckValue(stream, nameof(stream)); + _host.CheckValue(data, nameof(data)); + _host.CheckValueOrNull(cols); + + if (cols == null) + cols = new int[0]; + + using (var ch = _host.Start("Saving")) + { + var labelCol = data.Schema.GetColumnOrNull(_labelCol); + if (!labelCol.HasValue) + throw ch.Except($"Column {_labelCol} not found in data"); + + var featureCol = data.Schema.GetColumnOrNull(_featureCol); + if (!featureCol.HasValue) + throw ch.Except($"Column {_featureCol} not found in data"); + + var groupCol = !string.IsNullOrWhiteSpace(_groupCol) ? data.Schema.GetColumnOrNull(_groupCol) : default; + if (!string.IsNullOrWhiteSpace(_groupCol) && !groupCol.HasValue) + throw ch.Except($"Column {_groupCol} not found in data"); + + var weightCol = !string.IsNullOrWhiteSpace(_weightCol) ? data.Schema.GetColumnOrNull(_weightCol) : default; + if (!string.IsNullOrWhiteSpace(_weightCol) && !weightCol.HasValue) + throw ch.Except($"Column {_weightCol} not found in data"); + + foreach (var col in cols) + { + _host.Check(col < data.Schema.Count); + var column = data.Schema[col]; + if (column.Name != _labelCol && column.Name != _featureCol && column.Name != _groupCol && column.Name != _weightCol) + ch.Warning($"Column {column.Name} will not be saved. SVM-light saver saves the label column, feature column, optional group column and optional weight column."); + } + + var columns = new List() { labelCol.Value, featureCol.Value }; + if (groupCol.HasValue) + columns.Add(groupCol.Value); + if (weightCol.HasValue) + columns.Add(weightCol.Value); + using (var writer = new StreamWriter(stream)) + using (var cursor = data.GetRowCursor(columns)) + { + // Getting the getters will fail with type errors if the types are not correct, + // so we rely on those messages. + var labelGetter = cursor.GetGetter(labelCol.Value); + var featuresGetter = cursor.GetGetter>(featureCol.Value); + var groupGetter = groupCol.HasValue ? cursor.GetGetter(groupCol.Value) : null; + var weightGetter = weightCol.HasValue ? cursor.GetGetter(weightCol.Value) : null; + VBuffer features = default; + while (cursor.MoveNext()) + { + float lab = default; + labelGetter(ref lab); + if (_binary) + writer.Write(float.IsNaN(lab) ? 0 : (lab > 0 ? 1 : -1)); + else + writer.Write("{0:R}", lab); + if (groupGetter != null) + { + ulong groupId = default; + groupGetter(ref groupId); + if (groupId > 0) + writer.Write(" qid:{0}", groupId - 1); + } + if (weightGetter != null) + { + float weight = default; + weightGetter(ref weight); + if (weight != 1) + writer.Write(" cost:{0:R}", weight); + } + + featuresGetter(ref features); + bool any = false; + foreach (var pair in features.Items().Where(p => p.Value != 0)) + { + writer.Write(" {0}:{1}", _zero ? pair.Key : (pair.Key + 1), pair.Value); + any = true; + } + // If there were no non-zero items, write a dummy item. Some parsers can handle + // empty arrays correctly, but some assume there is at least one defined item. + if (!any) + writer.Write(" {0}:0", _zero ? 0 : 1); + writer.WriteLine(); + } + } + } + } + } +} diff --git a/test/BaselineOutput/Common/Command/CommandSaveDataSvmLight-data-0.txt b/test/BaselineOutput/Common/Command/CommandSaveDataSvmLight-data-0.txt new file mode 100644 index 0000000000..cf79b6fd24 --- /dev/null +++ b/test/BaselineOutput/Common/Command/CommandSaveDataSvmLight-data-0.txt @@ -0,0 +1,683 @@ +0 0:5 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:5 1:4 2:4 3:5 4:7 5:10 6:3 7:2 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:2 6:3 7:1 8:1 +0 0:6 1:8 2:8 3:1 4:3 5:4 6:3 7:7 8:1 +0 0:4 1:1 2:1 3:3 4:2 5:1 6:3 7:1 8:1 +1 0:8 1:10 2:10 3:8 4:7 5:10 6:9 7:7 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:10 6:3 7:1 8:1 +0 0:2 1:1 2:2 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:2 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:5 +0 0:4 1:2 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:1 5:1 6:3 7:1 8:1 +0 0:2 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +1 0:5 1:3 2:3 3:3 4:2 5:3 6:4 7:4 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:3 6:3 7:1 8:1 +1 0:8 1:7 2:5 3:10 4:7 5:9 6:5 7:5 8:4 +1 0:7 1:4 2:6 3:4 4:6 5:1 6:4 7:3 8:1 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +1 0:10 1:7 2:7 3:6 4:4 5:10 6:4 7:1 8:2 +0 0:6 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +1 0:7 1:3 2:2 3:10 4:5 5:10 6:5 7:4 8:4 +1 0:10 1:5 2:5 3:3 4:6 5:7 6:7 7:10 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +1 0:5 1:2 2:3 3:4 4:2 5:7 6:3 7:6 8:1 +0 0:3 1:2 2:1 3:1 4:1 5:1 6:2 7:1 8:1 +0 0:5 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:2 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:1 1:1 2:3 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:1 5:1 6:2 7:1 8:1 +0 0:2 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +1 0:10 1:7 2:7 3:3 4:8 5:5 6:7 7:4 8:3 +0 0:2 1:1 2:1 3:2 4:2 5:1 6:3 7:1 8:1 +0 0:3 1:1 2:2 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:2 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +1 0:10 1:10 2:10 3:8 4:6 5:1 6:8 7:9 8:1 +0 0:6 1:2 2:1 3:1 4:1 5:1 6:7 7:1 8:1 +1 0:5 1:4 2:4 3:9 4:2 5:10 6:5 7:6 8:1 +1 0:2 1:5 2:3 3:3 4:6 5:7 6:7 7:5 8:1 +1 0:10 1:4 2:3 3:1 4:3 5:3 6:6 7:5 8:2 +1 0:6 1:10 2:10 3:2 4:8 5:10 6:7 7:3 8:3 +1 0:5 1:6 2:5 3:6 4:10 5:1 6:3 7:1 8:1 +1 0:10 1:10 2:10 3:4 4:8 5:1 6:8 7:10 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:2 +1 0:3 1:7 2:7 3:4 4:4 5:9 6:4 7:8 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:4 1:1 2:1 3:3 4:2 5:1 6:3 7:1 8:1 +1 0:7 1:8 2:7 3:2 4:4 5:8 6:3 7:8 8:2 +1 0:9 1:5 2:8 3:1 4:2 5:3 6:2 7:1 8:5 +1 0:5 1:3 2:3 3:4 4:2 5:4 6:3 7:4 8:1 +1 0:10 1:3 2:6 3:2 4:3 5:5 6:4 7:10 8:2 +1 0:5 1:5 2:5 3:8 4:10 5:8 6:7 7:3 8:7 +1 0:10 1:5 2:5 3:6 4:8 5:8 6:7 7:1 8:1 +1 0:10 1:6 2:6 3:3 4:4 5:5 6:3 7:6 8:1 +1 0:8 1:10 2:10 3:1 4:3 5:6 6:3 7:9 8:1 +1 0:8 1:2 2:4 3:1 4:5 5:1 6:5 7:4 8:4 +1 0:5 1:2 2:3 3:1 4:6 5:10 6:5 7:1 8:1 +1 0:9 1:5 2:5 3:2 4:2 5:2 6:5 7:1 8:1 +1 0:5 1:3 2:5 3:5 4:3 5:3 6:4 7:10 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:2 6:2 7:1 8:1 +1 0:9 1:10 2:10 3:1 4:10 5:8 6:3 7:3 8:1 +1 0:6 1:3 2:4 3:1 4:5 5:2 6:3 7:9 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +1 0:10 1:4 2:2 3:1 4:3 5:2 6:4 7:3 8:10 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +1 0:5 1:3 2:4 3:1 4:8 5:10 6:4 7:9 8:1 +1 0:8 1:3 2:8 3:3 4:4 5:9 6:8 7:9 8:8 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:3 7:2 8:1 +0 0:5 1:1 2:3 3:1 4:2 5:1 6:2 7:1 8:1 +1 0:6 1:10 2:2 3:8 4:10 5:2 6:7 7:8 8:10 +0 0:1 1:3 2:3 3:2 4:2 5:1 6:7 7:2 8:1 +1 0:9 1:4 2:5 3:10 4:6 5:10 6:4 7:8 8:1 +1 0:10 1:6 2:4 3:1 4:3 5:4 6:3 7:2 8:3 +0 0:1 1:1 2:2 3:1 4:2 5:2 6:4 7:2 8:1 +0 0:1 1:1 2:4 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:5 1:3 2:1 3:2 4:2 5:1 6:2 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:3 6:3 7:1 8:1 +0 0:2 1:1 2:1 3:1 4:3 5:1 6:2 7:1 8:1 +0 0:2 1:2 2:2 3:1 4:1 5:1 6:7 7:1 8:1 +0 0:4 1:1 2:1 3:2 4:2 5:1 6:2 7:1 8:1 +0 0:5 1:2 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:2 6:7 7:1 8:1 +1 0:3 1:5 2:7 3:8 4:8 5:9 6:7 7:10 8:7 +1 0:5 1:10 2:6 3:1 4:10 5:4 6:4 7:10 8:10 +1 0:3 1:3 2:6 3:4 4:5 5:8 6:4 7:4 8:1 +1 0:3 1:6 2:6 3:6 4:5 5:10 6:6 7:8 8:3 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:2 1:1 2:1 3:2 4:3 5:1 6:2 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:3 1:1 2:1 3:2 4:2 5:1 6:1 7:1 8:1 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:2 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:2 1:1 2:1 3:2 4:2 5:1 6:1 7:1 8:1 +0 0:5 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +1 0:9 1:6 2:9 3:2 4:10 5:6 6:2 7:9 8:10 +1 0:7 1:5 2:6 3:10 4:5 5:10 6:7 7:9 8:4 +1 0:10 1:3 2:5 3:1 4:10 5:5 6:3 7:10 8:2 +1 0:2 1:3 2:4 3:4 4:2 5:5 6:2 7:5 8:1 +0 0:4 1:1 2:2 3:1 4:2 5:1 6:3 7:1 8:1 +1 0:8 1:2 2:3 3:1 4:6 5:3 6:7 7:1 8:1 +1 0:10 1:10 2:10 3:10 4:10 5:1 6:8 7:8 8:8 +1 0:7 1:3 2:4 3:4 4:3 5:3 6:3 7:2 8:7 +1 0:10 1:10 2:10 3:8 4:2 5:10 6:4 7:1 8:1 +1 0:1 1:6 2:8 3:10 4:8 5:10 6:5 7:7 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:2 7:3 8:1 +1 0:6 1:5 2:4 3:4 4:3 5:9 6:7 7:8 8:3 +0 0:1 1:3 2:1 3:2 4:2 5:2 6:5 7:3 8:2 +1 0:8 1:6 2:4 3:3 4:5 5:9 6:3 7:1 8:1 +1 0:10 1:3 2:3 3:10 4:2 5:10 6:7 7:3 8:3 +1 0:10 1:10 2:10 3:3 4:10 5:8 6:8 7:1 8:1 +0 0:3 1:3 2:2 3:1 4:2 5:3 6:3 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:5 6:1 7:1 8:1 +0 0:8 1:3 2:3 3:1 4:2 5:2 6:3 7:2 8:1 +1 0:4 1:5 2:5 3:10 4:4 5:10 6:7 7:5 8:8 +0 0:1 1:1 2:1 3:1 4:4 5:3 6:1 7:1 8:1 +0 0:3 1:2 2:1 3:1 4:2 5:2 6:3 7:1 8:1 +0 0:1 1:1 2:2 3:2 4:2 5:1 6:3 7:1 8:1 +0 0:4 1:2 2:1 3:1 4:2 5:2 6:3 7:1 8:1 +1 0:10 1:10 2:10 3:2 4:10 5:10 6:5 7:3 8:3 +1 0:5 1:3 2:5 3:1 4:8 5:10 6:5 7:3 8:1 +1 0:5 1:4 2:6 3:7 4:9 5:7 6:8 7:10 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +1 0:7 1:5 2:3 3:7 4:4 5:10 6:7 7:5 8:5 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +1 0:8 1:3 2:5 3:4 4:5 5:10 6:1 7:6 8:2 +0 0:1 1:1 2:1 3:1 4:10 5:1 6:1 7:1 8:1 +0 0:5 1:1 2:3 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:2 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +1 0:5 1:10 2:8 3:10 4:8 5:10 6:3 7:6 8:3 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:2 7:2 8:1 +0 0:3 1:1 2:1 3:1 4:3 5:1 6:2 7:1 8:1 +0 0:5 1:1 2:1 3:1 4:2 5:2 6:3 7:3 8:1 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:4 1:1 2:2 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:2 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +1 0:9 1:5 2:5 3:4 4:4 5:5 6:4 7:3 8:3 +0 0:1 1:1 2:1 3:1 4:2 5:5 6:1 7:1 8:1 +0 0:2 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +1 0:3 1:4 2:5 3:2 4:6 5:8 6:4 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:3 5:2 6:2 7:1 8:1 +0 0:3 1:1 2:1 3:3 4:8 5:1 6:5 7:8 8:1 +1 0:8 1:8 2:7 3:4 4:10 5:10 6:7 7:8 8:7 +0 0:1 1:1 2:1 3:1 4:1 5:1 6:3 7:1 8:1 +1 0:7 1:2 2:4 3:1 4:6 5:10 6:5 7:4 8:3 +1 0:10 1:10 2:8 3:6 4:4 5:5 6:8 7:10 8:1 +0 0:4 1:1 2:1 3:1 4:2 5:3 6:1 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +1 0:5 1:5 2:5 3:6 4:3 5:10 6:3 7:1 8:1 +0 0:1 1:2 2:2 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:2 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +1 0:9 1:9 2:10 3:3 4:6 5:10 6:7 7:10 8:6 +1 0:10 1:7 2:7 3:4 4:5 5:10 6:5 7:7 8:2 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:3 7:2 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:1 1:1 2:1 3:2 4:1 5:3 6:1 7:1 8:7 +0 0:4 1:1 2:1 3:1 4:2 5:2 6:3 7:2 8:1 +1 0:5 1:6 2:7 3:8 4:8 5:10 6:3 7:10 8:3 +1 0:10 1:8 2:10 3:10 4:6 5:1 6:3 7:1 8:10 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:1 1:1 2:1 3:2 4:1 5:1 6:1 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +1 0:6 1:10 2:10 3:10 4:8 5:10 6:10 7:10 8:7 +1 0:8 1:6 2:5 3:4 4:3 5:10 6:6 7:1 8:1 +1 0:5 1:8 2:7 3:7 4:10 5:10 6:5 7:7 8:1 +0 0:2 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +1 0:5 1:10 2:10 3:3 4:8 5:1 6:5 7:10 8:3 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +1 0:5 1:3 2:3 3:3 4:6 5:10 6:3 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:1 5:1 6:3 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:6 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +1 0:5 1:8 2:8 3:8 4:5 5:10 6:7 7:8 8:1 +1 0:8 1:7 2:6 3:4 4:4 5:10 6:5 7:1 8:1 +0 0:2 1:1 2:1 3:1 4:1 5:1 6:3 7:1 8:1 +1 0:1 1:5 2:8 3:6 4:5 5:8 6:7 7:10 8:1 +1 0:10 1:5 2:6 3:10 4:6 5:10 6:7 7:7 8:10 +1 0:5 1:8 2:4 3:10 4:5 5:8 6:9 7:10 8:1 +0 0:1 1:2 2:3 3:1 4:2 5:1 6:3 7:1 8:1 +1 0:10 1:10 2:10 3:8 4:6 5:8 6:7 7:10 8:1 +1 0:7 1:5 2:10 3:10 4:10 5:10 6:4 7:10 8:3 +0 0:5 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:8 1:4 2:4 3:5 4:4 5:7 6:7 7:8 8:2 +0 0:5 1:1 2:1 3:4 4:2 5:1 6:3 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +1 0:9 1:7 2:7 3:5 4:5 5:10 6:7 7:8 8:3 +1 0:10 1:8 2:8 3:4 4:10 5:10 6:8 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:5 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +1 0:5 1:10 2:10 3:9 4:6 5:10 6:7 7:10 8:5 +1 0:10 1:10 2:9 3:3 4:7 5:5 6:3 7:5 8:1 +0 0:1 1:1 2:1 3:1 4:1 5:1 6:3 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:1 5:1 6:3 7:1 8:1 +0 0:5 1:1 2:1 3:1 4:1 5:1 6:3 7:1 8:1 +1 0:8 1:10 2:10 3:10 4:5 5:10 6:8 7:10 8:6 +1 0:8 1:10 2:8 3:8 4:4 5:8 6:7 7:7 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +1 0:10 1:10 2:10 3:10 4:7 5:10 6:7 7:10 8:4 +1 0:10 1:10 2:10 3:10 4:3 5:10 6:10 7:6 8:1 +1 0:8 1:7 2:8 3:7 4:5 5:5 6:5 7:10 8:2 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +1 0:6 1:10 2:7 3:7 4:6 5:4 6:8 7:10 8:2 +0 0:6 1:1 2:3 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:1 1:1 2:1 3:2 4:2 5:1 6:3 7:1 8:1 +1 0:10 1:6 2:4 3:3 4:10 5:10 6:9 7:10 8:1 +1 0:4 1:1 2:1 3:3 4:1 5:5 6:2 7:1 8:1 +1 0:7 1:5 2:6 3:3 4:3 5:8 6:7 7:4 8:1 +1 0:10 1:5 2:5 3:6 4:3 5:10 6:7 7:9 8:2 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +1 0:10 1:5 2:7 3:4 4:4 5:10 6:8 7:9 8:1 +1 0:8 1:9 2:9 3:5 4:3 5:5 6:7 7:7 8:1 +0 0:1 1:1 2:1 3:1 4:1 5:1 6:3 7:1 8:1 +1 0:10 1:10 2:10 3:3 4:10 5:10 6:9 7:10 8:1 +1 0:7 1:4 2:7 3:4 4:3 5:7 6:7 7:6 8:1 +1 0:6 1:8 2:7 3:5 4:6 5:8 6:8 7:9 8:2 +0 0:8 1:4 2:6 3:3 4:3 5:1 6:4 7:3 8:1 +1 0:10 1:4 2:5 3:5 4:5 5:10 6:4 7:1 8:1 +0 0:3 1:3 2:2 3:1 4:3 5:1 6:3 7:6 8:1 +1 0:10 1:8 2:8 3:2 4:8 5:10 6:4 7:8 8:10 +1 0:9 1:8 2:8 3:5 4:6 5:2 6:4 7:10 8:4 +1 0:8 1:10 2:10 3:8 4:6 5:9 6:3 7:10 8:10 +1 0:10 1:4 2:3 3:2 4:3 5:10 6:5 7:3 8:2 +0 0:5 1:1 2:3 3:3 4:2 5:2 6:2 7:3 8:1 +0 0:3 1:1 2:1 3:3 4:1 5:1 6:3 7:1 8:1 +0 0:2 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:5 6:5 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:5 1:1 2:1 3:2 4:2 5:2 6:3 7:1 8:1 +1 0:8 1:10 2:10 3:8 4:5 5:10 6:7 7:8 8:1 +1 0:8 1:4 2:4 3:1 4:2 5:9 6:3 7:3 8:1 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:3 7:6 8:1 +0 0:1 1:2 2:2 3:1 4:2 5:1 6:1 7:1 8:1 +1 0:10 1:4 2:4 3:10 4:2 5:10 6:5 7:3 8:3 +0 0:6 1:3 2:3 3:5 4:3 5:10 6:3 7:5 8:3 +1 0:6 1:10 2:10 3:2 4:8 5:10 6:7 7:3 8:3 +1 0:9 1:10 2:10 3:1 4:10 5:8 6:3 7:3 8:1 +1 0:5 1:6 2:6 3:2 4:4 5:10 6:3 7:6 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:5 1:7 2:7 3:1 4:5 5:8 6:3 7:4 8:1 +1 0:10 1:5 2:8 3:10 4:3 5:10 6:5 7:1 8:3 +1 0:5 1:10 2:10 3:6 4:10 5:10 6:10 7:6 8:5 +1 0:8 1:8 2:9 3:4 4:5 5:10 6:7 7:8 8:1 +1 0:10 1:4 2:4 3:10 4:6 5:10 6:5 7:5 8:1 +1 0:7 1:9 2:4 3:10 4:10 5:3 6:5 7:3 8:3 +0 0:5 1:1 2:4 3:1 4:2 5:1 6:3 7:2 8:1 +1 0:10 1:10 2:6 3:3 4:3 5:10 6:4 7:3 8:2 +1 0:3 1:3 2:5 3:2 4:3 5:10 6:7 7:1 8:1 +1 0:10 1:8 2:8 3:2 4:3 5:4 6:8 7:7 8:8 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +1 0:8 1:4 2:7 3:1 4:3 5:10 6:3 7:9 8:2 +0 0:5 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +1 0:3 1:3 2:5 3:2 4:3 5:10 6:7 7:1 8:1 +1 0:7 1:2 2:4 3:1 4:3 5:4 6:3 7:3 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:3 7:2 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +1 0:10 1:5 2:7 3:3 4:3 5:7 6:3 7:3 8:8 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:2 1:1 2:1 3:2 4:2 5:1 6:3 7:1 8:1 +1 0:1 1:4 2:3 3:10 4:4 5:10 6:5 7:6 8:1 +1 0:10 1:4 2:6 3:1 4:2 5:10 6:5 7:3 8:1 +1 0:7 1:4 2:5 3:10 4:2 5:10 6:3 7:8 8:2 +1 0:8 1:10 2:10 3:10 4:8 5:10 6:10 7:7 8:3 +1 0:10 1:10 2:10 3:10 4:10 5:10 6:4 7:10 8:10 +0 0:3 1:1 2:1 3:1 4:3 5:1 6:2 7:1 8:1 +1 0:6 1:1 2:3 3:1 4:4 5:5 6:5 7:10 8:1 +1 0:5 1:6 2:6 3:8 4:6 5:10 6:4 7:10 8:4 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +1 0:10 1:4 2:4 3:6 4:2 5:10 6:2 7:3 8:1 +1 0:5 1:5 2:7 3:8 4:6 5:10 6:7 7:4 8:1 +0 0:5 1:3 2:4 3:3 4:4 5:5 6:4 7:7 8:1 +0 0:8 1:2 2:1 3:1 4:5 5:1 6:1 7:1 8:1 +1 0:9 1:1 2:2 3:6 4:4 5:10 6:7 7:7 8:2 +1 0:8 1:4 2:10 3:5 4:4 5:4 6:7 7:10 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +1 0:10 1:10 2:10 3:7 4:9 5:10 6:7 7:10 8:10 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +1 0:8 1:3 2:4 3:9 4:3 5:10 6:3 7:3 8:1 +1 0:10 1:8 2:4 3:4 4:4 5:10 6:3 7:10 8:4 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +1 0:7 1:8 2:7 3:6 4:4 5:3 6:8 7:8 8:4 +0 0:3 1:1 2:1 3:1 4:2 5:5 6:5 7:1 8:1 +0 0:2 1:1 2:1 3:1 4:3 5:1 6:2 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +1 0:8 1:6 2:4 3:10 4:10 5:1 6:3 7:5 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:1 5:1 6:2 7:1 8:1 +1 0:5 1:5 2:5 3:2 4:5 5:10 6:4 7:3 8:1 +1 0:6 1:8 2:7 3:8 4:6 5:8 6:8 7:9 8:1 +0 0:1 1:1 2:1 3:1 4:5 5:1 6:3 7:1 8:1 +0 0:4 1:4 2:4 3:4 4:6 5:5 6:7 7:3 8:1 +1 0:7 1:6 2:3 3:2 4:5 5:10 6:7 7:4 8:6 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +1 0:5 1:4 2:6 3:10 4:2 5:10 6:4 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:3 1:2 2:2 3:1 4:2 5:1 6:2 7:3 8:1 +1 0:10 1:1 2:1 3:1 4:2 5:10 6:5 7:4 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +1 0:8 1:10 2:3 3:2 4:6 5:4 6:3 7:10 8:1 +1 0:10 1:4 2:6 3:4 4:5 5:10 6:7 7:1 8:1 +1 0:10 1:4 2:7 3:2 4:2 5:8 6:6 7:1 8:1 +0 0:5 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:2 +0 0:5 1:2 2:2 3:2 4:2 5:1 6:2 7:2 8:1 +1 0:5 1:4 2:6 3:6 4:4 5:10 6:4 7:3 8:1 +1 0:8 1:6 2:7 3:3 4:3 5:10 6:3 7:4 8:2 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +1 0:6 1:5 2:5 3:8 4:4 5:10 6:3 7:4 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:1 5:1 6:2 7:1 8:1 +1 0:8 1:5 2:5 3:5 4:2 5:10 6:4 7:3 8:1 +1 0:10 1:3 2:3 3:1 4:2 5:10 6:7 7:6 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:2 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +1 0:7 1:6 2:4 3:8 4:10 5:10 6:9 7:5 8:3 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:5 1:2 2:2 3:2 4:3 5:1 6:1 7:3 8:1 +0 0:1 1:1 2:1 3:1 4:1 5:1 6:1 7:3 8:1 +1 0:3 1:4 2:4 3:10 4:5 5:1 6:3 7:3 8:1 +1 0:4 1:2 2:3 3:5 4:3 5:8 6:7 7:6 8:1 +0 0:5 1:1 2:1 3:3 4:2 5:1 6:1 7:1 8:1 +0 0:2 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:3 1:4 2:5 3:3 4:7 5:3 6:4 7:6 8:1 +1 0:2 1:7 2:10 3:10 4:7 5:10 6:4 7:9 8:4 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:4 1:1 2:1 3:1 4:3 5:1 6:2 7:2 8:1 +1 0:5 1:3 2:3 3:1 4:3 5:3 6:3 7:3 8:3 +1 0:8 1:10 2:10 3:7 4:10 5:10 6:7 7:3 8:8 +1 0:8 1:10 2:5 3:3 4:8 5:4 6:4 7:10 8:3 +1 0:10 1:3 2:5 3:4 4:3 5:7 6:3 7:5 8:3 +1 0:6 1:10 2:10 3:10 4:10 5:10 6:8 7:10 8:10 +1 0:3 1:10 2:3 3:10 4:6 5:10 6:5 7:1 8:4 +0 0:3 1:2 2:2 3:1 4:4 5:3 6:2 7:1 8:1 +0 0:4 1:4 2:4 3:2 4:2 5:3 6:2 7:1 8:1 +0 0:2 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:2 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +1 0:6 1:10 2:10 3:10 4:8 5:10 6:7 7:10 8:7 +1 0:5 1:8 2:8 3:10 4:5 5:10 6:8 7:10 8:3 +0 0:1 1:1 2:3 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:1 1:1 2:3 3:1 4:1 5:1 6:2 7:1 8:1 +0 0:4 1:3 2:2 3:1 4:3 5:1 6:2 7:1 8:1 +0 0:1 1:1 2:3 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:4 1:1 2:2 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:5 1:1 2:1 3:2 4:2 5:1 6:2 7:1 8:1 +0 0:3 1:1 2:2 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:1 5:1 6:2 7:1 8:1 +0 0:3 1:1 2:1 3:4 4:3 5:1 6:2 7:2 8:1 +0 0:5 1:3 2:4 3:1 4:4 5:1 6:3 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +1 0:10 1:6 2:3 3:6 4:4 5:10 6:7 7:8 8:4 +0 0:3 1:2 2:2 3:2 4:2 5:1 6:3 7:2 8:1 +0 0:2 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:2 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:3 1:3 2:2 3:2 4:3 5:1 6:1 7:2 8:3 +1 0:7 1:6 2:6 3:3 4:2 5:10 6:7 7:1 8:1 +0 0:5 1:3 2:3 3:2 4:3 5:1 6:3 7:1 8:1 +0 0:2 1:1 2:1 3:1 4:2 5:1 6:2 7:2 8:1 +0 0:5 1:1 2:1 3:1 4:3 5:2 6:2 7:2 8:1 +0 0:1 1:1 2:1 3:2 4:2 5:1 6:2 7:1 8:1 +1 0:10 1:8 2:7 3:4 4:3 5:10 6:7 7:9 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:1 5:1 6:1 7:1 8:1 +0 0:1 1:2 2:3 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:3 1:2 2:1 3:1 4:2 5:1 6:2 7:2 8:1 +0 0:1 1:2 2:3 3:1 4:2 5:1 6:1 7:1 8:1 +1 0:3 1:10 2:8 3:7 4:6 5:9 6:9 7:3 8:8 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:5 1:3 2:3 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:4 6:1 7:1 8:1 +0 0:1 1:2 2:1 3:3 4:2 5:1 6:1 7:2 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:4 1:2 2:2 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:2 1:3 2:2 3:2 4:2 5:2 6:3 7:1 8:1 +0 0:3 1:1 2:2 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +1 0:10 1:10 2:10 3:6 4:8 5:4 6:8 7:5 8:1 +0 0:5 1:1 2:2 3:1 4:2 5:1 6:3 7:1 8:1 +1 0:8 1:5 2:6 3:2 4:3 5:10 6:6 7:6 8:1 +0 0:3 1:3 2:2 3:6 4:3 5:3 6:3 7:5 8:1 +1 0:8 1:7 2:8 3:5 4:10 5:10 6:7 7:2 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:5 1:2 2:2 3:2 4:2 5:2 6:3 7:2 8:2 +0 0:2 1:3 2:1 3:1 4:5 5:1 6:1 7:1 8:1 +0 0:3 1:2 2:2 3:3 4:2 5:3 6:3 7:1 8:1 +1 0:10 1:10 2:10 3:7 4:10 5:10 6:8 7:2 8:1 +0 0:4 1:3 2:3 3:1 4:2 5:1 6:3 7:3 8:1 +0 0:5 1:1 2:3 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +1 0:9 1:10 2:10 3:10 4:10 5:10 6:10 7:10 8:1 +0 0:5 1:3 2:6 3:1 4:2 5:1 6:1 7:1 8:1 +1 0:8 1:7 2:8 3:2 4:4 5:2 6:5 7:10 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:2 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:1 1:3 2:1 3:1 4:2 5:1 6:2 7:2 8:1 +0 0:5 1:1 2:1 3:3 4:4 5:1 6:3 7:2 8:1 +0 0:5 1:1 2:1 3:1 4:2 5:1 6:2 7:2 8:1 +0 0:3 1:2 2:2 3:3 4:2 5:1 6:1 7:1 8:1 +0 0:6 1:9 2:7 3:5 4:5 5:8 6:4 7:2 8:1 +1 0:10 1:8 2:10 3:1 4:3 5:10 6:5 7:1 8:1 +1 0:10 1:10 2:10 3:1 4:6 5:1 6:2 7:8 8:1 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:4 1:1 2:3 3:3 4:2 5:1 6:1 7:1 8:1 +0 0:5 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +1 0:10 1:4 2:3 3:10 4:4 5:10 6:10 7:1 8:1 +0 0:5 1:2 2:2 3:4 4:2 5:4 6:1 7:1 8:1 +0 0:1 1:1 2:1 3:3 4:2 5:3 6:1 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:2 6:1 7:1 8:1 +0 0:5 1:1 2:1 3:6 4:3 5:1 6:2 7:1 8:1 +0 0:2 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:5 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:1 5:1 6:1 7:1 8:1 +1 0:5 1:7 2:9 3:8 4:6 5:10 6:8 7:10 8:1 +0 0:4 1:1 2:1 3:3 4:1 5:1 6:2 7:1 8:1 +0 0:5 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:3 1:1 2:1 3:3 4:2 5:1 6:1 7:1 8:1 +1 0:4 1:5 2:5 3:8 4:6 5:10 6:10 7:7 8:1 +0 0:2 1:3 2:1 3:1 4:3 5:1 6:1 7:1 8:1 +1 0:10 1:2 2:2 3:1 4:2 5:6 6:1 7:1 8:2 +1 0:10 1:6 2:5 3:8 4:5 5:10 6:8 7:6 8:1 +1 0:8 1:8 2:9 3:6 4:6 5:3 6:10 7:10 8:1 +0 0:5 1:1 2:2 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:5 1:1 2:3 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:5 1:1 2:1 3:3 4:2 5:1 6:1 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:5 6:1 7:1 8:1 +0 0:6 1:1 2:1 3:3 4:2 5:1 6:1 7:1 8:1 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:1 7:2 8:1 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +1 0:10 1:9 2:8 3:7 4:6 5:4 6:7 7:10 8:3 +1 0:10 1:6 2:6 3:2 4:4 5:10 6:9 7:7 8:1 +1 0:6 1:6 2:6 3:5 4:4 5:10 6:7 7:6 8:2 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:1 1:1 2:2 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:1 5:1 6:2 7:1 8:1 +0 0:6 1:1 2:1 3:3 4:2 5:1 6:1 7:1 8:1 +0 0:6 1:1 2:1 3:1 4:1 5:1 6:1 7:1 8:1 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:5 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:4 1:1 2:2 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:5 1:2 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +1 0:4 1:8 2:7 3:10 4:4 5:10 6:7 7:5 8:1 +0 0:5 1:1 2:1 3:1 4:1 5:1 6:1 7:1 8:1 +0 0:5 1:3 2:2 3:4 4:2 5:1 6:1 7:1 8:1 +1 0:9 1:10 2:10 3:10 4:10 5:5 6:10 7:10 8:10 +1 0:8 1:7 2:8 3:5 4:5 5:10 6:9 7:10 8:1 +0 0:5 1:1 2:2 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:1 1:1 2:1 3:3 4:1 5:3 6:1 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:1 5:1 6:2 7:1 8:1 +1 0:10 1:10 2:10 3:10 4:6 5:10 6:8 7:1 8:5 +1 0:3 1:6 2:4 3:10 4:3 5:3 6:3 7:4 8:1 +1 0:6 1:3 2:2 3:1 4:3 5:4 6:4 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +1 0:5 1:8 2:9 3:4 4:3 5:10 6:7 7:1 8:1 +0 0:4 1:1 2:1 3:1 4:1 5:1 6:2 7:1 8:1 +1 0:5 1:10 2:10 3:10 4:6 5:10 6:6 7:5 8:2 +0 0:5 1:1 2:2 3:10 4:4 5:5 6:2 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:1 5:1 6:2 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:1 5:1 6:1 7:1 8:1 +0 0:4 1:2 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:6 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:4 1:1 2:1 3:2 4:2 5:1 6:2 7:1 8:1 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:3 1:3 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +1 0:8 1:10 2:10 3:10 4:7 5:5 6:4 7:8 8:7 +0 0:1 1:1 2:1 3:1 4:2 5:4 6:1 7:1 8:1 +0 0:5 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:2 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:5 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:5 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:1 5:1 6:2 7:1 8:1 +1 0:6 1:6 2:7 3:10 4:3 5:10 6:8 7:10 8:2 +1 0:4 1:10 2:4 3:7 4:3 5:10 6:9 7:10 8:1 +0 0:1 1:1 2:1 3:1 4:1 5:1 6:1 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:1 5:1 6:2 7:1 8:1 +0 0:3 1:1 2:2 3:2 4:2 5:1 6:1 7:1 8:1 +1 0:4 1:7 2:8 3:3 4:4 5:10 6:9 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:3 5:1 6:1 7:1 8:1 +0 0:4 1:1 2:1 3:1 4:3 5:1 6:1 7:1 8:1 +1 0:10 1:4 2:5 3:4 4:3 5:5 6:7 7:3 8:1 +1 0:7 1:5 2:6 3:10 4:4 5:10 6:5 7:3 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:3 1:1 2:1 3:2 4:2 5:1 6:1 7:1 8:1 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:6 1:1 2:3 3:2 4:2 5:1 6:1 7:1 8:1 +0 0:4 1:1 2:1 3:1 4:1 5:1 6:2 7:1 8:1 +1 0:7 1:4 2:4 3:3 4:4 5:10 6:6 7:9 8:1 +0 0:4 1:2 2:2 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:1 5:1 6:3 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:2 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:1 1:1 2:3 3:2 4:2 5:1 6:3 7:1 8:1 +0 0:5 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:5 1:1 2:2 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:6 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:5 1:1 2:1 3:1 4:2 5:2 6:2 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:5 1:3 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:2 1:1 2:3 3:2 4:2 5:1 6:2 7:1 8:1 +0 0:5 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +1 0:6 1:10 2:10 3:10 4:4 5:10 6:7 7:10 8:1 +0 0:2 1:1 2:1 3:1 4:1 5:1 6:1 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:1 5:1 6:1 7:1 8:1 +1 0:7 1:8 2:3 3:7 4:4 5:5 6:7 7:8 8:2 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:3 1:2 2:2 3:2 4:2 5:1 6:4 7:2 8:1 +0 0:4 1:4 2:2 3:1 4:2 5:5 6:2 7:1 8:2 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:4 1:3 2:1 3:1 4:2 5:1 6:4 7:8 8:1 +0 0:5 1:2 2:2 3:2 4:1 5:1 6:2 7:1 8:1 +0 0:5 1:1 2:1 3:3 4:2 5:1 6:1 7:1 8:1 +0 0:2 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:5 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:5 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:5 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:3 7:2 8:1 +1 0:5 1:7 2:10 3:10 4:5 5:10 6:10 7:10 8:1 +0 0:3 1:1 2:2 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:4 1:1 2:1 3:1 4:2 5:3 6:2 7:1 8:1 +1 0:8 1:4 2:4 3:1 4:6 5:10 6:2 7:5 8:2 +1 0:10 1:10 2:8 3:10 4:6 5:5 6:10 7:3 8:1 +1 0:8 1:10 2:4 3:4 4:8 5:10 6:8 7:2 8:1 +1 0:7 1:6 2:10 3:5 4:3 5:10 6:9 7:10 8:2 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +1 0:10 1:9 2:7 3:3 4:4 5:2 6:7 7:7 8:1 +0 0:5 1:1 2:2 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:5 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:5 1:1 2:2 3:1 4:2 5:1 6:2 7:1 8:1 +1 0:5 1:7 2:10 3:6 4:5 5:10 6:7 7:5 8:1 +1 0:6 1:10 2:5 3:5 4:4 5:10 6:6 7:10 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:5 1:1 2:1 3:6 4:3 5:1 6:1 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +1 0:8 1:10 2:10 3:10 4:6 5:10 6:10 7:10 8:1 +0 0:5 1:1 2:1 3:1 4:2 5:1 6:2 7:2 8:1 +1 0:9 1:8 2:8 3:9 4:6 5:3 6:4 7:1 8:1 +0 0:5 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +1 0:4 1:10 2:8 3:5 4:4 5:1 6:10 7:1 8:1 +1 0:2 1:5 2:7 3:6 4:4 5:10 6:7 7:6 8:1 +1 0:10 1:3 2:4 3:5 4:3 5:10 6:4 7:1 8:1 +0 0:5 1:1 2:2 3:1 4:2 5:1 6:1 7:1 8:1 +1 0:4 1:8 2:6 3:3 4:4 5:10 6:7 7:1 8:1 +0 0:5 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:4 1:1 2:2 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:5 1:1 2:3 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:5 1:2 2:4 3:1 4:1 5:1 6:1 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:1 5:1 6:2 7:1 8:1 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +1 0:5 1:4 2:6 3:8 4:4 5:1 6:8 7:10 8:1 +1 0:5 1:3 2:2 3:8 4:5 5:10 6:8 7:1 8:2 +1 0:10 1:5 2:10 3:3 4:5 5:8 6:7 7:8 8:3 +0 0:4 1:1 2:1 3:2 4:2 5:1 6:1 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +1 0:5 1:10 2:10 3:10 4:10 5:10 6:10 7:1 8:1 +0 0:5 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +1 0:10 1:4 2:3 3:10 4:3 5:10 6:7 7:1 8:2 +1 0:5 1:10 2:10 3:10 4:5 5:2 6:8 7:5 8:1 +1 0:8 1:10 2:10 3:10 4:6 5:10 6:10 7:10 8:10 +0 0:2 1:3 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:2 1:1 2:1 3:1 4:1 5:1 6:2 7:1 8:1 +0 0:4 1:1 2:3 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:5 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:6 1:3 2:3 3:3 4:3 5:2 6:6 7:1 8:1 +0 0:7 1:1 2:2 3:3 4:2 5:1 6:2 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:5 1:1 2:1 3:2 4:1 5:1 6:2 7:1 8:1 +0 0:3 1:1 2:3 3:1 4:3 5:4 6:1 7:1 8:1 +1 0:4 1:6 2:6 3:5 4:7 5:6 6:7 7:7 8:3 +0 0:2 1:1 2:1 3:1 4:2 5:5 6:1 7:1 8:1 +0 0:2 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:6 1:2 2:3 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:5 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +1 0:8 1:7 2:4 3:4 4:5 5:3 6:5 7:10 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:3 1:1 2:4 3:1 4:2 5:1 6:1 7:1 8:1 +1 0:10 1:10 2:7 3:8 4:7 5:1 6:10 7:10 8:3 +0 0:4 1:2 2:4 3:3 4:2 5:2 6:2 7:1 8:1 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:5 1:1 2:1 3:3 4:2 5:1 6:1 7:1 8:1 +0 0:4 1:1 2:1 3:3 4:2 5:1 6:1 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:2 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:1 1:2 2:2 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:1 1:1 2:1 3:3 4:2 5:1 6:1 7:1 8:1 +1 0:5 1:10 2:10 3:10 4:10 5:2 6:10 7:10 8:10 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:3 1:1 2:1 3:2 4:3 5:4 6:1 7:1 8:1 +0 0:1 1:2 2:1 3:3 4:2 5:1 6:2 7:1 8:1 +0 0:5 1:1 2:1 3:1 4:2 5:1 6:2 7:2 8:1 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:5 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:5 1:4 2:5 3:1 4:8 5:1 6:3 7:6 8:1 +1 0:7 1:8 2:8 3:7 4:3 5:10 6:7 7:2 8:3 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:1 1:1 2:3 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:1 1:1 2:3 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:3 1:1 2:1 3:3 4:2 5:1 6:2 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:5 1:2 2:2 3:2 4:2 5:1 6:1 7:1 8:2 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +1 0:5 1:7 2:4 3:1 4:6 5:1 6:7 7:10 8:3 +1 0:5 1:10 2:10 3:8 4:5 5:5 6:7 7:10 8:1 +1 0:3 1:10 2:7 3:8 4:5 5:8 6:7 7:4 8:1 +0 0:3 1:2 2:1 3:2 4:2 5:1 6:3 7:1 8:1 +0 0:2 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 +0 0:5 1:3 2:2 3:1 4:3 5:1 6:1 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:4 1:1 2:4 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:1 1:1 2:2 3:1 4:2 5:1 6:2 7:1 8:1 +0 0:5 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:2 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +1 0:10 1:10 2:10 3:10 4:5 5:10 6:10 7:10 8:7 +1 0:5 1:10 2:10 3:10 4:4 5:10 6:5 7:6 8:3 +0 0:5 1:1 2:1 3:1 4:2 5:1 6:3 7:2 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:2 7:3 8:1 +0 0:4 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:1 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:8 +0 0:1 1:1 2:1 3:3 4:2 5:1 6:1 7:1 8:1 +1 0:5 1:10 2:10 3:5 4:4 5:5 6:4 7:4 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +0 0:3 1:1 2:1 3:1 4:2 5:1 6:2 7:1 8:2 +0 0:3 1:1 2:1 3:1 4:3 5:2 6:1 7:1 8:1 +0 0:2 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 +1 0:5 1:10 2:10 3:3 4:7 5:3 6:8 7:10 8:2 +1 0:4 1:8 2:6 3:4 4:3 5:4 6:10 7:6 8:1 +1 0:4 1:8 2:8 3:5 4:4 5:5 6:10 7:4 8:1 diff --git a/test/BaselineOutput/Common/Command/CommandSaveDataSvmLight-data.txt b/test/BaselineOutput/Common/Command/CommandSaveDataSvmLight-data.txt new file mode 100644 index 0000000000..98617818a5 --- /dev/null +++ b/test/BaselineOutput/Common/Command/CommandSaveDataSvmLight-data.txt @@ -0,0 +1,683 @@ +-1 1:5 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:5 2:4 3:4 4:5 5:7 6:10 7:3 8:2 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:2 7:3 8:1 9:1 +-1 1:6 2:8 3:8 4:1 5:3 6:4 7:3 8:7 9:1 +-1 1:4 2:1 3:1 4:3 5:2 6:1 7:3 8:1 9:1 +1 1:8 2:10 3:10 4:8 5:7 6:10 7:9 8:7 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:10 7:3 8:1 9:1 +-1 1:2 2:1 3:2 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:2 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:5 +-1 1:4 2:2 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:1 6:1 7:3 8:1 9:1 +-1 1:2 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +1 1:5 2:3 3:3 4:3 5:2 6:3 7:4 8:4 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:3 7:3 8:1 9:1 +1 1:8 2:7 3:5 4:10 5:7 6:9 7:5 8:5 9:4 +1 1:7 2:4 3:6 4:4 5:6 6:1 7:4 8:3 9:1 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +1 1:10 2:7 3:7 4:6 5:4 6:10 7:4 8:1 9:2 +-1 1:6 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +1 1:7 2:3 3:2 4:10 5:5 6:10 7:5 8:4 9:4 +1 1:10 2:5 3:5 4:3 5:6 6:7 7:7 8:10 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +1 1:5 2:2 3:3 4:4 5:2 6:7 7:3 8:6 9:1 +-1 1:3 2:2 3:1 4:1 5:1 6:1 7:2 8:1 9:1 +-1 1:5 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:2 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:1 2:1 3:3 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:1 6:1 7:2 8:1 9:1 +-1 1:2 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +1 1:10 2:7 3:7 4:3 5:8 6:5 7:7 8:4 9:3 +-1 1:2 2:1 3:1 4:2 5:2 6:1 7:3 8:1 9:1 +-1 1:3 2:1 3:2 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:2 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +1 1:10 2:10 3:10 4:8 5:6 6:1 7:8 8:9 9:1 +-1 1:6 2:2 3:1 4:1 5:1 6:1 7:7 8:1 9:1 +1 1:5 2:4 3:4 4:9 5:2 6:10 7:5 8:6 9:1 +1 1:2 2:5 3:3 4:3 5:6 6:7 7:7 8:5 9:1 +1 1:10 2:4 3:3 4:1 5:3 6:3 7:6 8:5 9:2 +1 1:6 2:10 3:10 4:2 5:8 6:10 7:7 8:3 9:3 +1 1:5 2:6 3:5 4:6 5:10 6:1 7:3 8:1 9:1 +1 1:10 2:10 3:10 4:4 5:8 6:1 7:8 8:10 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:2 +1 1:3 2:7 3:7 4:4 5:4 6:9 7:4 8:8 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:4 2:1 3:1 4:3 5:2 6:1 7:3 8:1 9:1 +1 1:7 2:8 3:7 4:2 5:4 6:8 7:3 8:8 9:2 +1 1:9 2:5 3:8 4:1 5:2 6:3 7:2 8:1 9:5 +1 1:5 2:3 3:3 4:4 5:2 6:4 7:3 8:4 9:1 +1 1:10 2:3 3:6 4:2 5:3 6:5 7:4 8:10 9:2 +1 1:5 2:5 3:5 4:8 5:10 6:8 7:7 8:3 9:7 +1 1:10 2:5 3:5 4:6 5:8 6:8 7:7 8:1 9:1 +1 1:10 2:6 3:6 4:3 5:4 6:5 7:3 8:6 9:1 +1 1:8 2:10 3:10 4:1 5:3 6:6 7:3 8:9 9:1 +1 1:8 2:2 3:4 4:1 5:5 6:1 7:5 8:4 9:4 +1 1:5 2:2 3:3 4:1 5:6 6:10 7:5 8:1 9:1 +1 1:9 2:5 3:5 4:2 5:2 6:2 7:5 8:1 9:1 +1 1:5 2:3 3:5 4:5 5:3 6:3 7:4 8:10 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:2 7:2 8:1 9:1 +1 1:9 2:10 3:10 4:1 5:10 6:8 7:3 8:3 9:1 +1 1:6 2:3 3:4 4:1 5:5 6:2 7:3 8:9 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +1 1:10 2:4 3:2 4:1 5:3 6:2 7:4 8:3 9:10 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +1 1:5 2:3 3:4 4:1 5:8 6:10 7:4 8:9 9:1 +1 1:8 2:3 3:8 4:3 5:4 6:9 7:8 8:9 9:8 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:3 8:2 9:1 +-1 1:5 2:1 3:3 4:1 5:2 6:1 7:2 8:1 9:1 +1 1:6 2:10 3:2 4:8 5:10 6:2 7:7 8:8 9:10 +-1 1:1 2:3 3:3 4:2 5:2 6:1 7:7 8:2 9:1 +1 1:9 2:4 3:5 4:10 5:6 6:10 7:4 8:8 9:1 +1 1:10 2:6 3:4 4:1 5:3 6:4 7:3 8:2 9:3 +-1 1:1 2:1 3:2 4:1 5:2 6:2 7:4 8:2 9:1 +-1 1:1 2:1 3:4 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:5 2:3 3:1 4:2 5:2 6:1 7:2 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:3 7:3 8:1 9:1 +-1 1:2 2:1 3:1 4:1 5:3 6:1 7:2 8:1 9:1 +-1 1:2 2:2 3:2 4:1 5:1 6:1 7:7 8:1 9:1 +-1 1:4 2:1 3:1 4:2 5:2 6:1 7:2 8:1 9:1 +-1 1:5 2:2 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:2 7:7 8:1 9:1 +1 1:3 2:5 3:7 4:8 5:8 6:9 7:7 8:10 9:7 +1 1:5 2:10 3:6 4:1 5:10 6:4 7:4 8:10 9:10 +1 1:3 2:3 3:6 4:4 5:5 6:8 7:4 8:4 9:1 +1 1:3 2:6 3:6 4:6 5:5 6:10 7:6 8:8 9:3 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:2 2:1 3:1 4:2 5:3 6:1 7:2 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:3 2:1 3:1 4:2 5:2 6:1 7:1 8:1 9:1 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:2 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:2 2:1 3:1 4:2 5:2 6:1 7:1 8:1 9:1 +-1 1:5 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +1 1:9 2:6 3:9 4:2 5:10 6:6 7:2 8:9 9:10 +1 1:7 2:5 3:6 4:10 5:5 6:10 7:7 8:9 9:4 +1 1:10 2:3 3:5 4:1 5:10 6:5 7:3 8:10 9:2 +1 1:2 2:3 3:4 4:4 5:2 6:5 7:2 8:5 9:1 +-1 1:4 2:1 3:2 4:1 5:2 6:1 7:3 8:1 9:1 +1 1:8 2:2 3:3 4:1 5:6 6:3 7:7 8:1 9:1 +1 1:10 2:10 3:10 4:10 5:10 6:1 7:8 8:8 9:8 +1 1:7 2:3 3:4 4:4 5:3 6:3 7:3 8:2 9:7 +1 1:10 2:10 3:10 4:8 5:2 6:10 7:4 8:1 9:1 +1 1:1 2:6 3:8 4:10 5:8 6:10 7:5 8:7 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:2 8:3 9:1 +1 1:6 2:5 3:4 4:4 5:3 6:9 7:7 8:8 9:3 +-1 1:1 2:3 3:1 4:2 5:2 6:2 7:5 8:3 9:2 +1 1:8 2:6 3:4 4:3 5:5 6:9 7:3 8:1 9:1 +1 1:10 2:3 3:3 4:10 5:2 6:10 7:7 8:3 9:3 +1 1:10 2:10 3:10 4:3 5:10 6:8 7:8 8:1 9:1 +-1 1:3 2:3 3:2 4:1 5:2 6:3 7:3 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:5 7:1 8:1 9:1 +-1 1:8 2:3 3:3 4:1 5:2 6:2 7:3 8:2 9:1 +1 1:4 2:5 3:5 4:10 5:4 6:10 7:7 8:5 9:8 +-1 1:1 2:1 3:1 4:1 5:4 6:3 7:1 8:1 9:1 +-1 1:3 2:2 3:1 4:1 5:2 6:2 7:3 8:1 9:1 +-1 1:1 2:1 3:2 4:2 5:2 6:1 7:3 8:1 9:1 +-1 1:4 2:2 3:1 4:1 5:2 6:2 7:3 8:1 9:1 +1 1:10 2:10 3:10 4:2 5:10 6:10 7:5 8:3 9:3 +1 1:5 2:3 3:5 4:1 5:8 6:10 7:5 8:3 9:1 +1 1:5 2:4 3:6 4:7 5:9 6:7 7:8 8:10 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +1 1:7 2:5 3:3 4:7 5:4 6:10 7:7 8:5 9:5 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +1 1:8 2:3 3:5 4:4 5:5 6:10 7:1 8:6 9:2 +-1 1:1 2:1 3:1 4:1 5:10 6:1 7:1 8:1 9:1 +-1 1:5 2:1 3:3 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:2 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +1 1:5 2:10 3:8 4:10 5:8 6:10 7:3 8:6 9:3 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:2 8:2 9:1 +-1 1:3 2:1 3:1 4:1 5:3 6:1 7:2 8:1 9:1 +-1 1:5 2:1 3:1 4:1 5:2 6:2 7:3 8:3 9:1 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:4 2:1 3:2 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:2 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +1 1:9 2:5 3:5 4:4 5:4 6:5 7:4 8:3 9:3 +-1 1:1 2:1 3:1 4:1 5:2 6:5 7:1 8:1 9:1 +-1 1:2 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +1 1:3 2:4 3:5 4:2 5:6 6:8 7:4 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:3 6:2 7:2 8:1 9:1 +-1 1:3 2:1 3:1 4:3 5:8 6:1 7:5 8:8 9:1 +1 1:8 2:8 3:7 4:4 5:10 6:10 7:7 8:8 9:7 +-1 1:1 2:1 3:1 4:1 5:1 6:1 7:3 8:1 9:1 +1 1:7 2:2 3:4 4:1 5:6 6:10 7:5 8:4 9:3 +1 1:10 2:10 3:8 4:6 5:4 6:5 7:8 8:10 9:1 +-1 1:4 2:1 3:1 4:1 5:2 6:3 7:1 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +1 1:5 2:5 3:5 4:6 5:3 6:10 7:3 8:1 9:1 +-1 1:1 2:2 3:2 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:2 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +1 1:9 2:9 3:10 4:3 5:6 6:10 7:7 8:10 9:6 +1 1:10 2:7 3:7 4:4 5:5 6:10 7:5 8:7 9:2 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:3 8:2 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:1 2:1 3:1 4:2 5:1 6:3 7:1 8:1 9:7 +-1 1:4 2:1 3:1 4:1 5:2 6:2 7:3 8:2 9:1 +1 1:5 2:6 3:7 4:8 5:8 6:10 7:3 8:10 9:3 +1 1:10 2:8 3:10 4:10 5:6 6:1 7:3 8:1 9:10 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:1 2:1 3:1 4:2 5:1 6:1 7:1 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +1 1:6 2:10 3:10 4:10 5:8 6:10 7:10 8:10 9:7 +1 1:8 2:6 3:5 4:4 5:3 6:10 7:6 8:1 9:1 +1 1:5 2:8 3:7 4:7 5:10 6:10 7:5 8:7 9:1 +-1 1:2 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +1 1:5 2:10 3:10 4:3 5:8 6:1 7:5 8:10 9:3 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +1 1:5 2:3 3:3 4:3 5:6 6:10 7:3 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:1 6:1 7:3 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:6 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +1 1:5 2:8 3:8 4:8 5:5 6:10 7:7 8:8 9:1 +1 1:8 2:7 3:6 4:4 5:4 6:10 7:5 8:1 9:1 +-1 1:2 2:1 3:1 4:1 5:1 6:1 7:3 8:1 9:1 +1 1:1 2:5 3:8 4:6 5:5 6:8 7:7 8:10 9:1 +1 1:10 2:5 3:6 4:10 5:6 6:10 7:7 8:7 9:10 +1 1:5 2:8 3:4 4:10 5:5 6:8 7:9 8:10 9:1 +-1 1:1 2:2 3:3 4:1 5:2 6:1 7:3 8:1 9:1 +1 1:10 2:10 3:10 4:8 5:6 6:8 7:7 8:10 9:1 +1 1:7 2:5 3:10 4:10 5:10 6:10 7:4 8:10 9:3 +-1 1:5 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:8 2:4 3:4 4:5 5:4 6:7 7:7 8:8 9:2 +-1 1:5 2:1 3:1 4:4 5:2 6:1 7:3 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +1 1:9 2:7 3:7 4:5 5:5 6:10 7:7 8:8 9:3 +1 1:10 2:8 3:8 4:4 5:10 6:10 7:8 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:5 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +1 1:5 2:10 3:10 4:9 5:6 6:10 7:7 8:10 9:5 +1 1:10 2:10 3:9 4:3 5:7 6:5 7:3 8:5 9:1 +-1 1:1 2:1 3:1 4:1 5:1 6:1 7:3 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:1 6:1 7:3 8:1 9:1 +-1 1:5 2:1 3:1 4:1 5:1 6:1 7:3 8:1 9:1 +1 1:8 2:10 3:10 4:10 5:5 6:10 7:8 8:10 9:6 +1 1:8 2:10 3:8 4:8 5:4 6:8 7:7 8:7 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +1 1:10 2:10 3:10 4:10 5:7 6:10 7:7 8:10 9:4 +1 1:10 2:10 3:10 4:10 5:3 6:10 7:10 8:6 9:1 +1 1:8 2:7 3:8 4:7 5:5 6:5 7:5 8:10 9:2 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +1 1:6 2:10 3:7 4:7 5:6 6:4 7:8 8:10 9:2 +-1 1:6 2:1 3:3 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:1 2:1 3:1 4:2 5:2 6:1 7:3 8:1 9:1 +1 1:10 2:6 3:4 4:3 5:10 6:10 7:9 8:10 9:1 +1 1:4 2:1 3:1 4:3 5:1 6:5 7:2 8:1 9:1 +1 1:7 2:5 3:6 4:3 5:3 6:8 7:7 8:4 9:1 +1 1:10 2:5 3:5 4:6 5:3 6:10 7:7 8:9 9:2 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +1 1:10 2:5 3:7 4:4 5:4 6:10 7:8 8:9 9:1 +1 1:8 2:9 3:9 4:5 5:3 6:5 7:7 8:7 9:1 +-1 1:1 2:1 3:1 4:1 5:1 6:1 7:3 8:1 9:1 +1 1:10 2:10 3:10 4:3 5:10 6:10 7:9 8:10 9:1 +1 1:7 2:4 3:7 4:4 5:3 6:7 7:7 8:6 9:1 +1 1:6 2:8 3:7 4:5 5:6 6:8 7:8 8:9 9:2 +-1 1:8 2:4 3:6 4:3 5:3 6:1 7:4 8:3 9:1 +1 1:10 2:4 3:5 4:5 5:5 6:10 7:4 8:1 9:1 +-1 1:3 2:3 3:2 4:1 5:3 6:1 7:3 8:6 9:1 +1 1:10 2:8 3:8 4:2 5:8 6:10 7:4 8:8 9:10 +1 1:9 2:8 3:8 4:5 5:6 6:2 7:4 8:10 9:4 +1 1:8 2:10 3:10 4:8 5:6 6:9 7:3 8:10 9:10 +1 1:10 2:4 3:3 4:2 5:3 6:10 7:5 8:3 9:2 +-1 1:5 2:1 3:3 4:3 5:2 6:2 7:2 8:3 9:1 +-1 1:3 2:1 3:1 4:3 5:1 6:1 7:3 8:1 9:1 +-1 1:2 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:5 7:5 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:5 2:1 3:1 4:2 5:2 6:2 7:3 8:1 9:1 +1 1:8 2:10 3:10 4:8 5:5 6:10 7:7 8:8 9:1 +1 1:8 2:4 3:4 4:1 5:2 6:9 7:3 8:3 9:1 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:3 8:6 9:1 +-1 1:1 2:2 3:2 4:1 5:2 6:1 7:1 8:1 9:1 +1 1:10 2:4 3:4 4:10 5:2 6:10 7:5 8:3 9:3 +-1 1:6 2:3 3:3 4:5 5:3 6:10 7:3 8:5 9:3 +1 1:6 2:10 3:10 4:2 5:8 6:10 7:7 8:3 9:3 +1 1:9 2:10 3:10 4:1 5:10 6:8 7:3 8:3 9:1 +1 1:5 2:6 3:6 4:2 5:4 6:10 7:3 8:6 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:5 2:7 3:7 4:1 5:5 6:8 7:3 8:4 9:1 +1 1:10 2:5 3:8 4:10 5:3 6:10 7:5 8:1 9:3 +1 1:5 2:10 3:10 4:6 5:10 6:10 7:10 8:6 9:5 +1 1:8 2:8 3:9 4:4 5:5 6:10 7:7 8:8 9:1 +1 1:10 2:4 3:4 4:10 5:6 6:10 7:5 8:5 9:1 +1 1:7 2:9 3:4 4:10 5:10 6:3 7:5 8:3 9:3 +-1 1:5 2:1 3:4 4:1 5:2 6:1 7:3 8:2 9:1 +1 1:10 2:10 3:6 4:3 5:3 6:10 7:4 8:3 9:2 +1 1:3 2:3 3:5 4:2 5:3 6:10 7:7 8:1 9:1 +1 1:10 2:8 3:8 4:2 5:3 6:4 7:8 8:7 9:8 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +1 1:8 2:4 3:7 4:1 5:3 6:10 7:3 8:9 9:2 +-1 1:5 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +1 1:3 2:3 3:5 4:2 5:3 6:10 7:7 8:1 9:1 +1 1:7 2:2 3:4 4:1 5:3 6:4 7:3 8:3 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:3 8:2 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +1 1:10 2:5 3:7 4:3 5:3 6:7 7:3 8:3 9:8 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:2 2:1 3:1 4:2 5:2 6:1 7:3 8:1 9:1 +1 1:1 2:4 3:3 4:10 5:4 6:10 7:5 8:6 9:1 +1 1:10 2:4 3:6 4:1 5:2 6:10 7:5 8:3 9:1 +1 1:7 2:4 3:5 4:10 5:2 6:10 7:3 8:8 9:2 +1 1:8 2:10 3:10 4:10 5:8 6:10 7:10 8:7 9:3 +1 1:10 2:10 3:10 4:10 5:10 6:10 7:4 8:10 9:10 +-1 1:3 2:1 3:1 4:1 5:3 6:1 7:2 8:1 9:1 +1 1:6 2:1 3:3 4:1 5:4 6:5 7:5 8:10 9:1 +1 1:5 2:6 3:6 4:8 5:6 6:10 7:4 8:10 9:4 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +1 1:10 2:4 3:4 4:6 5:2 6:10 7:2 8:3 9:1 +1 1:5 2:5 3:7 4:8 5:6 6:10 7:7 8:4 9:1 +-1 1:5 2:3 3:4 4:3 5:4 6:5 7:4 8:7 9:1 +-1 1:8 2:2 3:1 4:1 5:5 6:1 7:1 8:1 9:1 +1 1:9 2:1 3:2 4:6 5:4 6:10 7:7 8:7 9:2 +1 1:8 2:4 3:10 4:5 5:4 6:4 7:7 8:10 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +1 1:10 2:10 3:10 4:7 5:9 6:10 7:7 8:10 9:10 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +1 1:8 2:3 3:4 4:9 5:3 6:10 7:3 8:3 9:1 +1 1:10 2:8 3:4 4:4 5:4 6:10 7:3 8:10 9:4 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +1 1:7 2:8 3:7 4:6 5:4 6:3 7:8 8:8 9:4 +-1 1:3 2:1 3:1 4:1 5:2 6:5 7:5 8:1 9:1 +-1 1:2 2:1 3:1 4:1 5:3 6:1 7:2 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +1 1:8 2:6 3:4 4:10 5:10 6:1 7:3 8:5 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:1 6:1 7:2 8:1 9:1 +1 1:5 2:5 3:5 4:2 5:5 6:10 7:4 8:3 9:1 +1 1:6 2:8 3:7 4:8 5:6 6:8 7:8 8:9 9:1 +-1 1:1 2:1 3:1 4:1 5:5 6:1 7:3 8:1 9:1 +-1 1:4 2:4 3:4 4:4 5:6 6:5 7:7 8:3 9:1 +1 1:7 2:6 3:3 4:2 5:5 6:10 7:7 8:4 9:6 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +1 1:5 2:4 3:6 4:10 5:2 6:10 7:4 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:3 2:2 3:2 4:1 5:2 6:1 7:2 8:3 9:1 +1 1:10 2:1 3:1 4:1 5:2 6:10 7:5 8:4 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +1 1:8 2:10 3:3 4:2 5:6 6:4 7:3 8:10 9:1 +1 1:10 2:4 3:6 4:4 5:5 6:10 7:7 8:1 9:1 +1 1:10 2:4 3:7 4:2 5:2 6:8 7:6 8:1 9:1 +-1 1:5 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:2 +-1 1:5 2:2 3:2 4:2 5:2 6:1 7:2 8:2 9:1 +1 1:5 2:4 3:6 4:6 5:4 6:10 7:4 8:3 9:1 +1 1:8 2:6 3:7 4:3 5:3 6:10 7:3 8:4 9:2 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +1 1:6 2:5 3:5 4:8 5:4 6:10 7:3 8:4 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:1 6:1 7:2 8:1 9:1 +1 1:8 2:5 3:5 4:5 5:2 6:10 7:4 8:3 9:1 +1 1:10 2:3 3:3 4:1 5:2 6:10 7:7 8:6 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:2 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +1 1:7 2:6 3:4 4:8 5:10 6:10 7:9 8:5 9:3 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:5 2:2 3:2 4:2 5:3 6:1 7:1 8:3 9:1 +-1 1:1 2:1 3:1 4:1 5:1 6:1 7:1 8:3 9:1 +1 1:3 2:4 3:4 4:10 5:5 6:1 7:3 8:3 9:1 +1 1:4 2:2 3:3 4:5 5:3 6:8 7:7 8:6 9:1 +-1 1:5 2:1 3:1 4:3 5:2 6:1 7:1 8:1 9:1 +-1 1:2 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:3 2:4 3:5 4:3 5:7 6:3 7:4 8:6 9:1 +1 1:2 2:7 3:10 4:10 5:7 6:10 7:4 8:9 9:4 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:4 2:1 3:1 4:1 5:3 6:1 7:2 8:2 9:1 +1 1:5 2:3 3:3 4:1 5:3 6:3 7:3 8:3 9:3 +1 1:8 2:10 3:10 4:7 5:10 6:10 7:7 8:3 9:8 +1 1:8 2:10 3:5 4:3 5:8 6:4 7:4 8:10 9:3 +1 1:10 2:3 3:5 4:4 5:3 6:7 7:3 8:5 9:3 +1 1:6 2:10 3:10 4:10 5:10 6:10 7:8 8:10 9:10 +1 1:3 2:10 3:3 4:10 5:6 6:10 7:5 8:1 9:4 +-1 1:3 2:2 3:2 4:1 5:4 6:3 7:2 8:1 9:1 +-1 1:4 2:4 3:4 4:2 5:2 6:3 7:2 8:1 9:1 +-1 1:2 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:2 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +1 1:6 2:10 3:10 4:10 5:8 6:10 7:7 8:10 9:7 +1 1:5 2:8 3:8 4:10 5:5 6:10 7:8 8:10 9:3 +-1 1:1 2:1 3:3 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:1 2:1 3:3 4:1 5:1 6:1 7:2 8:1 9:1 +-1 1:4 2:3 3:2 4:1 5:3 6:1 7:2 8:1 9:1 +-1 1:1 2:1 3:3 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:4 2:1 3:2 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:5 2:1 3:1 4:2 5:2 6:1 7:2 8:1 9:1 +-1 1:3 2:1 3:2 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:1 6:1 7:2 8:1 9:1 +-1 1:3 2:1 3:1 4:4 5:3 6:1 7:2 8:2 9:1 +-1 1:5 2:3 3:4 4:1 5:4 6:1 7:3 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +1 1:10 2:6 3:3 4:6 5:4 6:10 7:7 8:8 9:4 +-1 1:3 2:2 3:2 4:2 5:2 6:1 7:3 8:2 9:1 +-1 1:2 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:2 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:3 2:3 3:2 4:2 5:3 6:1 7:1 8:2 9:3 +1 1:7 2:6 3:6 4:3 5:2 6:10 7:7 8:1 9:1 +-1 1:5 2:3 3:3 4:2 5:3 6:1 7:3 8:1 9:1 +-1 1:2 2:1 3:1 4:1 5:2 6:1 7:2 8:2 9:1 +-1 1:5 2:1 3:1 4:1 5:3 6:2 7:2 8:2 9:1 +-1 1:1 2:1 3:1 4:2 5:2 6:1 7:2 8:1 9:1 +1 1:10 2:8 3:7 4:4 5:3 6:10 7:7 8:9 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:1 6:1 7:1 8:1 9:1 +-1 1:1 2:2 3:3 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:3 2:2 3:1 4:1 5:2 6:1 7:2 8:2 9:1 +-1 1:1 2:2 3:3 4:1 5:2 6:1 7:1 8:1 9:1 +1 1:3 2:10 3:8 4:7 5:6 6:9 7:9 8:3 9:8 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:5 2:3 3:3 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:4 7:1 8:1 9:1 +-1 1:1 2:2 3:1 4:3 5:2 6:1 7:1 8:2 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:4 2:2 3:2 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:2 2:3 3:2 4:2 5:2 6:2 7:3 8:1 9:1 +-1 1:3 2:1 3:2 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +1 1:10 2:10 3:10 4:6 5:8 6:4 7:8 8:5 9:1 +-1 1:5 2:1 3:2 4:1 5:2 6:1 7:3 8:1 9:1 +1 1:8 2:5 3:6 4:2 5:3 6:10 7:6 8:6 9:1 +-1 1:3 2:3 3:2 4:6 5:3 6:3 7:3 8:5 9:1 +1 1:8 2:7 3:8 4:5 5:10 6:10 7:7 8:2 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:5 2:2 3:2 4:2 5:2 6:2 7:3 8:2 9:2 +-1 1:2 2:3 3:1 4:1 5:5 6:1 7:1 8:1 9:1 +-1 1:3 2:2 3:2 4:3 5:2 6:3 7:3 8:1 9:1 +1 1:10 2:10 3:10 4:7 5:10 6:10 7:8 8:2 9:1 +-1 1:4 2:3 3:3 4:1 5:2 6:1 7:3 8:3 9:1 +-1 1:5 2:1 3:3 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +1 1:9 2:10 3:10 4:10 5:10 6:10 7:10 8:10 9:1 +-1 1:5 2:3 3:6 4:1 5:2 6:1 7:1 8:1 9:1 +1 1:8 2:7 3:8 4:2 5:4 6:2 7:5 8:10 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:2 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:1 2:3 3:1 4:1 5:2 6:1 7:2 8:2 9:1 +-1 1:5 2:1 3:1 4:3 5:4 6:1 7:3 8:2 9:1 +-1 1:5 2:1 3:1 4:1 5:2 6:1 7:2 8:2 9:1 +-1 1:3 2:2 3:2 4:3 5:2 6:1 7:1 8:1 9:1 +-1 1:6 2:9 3:7 4:5 5:5 6:8 7:4 8:2 9:1 +1 1:10 2:8 3:10 4:1 5:3 6:10 7:5 8:1 9:1 +1 1:10 2:10 3:10 4:1 5:6 6:1 7:2 8:8 9:1 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:4 2:1 3:3 4:3 5:2 6:1 7:1 8:1 9:1 +-1 1:5 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +1 1:10 2:4 3:3 4:10 5:4 6:10 7:10 8:1 9:1 +-1 1:5 2:2 3:2 4:4 5:2 6:4 7:1 8:1 9:1 +-1 1:1 2:1 3:1 4:3 5:2 6:3 7:1 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:2 7:1 8:1 9:1 +-1 1:5 2:1 3:1 4:6 5:3 6:1 7:2 8:1 9:1 +-1 1:2 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:5 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:1 6:1 7:1 8:1 9:1 +1 1:5 2:7 3:9 4:8 5:6 6:10 7:8 8:10 9:1 +-1 1:4 2:1 3:1 4:3 5:1 6:1 7:2 8:1 9:1 +-1 1:5 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:3 2:1 3:1 4:3 5:2 6:1 7:1 8:1 9:1 +1 1:4 2:5 3:5 4:8 5:6 6:10 7:10 8:7 9:1 +-1 1:2 2:3 3:1 4:1 5:3 6:1 7:1 8:1 9:1 +1 1:10 2:2 3:2 4:1 5:2 6:6 7:1 8:1 9:2 +1 1:10 2:6 3:5 4:8 5:5 6:10 7:8 8:6 9:1 +1 1:8 2:8 3:9 4:6 5:6 6:3 7:10 8:10 9:1 +-1 1:5 2:1 3:2 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:5 2:1 3:3 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:5 2:1 3:1 4:3 5:2 6:1 7:1 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:5 7:1 8:1 9:1 +-1 1:6 2:1 3:1 4:3 5:2 6:1 7:1 8:1 9:1 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:1 8:2 9:1 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +1 1:10 2:9 3:8 4:7 5:6 6:4 7:7 8:10 9:3 +1 1:10 2:6 3:6 4:2 5:4 6:10 7:9 8:7 9:1 +1 1:6 2:6 3:6 4:5 5:4 6:10 7:7 8:6 9:2 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:1 2:1 3:2 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:1 6:1 7:2 8:1 9:1 +-1 1:6 2:1 3:1 4:3 5:2 6:1 7:1 8:1 9:1 +-1 1:6 2:1 3:1 4:1 5:1 6:1 7:1 8:1 9:1 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:5 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:4 2:1 3:2 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:5 2:2 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +1 1:4 2:8 3:7 4:10 5:4 6:10 7:7 8:5 9:1 +-1 1:5 2:1 3:1 4:1 5:1 6:1 7:1 8:1 9:1 +-1 1:5 2:3 3:2 4:4 5:2 6:1 7:1 8:1 9:1 +1 1:9 2:10 3:10 4:10 5:10 6:5 7:10 8:10 9:10 +1 1:8 2:7 3:8 4:5 5:5 6:10 7:9 8:10 9:1 +-1 1:5 2:1 3:2 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:1 2:1 3:1 4:3 5:1 6:3 7:1 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:1 6:1 7:2 8:1 9:1 +1 1:10 2:10 3:10 4:10 5:6 6:10 7:8 8:1 9:5 +1 1:3 2:6 3:4 4:10 5:3 6:3 7:3 8:4 9:1 +1 1:6 2:3 3:2 4:1 5:3 6:4 7:4 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +1 1:5 2:8 3:9 4:4 5:3 6:10 7:7 8:1 9:1 +-1 1:4 2:1 3:1 4:1 5:1 6:1 7:2 8:1 9:1 +1 1:5 2:10 3:10 4:10 5:6 6:10 7:6 8:5 9:2 +-1 1:5 2:1 3:2 4:10 5:4 6:5 7:2 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:1 6:1 7:2 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:1 6:1 7:1 8:1 9:1 +-1 1:4 2:2 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:6 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:4 2:1 3:1 4:2 5:2 6:1 7:2 8:1 9:1 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:3 2:3 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +1 1:8 2:10 3:10 4:10 5:7 6:5 7:4 8:8 9:7 +-1 1:1 2:1 3:1 4:1 5:2 6:4 7:1 8:1 9:1 +-1 1:5 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:2 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:5 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:5 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:1 6:1 7:2 8:1 9:1 +1 1:6 2:6 3:7 4:10 5:3 6:10 7:8 8:10 9:2 +1 1:4 2:10 3:4 4:7 5:3 6:10 7:9 8:10 9:1 +-1 1:1 2:1 3:1 4:1 5:1 6:1 7:1 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:1 6:1 7:2 8:1 9:1 +-1 1:3 2:1 3:2 4:2 5:2 6:1 7:1 8:1 9:1 +1 1:4 2:7 3:8 4:3 5:4 6:10 7:9 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:3 6:1 7:1 8:1 9:1 +-1 1:4 2:1 3:1 4:1 5:3 6:1 7:1 8:1 9:1 +1 1:10 2:4 3:5 4:4 5:3 6:5 7:7 8:3 9:1 +1 1:7 2:5 3:6 4:10 5:4 6:10 7:5 8:3 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:3 2:1 3:1 4:2 5:2 6:1 7:1 8:1 9:1 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:6 2:1 3:3 4:2 5:2 6:1 7:1 8:1 9:1 +-1 1:4 2:1 3:1 4:1 5:1 6:1 7:2 8:1 9:1 +1 1:7 2:4 3:4 4:3 5:4 6:10 7:6 8:9 9:1 +-1 1:4 2:2 3:2 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:1 6:1 7:3 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:2 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:1 2:1 3:3 4:2 5:2 6:1 7:3 8:1 9:1 +-1 1:5 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:5 2:1 3:2 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:6 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:5 2:1 3:1 4:1 5:2 6:2 7:2 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:5 2:3 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:2 2:1 3:3 4:2 5:2 6:1 7:2 8:1 9:1 +-1 1:5 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +1 1:6 2:10 3:10 4:10 5:4 6:10 7:7 8:10 9:1 +-1 1:2 2:1 3:1 4:1 5:1 6:1 7:1 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:1 6:1 7:1 8:1 9:1 +1 1:7 2:8 3:3 4:7 5:4 6:5 7:7 8:8 9:2 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:3 2:2 3:2 4:2 5:2 6:1 7:4 8:2 9:1 +-1 1:4 2:4 3:2 4:1 5:2 6:5 7:2 8:1 9:2 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:4 2:3 3:1 4:1 5:2 6:1 7:4 8:8 9:1 +-1 1:5 2:2 3:2 4:2 5:1 6:1 7:2 8:1 9:1 +-1 1:5 2:1 3:1 4:3 5:2 6:1 7:1 8:1 9:1 +-1 1:2 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:5 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:5 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:5 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:3 8:2 9:1 +1 1:5 2:7 3:10 4:10 5:5 6:10 7:10 8:10 9:1 +-1 1:3 2:1 3:2 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:4 2:1 3:1 4:1 5:2 6:3 7:2 8:1 9:1 +1 1:8 2:4 3:4 4:1 5:6 6:10 7:2 8:5 9:2 +1 1:10 2:10 3:8 4:10 5:6 6:5 7:10 8:3 9:1 +1 1:8 2:10 3:4 4:4 5:8 6:10 7:8 8:2 9:1 +1 1:7 2:6 3:10 4:5 5:3 6:10 7:9 8:10 9:2 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +1 1:10 2:9 3:7 4:3 5:4 6:2 7:7 8:7 9:1 +-1 1:5 2:1 3:2 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:5 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:5 2:1 3:2 4:1 5:2 6:1 7:2 8:1 9:1 +1 1:5 2:7 3:10 4:6 5:5 6:10 7:7 8:5 9:1 +1 1:6 2:10 3:5 4:5 5:4 6:10 7:6 8:10 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:5 2:1 3:1 4:6 5:3 6:1 7:1 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +1 1:8 2:10 3:10 4:10 5:6 6:10 7:10 8:10 9:1 +-1 1:5 2:1 3:1 4:1 5:2 6:1 7:2 8:2 9:1 +1 1:9 2:8 3:8 4:9 5:6 6:3 7:4 8:1 9:1 +-1 1:5 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +1 1:4 2:10 3:8 4:5 5:4 6:1 7:10 8:1 9:1 +1 1:2 2:5 3:7 4:6 5:4 6:10 7:7 8:6 9:1 +1 1:10 2:3 3:4 4:5 5:3 6:10 7:4 8:1 9:1 +-1 1:5 2:1 3:2 4:1 5:2 6:1 7:1 8:1 9:1 +1 1:4 2:8 3:6 4:3 5:4 6:10 7:7 8:1 9:1 +-1 1:5 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:4 2:1 3:2 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:5 2:1 3:3 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:5 2:2 3:4 4:1 5:1 6:1 7:1 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:1 6:1 7:2 8:1 9:1 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +1 1:5 2:4 3:6 4:8 5:4 6:1 7:8 8:10 9:1 +1 1:5 2:3 3:2 4:8 5:5 6:10 7:8 8:1 9:2 +1 1:10 2:5 3:10 4:3 5:5 6:8 7:7 8:8 9:3 +-1 1:4 2:1 3:1 4:2 5:2 6:1 7:1 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +1 1:5 2:10 3:10 4:10 5:10 6:10 7:10 8:1 9:1 +-1 1:5 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +1 1:10 2:4 3:3 4:10 5:3 6:10 7:7 8:1 9:2 +1 1:5 2:10 3:10 4:10 5:5 6:2 7:8 8:5 9:1 +1 1:8 2:10 3:10 4:10 5:6 6:10 7:10 8:10 9:10 +-1 1:2 2:3 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:2 2:1 3:1 4:1 5:1 6:1 7:2 8:1 9:1 +-1 1:4 2:1 3:3 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:5 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:6 2:3 3:3 4:3 5:3 6:2 7:6 8:1 9:1 +-1 1:7 2:1 3:2 4:3 5:2 6:1 7:2 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:5 2:1 3:1 4:2 5:1 6:1 7:2 8:1 9:1 +-1 1:3 2:1 3:3 4:1 5:3 6:4 7:1 8:1 9:1 +1 1:4 2:6 3:6 4:5 5:7 6:6 7:7 8:7 9:3 +-1 1:2 2:1 3:1 4:1 5:2 6:5 7:1 8:1 9:1 +-1 1:2 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:6 2:2 3:3 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:5 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +1 1:8 2:7 3:4 4:4 5:5 6:3 7:5 8:10 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:3 2:1 3:4 4:1 5:2 6:1 7:1 8:1 9:1 +1 1:10 2:10 3:7 4:8 5:7 6:1 7:10 8:10 9:3 +-1 1:4 2:2 3:4 4:3 5:2 6:2 7:2 8:1 9:1 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:5 2:1 3:1 4:3 5:2 6:1 7:1 8:1 9:1 +-1 1:4 2:1 3:1 4:3 5:2 6:1 7:1 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:2 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:1 2:2 3:2 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:1 2:1 3:1 4:3 5:2 6:1 7:1 8:1 9:1 +1 1:5 2:10 3:10 4:10 5:10 6:2 7:10 8:10 9:10 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:3 2:1 3:1 4:2 5:3 6:4 7:1 8:1 9:1 +-1 1:1 2:2 3:1 4:3 5:2 6:1 7:2 8:1 9:1 +-1 1:5 2:1 3:1 4:1 5:2 6:1 7:2 8:2 9:1 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:5 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:5 2:4 3:5 4:1 5:8 6:1 7:3 8:6 9:1 +1 1:7 2:8 3:8 4:7 5:3 6:10 7:7 8:2 9:3 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:1 2:1 3:3 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:1 2:1 3:3 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:3 2:1 3:1 4:3 5:2 6:1 7:2 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:5 2:2 3:2 4:2 5:2 6:1 7:1 8:1 9:2 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +1 1:5 2:7 3:4 4:1 5:6 6:1 7:7 8:10 9:3 +1 1:5 2:10 3:10 4:8 5:5 6:5 7:7 8:10 9:1 +1 1:3 2:10 3:7 4:8 5:5 6:8 7:7 8:4 9:1 +-1 1:3 2:2 3:1 4:2 5:2 6:1 7:3 8:1 9:1 +-1 1:2 2:1 3:1 4:1 5:2 6:1 7:3 8:1 9:1 +-1 1:5 2:3 3:2 4:1 5:3 6:1 7:1 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:4 2:1 3:4 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:1 2:1 3:2 4:1 5:2 6:1 7:2 8:1 9:1 +-1 1:5 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:2 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +1 1:10 2:10 3:10 4:10 5:5 6:10 7:10 8:10 9:7 +1 1:5 2:10 3:10 4:10 5:4 6:10 7:5 8:6 9:3 +-1 1:5 2:1 3:1 4:1 5:2 6:1 7:3 8:2 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:2 8:3 9:1 +-1 1:4 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:1 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:8 +-1 1:1 2:1 3:1 4:3 5:2 6:1 7:1 8:1 9:1 +1 1:5 2:10 3:10 4:5 5:4 6:5 7:4 8:4 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +-1 1:3 2:1 3:1 4:1 5:2 6:1 7:2 8:1 9:2 +-1 1:3 2:1 3:1 4:1 5:3 6:2 7:1 8:1 9:1 +-1 1:2 2:1 3:1 4:1 5:2 6:1 7:1 8:1 9:1 +1 1:5 2:10 3:10 4:3 5:7 6:3 7:8 8:10 9:2 +1 1:4 2:8 3:6 4:4 5:3 6:4 7:10 8:6 9:1 +1 1:4 2:8 3:8 4:5 5:4 6:5 7:10 8:4 9:1 diff --git a/test/BaselineOutput/Common/Command/CommandSaveDataSvmLight-out.txt b/test/BaselineOutput/Common/Command/CommandSaveDataSvmLight-out.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/BaselineOutput/Common/Command/CommandShowDataSvmLight-1-out.txt b/test/BaselineOutput/Common/Command/CommandShowDataSvmLight-1-out.txt new file mode 100644 index 0000000000..1e481d70fa --- /dev/null +++ b/test/BaselineOutput/Common/Command/CommandShowDataSvmLight-1-out.txt @@ -0,0 +1,16 @@ +Observed max was 6 +#@ TextLoader{ +#@ header+ +#@ sep=tab +#@ col=Label:R4:0 +#@ col=Weight:R4:1 +#@ col=GroupId:U8[18446744073709551614]:2 +#@ col=Comment:TX:3 +#@ col=Features:R4:4-9 +#@ } +Label Weight GroupId Comment 6 0:"" +1 1 8 2:3 5:6 +-1 5 8 3:4 5:7 7:-1 +1 1 " A comment! 2:3" 6 4:-2 +1 0.5 8 3:3.14159 +Wrote 4 rows of length 10 diff --git a/test/BaselineOutput/Common/Command/CommandShowDataSvmLight-2-out.txt b/test/BaselineOutput/Common/Command/CommandShowDataSvmLight-2-out.txt new file mode 100644 index 0000000000..bf467ae6d9 --- /dev/null +++ b/test/BaselineOutput/Common/Command/CommandShowDataSvmLight-2-out.txt @@ -0,0 +1,13 @@ +#@ TextLoader{ +#@ header+ +#@ sep=tab +#@ col=Label:R4:0 +#@ col=Weight:R4:1 +#@ col=GroupId:U8[18446744073709551614]:2 +#@ col=Comment:TX:3 +#@ col=Features:R4:4-6 +#@ } +Label Weight GroupId Comment aurora beachwood chagrin +1 1 1 "" 3.14159 123 0 +-1 1 5 "" 0 345 -21 +Wrote 2 rows of length 7 diff --git a/test/BaselineOutput/Common/Command/CommandShowDataSvmLight-3-out.txt b/test/BaselineOutput/Common/Command/CommandShowDataSvmLight-3-out.txt new file mode 100644 index 0000000000..09782c4792 --- /dev/null +++ b/test/BaselineOutput/Common/Command/CommandShowDataSvmLight-3-out.txt @@ -0,0 +1,13 @@ +#@ TextLoader{ +#@ header+ +#@ sep=tab +#@ col=Label:R4:0 +#@ col=Weight:R4:1 +#@ col=GroupId:U8[18446744073709551614]:2 +#@ col=Comment:TX:3 +#@ col=Features:R4:4-6 +#@ } +Label Weight GroupId Comment aurora beachwood chagrin +-1 1 5 2:1 4:2 +1 1 5 4:3 +Wrote 2 rows of length 7 diff --git a/test/BaselineOutput/Common/Command/CommandShowDataSvmLight-4-out.txt b/test/BaselineOutput/Common/Command/CommandShowDataSvmLight-4-out.txt new file mode 100644 index 0000000000..6a0a65745c --- /dev/null +++ b/test/BaselineOutput/Common/Command/CommandShowDataSvmLight-4-out.txt @@ -0,0 +1,12 @@ +#@ TextLoader{ +#@ header+ +#@ sep=tab +#@ col=Label:R4:0 +#@ col=Weight:R4:1 +#@ col=GroupId:U8[18446744073709551614]:2 +#@ col=Comment:TX:3 +#@ col=Features:R4:4-4 +#@ } +Label Weight GroupId Comment aurora +1 1 "" 2 +Wrote 1 rows of length 5 diff --git a/test/BaselineOutput/Common/Command/CommandShowDataSvmLight-5-out.txt b/test/BaselineOutput/Common/Command/CommandShowDataSvmLight-5-out.txt new file mode 100644 index 0000000000..a6604ca83e --- /dev/null +++ b/test/BaselineOutput/Common/Command/CommandShowDataSvmLight-5-out.txt @@ -0,0 +1,15 @@ +#@ TextLoader{ +#@ header+ +#@ sep=tab +#@ col=Label:R4:0 +#@ col=Weight:R4:1 +#@ col=GroupId:U8[18446744073709551614]:2 +#@ col=Comment:TX:3 +#@ col=Features:R4:4-8 +#@ } +Label Weight GroupId Comment 5 0:"" +1 1 7 3:3 6:6 +-1 5 7 4:4 6:7 +1 1 " A comment! 2:3" 5 0:0 +1 0.5 7 4:3.14159 +Wrote 4 rows of length 9 diff --git a/test/BaselineOutput/Common/Command/CommandShowDataSvmLight-out.txt b/test/BaselineOutput/Common/Command/CommandShowDataSvmLight-out.txt new file mode 100644 index 0000000000..71135efcd2 --- /dev/null +++ b/test/BaselineOutput/Common/Command/CommandShowDataSvmLight-out.txt @@ -0,0 +1,15 @@ +#@ TextLoader{ +#@ header+ +#@ sep=tab +#@ col=Label:R4:0 +#@ col=Weight:R4:1 +#@ col=GroupId:U8[18446744073709551614]:2 +#@ col=Comment:TX:3 +#@ col=Features:R4:4-8 +#@ } +Label Weight GroupId Comment 5 0:"" +1 1 7 2:3 5:6 +-1 5 7 3:4 5:7 +1 1 " A comment! 2:3" 5 4:-2 +1 0.5 7 3:3.14159 +Wrote 4 rows of length 9 diff --git a/test/Microsoft.ML.TestFramework/TestCommandBase.cs b/test/Microsoft.ML.TestFramework/TestCommandBase.cs index 29c6440699..c86b2998f8 100644 --- a/test/Microsoft.ML.TestFramework/TestCommandBase.cs +++ b/test/Microsoft.ML.TestFramework/TestCommandBase.cs @@ -2157,5 +2157,75 @@ public void SavePipeChooseColumnsByIndexDrop() TestCore("showdata", dataPath, string.Format("in={{{0}}}", modelPath.Path), ""); Done(); } + + [Fact] + public void CommandShowDataSvmLight() + { + // Test with a specified size parameter. The "6" feature should be omitted. + // Also the blank and completely fully commented lines should be omitted, + // and the feature 2:3 that appears in the comment should not appear. + var path = CreateOutputPath("DataA.txt"); + File.WriteAllLines(path.Path, new string[] { + "1\t1:3\t4:6", + " -1 cost:5\t2:4 \t4:7\t6:-1 ", + "", + "1\t5:-2 # A comment! 2:3", + "# What a nice full line comment", + "1 cost:0.5\t2:3.14159", + }); + var pathA = path.Path; + const string chooseXf = " xf=select{keepcol=Label keepcol=Weight keepcol=GroupId keepcol=Comment keepcol=Features}"; + TestReloadedCore("showdata", path.Path, "loader=svm{size=5}" + chooseXf, "", ""); + + // Test with autodetermined sizes. The the "6" feature should be included, + // and the feature vector should have length 6. + _step++; + TestCore("showdata", path.Path, "loader=svm" + chooseXf, ""); + + // Test with a term mapping, instead of the actual SVM^light format that + // requires positive integers. ALso check that qid works here. + _step++; + var modelPath = ModelPath(); + path = CreateOutputPath("DataB.txt"); + File.WriteAllLines(path.Path, new string[] { + "1 qid:1 aurora:3.14159 beachwood:123", + "-1 qid:5 beachwood:345 chagrin:-21", + }); + TestReloadedCore("showdata", path.Path, "loader=svm{indices=names}" + chooseXf, "", ""); + + // We reload the model, but on a new set of data. The "euclid" key should be + // ignored as it would not have been detected by the term transform. + _step++; + path = CreateOutputPath("DataC.txt"); + File.WriteAllLines(path.Path, new string[] { + "-1 aurora:1 chagrin:2", + "1 chagrin:3 euclid:4" + }); + TestInCore("showdata", path.Path, modelPath, ""); + + _step++; + path = CreateOutputPath("DataD.txt"); + File.WriteAllLines(path.Path, new string[] { "1 aurora:2 :3" }); + TestReloadedCore("showdata", path.Path, "loader=svm{indices=names}" + chooseXf, "", ""); + + _step++; + + // If we specify the size parameter, and zero-based feature indices, both indices 5 and 6 should + // not appear. + TestReloadedCore("showdata", pathA, "loader=svm{size=5 indices=zerobased}" + chooseXf, "", ""); + + Done(); + } + + [Fact] + public void CommandSaveDataSvmLight() + { + string pathData = GetDataPath("breast-cancer-withheader.txt"); + OutputPath dataPath = CreateOutputPath("data.txt"); + TestReloadedCore("savedata", pathData, "loader=text{header+}", "saver=svmlight{b+}", null, dataPath.Arg("dout")); + dataPath = CreateOutputPath("data-0.txt"); + TestReloadedCore("savedata", pathData, "loader=text{header+}", "saver=svmlight{zero+}", null, dataPath.Arg("dout")); + Done(); + } } } diff --git a/test/Microsoft.ML.Tests/SvmLightTests.cs b/test/Microsoft.ML.Tests/SvmLightTests.cs new file mode 100644 index 0000000000..89d65fc309 --- /dev/null +++ b/test/Microsoft.ML.Tests/SvmLightTests.cs @@ -0,0 +1,538 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.IO; +using Microsoft.ML.Data; +using Microsoft.ML.RunTests; +using Microsoft.ML.TestFrameworkCommon; +using Microsoft.ML.Transforms; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.ML.Tests +{ + public sealed class SvmLightTests : TestDataPipeBase + { + public SvmLightTests(ITestOutputHelper output) : base(output) + { + } + +#pragma warning disable 0649 // Disable warnings about unused members. They are used through reflection. + private sealed class SvmLightOutput + { + public float Label; + public float Weight; + [KeyType(ulong.MaxValue - 1)] + public ulong GroupId = ulong.MaxValue; + public ReadOnlyMemory Comment; + public VBuffer Features; + } +#pragma warning restore 0649 + + private string CreateDataset(string name, string[] data) + { + var path = DeleteOutputPath(TestName + name); + File.WriteAllLines(path, data); + return path; + } + + private void TestSvmLight(string path, string savingPath, int inputSize, int expectedInputSize, bool zeroBased, IDataView expectedData, long? numberOfRows = null) + { + var data = ML.Data.LoadFromSvmLightFile(path, inputSize: inputSize, zeroBased: zeroBased, numberOfRows: numberOfRows); + Assert.True(data.Schema["Features"].Type.GetValueCount() == expectedInputSize); + + CheckSameValues(data, expectedData, checkId: false); + + // Save, reload and compare dataviews again. + using (var stream = File.Create(savingPath)) + ML.Data.SaveInSvmLightFormat(expectedData, stream, zeroBasedIndexing: zeroBased, exampleWeightColumnName: "Weight"); + data = ML.Data.LoadFromSvmLightFile(savingPath, inputSize: inputSize, zeroBased: zeroBased); + CheckSameValues(ColumnSelectingTransformer.CreateDrop(Env, data, "Comment"), + ColumnSelectingTransformer.CreateDrop(Env, expectedData, "Comment"), checkId: false); + } + + [Fact] + public void TestSvmLightLoaderAndSaverWithSpecifiedInputSize() + { + // Test with a specified size parameter. The "6" feature should be omitted. + // Also the blank and completely fully commented lines should be omitted, + // and the feature 2:3 that appears in the comment should not appear. + + var path = CreateDataset("-data.txt", new string[] { + "1\t1:3\t4:6", + " -1 cost:5\t2:4 \t4:7\t6:-1 ", + "", + "1\t5:-2 # A comment! 2:3", + "# What a nice full line comment", + "1 cost:0.5\t2:3.14159", + }); + + var schemaDef = SchemaDefinition.Create(typeof(SvmLightOutput)); + schemaDef["Features"].ColumnType = new VectorDataViewType(NumberDataViewType.Single, 5); + + var expectedData = ML.Data.LoadFromEnumerable(new SvmLightOutput[] + { + new SvmLightOutput() { Label = 1, Weight = 1, Features = new VBuffer(5, 2, new[] { 3f, 6f }, new[] { 0, 3 }) }, + new SvmLightOutput() { Label = -1, Weight = 5, Features = new VBuffer(5, 2, new[] { 4f, 7f }, new[] { 1, 3 }) }, + new SvmLightOutput() { Label = 1, Weight = 1, Features = new VBuffer(5, 1, new[] { -2f }, new[] { 4 }), Comment = " A comment! 2:3".AsMemory() }, + new SvmLightOutput() { Label = 1, Weight = 0.5f, Features = new VBuffer(5, 1, new[] { 3.14159f }, new[] { 1 }) }, + }, schemaDef); + var savingPath = DeleteOutputPath(TestName + "-saved-data.txt"); + TestSvmLight(path, savingPath, 5, 5, false, expectedData); + } + + [Fact] + public void TestSvmLightLoaderAndSaverWithSpecifiedInputSizeZeroBased() + { + // If we specify the size parameter, and zero-based feature indices, both indices 5 and 6 should + // not appear. + + var path = CreateDataset("-data.txt", new string[] { + "1\t1:3\t4:6", + " -1 cost:5\t2:4 \t4:7\t6:-1 ", + "", + "1\t5:-2 # A comment! 2:3", + "# What a nice full line comment", + "1 cost:0.5\t2:3.14159", + }); + + var schemaDef = SchemaDefinition.Create(typeof(SvmLightOutput)); + schemaDef["Features"].ColumnType = new VectorDataViewType(NumberDataViewType.Single, 5); + var expectedData = ML.Data.LoadFromEnumerable(new SvmLightOutput[] + { + new SvmLightOutput() { Label = 1, Weight = 1, Features = new VBuffer(5, 2, new[] { 3f, 6f }, new[] { 1, 4 }) }, + new SvmLightOutput() { Label = -1, Weight = 5, Features = new VBuffer(5, 2, new[] { 4f, 7f }, new[] { 2, 4 }) }, + new SvmLightOutput() { Label = 1, Weight = 1, Features = new VBuffer(5, 0, new float[0], new int[0]), Comment = " A comment! 2:3".AsMemory() }, + new SvmLightOutput() { Label = 1, Weight = 0.5f, Features = new VBuffer(5, 1, new[] { 3.14159f }, new[] { 2 }) }, + }, schemaDef); + var savingPath = DeleteOutputPath(TestName + "-saved-data.txt"); + TestSvmLight(path, savingPath, 5, 5, true, expectedData); + } + + [Fact] + public void TestSvmLightLoaderAndSaverAutoDetectInputSize() + { + // Test with autodetermined sizes. The the "6" feature should be included, + // and the feature vector should have length 6. + + var path = CreateDataset("-data.txt", new string[] { + "1\t1:3\t4:6", + " -1 cost:5\t2:4 \t4:7\t6:-1 ", + "", + "1\t5:-2 # A comment! 2:3", + "# What a nice full line comment", + "1 cost:0.5\t2:3.14159", + }); + + var schemaDef = SchemaDefinition.Create(typeof(SvmLightOutput)); + schemaDef["Features"].ColumnType = new VectorDataViewType(NumberDataViewType.Single, 6); + + var expectedData = ML.Data.LoadFromEnumerable(new SvmLightOutput[] + { + new SvmLightOutput() { Label = 1, Weight = 1, Features = new VBuffer(6, 2, new[] { 3f, 6f }, new[] { 0, 3 }) }, + new SvmLightOutput() { Label = -1, Weight = 5, Features = new VBuffer(6, 3, new[] { 4f, 7f, -1f }, new[] { 1, 3, 5 }) }, + new SvmLightOutput() { Label = 1, Weight = 1, Features = new VBuffer(6, 1, new[] { -2f }, new[] { 4 }), Comment = " A comment! 2:3".AsMemory() }, + new SvmLightOutput() { Label = 1, Weight = 0.5f, Features = new VBuffer(6, 1, new[] { 3.14159f }, new[] { 1 }) }, + }, schemaDef); + var savingPath = DeleteOutputPath(TestName + "-saved-data.txt"); + TestSvmLight(path, savingPath, 0, 6, false, expectedData); + } + + [Fact] + public void TestSvmLightLoaderAndSaverWithTermMapping() + { + // Test with a term mapping, instead of the actual SVM^light format that + // requires positive integers. ALso check that qid works here. + var path = CreateDataset("-data.txt", new string[] { + "1 qid:1 aurora:3.14159 beachwood:123", + "-1 qid:5 beachwood:345 chagrin:-21", + }); + + var model = ML.Data.CreateSvmLightLoaderWithFeatureNames(dataSample: new MultiFileSource(path)); + var data = model.Load(path); + Assert.True(data.Schema["Features"].Type.GetValueCount() == 3); + + var schemaDef = SchemaDefinition.Create(typeof(SvmLightOutput)); + schemaDef["Features"].ColumnType = new VectorDataViewType(NumberDataViewType.Single, 3); + schemaDef["Features"].AddAnnotation( + AnnotationUtils.Kinds.SlotNames, new VBuffer>(3, new[] { "aurora".AsMemory(), "beachwood".AsMemory(), "chagrin".AsMemory() }), + new VectorDataViewType(TextDataViewType.Instance, 3)); + var expectedData = ML.Data.LoadFromEnumerable(new SvmLightOutput[] + { + new SvmLightOutput() { Label = 1, Weight = 1, GroupId = 1, Features = new VBuffer(3, 2, new[] { 3.14159f, 123f }, new[] { 0, 1 }) }, + new SvmLightOutput() { Label = -1, Weight = 1, GroupId = 5, Features = new VBuffer(3, 2, new[] { 345f, -21f }, new[] { 1, 2 }) }, + }, schemaDef); + CheckSameValues(data, expectedData, checkId: false); + TestCommon.CheckSameSchemas(data.Schema, expectedData.Schema); + + // Save, reload and compare dataviews again. + var outputPath = DeleteOutputPath(TestName + "-saved-data.txt"); + using (var stream = File.Create(outputPath)) + ML.Data.SaveInSvmLightFormat(expectedData, stream, zeroBasedIndexing: true, rowGroupColumnName: "GroupId"); + data = ML.Data.LoadFromSvmLightFile(outputPath, zeroBased: true); + CheckSameValues(data, expectedData, checkId: false); + + // We reload the model, but on a new set of data. The "euclid" key should be + // ignored as it would not have been detected by the term transform. + path = CreateDataset("-data2.txt", new string[] { + "-1 aurora:1 chagrin:2", + "1 chagrin:3 euclid:4" + }); + data = model.Load(path); + Assert.True(data.Schema["Features"].Type.GetValueCount() == 3); + + expectedData = ML.Data.LoadFromEnumerable(new SvmLightOutput[] + { + new SvmLightOutput() { Label = -1, Weight = 1, Features = new VBuffer(3, 2, new[] { 1f, 2f }, new[] { 0, 2 }) }, + new SvmLightOutput() { Label = 1, Weight = 1, Features = new VBuffer(3, 1, new[] { 3f }, new[] { 2 }) }, + }, schemaDef); + CheckSameValues(data, expectedData, checkId: false); + + // Save, reload and compare dataviews again. + outputPath = DeleteOutputPath(TestName + "-saved-data2.txt"); + using (var stream = File.Create(outputPath)) + ML.Data.SaveInSvmLightFormat(expectedData, stream); + data = ML.Data.LoadFromSvmLightFile(outputPath); + CheckSameValues(data, expectedData, checkId: false); + } + + [Fact] + public void TestSvmLightLoaderAndSaverWithTermMappingWithEmptyName() + { + var path = CreateDataset("-data.txt", new string[] { "1 aurora:2 :3" }); + var data = ML.Data.LoadFromSvmLightFileWithFeatureNames(path); + Assert.True(data.Schema["Features"].Type.GetValueCount() == 1); + + var schemaDef = SchemaDefinition.Create(typeof(SvmLightOutput)); + schemaDef["Features"].ColumnType = new VectorDataViewType(NumberDataViewType.Single, 1); + var expectedData = ML.Data.LoadFromEnumerable(new SvmLightOutput[] + { + new SvmLightOutput() { Label = 1, Weight = 1, Features = new VBuffer(1, 1, new[] { 2f }, new[] { 0 }) }, + }, schemaDef); + CheckSameValues(data, expectedData, checkId: false); + + // Save, reload and compare dataviews again. + var outputPath = DeleteOutputPath("reloaded-output.txt"); + using (var stream = File.Create(outputPath)) + ML.Data.SaveInSvmLightFormat(expectedData, stream, zeroBasedIndexing: true); + data = ML.Data.LoadFromSvmLightFile(outputPath, zeroBased: true); + CheckSameValues(data, expectedData, checkId: false); + + Done(); + } + + [Fact] + public void TestSvmLightLoaderNoDuplicateKeys() + { + var path = CreateDataset("-data.txt", new string[] { + "-1 aurora:1 chagrin:2", + "1 chagrin:3 euclid:4 chagrin:5" + }); + + var ex = Assert.Throws(() => + { + var view = ML.Data.LoadFromSvmLightFileWithFeatureNames(path); + using (var curs = view.GetRowCursor(view.Schema)) + { + var featuresGetter = curs.GetGetter>(view.Schema["Features"]); + VBuffer buffer = default; + while (curs.MoveNext()) + featuresGetter(ref buffer); + } + }); + Assert.Contains("Duplicate keys found in dataset", ex.InnerException.Message); + } + + [Fact] + public void TestSvmLightLoaderBadLabel() + { + var path = CreateDataset("-data.txt", new string[] { + "q\t1:3\t4:6", + " -1a cost:5\t2:4 \t4:7\t6:-1 ", + }); + + var data = ML.Data.LoadFromSvmLightFile(path); + using (var curs = data.GetRowCursor(data.Schema["Label"])) + { + var getter = curs.GetGetter(data.Schema["Label"]); + float label = default; + while (curs.MoveNext()) + { + getter(ref label); + Assert.True(float.IsNaN(label)); + } + } + } + + [Fact] + public void TestSvmLightLoaderMissingGroupId() + { + var path = CreateDataset("-data.txt", new string[] { + "1\tqid:-3\t1:3\t4:6", + }); + var data = ML.Data.LoadFromSvmLightFile(path); + using (var curs = data.GetRowCursor(data.Schema["GroupId"])) + { + var getter = curs.GetGetter(data.Schema["GroupId"]); + ulong group = default; + while (curs.MoveNext()) + { + getter(ref group); + Assert.True(group == 0); + } + } + } + + [Fact] + public void TestSvmLightLoaderBadFeature() + { + // Test with a dataset that has a feature that cannot be parsed. The loader should ignore the value. + var path = CreateDataset("-data.txt", new string[] { + "1\t1:3\t4:6", + " -1 cost:5\t2:4 \t4:7\t6:-1 ", + "", + "1\t5:-2 # A comment! 2:3", + "# What a nice full line comment", + "1 cost:0.5\t2:3.14159", + "-1 3:2 4:hello" + }); + + var schemaDef = SchemaDefinition.Create(typeof(SvmLightOutput)); + schemaDef["Features"].ColumnType = new VectorDataViewType(NumberDataViewType.Single, 6); + + var expectedData = ML.Data.LoadFromEnumerable(new SvmLightOutput[] + { + new SvmLightOutput() { Label = 1, Weight = 1, Features = new VBuffer(6, 2, new[] { 3f, 6f }, new[] { 0, 3 }) }, + new SvmLightOutput() { Label = -1, Weight = 5, Features = new VBuffer(6, 3, new[] { 4f, 7f, -1f }, new[] { 1, 3, 5 }) }, + new SvmLightOutput() { Label = 1, Weight = 1, Features = new VBuffer(6, 1, new[] { -2f }, new[] { 4 }), Comment = " A comment! 2:3".AsMemory() }, + new SvmLightOutput() { Label = 1, Weight = 0.5f, Features = new VBuffer(6, 1, new[] { 3.14159f }, new[] { 1 }) }, + new SvmLightOutput() { Label = -1, Weight = 1, Features = new VBuffer(6, 1, new[] { 2f }, new[] { 2 }) }, + }, schemaDef); + var savingPath = DeleteOutputPath(TestName + "-saved-data.txt"); + TestSvmLight(path, savingPath, 0, 6, false, expectedData); + } + + [Fact] + public void TestSvmLightLoaderNoColon() + { + var path = CreateDataset("-data.txt", new string[] { + "1\t1;3\t4:6", + }); + var data = ML.Data.LoadFromSvmLightFile(path); + using (var curs = data.GetRowCursor(data.Schema["Features"])) + { + var getter = curs.GetGetter>(data.Schema["Features"]); + VBuffer features = default; + while (curs.MoveNext()) + { + getter(ref features); + Assert.True(features.Length == 4); + Assert.True(features.GetValues().Length == 1); + Assert.True(features.GetIndices().Length == 1); + Assert.True(features.GetValues()[0] == 6); + Assert.True(features.GetIndices()[0] == 3); + } + } + } + + [Fact] + public void TestSvmLightLoaderBadIndex() + { + // 0 index in 1-based parsing. + var path = CreateDataset("-data.txt", new string[] { + "1\t0:3\t4:6", + }); + var data = ML.Data.LoadFromSvmLightFile(path); + var ex = Assert.Throws(() => + { + using (var curs = data.GetRowCursor(data.Schema["Features"])) + { + var getter = curs.GetGetter>(data.Schema["Features"]); + VBuffer features = default; + while (curs.MoveNext()) + { + getter(ref features); + } + } + }); + Assert.Contains("Encountered 0 index while parsing a 1-based dataset", ex.InnerException.Message); + + // negative index in 0-based parsing. + path = CreateDataset("-data1.txt", new string[] { + "1\t-1:3\t4:6", + }); + data = ML.Data.LoadFromSvmLightFile(path); + ex = Assert.Throws(() => + { + using (var curs = data.GetRowCursor(data.Schema["Features"])) + { + var getter = curs.GetGetter>(data.Schema["Features"]); + VBuffer features = default; + while (curs.MoveNext()) + { + getter(ref features); + } + } + }); + Assert.Contains("Encountered non-parsable index '-1' while parsing dataset", ex.InnerException.Message); + + // non-parsable index. + path = CreateDataset("-data2.txt", new string[] { + "1\ta:3\t4:6", + }); + data = ML.Data.LoadFromSvmLightFile(path); + ex = Assert.Throws(() => + { + using (var curs = data.GetRowCursor(data.Schema["Features"])) + { + var getter = curs.GetGetter>(data.Schema["Features"]); + VBuffer features = default; + while (curs.MoveNext()) + { + getter(ref features); + } + } + }); + Assert.Contains("Encountered non-parsable index 'a' while parsing dataset", ex.InnerException.Message); + + // Only non-parsable indices. + path = CreateDataset("-data3.txt", new string[] { + "1\ta:3\tb:6", + }); + ex = Assert.Throws(() => ML.Data.LoadFromSvmLightFile(path)); + Assert.Contains("No int parsable keys found during key transform inference", ex.Message); + } + + [Fact] + public void TestSvmLightLoaderMultiStreamSourceSpecialCases() + { + var path1 = CreateDataset("-data1.txt", new string[] { + "1\t1:3\t4:6", + }); + var path2 = CreateDataset("-data2.txt", new string[] { + "1\t1:3\t4:6", + }); + var loader = ML.Data.CreateSvmLightLoader(inputSize: 4); + var data = loader.Load(new MultiFileSource(path1, path2)); + using (var curs = data.GetRowCursor(data.Schema["Features"])) + { + var getter = curs.GetGetter>(data.Schema["Features"]); + VBuffer features = default; + curs.MoveNext(); + getter(ref features); + curs.MoveNext(); + getter(ref features); + Assert.False(curs.MoveNext()); + } + + loader = ML.Data.CreateSvmLightLoader(inputSize: 3); + data = loader.Load(new MultiFileSource(null)); + using (var curs = data.GetRowCursor()) + { + Assert.False(curs.MoveNext()); + } + } + + [Fact] + public void TestSvmLightLoaderNoDataSample() + { + var ex = Assert.Throws(() => ML.Data.CreateSvmLightLoader()); + Assert.Contains("If the number of features is not specified, a dataset must be provided to infer it.", ex.Message); + ex = Assert.Throws(() => ML.Data.CreateSvmLightLoaderWithFeatureNames()); + Assert.Contains("To use the text feature names option, a dataset must be provided", ex.Message); + } + + [Fact] + public void TestSvmLightLoaderAndSaverTrainOnSubsetOfRows() + { + var path = CreateDataset("-data.txt", new string[] { + "1\t1:3\t4:6", + " -1 cost:5\t2:4 \t4:7\t6:-1 ", + "", + "1\t5:-2 # A comment! 2:3", + "1 cost:0.5\t2:3.14159", + "-1 2:5 11:0.34" + }); + + var schemaDef = SchemaDefinition.Create(typeof(SvmLightOutput)); + schemaDef["Features"].ColumnType = new VectorDataViewType(NumberDataViewType.Single, 6); + + var expectedData = ML.Data.LoadFromEnumerable(new SvmLightOutput[] + { + new SvmLightOutput() { Label = 1, Weight = 1, Features = new VBuffer(6, 2, new[] { 3f, 6f }, new[] { 0, 3 }) }, + new SvmLightOutput() { Label = -1, Weight = 5, Features = new VBuffer(6, 3, new[] { 4f, 7f, -1f }, new[] { 1, 3, 5 }) }, + new SvmLightOutput() { Label = 1, Weight = 1, Features = new VBuffer(6, 1, new[] { -2f }, new[] { 4 }), Comment = " A comment! 2:3".AsMemory() }, + new SvmLightOutput() { Label = 1, Weight = 0.5f, Features = new VBuffer(6, 1, new[] { 3.14159f }, new[] { 1 }) }, + new SvmLightOutput() { Label = -1, Weight = 1, Features = new VBuffer(6, 1, new[] { 5f }, new[] { 1 }) } + }, schemaDef); + var savingPath = DeleteOutputPath(TestName + "-saved-data.txt"); + TestSvmLight(path, savingPath, 0, 6, false, expectedData, numberOfRows: 4); + } + + [Fact] + public void TestSvmLightLoaderLongIndex() + { + var path = CreateDataset("-data.txt", new string[] { + "1\t1:3\t4:6", + " -1 cost:5\t2:4 \t4:7\t6:-1 ", + "", + "1\t5:-2 # A comment! 2:3", + "1 cost:0.5\t2:3.14159", + $"-1 2:5 {(long)int.MaxValue + 2}:0.34" + }); + + var schemaDef = SchemaDefinition.Create(typeof(SvmLightOutput)); + schemaDef["Features"].ColumnType = new VectorDataViewType(NumberDataViewType.Single, 6); + + var expectedData = ML.Data.LoadFromEnumerable(new SvmLightOutput[] + { + new SvmLightOutput() { Label = 1, Weight = 1, Features = new VBuffer(6, 2, new[] { 3f, 6f }, new[] { 0, 3 }) }, + new SvmLightOutput() { Label = -1, Weight = 5, Features = new VBuffer(6, 3, new[] { 4f, 7f, -1f }, new[] { 1, 3, 5 }) }, + new SvmLightOutput() { Label = 1, Weight = 1, Features = new VBuffer(6, 1, new[] { -2f }, new[] { 4 }), Comment = " A comment! 2:3".AsMemory() }, + new SvmLightOutput() { Label = 1, Weight = 0.5f, Features = new VBuffer(6, 1, new[] { 3.14159f }, new[] { 1 }) }, + new SvmLightOutput() { Label = -1, Weight = 1, Features = new VBuffer(6, 1, new[] { 5f }, new[] { 1 }) } + }, schemaDef); + var savingPath = DeleteOutputPath(TestName + "-saved-data.txt"); + TestSvmLight(path, savingPath, 0, int.MaxValue, false, expectedData); + } + + [Fact] + public void TestSvmLightSaverBadInputSchema() + { + var loader = ML.Data.CreateTextLoader(new[] { new TextLoader.Column("Column", DataKind.Single, 0) }); + var ex = Assert.Throws(() => + { + var path = DeleteOutputPath(TestName + "-no-label.txt"); + using (var stream = new FileStream(path, FileMode.Create)) + ML.Data.SaveInSvmLightFormat(loader.Load(new MultiFileSource(null)), stream); + }); + Assert.Contains("Column Label not found in data", ex.Message); + + ex = Assert.Throws(() => + { + var path = DeleteOutputPath(TestName + "-no-features.txt"); + using (var stream = new FileStream(path, FileMode.Create)) + ML.Data.SaveInSvmLightFormat(loader.Load(new MultiFileSource(null)), stream, labelColumnName: "Column"); + }); + Assert.Contains("Column Features not found in data", ex.Message); + + ex = Assert.Throws(() => + { + var path = DeleteOutputPath(TestName + "-no-group.txt"); + using (var stream = new FileStream(path, FileMode.Create)) + ML.Data.SaveInSvmLightFormat(loader.Load(new MultiFileSource(null)), stream, labelColumnName: "Column", featureColumnName: "Column", rowGroupColumnName: "Group"); + }); + Assert.Contains("Column Group not found in data", ex.Message); + + ex = Assert.Throws(() => + { + var path = DeleteOutputPath(TestName + "-no-weight.txt"); + using (var stream = new FileStream(path, FileMode.Create)) + ML.Data.SaveInSvmLightFormat(loader.Load(new MultiFileSource(null)), stream, labelColumnName: "Column", featureColumnName: "Column", exampleWeightColumnName: "Weight"); + }); + Assert.Contains("Column Weight not found in data", ex.Message); + } + } +}