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() | ||||