1 /**
2     This package contains the framework for constructing and executing operation graphs.
3 
4     $(UL
5         $(LI $(D dopt.core.ops) provides functions for constructing nodes in the operation graph.)
6         $(LI $(D dopt.core.grads) provides functions for computing the derivatives of operations.)
7         $(LI $(D dopt.core.cpu) contains a backend that executes operation graphs using the CPU.)
8         $(LI $(D dopt.core.cuda) contains a backend that executes operation graphs using a CUDA enabled GPU.)
9     )
10 
11     Authors: Henry Gouk
12 */
13 module dopt.core;
14 
15 public
16 {
17     import dopt.core.grads;
18     import dopt.core.ops;
19     import dopt.core.types;
20 }
21 
22 alias Evaluator = Buffer[] delegate(Operation[] ops, Buffer[Operation] args);
23 alias Compiler = Plan delegate(Operation[] ops);
24 
25 private __gshared Evaluator mDefaultEvaluator;
26 private __gshared Compiler mDefaultCompiler;
27 
28 Evaluator defaultEvaluator()
29 {
30     return mDefaultEvaluator;
31 }
32 
33 void defaultEvaluator(Evaluator de)
34 {
35     mDefaultEvaluator = de;
36 }
37 
38 Compiler defaultCompiler()
39 {
40     return mDefaultCompiler;
41 }
42 
43 void defaultCompiler(Compiler de)
44 {
45     mDefaultCompiler = de;
46 }
47 
48 shared static this()
49 {
50     import std.functional : toDelegate;
51 
52     dopt.core.ops.initialize();
53     dopt.core.grads.initialize();
54 }
55 
56 /**
57     Evaluates a several nodes from the operation graph.
58 
59     Params:
60         ops = The nodes of the operation graph that values should be computed for.
61         args = A set of variable assignments.
62 
63     Returns:
64         An array of $(D Buffer) objects, each containing the value of the corresponding element in $(D ops).
65 */
66 Buffer[] evaluate(Operation[] ops, Buffer[Operation] args = null)
67 {
68     return mDefaultEvaluator(ops, args);
69 }
70 
71 /**
72     Evaluates an operation graph with a single root node.
73 
74     This overload is here for convenience. Internally, the multi-output version of evaluate is called.
75 
76     Params:
77         op = The root node of the operation graph.
78         args = A set of variable assignments.
79 
80     Returns:
81         A $(D Buffer) containing the result of the computation.
82 */
83 Buffer evaluate(Operation op, Buffer[Operation] args = null)
84 {
85     return evaluate([op], args)[0];
86 }
87 
88 /**
89     Compile an Operation graph into a reusable execution plan.
90 
91     This can be useful in the case where the function might need to be evaluated multiple times, as it will avoid
92     repeating initialisation and optimisation procedures.
93 
94     Params:
95         outputs = The output nodes of the Operation graph.
96     
97     Returns:
98         A $(D Plan) that can be executed.
99 */
100 Plan compile(Operation[] outputs)
101 {
102     return mDefaultCompiler(outputs);
103 }
104 
105 class Plan
106 {
107     public
108     {
109         this(Operation[] outputs)
110         {
111             import std.array : array;
112 
113             mOutputs = outputs.array();
114         }
115 
116         /**
117             Executes the plan.
118 
119             Params:
120                 args = A set of variable assignments.
121         */
122         Buffer[] execute(Buffer[Operation] args = null)
123         {
124             auto rets = new Buffer[mOutputs.length];
125 
126             foreach(i, o; mOutputs)
127             {
128                 rets[i] = Buffer(new ubyte[o.outputType.volume * o.outputType.elementType.sizeOf()]);
129             }
130 
131             execute(args, rets);
132 
133             return rets;
134         }
135 
136         ///
137         void execute(Buffer[Operation] args, Buffer[] rets)
138         {
139             executeImpl(args, rets);
140         }
141     }
142 
143     protected
144     {
145         Operation[] mOutputs;
146 
147         abstract void executeImpl(Buffer[Operation] args, Buffer[] rets);
148     }
149 }