fixed nested with statement dependency bug in graphtags/v0.9
| @@ -30,15 +30,15 @@ namespace Tensorflow | |||||
| /// <returns>A list of control inputs for the op to be created.</returns> | /// <returns>A list of control inputs for the op to be created.</returns> | ||||
| private ITensorOrOperation[] _control_dependencies_for_inputs(ITensorOrOperation[] input_ops) | private ITensorOrOperation[] _control_dependencies_for_inputs(ITensorOrOperation[] input_ops) | ||||
| { | { | ||||
| var ret = new ITensorOrOperation[0]; | |||||
| var ret = new List<ITensorOrOperation>(); | |||||
| foreach(var controller in _control_dependencies_stack) | |||||
| foreach (var controller in _control_dependencies_stack) | |||||
| { | { | ||||
| bool dominated = false; | bool dominated = false; | ||||
| // If any of the input_ops already depends on the inputs from controller, | // If any of the input_ops already depends on the inputs from controller, | ||||
| // we say that the new op is dominated (by that input), and we therefore | // we say that the new op is dominated (by that input), and we therefore | ||||
| // do not need to add control dependencies for this controller's inputs. | // do not need to add control dependencies for this controller's inputs. | ||||
| foreach(var op in input_ops) | |||||
| foreach (var op in input_ops) | |||||
| { | { | ||||
| if (controller.op_in_group(op)) | if (controller.op_in_group(op)) | ||||
| { | { | ||||
| @@ -48,12 +48,22 @@ namespace Tensorflow | |||||
| } | } | ||||
| if (!dominated) | if (!dominated) | ||||
| ret = controller.control_inputs.Where(x => !input_ops.Contains(x)).ToArray(); | |||||
| ret.AddRange(controller.control_inputs.Where(x => !input_ops.Contains(x))); | |||||
| } | } | ||||
| return ret; | |||||
| return ret.ToArray(); | |||||
| } | } | ||||
| /// <summary> | |||||
| /// Returns a context manager that specifies control dependencies. | |||||
| /// | |||||
| /// Use with the `with` keyword to specify that all operations constructed | |||||
| /// within the context should have control dependencies on | |||||
| /// `control_inputs`. | |||||
| /// </summary> | |||||
| public _ControlDependenciesController control_dependencies(ITensorOrOperation[] control_inputs) | |||||
| => control_dependencies(control_inputs == null ? null : control_inputs.OfType<object>().ToArray()); | |||||
| /// <summary> | /// <summary> | ||||
| /// Returns a context manager that specifies control dependencies. | /// Returns a context manager that specifies control dependencies. | ||||
| /// | /// | ||||
| @@ -61,7 +71,7 @@ namespace Tensorflow | |||||
| /// within the context should have control dependencies on | /// within the context should have control dependencies on | ||||
| /// `control_inputs`. | /// `control_inputs`. | ||||
| /// </summary> | /// </summary> | ||||
| public _ControlDependenciesController control_dependencies(ITensorOrOperation[] control_inputs) | |||||
| public _ControlDependenciesController control_dependencies(object[] control_inputs) | |||||
| { | { | ||||
| if (control_inputs == null) | if (control_inputs == null) | ||||
| return new _ControlDependenciesController(this, null); | return new _ControlDependenciesController(this, null); | ||||
| @@ -69,9 +79,26 @@ namespace Tensorflow | |||||
| var control_ops = new List<ITensorOrOperation>(); | var control_ops = new List<ITensorOrOperation>(); | ||||
| foreach (var c in control_inputs) | foreach (var c in control_inputs) | ||||
| { | { | ||||
| control_ops.Add(c); | |||||
| switch (c) | |||||
| { | |||||
| // TODO: implement IndexedSlices | |||||
| //case IndexedSlices islice: | |||||
| // control_ops.Add(islice.op); | |||||
| // break; | |||||
| case Tensor t: | |||||
| control_ops.Add(t.op); | |||||
| break; | |||||
| case Operation op: | |||||
| control_ops.Add(op); | |||||
| break; | |||||
| default: | |||||
| var t1 = _as_graph_element(c); | |||||
| if (t1 == null) | |||||
| throw new TypeError($"Control input must be Operation or Tensor:{c}"); | |||||
| control_ops.Add(t1.op); | |||||
| break; | |||||
| } | |||||
| } | } | ||||
| return new _ControlDependenciesController(this, control_ops); | return new _ControlDependenciesController(this, control_ops); | ||||
| } | } | ||||
| @@ -103,6 +130,9 @@ namespace Tensorflow | |||||
| _control_dependencies_stack.Dequeue(); | _control_dependencies_stack.Dequeue(); | ||||
| } | } | ||||
| /// <summary> | |||||
| /// Record that the given op depends on all registered control dependencies. | |||||
| /// </summary> | |||||
| public void _record_op_seen_by_control_dependencies(Operation op) | public void _record_op_seen_by_control_dependencies(Operation op) | ||||
| { | { | ||||
| foreach (var controller in _control_dependencies_stack) | foreach (var controller in _control_dependencies_stack) | ||||
| @@ -21,8 +21,14 @@ namespace Tensorflow | |||||
| public OperationDescription NewOperation(string opType, string opName) | public OperationDescription NewOperation(string opType, string opName) | ||||
| { | { | ||||
| return c_api.TF_NewOperation(_handle, opType, opName); | return c_api.TF_NewOperation(_handle, opType, opName); | ||||
| } | |||||
| } | |||||
| /// <summary> | |||||
| /// Returns the `Operation` with the given `name`. | |||||
| /// | |||||
| /// This method may be called concurrently from multiple threads. | |||||
| /// </summary> | |||||
| /// <param name="name">The name of the `Operation` to return.</param> | |||||
| public Operation get_operation_by_name(string name) | public Operation get_operation_by_name(string name) | ||||
| => as_graph_element(name, allow_tensor: false, allow_operation: true) as Operation; | => as_graph_element(name, allow_tensor: false, allow_operation: true) as Operation; | ||||
| @@ -17,8 +17,29 @@ namespace Tensorflow | |||||
| private bool _new_stack; | private bool _new_stack; | ||||
| private IControlFlowContext _old_control_flow_context; | private IControlFlowContext _old_control_flow_context; | ||||
| public ITensorOrOperation[] control_inputs => _control_inputs_val.ToArray(); | |||||
| public ITensorOrOperation[] control_inputs => _control_inputs_val.ToArray(); | |||||
| /// <summary> | |||||
| /// Create a new `_ControlDependenciesController`. | |||||
| /// | |||||
| /// A `_ControlDependenciesController` is the context manager for | |||||
| /// `with tf.control_dependencies()` blocks.These normally nest, | |||||
| /// as described in the documentation for `control_dependencies()`. | |||||
| /// | |||||
| /// The `control_inputs` argument list control dependencies that must be | |||||
| /// added to the current set of control dependencies.Because of | |||||
| /// uniquification the set can be empty even if the caller passed a list of | |||||
| /// ops.The special value `None` indicates that we want to start a new | |||||
| /// empty set of control dependencies instead of extending the current set. | |||||
| /// | |||||
| /// In that case we also clear the current control flow context, which is an | |||||
| /// additional mechanism to add control dependencies. | |||||
| /// </summary> | |||||
| /// <param name="graph">The graph that this controller is managing.</param> | |||||
| /// <param name="control_inputs">List of ops to use as control inputs in addition | |||||
| /// to the current control dependencies.None to indicate that | |||||
| /// the dependencies should be cleared. | |||||
| /// </param> | |||||
| public _ControlDependenciesController(Graph graph, List<ITensorOrOperation> control_inputs) | public _ControlDependenciesController(Graph graph, List<ITensorOrOperation> control_inputs) | ||||
| { | { | ||||
| _graph = graph; | _graph = graph; | ||||
| @@ -1,68 +1,79 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Runtime.InteropServices; | |||||
| using System.Text; | |||||
| namespace Tensorflow | |||||
| { | |||||
| public partial class Operation | |||||
| { | |||||
| public TF_Output Input(int index) => c_api.TF_OperationInput(new TF_Input(_handle, index)); | |||||
| public TF_DataType InputType(int index) => c_api.TF_OperationInputType(new TF_Input(_handle, index)); | |||||
| public int InputListLength(string name) => c_api.TF_OperationInputListLength(_handle, name, status); | |||||
| public int NumInputs => c_api.TF_OperationNumInputs(_handle); | |||||
| private TF_DataType[] _input_types => _inputs._inputs.Select(x => x.dtype).ToArray(); | |||||
| private InputList _inputs; | |||||
| public InputList inputs | |||||
| { | |||||
| get | |||||
| { | |||||
| if (_inputs == null) | |||||
| { | |||||
| var retval = new Tensor[NumInputs]; | |||||
| for (int i = 0; i < NumInputs; i++) | |||||
| { | |||||
| var tf_outpus = Input(i); | |||||
| var op = new Operation(tf_outpus.oper); | |||||
| retval[i] = op.outputs[tf_outpus.index]; | |||||
| } | |||||
| _inputs = new InputList(retval); | |||||
| } | |||||
| return _inputs; | |||||
| } | |||||
| } | |||||
| public int NumControlInputs => c_api.TF_OperationNumControlInputs(_handle); | |||||
| public Operation[] control_inputs | |||||
| { | |||||
| get | |||||
| { | |||||
| return GetControlInputs(); | |||||
| } | |||||
| } | |||||
| public unsafe Operation[] GetControlInputs() | |||||
| { | |||||
| var control_inputs = new Operation[NumControlInputs]; | |||||
| if (NumControlInputs > 0) | |||||
| { | |||||
| IntPtr control_input_handle = Marshal.AllocHGlobal(Marshal.SizeOf<IntPtr>() * NumControlInputs); | |||||
| c_api.TF_OperationGetControlInputs(_handle, control_input_handle, NumControlInputs); | |||||
| for (int i = 0; i < NumControlInputs; i++) | |||||
| { | |||||
| var handle = control_input_handle + Marshal.SizeOf<IntPtr>() * i; | |||||
| control_inputs[i] = new Operation(*(IntPtr*)handle); | |||||
| } | |||||
| } | |||||
| return control_inputs; | |||||
| } | |||||
| } | |||||
| } | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Runtime.InteropServices; | |||||
| using System.Text; | |||||
| namespace Tensorflow | |||||
| { | |||||
| // from ops.py | |||||
| public partial class Operation | |||||
| { | |||||
| public TF_Output Input(int index) => c_api.TF_OperationInput(new TF_Input(_handle, index)); | |||||
| public TF_DataType InputType(int index) => c_api.TF_OperationInputType(new TF_Input(_handle, index)); | |||||
| public int InputListLength(string name) => c_api.TF_OperationInputListLength(_handle, name, status); | |||||
| public int NumInputs => c_api.TF_OperationNumInputs(_handle); | |||||
| private TF_DataType[] _input_types => _inputs._inputs.Select(x => x.dtype).ToArray(); | |||||
| private InputList _inputs; | |||||
| public InputList inputs | |||||
| { | |||||
| get | |||||
| { | |||||
| if (_inputs == null) | |||||
| { | |||||
| var retval = new Tensor[NumInputs]; | |||||
| for (int i = 0; i < NumInputs; i++) | |||||
| { | |||||
| var tf_outpus = Input(i); | |||||
| var op = new Operation(tf_outpus.oper); | |||||
| retval[i] = op.outputs[tf_outpus.index]; | |||||
| } | |||||
| _inputs = new InputList(retval); | |||||
| } | |||||
| return _inputs; | |||||
| } | |||||
| } | |||||
| public int NumControlInputs => c_api.TF_OperationNumControlInputs(_handle); | |||||
| /// <summary> | |||||
| /// The `Operation` objects on which this op has a control dependency. | |||||
| /// | |||||
| /// Before this op is executed, TensorFlow will ensure that the | |||||
| /// operations in `self.control_inputs` have finished executing.This | |||||
| /// mechanism can be used to run ops sequentially for performance | |||||
| /// reasons, or to ensure that the side effects of an op are observed | |||||
| /// in the correct order. | |||||
| /// </summary> | |||||
| public Operation[] control_inputs | |||||
| { | |||||
| get | |||||
| { | |||||
| return GetControlInputs(); | |||||
| } | |||||
| } | |||||
| public unsafe Operation[] GetControlInputs() | |||||
| { | |||||
| var control_inputs = new Operation[NumControlInputs]; | |||||
| if (NumControlInputs > 0) | |||||
| { | |||||
| IntPtr control_input_handle = Marshal.AllocHGlobal(Marshal.SizeOf<IntPtr>() * NumControlInputs); | |||||
| c_api.TF_OperationGetControlInputs(_handle, control_input_handle, NumControlInputs); | |||||
| for (int i = 0; i < NumControlInputs; i++) | |||||
| { | |||||
| var handle = control_input_handle + Marshal.SizeOf<IntPtr>() * i; | |||||
| control_inputs[i] = new Operation(*(IntPtr*)handle); | |||||
| } | |||||
| } | |||||
| return control_inputs; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -45,7 +45,6 @@ Bug memory leak issue when allocating Tensor.</PackageReleaseNotes> | |||||
| <ItemGroup> | <ItemGroup> | ||||
| <PackageReference Include="Google.Protobuf" Version="3.7.0" /> | <PackageReference Include="Google.Protobuf" Version="3.7.0" /> | ||||
| <PackageReference Include="NumSharp" Version="0.8.3" /> | |||||
| </ItemGroup> | </ItemGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| @@ -119,11 +119,14 @@ namespace Tensorflow | |||||
| /// A context manager that specifies control dependencies for all | /// A context manager that specifies control dependencies for all | ||||
| /// operations constructed within the context. | /// operations constructed within the context. | ||||
| /// </returns> | /// </returns> | ||||
| public static _ControlDependenciesController control_dependencies(Operation[] control_inputs) | |||||
| public static _ControlDependenciesController control_dependencies(object[] control_inputs) | |||||
| { | { | ||||
| return get_default_graph().control_dependencies(control_inputs); | return get_default_graph().control_dependencies(control_inputs); | ||||
| } | } | ||||
| public static _ControlDependenciesController control_dependencies(ITensorOrOperation[] control_inputs) | |||||
| => control_dependencies(control_inputs == null ? null : control_inputs.OfType<object>().ToArray()); | |||||
| /// <summary> | /// <summary> | ||||
| /// Creates a TF_Operation. | /// Creates a TF_Operation. | ||||
| /// </summary> | /// </summary> | ||||
| @@ -23,7 +23,7 @@ namespace TensorFlowNET.UnitTest | |||||
| { | { | ||||
| a = constant_op.constant(1.0); | a = constant_op.constant(1.0); | ||||
| b = constant_op.constant(1.0); | b = constant_op.constant(1.0); | ||||
| with(g.control_dependencies(new ITensorOrOperation[] { a }), x => | |||||
| with(g.control_dependencies(new[] { a }), x => | |||||
| { | { | ||||
| c = constant_op.constant(1.0); | c = constant_op.constant(1.0); | ||||
| d = array_ops.identity(b); | d = array_ops.identity(b); | ||||
| @@ -36,15 +36,15 @@ namespace TensorFlowNET.UnitTest | |||||
| Assert.AreEqual(0, e.op.control_inputs.Length); | Assert.AreEqual(0, e.op.control_inputs.Length); | ||||
| } | } | ||||
| [Ignore("Part of this test is not compiling")] | |||||
| [Ignore("Future is not supported yet")] | |||||
| [TestMethod] | [TestMethod] | ||||
| public void TestEager() | public void TestEager() | ||||
| { | { | ||||
| Tensor a = null, b = null, c = null, d = null, e = null; | |||||
| Tensor a = null, c = null, d = null, e = null; | |||||
| object b = null; | |||||
| var calls = 0; | var calls = 0; | ||||
| Func<Tensor> future = () => | Func<Tensor> future = () => | ||||
| { | { | ||||
| calls += 1; | calls += 1; | ||||
| return constant_op.constant(2.0); | return constant_op.constant(2.0); | ||||
| }; | }; | ||||
| @@ -55,26 +55,26 @@ namespace TensorFlowNET.UnitTest | |||||
| if (context.executing_eagerly()) | if (context.executing_eagerly()) | ||||
| { | { | ||||
| // TODO: make this compile (see original Python code below) | // TODO: make this compile (see original Python code below) | ||||
| //a = constant_op.constant(1.0); | |||||
| //b = future; // <--- {henon} obviously, this doesn't compile, looks like control_dependencies needs to be able to take callables as well. | |||||
| //with(ops.control_dependencies(new Operation[] {a, b}), ctrl => | |||||
| //{ | |||||
| // return c = constant_op.constant(3.0); | |||||
| //}); | |||||
| //Assert.AreEqual(calls, 1); | |||||
| a = constant_op.constant(1.0); | |||||
| b = future; // <--- {henon} obviously, this doesn't compile, looks like control_dependencies needs to be able to take callables as well. | |||||
| with(ops.control_dependencies(new object[] { a, b }), ctrl => | |||||
| { | |||||
| return c = constant_op.constant(3.0); | |||||
| }); | |||||
| Assert.AreEqual(calls, 1); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| var graph = tf.Graph(); | |||||
| with<Graph>(graph.as_default(), g => | |||||
| var graph = tf.Graph().as_default(); | |||||
| with<Graph>(graph, g => | |||||
| { | { | ||||
| a = constant_op.constant(1.0); | a = constant_op.constant(1.0); | ||||
| b = future(); | |||||
| with(g.control_dependencies(new ITensorOrOperation[] { a, b }), ctrl => | |||||
| { | |||||
| c = constant_op.constant(3.0); | |||||
| }); | |||||
| Assert.IsTrue(Enumerable.SequenceEqual(c.op.control_inputs, new[] { a.op, b.op })); | |||||
| var b1 = future(); | |||||
| with(g.control_dependencies(new [] { a, b}), ctrl => | |||||
| { | |||||
| c = constant_op.constant(3.0); | |||||
| }); | |||||
| Assert.IsTrue(Enumerable.SequenceEqual(c.op.control_inputs, new[] { a.op, b1.op })); | |||||
| Assert.AreEqual(1, calls); | Assert.AreEqual(1, calls); | ||||
| }); | }); | ||||
| @@ -106,100 +106,107 @@ namespace TensorFlowNET.UnitTest | |||||
| } | } | ||||
| // Note: {henon}, all tests below use the function _apply_op which is not really portable in C#, see original source below | |||||
| // but I think _apply_op(...) can just be replaced by g.create_op(...). | |||||
| /* | |||||
| def _apply_op(g, *args, **kwargs): | |||||
| op = g.create_op(*args, **kwargs) | |||||
| if len(op.outputs) == 1: | |||||
| return op.outputs[0] | |||||
| else: | |||||
| return op.outputs | |||||
| */ | |||||
| [Ignore("")] | |||||
| [Ignore("How to port the ConvertibleObj?")] | |||||
| [TestMethod] | [TestMethod] | ||||
| public void TestBasicWithConversion() | public void TestBasicWithConversion() | ||||
| { | { | ||||
| var g = ops.get_default_graph(); | |||||
| var g = tf.Graph().as_default(); | |||||
| // Note: _apply_op can be replaced by g.create_op | // Note: _apply_op can be replaced by g.create_op | ||||
| var a = g.create_op("FloatOutput", new Tensor[] { }, new[] { TF_DataType.TF_FLOAT }); | var a = g.create_op("FloatOutput", new Tensor[] { }, new[] { TF_DataType.TF_FLOAT }); | ||||
| // TODO: ConvertibleObj, see original source below | // TODO: ConvertibleObj, see original source below | ||||
| /* | /* | ||||
| def testBasicWithConversion(self): | |||||
| g = ops.Graph() | |||||
| a = _apply_op(g, "FloatOutput", [], [dtypes.float32]) | |||||
| def testBasicWithConversion(self): | |||||
| g = ops.Graph() | |||||
| a = _apply_op(g, "FloatOutput", [], [dtypes.float32]) | |||||
| class ConvertibleObj(object): | |||||
| class ConvertibleObj(object): | |||||
| def _as_graph_element(self): | |||||
| return a | |||||
| def _as_graph_element(self): | |||||
| return a | |||||
| with g.control_dependencies([ConvertibleObj()]): | |||||
| c = _apply_op(g, "FloatOutput", [], [dtypes.float32]) | |||||
| with g.control_dependencies([ConvertibleObj()]): | |||||
| c = _apply_op(g, "FloatOutput", [], [dtypes.float32]) | |||||
| self.assertEqual(c.op.control_inputs, [a.op]) | |||||
| self.assertEqual(c.op.control_inputs, [a.op]) | |||||
| */ | */ | ||||
| } | } | ||||
| [Ignore("Fails with message: Op type not registered 'FloatOutput' in binary running on ...")] | |||||
| [TestMethod] | [TestMethod] | ||||
| public void TestNested() | public void TestNested() | ||||
| { | { | ||||
| var g = ops.get_default_graph(); | |||||
| var a_1 = g.create_op("FloatOutput", new Tensor[] { }, new[] { TF_DataType.TF_FLOAT }); | |||||
| var a_2 = g.create_op("FloatOutput", new Tensor[] { }, new[] { TF_DataType.TF_FLOAT }); | |||||
| var a_3 = g.create_op("FloatOutput", new Tensor[] { }, new[] { TF_DataType.TF_FLOAT }); | |||||
| var a_4 = g.create_op("FloatOutput", new Tensor[] { }, new[] { TF_DataType.TF_FLOAT }); | |||||
| var g = tf.Graph().as_default(); | |||||
| var a_1 = constant_op.constant(1.0); | |||||
| var a_2 = constant_op.constant(3.0); | |||||
| var a_3 = constant_op.constant(4.0); | |||||
| var a_4 = constant_op.constant(5.0); | |||||
| Operation b_1 = null, b_2 = null; | Operation b_1 = null, b_2 = null; | ||||
| with(g.control_dependencies(new ITensorOrOperation[] { a_1, a_2, a_3, a_4 }), ctrl => | |||||
| { | |||||
| b_1 = g.create_op("FloatOutput", new Tensor[] { }, new[] { TF_DataType.TF_FLOAT }); | |||||
| }); | |||||
| with(g.control_dependencies(new ITensorOrOperation[] { a_1 }), ctrl1 => | |||||
| { | |||||
| with(g.control_dependencies(new ITensorOrOperation[] { a_2 }), ctrl2 => | |||||
| { | |||||
| with(g.control_dependencies(new ITensorOrOperation[] { a_3 }), ctrl3 => | |||||
| { | |||||
| with(g.control_dependencies(new ITensorOrOperation[] { a_4 }), ctrl4 => | |||||
| { | |||||
| b_2 = g.create_op("FloatOutput", new Tensor[] { }, new[] { TF_DataType.TF_FLOAT }); | |||||
| }); | |||||
| }); | |||||
| }); | |||||
| }); | |||||
| AssertItemsEqual(new[] {a_1.op, a_2.op, a_3.op, a_4.op}, b_1.op.control_inputs); | |||||
| with(g.control_dependencies(new[] { a_1, a_2, a_3, a_4 }), ctrl => | |||||
| { | |||||
| b_1 = constant_op.constant(6.0); | |||||
| }); | |||||
| with(g.control_dependencies(new[] { a_1 }), ctrl1 => | |||||
| { | |||||
| with(g.control_dependencies(new[] { a_2 }), ctrl2 => | |||||
| { | |||||
| with(g.control_dependencies(new[] { a_3 }), ctrl3 => | |||||
| { | |||||
| with(g.control_dependencies(new[] { a_4 }), ctrl4 => | |||||
| { | |||||
| b_2 = constant_op.constant(7.0); | |||||
| }); | |||||
| }); | |||||
| }); | |||||
| }); | |||||
| AssertItemsEqual(new[] { a_1.op, a_2.op, a_3.op, a_4.op }, b_1.op.control_inputs); | |||||
| AssertItemsEqual(b_1.op.control_inputs, b_2.op.control_inputs); | AssertItemsEqual(b_1.op.control_inputs, b_2.op.control_inputs); | ||||
| /* | |||||
| def testNested(self): | |||||
| g = ops.Graph() | |||||
| a_1 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) | |||||
| a_2 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) | |||||
| a_3 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) | |||||
| a_4 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) | |||||
| with g.control_dependencies([a_1, a_2, a_3, a_4]): | |||||
| b_1 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) | |||||
| with g.control_dependencies([a_1]): | |||||
| with g.control_dependencies([a_2]): | |||||
| with g.control_dependencies([a_3]): | |||||
| with g.control_dependencies([a_4]): | |||||
| b_2 = _apply_op(g, "FloatOutput", [], [dtypes.float32]) | |||||
| self.assertItemsEqual([a_1.op, a_2.op, a_3.op, a_4.op], | |||||
| b_1.op.control_inputs) | |||||
| self.assertItemsEqual(b_1.op.control_inputs, b_2.op.control_inputs) | |||||
| */ | |||||
| } | } | ||||
| [Ignore("will fail due to unsupported op 'FloatOutput'")] | |||||
| [Ignore("Fails")] | |||||
| [TestMethod] | [TestMethod] | ||||
| public void TestClear() | public void TestClear() | ||||
| { | { | ||||
| var g = tf.Graph().as_default(); | |||||
| var a_1 = constant_op.constant(1.0); | |||||
| var a_2 = constant_op.constant(3.0); | |||||
| var a_3 = constant_op.constant(4.0); | |||||
| var a_4 = constant_op.constant(5.0); | |||||
| Operation b_3_4 = null, b_3 = null, b_none = null, b_1 = null, b_1_2 = null, b_none2 = null; | |||||
| with(g.control_dependencies(new[] { a_1 }), ctrl1 => | |||||
| { | |||||
| with(g.control_dependencies(new[] { a_2 }), ctrl2 => | |||||
| { | |||||
| with(g.control_dependencies(null), ctrl3 => | |||||
| { | |||||
| with(g.control_dependencies(new[] { a_3 }), ctrl4 => | |||||
| { | |||||
| with(g.control_dependencies(new[] { a_4 }), ctrl5 => | |||||
| { | |||||
| // deps [a_3, a_4] | |||||
| b_3_4 = constant_op.constant(7.0); | |||||
| }); | |||||
| // deps = [a_3] | |||||
| b_3 = constant_op.constant(8.0); | |||||
| }); | |||||
| // deps back to None | |||||
| b_none = constant_op.constant(9.0); | |||||
| }); | |||||
| // deps back to [a_1, a_2] | |||||
| b_1_2 = constant_op.constant(10.0); | |||||
| }); | |||||
| // deps back to [a_1] | |||||
| b_1 = constant_op.constant(11.0); | |||||
| with(g.control_dependencies(null), ctrl6 => | |||||
| { | |||||
| // deps are None again | |||||
| b_none2 = constant_op.constant(12.0); | |||||
| }); | |||||
| }); | |||||
| AssertItemsEqual(new[] {a_3.op, a_4.op}, b_3_4.op.control_inputs); | |||||
| AssertItemsEqual(new[] {a_3.op}, b_3.op.control_inputs); | |||||
| AssertItemsEqual(new object[0], b_none.op.control_inputs); | |||||
| AssertItemsEqual(new[] {a_1.op, a_2.op}, b_1_2.op.control_inputs); | |||||
| AssertItemsEqual(new[] {a_1.op}, b_1.op.control_inputs); | |||||
| AssertItemsEqual(new object[0], b_none2.op.control_inputs); | |||||
| /* | /* | ||||
| def testClear(self): | def testClear(self): | ||||
| g = ops.Graph() | g = ops.Graph() | ||||