ExprGraph lists are read-only views over private arrays; test short evaluator inputs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2c5f71b959
commit
646cc59426
4 changed files with 86 additions and 12 deletions
|
|
@ -19,13 +19,14 @@ public static class ExprEvaluator
|
|||
var points = outputs.Length / outputCount;
|
||||
if (inputs.Length != points * graph.InputNames.Count) throw new ExprGraphException("column-shape");
|
||||
|
||||
var nodes = graph.Nodes;
|
||||
var nodes = graph.NodeArray;
|
||||
var outputNodes = graph.OutputNodeArray;
|
||||
var start = graph.SlotStart;
|
||||
var slots = new float[graph.SlotCount];
|
||||
Span<float> operands = stackalloc float[ExprOps.MaxArity];
|
||||
for (var point = 0; point < points; point++)
|
||||
{
|
||||
for (var i = 0; i < nodes.Count; i++)
|
||||
for (var i = 0; i < nodes.Length; i++)
|
||||
{
|
||||
var node = nodes[i];
|
||||
switch (node.Kind)
|
||||
|
|
@ -40,17 +41,17 @@ public static class ExprEvaluator
|
|||
slots[start[i]] = BitConverter.Int32BitsToSingle(node.Payload);
|
||||
break;
|
||||
case ExprNodeKind.Pick:
|
||||
slots[start[i]] = slots[start[node.Operands[0]] + node.Payload];
|
||||
slots[start[i]] = slots[start[node.OperandArray[0]] + node.Payload];
|
||||
break;
|
||||
default:
|
||||
var op = node.Op!;
|
||||
for (var k = 0; k < op.Arity; k++) operands[k] = slots[start[node.Operands[k]]];
|
||||
for (var k = 0; k < op.Arity; k++) operands[k] = slots[start[node.OperandArray[k]]];
|
||||
op.Evaluate(operands.Slice(0, op.Arity), new Span<float>(slots, start[i], op.ResultCount));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (var o = 0; o < outputCount; o++) outputs[o * points + point] = slots[start[graph.OutputNodes[o]]];
|
||||
for (var o = 0; o < outputCount; o++) outputs[o * points + point] = slots[start[outputNodes[o]]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,12 @@ public sealed class ExprGraph
|
|||
this.outputNames = outputNames;
|
||||
this.outputNodes = outputNodes;
|
||||
this.nodes = nodes;
|
||||
InputNames = Array.AsReadOnly(inputNames);
|
||||
ParamNames = Array.AsReadOnly(paramNames);
|
||||
ParamDefaultBits = Array.AsReadOnly(paramDefaultBits);
|
||||
OutputNames = Array.AsReadOnly(outputNames);
|
||||
OutputNodes = Array.AsReadOnly(outputNodes);
|
||||
Nodes = Array.AsReadOnly(nodes);
|
||||
SlotStart = new int[nodes.Length];
|
||||
var slots = 0;
|
||||
for (var i = 0; i < nodes.Length; i++)
|
||||
|
|
@ -37,19 +43,25 @@ public sealed class ExprGraph
|
|||
SlotCount = slots;
|
||||
}
|
||||
|
||||
public IReadOnlyList<string> InputNames => inputNames;
|
||||
public IReadOnlyList<string> InputNames { get; }
|
||||
|
||||
public IReadOnlyList<string> ParamNames => paramNames;
|
||||
public IReadOnlyList<string> ParamNames { get; }
|
||||
|
||||
/// <summary>The default of each param as float bits, so a NaN payload or a signed zero survives.</summary>
|
||||
public IReadOnlyList<int> ParamDefaultBits => paramDefaultBits;
|
||||
public IReadOnlyList<int> ParamDefaultBits { get; }
|
||||
|
||||
public IReadOnlyList<string> OutputNames => outputNames;
|
||||
public IReadOnlyList<string> OutputNames { get; }
|
||||
|
||||
/// <summary>The node each output reads.</summary>
|
||||
public IReadOnlyList<int> OutputNodes => outputNodes;
|
||||
public IReadOnlyList<int> OutputNodes { get; }
|
||||
|
||||
public IReadOnlyList<ExprNode> Nodes => nodes;
|
||||
public IReadOnlyList<ExprNode> Nodes { get; }
|
||||
|
||||
/// <summary>The evaluator's view of the nodes: the private array, never handed out.</summary>
|
||||
internal ExprNode[] NodeArray => nodes;
|
||||
|
||||
/// <summary>The evaluator's view of the output nodes: the private array, never handed out.</summary>
|
||||
internal int[] OutputNodeArray => outputNodes;
|
||||
|
||||
/// <summary>Where each node's results start in an evaluator's value array.</summary>
|
||||
internal int[] SlotStart { get; }
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ public sealed class ExprNode
|
|||
Kind = kind;
|
||||
WireName = wireName;
|
||||
operandArray = operands;
|
||||
Operands = Array.AsReadOnly(operands);
|
||||
Payload = payload;
|
||||
Op = op;
|
||||
ResultCount = op?.ResultCount ?? 1;
|
||||
|
|
@ -41,7 +42,10 @@ public sealed class ExprNode
|
|||
/// <summary>The persisted name: an op name for a call, otherwise <c>input</c>, <c>param</c>, <c>const</c> or <c>pick</c>.</summary>
|
||||
public string WireName { get; }
|
||||
|
||||
public IReadOnlyList<int> Operands => operandArray;
|
||||
public IReadOnlyList<int> Operands { get; }
|
||||
|
||||
/// <summary>The evaluator's view of the operands: the private array, never handed out.</summary>
|
||||
internal int[] OperandArray => operandArray;
|
||||
|
||||
public int Payload { get; }
|
||||
|
||||
|
|
|
|||
|
|
@ -414,6 +414,10 @@ public sealed class ExprTests
|
|||
var bindings = ExprBindings.Bind(graph);
|
||||
Assert.Equal("column-shape", Assert.Throws<ExprGraphException>(() => ExprEvaluator.Evaluate(graph, bindings, new float[2], new float[5])).Rule);
|
||||
Assert.Equal("column-shape", Assert.Throws<ExprGraphException>(() => ExprEvaluator.Evaluate(graph, bindings, new float[3], new float[4])).Rule);
|
||||
var short_ = new float[4];
|
||||
Assert.Equal("column-shape", Assert.Throws<ExprGraphException>(() => ExprEvaluator.Evaluate(graph, bindings, new float[1], short_)).Rule);
|
||||
Assert.Equal("column-shape", Assert.Throws<ExprGraphException>(() => ExprEvaluator.Evaluate(graph, bindings, ReadOnlySpan<float>.Empty, short_)).Rule);
|
||||
Assert.Equal(new float[4], short_);
|
||||
var outputs = new float[6];
|
||||
ExprEvaluator.Evaluate(graph, bindings, new[] { 1f, 2f, 3f }, outputs);
|
||||
var none = new ExprBuilder().Build();
|
||||
|
|
@ -421,6 +425,59 @@ public sealed class ExprTests
|
|||
Assert.Equal(new[] { 1f, 2f, 3f, -1f, -2f, -3f }, outputs);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AValidatedGraphCannotBeChangedThroughItsLists()
|
||||
{
|
||||
var b = new ExprBuilder();
|
||||
var x = b.Input("x");
|
||||
var k = b.Param("k", 2f);
|
||||
b.Output("o", b.Call("mul", x, k));
|
||||
var graph = b.Build();
|
||||
var input = new[] { 1f, 2f, 3f };
|
||||
var before = new float[3];
|
||||
ExprEvaluator.Evaluate(graph, ExprBindings.Bind(graph), input, before);
|
||||
var operands = graph.Nodes[2].Operands;
|
||||
|
||||
Assert.False(graph.InputNames is string[] || graph.ParamNames is string[] || graph.OutputNames is string[]);
|
||||
Assert.False(graph.ParamDefaultBits is int[] || graph.OutputNodes is int[] || operands is int[] || graph.Nodes is ExprNode[]);
|
||||
Assert.Throws<NotSupportedException>(() => ((IList<int>)operands)[0] = 7);
|
||||
Assert.Throws<NotSupportedException>(() => ((IList<int>)graph.OutputNodes)[0] = 99);
|
||||
Assert.Throws<NotSupportedException>(() => ((IList<int>)graph.ParamDefaultBits)[0] = 0);
|
||||
Assert.Throws<NotSupportedException>(() => ((IList<string>)graph.InputNames)[0] = "bad name");
|
||||
Assert.Throws<NotSupportedException>(() => ((IList<string>)graph.ParamNames)[0] = "bad name");
|
||||
Assert.Throws<NotSupportedException>(() => ((IList<string>)graph.OutputNames)[0] = "bad name");
|
||||
Assert.Throws<NotSupportedException>(() => ((IList<ExprNode>)graph.Nodes)[0] = graph.Nodes[1]);
|
||||
|
||||
var after = new float[3];
|
||||
ExprEvaluator.Evaluate(graph, ExprBindings.Bind(graph), input, after);
|
||||
Assert.Equal(before, after);
|
||||
Assert.Equal(new[] { 2f, 4f, 6f }, after);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateCopiesWhatItIsGiven()
|
||||
{
|
||||
var inputs = new[] { "x" };
|
||||
var outputs = new[] { "o" };
|
||||
var outputNodes = new[] { 0 };
|
||||
var nodes = new[] { ExprNode.Input(0) };
|
||||
var graph = ExprGraph.Create(ExprGraph.FormatVersion, inputs, Array.Empty<string>(), Array.Empty<int>(), outputs, outputNodes, nodes);
|
||||
|
||||
inputs[0] = "bad name";
|
||||
outputs[0] = "bad name";
|
||||
outputNodes[0] = 99;
|
||||
nodes[0] = ExprNode.Const(0);
|
||||
|
||||
Assert.Equal(new[] { "x" }, graph.InputNames);
|
||||
Assert.Equal(new[] { "o" }, graph.OutputNames);
|
||||
Assert.Equal(new[] { 0 }, graph.OutputNodes);
|
||||
Assert.Equal(ExprNodeKind.Input, graph.Nodes[0].Kind);
|
||||
var operands = new[] { 0 };
|
||||
var call = ExprNode.Call("neg", operands);
|
||||
operands[0] = 5;
|
||||
Assert.Equal(new[] { 0 }, call.Operands);
|
||||
}
|
||||
|
||||
private static ExprGraphException Refusal(Action action) => Assert.Throws<ExprGraphException>(action);
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
Loading…
Reference in a new issue