Skip to content

Commit 8c0a111

Browse files
authored
Expose schema for prediction engine (#2250)
1 parent 7ef45f1 commit 8c0a111

File tree

22 files changed

+113
-28
lines changed

22 files changed

+113
-28
lines changed

src/Microsoft.ML.Core/Data/MetadataUtils.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,9 @@ internal static bool HasSlotNames(this Schema.Column column, int vectorSize)
327327
public static void GetSlotNames(this Schema.Column column, ref VBuffer<ReadOnlyMemory<char>> slotNames)
328328
=> column.Metadata.GetValue(Kinds.SlotNames, ref slotNames);
329329

330+
public static void GetKeyValues<TValue>(this Schema.Column column, ref VBuffer<TValue> keyValues)
331+
=> column.Metadata.GetValue(Kinds.KeyValues, ref keyValues);
332+
330333
[BestFriend]
331334
internal static void GetSlotNames(RoleMappedSchema schema, RoleMappedSchema.ColumnRole role, int vectorSize, ref VBuffer<ReadOnlyMemory<char>> slotNames)
332335
{

src/Microsoft.ML.Data/EntryPoints/PredictorModelImpl.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ internal override string[] GetLabelInfo(IHostEnvironment env, out ColumnType lab
129129
if (trainRms.Label.Value.HasKeyValues(labelType))
130130
{
131131
VBuffer<ReadOnlyMemory<char>> keyValues = default;
132-
trainRms.Label.Value.Metadata.GetValue(MetadataUtils.Kinds.KeyValues, ref keyValues);
132+
trainRms.Label.Value.GetKeyValues(ref keyValues);
133133
return keyValues.DenseValues().Select(v => v.ToString()).ToArray();
134134
}
135135
}

src/Microsoft.ML.Data/Evaluators/BinaryClassifierEvaluator.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ private ReadOnlyMemory<char>[] GetClassNames(RoleMappedSchema schema)
178178
labelCol.Metadata.Schema.GetColumnOrNull(MetadataUtils.Kinds.KeyValues)?.Type is VectorType vecType &&
179179
vecType.Size > 0 && vecType.ItemType == TextType.Instance)
180180
{
181-
labelCol.Metadata.GetValue(MetadataUtils.Kinds.KeyValues, ref labelNames);
181+
labelCol.GetKeyValues(ref labelNames);
182182
}
183183
else
184184
labelNames = new VBuffer<ReadOnlyMemory<char>>(2, new[] { "positive".AsMemory(), "negative".AsMemory() });
@@ -1096,7 +1096,7 @@ private void CheckInputColumnTypes(Schema schema)
10961096
Host.AssertValueOrNull(_probCol);
10971097
Host.AssertNonEmpty(LabelCol);
10981098

1099-
var t = schema[(int) LabelIndex].Type;
1099+
var t = schema[(int)LabelIndex].Type;
11001100
if (t != NumberType.R4 && t != NumberType.R8 && t != BoolType.Instance && t.GetKeyCount() != 2)
11011101
throw Host.Except("Label column '{0}' has type '{1}' but must be R4, R8, BL or a 2-value key", LabelCol, t);
11021102

src/Microsoft.ML.Data/Evaluators/EvaluatorUtils.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,7 @@ private static int[][] MapKeys<T>(Schema[] schemas, string columnName, bool isVe
577577
if (!(typeItemType is KeyType itemKeyType) || typeItemType.RawType != typeof(uint))
578578
throw Contracts.Except($"Column '{columnName}' must be a U4 key type, but is '{typeItemType}'");
579579

580-
schema[indices[i]].Metadata.GetValue(MetadataUtils.Kinds.KeyValues, ref keyNamesCur);
580+
schema[indices[i]].GetKeyValues(ref keyNamesCur);
581581

582582
keyValueMappers[i] = new int[itemKeyType.Count];
583583
foreach (var kvp in keyNamesCur.Items(true))
@@ -1236,7 +1236,7 @@ internal static IDataView GetAverageToDataView(IHostEnvironment env, Schema sche
12361236
ValueGetter<VBuffer<ReadOnlyMemory<char>>> getKeyValues =
12371237
(ref VBuffer<ReadOnlyMemory<char>> dst) =>
12381238
{
1239-
schema[stratCol].Metadata.GetValue(MetadataUtils.Kinds.KeyValues, ref dst);
1239+
schema[stratCol].GetKeyValues(ref dst);
12401240
Contracts.Assert(dst.IsDense);
12411241
};
12421242

@@ -1254,7 +1254,7 @@ internal static IDataView GetAverageToDataView(IHostEnvironment env, Schema sche
12541254
else if (i == isWeightedCol)
12551255
{
12561256
env.AssertValue(weightedDvBldr);
1257-
dvBldr.AddColumn(MetricKinds.ColumnNames.IsWeighted, BoolType.Instance, foldCol >= 0 ? new[] { false, false} : new[] { false });
1257+
dvBldr.AddColumn(MetricKinds.ColumnNames.IsWeighted, BoolType.Instance, foldCol >= 0 ? new[] { false, false } : new[] { false });
12581258
weightedDvBldr.AddColumn(MetricKinds.ColumnNames.IsWeighted, BoolType.Instance, foldCol >= 0 ? new[] { true, true } : new[] { true });
12591259
}
12601260
else if (i == foldCol)

src/Microsoft.ML.Data/Prediction/PredictionEngine.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@ public abstract class PredictionEngineBase<TSrc, TDst> : IDisposable
101101
private readonly Action _disposer;
102102
private bool _disposed;
103103

104+
/// <summary>
105+
/// Provides output schema.
106+
/// </summary>
107+
public Schema OutputSchema;
108+
104109
[BestFriend]
105110
private protected ITransformer Transformer { get; }
106111

@@ -128,6 +133,7 @@ private protected PredictionEngineBase(IHostEnvironment env, ITransformer transf
128133
env.AssertValue(makeMapper);
129134
_inputRow = DataViewConstructionUtils.CreateInputRow<TSrc>(env, inputSchemaDefinition);
130135
PredictionEngineCore(env, _inputRow, makeMapper(_inputRow.Schema), ignoreMissingColumns, inputSchemaDefinition, outputSchemaDefinition, out _disposer, out _outputRow);
136+
OutputSchema = Transformer.GetOutputSchema(_inputRow.Schema);
131137
}
132138

133139
[BestFriend]

src/Microsoft.ML.Data/Scorers/BinaryClassifierScorer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ private static ISchemaBoundMapper WrapCore<T>(IHostEnvironment env, ISchemaBound
117117

118118
// Wrap the fetching of the metadata as a simple getter.
119119
ValueGetter<VBuffer<T>> getter = (ref VBuffer<T> value) =>
120-
labelColumn.Metadata.GetValue(MetadataUtils.Kinds.KeyValues, ref value);
120+
labelColumn.GetKeyValues(ref value);
121121

122122
return MultiClassClassifierScorer.LabelNameBindableMapper.CreateBound<T>(env, (ISchemaBoundRowMapper)mapper, type, getter, MetadataUtils.Kinds.TrainingLabelValues, CanWrap);
123123
}

src/Microsoft.ML.Data/Scorers/MultiClassClassifierScorer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,7 @@ internal static ISchemaBoundMapper WrapCore<T>(IHostEnvironment env, ISchemaBoun
438438
ValueGetter<VBuffer<T>> getter =
439439
(ref VBuffer<T> value) =>
440440
{
441-
trainSchema.Label.Value.Metadata.GetValue(MetadataUtils.Kinds.KeyValues, ref value);
441+
trainSchema.Label.Value.GetKeyValues(ref value);
442442
};
443443

444444
return LabelNameBindableMapper.CreateBound<T>(env, (ISchemaBoundRowMapper)mapper, type as VectorType, getter, MetadataUtils.Kinds.SlotNames, CanWrap);

src/Microsoft.ML.Data/Transforms/InvertHashUtils.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public static ValueMapper<T, StringBuilder> GetSimpleMapper<T>(Schema schema, in
5252
// REVIEW: Non-textual KeyValues are certainly possible. Should we handle them?
5353
// Get the key names.
5454
VBuffer<ReadOnlyMemory<char>> keyValues = default;
55-
schema[col].Metadata.GetValue(MetadataUtils.Kinds.KeyValues, ref keyValues);
55+
schema[col].GetKeyValues(ref keyValues);
5656
ReadOnlyMemory<char> value = default;
5757

5858
// REVIEW: We could optimize for identity, but it's probably not worthwhile.

src/Microsoft.ML.Data/Transforms/KeyToValue.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ private KeyToValueMap GetKeyMetadata<TKey, TValue>(int iinfo, ColumnType typeKey
257257
Host.Assert(valItemType.RawType == typeof(TValue));
258258

259259
var keyMetadata = default(VBuffer<TValue>);
260-
InputSchema[ColMapNewToOld[iinfo]].Metadata.GetValue(MetadataUtils.Kinds.KeyValues, ref keyMetadata);
260+
InputSchema[ColMapNewToOld[iinfo]].GetKeyValues(ref keyMetadata);
261261
Host.Check(keyMetadata.Length == keyItemType.GetKeyCountAsInt32(Host));
262262

263263
VBufferUtils.Densify(ref keyMetadata);

src/Microsoft.ML.Data/Transforms/ValueToKeyMappingTransformerImpl.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1116,7 +1116,7 @@ private bool AddMetadataCore<TMeta>(ColumnType srcMetaType, MetadataBuilder buil
11161116
(ref VBuffer<TMeta> dst) =>
11171117
{
11181118
VBuffer<TMeta> srcMeta = default(VBuffer<TMeta>);
1119-
_schema[srcCol].Metadata.GetValue(MetadataUtils.Kinds.KeyValues, ref srcMeta);
1119+
_schema[srcCol].GetKeyValues(ref srcMeta);
11201120
_host.Assert(srcMeta.Length == srcType.GetCountAsInt32(_host));
11211121

11221122
VBuffer<T> keyVals = default(VBuffer<T>);
@@ -1193,7 +1193,7 @@ private bool WriteTextTermsCore<TMeta>(PrimitiveType srcMetaType, TextWriter wri
11931193
_schema.TryGetColumnIndex(_infos[_iinfo].Source, out int srcCol);
11941194

11951195
VBuffer<TMeta> srcMeta = default(VBuffer<TMeta>);
1196-
_schema[srcCol].Metadata.GetValue(MetadataUtils.Kinds.KeyValues, ref srcMeta);
1196+
_schema[srcCol].GetKeyValues(ref srcMeta);
11971197
if (srcMeta.Length != srcType.GetCountAsInt32(_host))
11981198
return false;
11991199

src/Microsoft.ML.Ensemble/PipelineEnsemble.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -649,7 +649,7 @@ private static int CheckKeyLabelColumnCore<T>(IHostEnvironment env, PredictorMod
649649
env.Assert(keyValuesType.ItemType.RawType == typeof(T));
650650
env.AssertNonEmpty(models);
651651
var labelNames = default(VBuffer<T>);
652-
schema[labelIndex].Metadata.GetValue(MetadataUtils.Kinds.KeyValues, ref labelNames);
652+
schema[labelIndex].GetKeyValues(ref labelNames);
653653
var classCount = labelNames.Length;
654654

655655
var curLabelNames = default(VBuffer<T>);
@@ -670,7 +670,7 @@ private static int CheckKeyLabelColumnCore<T>(IHostEnvironment env, PredictorMod
670670
var mdType = labelCol.Metadata.Schema.GetColumnOrNull(MetadataUtils.Kinds.KeyValues)?.Type;
671671
if (!mdType.Equals(keyValuesType))
672672
throw env.Except("Label column of model {0} has different key value type than model 0", i);
673-
labelCol.Metadata.GetValue(MetadataUtils.Kinds.KeyValues, ref curLabelNames);
673+
labelCol.GetKeyValues(ref curLabelNames);
674674
if (!AreEqual(in labelNames, in curLabelNames))
675675
throw env.Except("Label of model {0} has different values than model 0", i);
676676
}

src/Microsoft.ML.EntryPoints/FeatureCombiner.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ private static string GetTerms(IDataView data, string colName)
124124
if (type == null || !type.IsKnownSize || !(type.ItemType is TextType))
125125
return null;
126126
var metadata = default(VBuffer<ReadOnlyMemory<char>>);
127-
col.Value.Metadata.GetValue(MetadataUtils.Kinds.KeyValues, ref metadata);
127+
col.Value.GetKeyValues(ref metadata);
128128
if (!metadata.IsDense)
129129
return null;
130130
var sb = new StringBuilder();

src/Microsoft.ML.StandardLearners/Standard/LogisticRegression/MulticlassLogisticRegression.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ private protected override void CheckLabel(RoleMappedData data)
131131
return;
132132
}
133133
VBuffer<ReadOnlyMemory<char>> labelNames = default;
134-
labelCol.Metadata.GetValue(MetadataUtils.Kinds.KeyValues, ref labelNames);
134+
labelCol.GetKeyValues(ref labelNames);
135135

136136
// If label names is not dense or contain NA or default value, then it follows that
137137
// at least one class does not have a valid name for its label. If the label names we

src/Microsoft.ML.Transforms/Text/NgramTransform.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,7 @@ private void GetSlotNames(int iinfo, int size, ref VBuffer<ReadOnlyMemory<char>>
604604

605605
// Get the key values of the unigrams.
606606
var keyCount = itemType.GetKeyCountAsInt32(Host);
607-
InputSchema[_srcCols[iinfo]].Metadata.GetValue(MetadataUtils.Kinds.KeyValues, ref unigramNames);
607+
InputSchema[_srcCols[iinfo]].GetKeyValues(ref unigramNames);
608608
Host.Check(unigramNames.Length == keyCount);
609609

610610
var pool = _parent._ngramMaps[iinfo];

test/Microsoft.ML.Tests/Scenarios/Api/ApiScenariosTests.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ public class IrisPrediction
4343
public string PredictedLabel;
4444
public float[] Score;
4545
}
46+
public class IrisPredictionNotCasted
47+
{
48+
public uint PredictedLabel;
49+
public float[] Score;
50+
}
4651

4752
public class SentimentData
4853
{
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Linq;
7+
using Microsoft.ML.Data;
8+
using Microsoft.ML.RunTests;
9+
using Microsoft.ML.Trainers;
10+
using Xunit;
11+
12+
namespace Microsoft.ML.Tests.Scenarios.Api
13+
{
14+
public partial class ApiScenariosTests
15+
{
16+
/// <summary>
17+
/// Multiclass predictions produce single PredictedLabel column and array of scores.
18+
/// This examples shows how to map score value to original label.
19+
/// In case if you don't apply KeyToValue estimator on top of predictor label we won't convert
20+
/// key value to original label value. This example also shows how to convert key value to original label.
21+
/// </summary>
22+
[Fact]
23+
void PredictAndMetadata()
24+
{
25+
var dataPath = GetDataPath(TestDatasets.irisData.trainFilename);
26+
var ml = new MLContext();
27+
28+
var data = ml.Data.ReadFromTextFile<IrisData>(dataPath, separatorChar: ',');
29+
30+
var pipeline = ml.Transforms.Concatenate("Features", "SepalLength", "SepalWidth", "PetalLength", "PetalWidth")
31+
.Append(ml.Transforms.Conversion.MapValueToKey("Label"), TransformerScope.TrainTest)
32+
.Append(ml.MulticlassClassification.Trainers.StochasticDualCoordinateAscent(
33+
new SdcaMultiClassTrainer.Options { MaxIterations = 100, Shuffle = true, NumThreads = 1, }));
34+
35+
var model = pipeline.Fit(data).GetModelFor(TransformerScope.Scoring);
36+
var engine = model.CreatePredictionEngine<IrisDataNoLabel, IrisPredictionNotCasted>(ml);
37+
38+
var testLoader = ml.Data.ReadFromTextFile(dataPath, TestDatasets.irisData.GetLoaderColumns(), hasHeader: true, separatorChar: ',');
39+
var testData = ml.CreateEnumerable<IrisData>(testLoader, false);
40+
41+
// During prediction we will get Score column with 3 float values.
42+
// We need to find way to map each score to original label.
43+
// In order to do what we need to get SlotNames from Score column.
44+
// Slot names on top of Score column represent original labels for i-th value in Score array.
45+
VBuffer<ReadOnlyMemory<char>> slotNames = default;
46+
engine.OutputSchema[nameof(IrisPrediction.Score)].GetSlotNames(ref slotNames);
47+
// Since we apply MapValueToKey estimator with default parameters, key values
48+
// depends on order of occurence in data file. Which is "Iris-setosa", "Iris-versicolor", "Iris-virginica"
49+
// So if we have Score column equal to [0.2, 0.3, 0.5] that's mean what score for
50+
// Iris-setosa is 0.2
51+
// Iris-versicolor is 0.3
52+
// Iris-virginica is 0.5.
53+
Assert.True(slotNames.GetItemOrDefault(0).ToString() == "Iris-setosa");
54+
Assert.True(slotNames.GetItemOrDefault(1).ToString() == "Iris-versicolor");
55+
Assert.True(slotNames.GetItemOrDefault(2).ToString() == "Iris-virginica");
56+
57+
// Let's look how we can convert key value for PredictedLabel to original labels.
58+
// We need to read KeyValues for "PredictedLabel" column.
59+
VBuffer<ReadOnlyMemory<char>> keys = default;
60+
engine.OutputSchema[nameof(IrisPrediction.PredictedLabel)].GetKeyValues(ref keys);
61+
foreach (var input in testData.Take(20))
62+
{
63+
var prediction = engine.Predict(input);
64+
// Predicted label is key type which internal representation starts from 1.
65+
// (0 reserved for NaN value) so in order to cast key to index in key metadata we need to distract 1 from it.
66+
var deciphieredLabel = keys.GetItemOrDefault((int)prediction.PredictedLabel - 1).ToString();
67+
Assert.True(deciphieredLabel == input.Label);
68+
}
69+
}
70+
}
71+
}

test/Microsoft.ML.Tests/TermEstimatorTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ void TestMetadataCopy()
147147
var names1 = default(VBuffer<ReadOnlyMemory<char>>);
148148
var type1 = result.Schema[termIndex].Type;
149149
var itemType1 = (type1 as VectorType)?.ItemType ?? type1;
150-
result.Schema[termIndex].Metadata.GetValue(MetadataUtils.Kinds.KeyValues, ref names1);
150+
result.Schema[termIndex].GetKeyValues(ref names1);
151151
Assert.True(names1.GetValues().Length > 0);
152152
}
153153

test/Microsoft.ML.Tests/Transformers/CategoricalHashTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,13 +184,13 @@ private void ValidateMetadata(IDataView result)
184184

185185
column = result.Schema["CatG"];
186186
Assert.Equal(column.Metadata.Schema.Select(x => x.Name), new string[1] { MetadataUtils.Kinds.KeyValues });
187-
column.Metadata.GetValue(MetadataUtils.Kinds.KeyValues, ref slots);
187+
column.GetKeyValues(ref slots);
188188
Assert.True(slots.Length == 65536);
189189
Assert.Equal(slots.Items().Select(x => x.Value.ToString()), new string[2] { "0:A", "1:B" });
190190

191191
column = result.Schema["CatH"];
192192
Assert.Equal(column.Metadata.Schema.Select(x => x.Name), new string[1] { MetadataUtils.Kinds.KeyValues });
193-
column.Metadata.GetValue(MetadataUtils.Kinds.KeyValues, ref slots);
193+
column.GetKeyValues(ref slots);
194194
Assert.True(slots.Length == 65536);
195195
Assert.Equal(slots.Items().Select(x => x.Value.ToString()), new string[1] { "C" });
196196

test/Microsoft.ML.Tests/Transformers/CategoricalTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,13 +258,13 @@ private void ValidateMetadata(IDataView result)
258258

259259
column = result.Schema["CatG"];
260260
Assert.Equal(column.Metadata.Schema.Select(x => x.Name), new string[1] { MetadataUtils.Kinds.KeyValues });
261-
column.Metadata.GetValue(MetadataUtils.Kinds.KeyValues, ref slots);
261+
column.GetKeyValues(ref slots);
262262
Assert.True(slots.Length == 3);
263263
Assert.Equal(slots.Items().Select(x => x.Value.ToString()), new string[3] { "A", "D", "E" });
264264

265265
column = result.Schema["CatH"];
266266
Assert.Equal(column.Metadata.Schema.Select(x => x.Name), new string[1] { MetadataUtils.Kinds.KeyValues });
267-
column.Metadata.GetValue(MetadataUtils.Kinds.KeyValues, ref slots);
267+
column.GetKeyValues(ref slots);
268268
Assert.True(slots.Length == 2);
269269
Assert.Equal(slots.Items().Select(x => x.Value.ToString()), new string[2] { "D", "E" });
270270

test/Microsoft.ML.Tests/Transformers/ConvertTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ private void ValidateMetadata(IDataView result)
232232
Assert.True(result.Schema["ConvA"].IsNormalized());
233233

234234
Assert.Equal(result.Schema["ConvB"].Metadata.Schema.Select(x => x.Name), new string[1] { MetadataUtils.Kinds.KeyValues });
235-
result.Schema["ConvB"].Metadata.GetValue(MetadataUtils.Kinds.KeyValues, ref slots);
235+
result.Schema["ConvB"].GetKeyValues(ref slots);
236236
Assert.True(slots.Length == 2);
237237
Assert.Equal(slots.Items().Select(x => x.Value.ToString()), new string[2] { "A", "B" });
238238
}

test/Microsoft.ML.Tests/Transformers/CopyColumnEstimatorTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,8 @@ void TestMetadataCopy()
145145
Assert.InRange<ulong>(key.Count, 0, int.MaxValue);
146146
int size = (int)key.Count;
147147
var type2 = result.Schema[copyIndex].Type;
148-
result.Schema[termIndex].Metadata.GetValue(MetadataUtils.Kinds.KeyValues, ref names1);
149-
result.Schema[copyIndex].Metadata.GetValue(MetadataUtils.Kinds.KeyValues, ref names2);
148+
result.Schema[termIndex].GetKeyValues(ref names1);
149+
result.Schema[copyIndex].GetKeyValues(ref names2);
150150
Assert.True(CompareVec(in names1, in names2, size, (a, b) => a.Span.SequenceEqual(b.Span)));
151151
}
152152

0 commit comments

Comments
 (0)