1 /** 2 Contains functions for creating variable nodes and subsequently manipulating their shapes. 3 4 Authors: Henry Gouk 5 */ 6 module dopt.core.ops.basic; 7 8 import dopt.core.ops; 9 import dopt.core.types; 10 11 import std.algorithm; 12 import std.array; 13 import std.exception; 14 import std.functional; 15 import std.range; 16 import std.variant; 17 18 package 19 { 20 void initialize() 21 { 22 registerOperation("slice", OpDef(toDelegate(&verifySlice), toDelegate(&judgeSlice))); 23 registerOperation("pad", OpDef(toDelegate(&verifyPad), toDelegate(&judgePad))); 24 registerOperation("reshape", OpDef(toDelegate(&verifyReshape), toDelegate(&judgeReshape))); 25 registerOperation("transpose", OpDef(toDelegate(&verifyTranspose), toDelegate(&judgeTranspose))); 26 registerOperation("repeat", OpDef(toDelegate(&verifyRepeat), toDelegate(&judgeRepeat))); 27 registerOperation("variable", OpDef(toDelegate(&verifyVariable), toDelegate(&judgeVariable))); 28 registerOperation("constant", OpDef(toDelegate(&verifyVariable), toDelegate(&judgeVariable))); 29 } 30 } 31 32 private 33 { 34 bool verifySlice(Operation op) 35 { 36 if(("start" in op.attributes) is null || ("stop" in op.attributes) is null) 37 { 38 return false; 39 } 40 41 auto startVar = op.attributes["start"]; 42 auto stopVar = op.attributes["stop"]; 43 44 if(startVar.peek!(size_t[]) is null || stopVar.peek!(size_t[]) is null) 45 { 46 return false; 47 } 48 49 auto start = startVar.get!(size_t[]); 50 auto stop = stopVar.get!(size_t[]); 51 52 return op.deps.length == 1 53 && start.length == stop.length 54 && start.length == op.deps[0].outputType.rank 55 && zip(start, op.deps[0].outputType.shape).all!(x => x[0] < x[1]) 56 && zip(stop, op.deps[0].outputType.shape).all!(x => x[0] <= x[1]) 57 && zip(start, stop).all!(x => x[0] < x[1]); 58 } 59 60 TensorType judgeSlice(Operation op) 61 { 62 auto start = op 63 .attributes["start"] 64 .get!(size_t[]); 65 66 auto stop = op 67 .attributes["stop"] 68 .get!(size_t[]); 69 70 auto shape = zip(start, stop) 71 .map!(x => x[1] - x[0]) 72 .array(); 73 74 return TensorType(op.deps[0].outputType.elementType, shape); 75 } 76 77 bool verifyPad(Operation op) 78 { 79 if(("before" in op.attributes) is null || ("after" in op.attributes) is null) 80 { 81 return false; 82 } 83 84 auto beforeVar = op.attributes["before"]; 85 auto afterVar = op.attributes["after"]; 86 87 if(beforeVar.peek!(size_t[]) is null || afterVar.peek!(size_t[]) is null) 88 { 89 return false; 90 } 91 92 auto before = beforeVar.get!(size_t[]); 93 auto after = afterVar.get!(size_t[]); 94 95 return op.deps.length == 1 96 && before.length == after.length 97 && before.length == op.deps[0].outputType.rank; 98 } 99 100 TensorType judgePad(Operation op) 101 { 102 auto before = op 103 .attributes["before"] 104 .get!(size_t[]); 105 106 auto after = op 107 .attributes["after"] 108 .get!(size_t[]); 109 110 auto shape = zip(before, after, op.deps[0].outputType.shape) 111 .map!(x => x[0] + x[1] + x[2]) 112 .array(); 113 114 return TensorType(op.deps[0].outputType.elementType, shape); 115 } 116 117 bool verifyReshape(Operation op) 118 { 119 auto newShape = "shape" in op.attributes; 120 121 return op.deps.length == 1 122 && newShape !is null 123 && newShape.peek!(size_t[]) !is null 124 && newShape.get!(size_t[]).fold!((a, b) => a * b)(cast(size_t)1) == op.deps[0].outputType.volume; 125 } 126 127 TensorType judgeReshape(Operation op) 128 { 129 return TensorType(op.deps[0].outputType.elementType, op.attributes["shape"].get!(size_t[])); 130 } 131 132 bool verifyTranspose(Operation op) 133 { 134 auto newOrder = "order" in op.attributes; 135 136 return op.deps.length == 1 137 && newOrder !is null 138 && newOrder.peek!(size_t[]) !is null 139 && newOrder.get!(size_t[]).dup.sort().equal(iota(0, op.deps[0].outputType.rank)); 140 } 141 142 TensorType judgeTranspose(Operation op) 143 { 144 auto order = op 145 .attributes["order"] 146 .get!(size_t[]); 147 148 auto newShape = order 149 .map!(x => op.deps[0].outputType.shape[x]) 150 .array(); 151 152 return TensorType(op.deps[0].outputType.elementType, newShape); 153 } 154 155 bool verifyRepeat(Operation op) 156 { 157 if(("repetitions" in op.attributes) is null) 158 { 159 return false; 160 } 161 162 auto reps = op.attributes["repetitions"].get!(size_t[]); 163 164 return op.deps.length == 1 165 && reps.length == op.deps[0].rank 166 && reps.all!(x => x > 0); 167 } 168 169 TensorType judgeRepeat(Operation op) 170 { 171 auto reps = op.attributes["repetitions"].get!(size_t[]); 172 auto shape = op.deps[0].shape.dup; 173 shape[] *= reps[]; 174 175 return TensorType(op.deps[0].elementType, shape); 176 } 177 178 bool verifyVariable(Operation op) 179 { 180 return op.deps.length == 0 181 && ("type" in op.attributes) !is null 182 && op.attributes["type"].peek!TensorType !is null; 183 } 184 185 TensorType judgeVariable(Operation op) 186 { 187 return op.attributes["type"].get!TensorType; 188 } 189 } 190 191 public 192 { 193 /** 194 Slices the result of an operation. 195 196 Params: 197 input = The operation that should be sliced. 198 start = The starting indices for each dimension. 199 stop = The stopping indices for each dimension. 200 201 Returns: 202 The new $(D Operation). 203 */ 204 Operation slice(Operation input, size_t[] start, size_t[] stop, 205 string mod = __MODULE__, size_t line = __LINE__) 206 { 207 return createOperation("slice", [input], ["start": Variant(start), "stop": Variant(stop)], mod, line); 208 } 209 210 /// 211 unittest 212 { 213 import dopt.core : evaluate; 214 215 auto s1 = int32([3, 3], [ 216 1, 2, 3, 217 4, 5, 6, 218 7, 8, 9 219 ]).slice([1, 1], [3, 3]); 220 221 assert(s1.evaluate().as!int == [ 222 5, 6, 223 8, 9 224 ]); 225 } 226 227 /** 228 Pads the result of an operation with zeros in each dimension. 229 230 Params: 231 input = The operation that should be padded. 232 before = The amount of padding that should be prepended for each dimension. 233 after = The amount of padding that should be appended for each dimension. 234 235 Returns: 236 The new $(D Operation). 237 */ 238 Operation pad(Operation input, size_t[] before, size_t[] after, 239 string mod = __MODULE__, size_t line = __LINE__) 240 { 241 return createOperation("pad", [input], ["before": Variant(before), "after": Variant(after)], mod, line); 242 } 243 244 /// 245 unittest 246 { 247 import dopt.core : evaluate; 248 249 auto p1 = int32([1, 1], [3]).pad([2, 1], [3, 3]); 250 251 assert(p1.evaluate().as!int == [ 252 0, 0, 0, 0, 0, 253 0, 0, 0, 0, 0, 254 0, 3, 0, 0, 0, 255 0, 0, 0, 0, 0, 256 0, 0, 0, 0, 0, 257 0, 0, 0, 0, 0 258 ]); 259 } 260 261 /** 262 Allows one to cast an operation to a different shape with the same volume. 263 264 Params: 265 input = The operation to be reshaped. 266 shape = The new shape. 267 268 Returns: 269 The new $(D Operation). 270 */ 271 Operation reshape(Operation input, size_t[] shape, string mod = __MODULE__, size_t line = __LINE__) 272 { 273 return createOperation("reshape", [input], ["shape": Variant(shape)], mod, line); 274 } 275 276 /// 277 unittest 278 { 279 import dopt.core : evaluate; 280 281 auto r1 = float32([2, 2], [1.0f, 2.0f, 3.0f, 4.0f]).reshape([1, 4]); 282 283 assert(r1.shape == [1, 4]); 284 assert(r1.evaluate().as!float == [1.0f, 2.0f, 3.0f, 4.0f]); 285 } 286 287 /** 288 Reorders the dimensions of output of an operation. 289 290 Params: 291 input = The operation that should have its dimensions reordered. 292 order = Determines how the dimensions are permuted. 293 294 Notes: 295 Currently only implemented for rank 2 tensors. 296 297 Returns: 298 The new $(D Operation). 299 */ 300 Operation transpose(Operation input, size_t[] order, string mod = __MODULE__, size_t line = __LINE__) 301 { 302 return createOperation("transpose", [input], ["order": Variant(order)], mod, line); 303 } 304 305 /// 306 unittest 307 { 308 import dopt.core : evaluate; 309 310 auto t1 = float32([2, 2], [1.0f, 2.0f, 3.0f, 4.0f]).transpose([1, 0]); 311 312 assert(t1.evaluate().as!float == [1.0f, 3.0f, 2.0f, 4.0f]); 313 } 314 315 /** 316 Repeats the output of an operation along each axis the given number of times. 317 318 Params: 319 input = The operation to have its output repeated. 320 repetitions = The number of repetitions to perform along each axis. 321 322 Return: 323 The new $(D Operation). 324 */ 325 Operation repeat(Operation input, size_t[] repetitions, string mod = __MODULE__, 326 size_t line = __LINE__) 327 { 328 enforce(repetitions.length == input.rank, 329 "The length of repetitions must be the same as the rank of the input."); 330 331 return createOperation("repeat", [input], ["repetitions": Variant(repetitions)], mod, line); 332 } 333 334 /// 335 unittest 336 { 337 import dopt.core : evaluate; 338 339 auto r1 = float32([1, 1], [3.0f]).repeat([2, 3]); 340 auto r2 = float32([2, 2], [1.0f, 2.0f, 3.0f, 4.0f]).repeat([3, 2]); 341 342 assert(r1.evaluate().as!float == [ 343 3.0f, 3.0f, 3.0f, 344 3.0f, 3.0f, 3.0f 345 ]); 346 347 assert(r2.evaluate().as!float == [ 348 1.0f, 2.0f, 1.0f, 2.0f, 349 3.0f, 4.0f, 3.0f, 4.0f, 350 1.0f, 2.0f, 1.0f, 2.0f, 351 3.0f, 4.0f, 3.0f, 4.0f, 352 1.0f, 2.0f, 1.0f, 2.0f, 353 3.0f, 4.0f, 3.0f, 4.0f 354 ]); 355 } 356 357 /** 358 Repeats the output of an operation the given number of times. 359 360 A new dimension is added, allowing one to index each of these repetitions. 361 362 Params: 363 input = The operation to have its output repeated. 364 repetitions = The number of repetitions to perform. 365 366 Return: 367 The new $(D Operation). 368 */ 369 Operation repeat(Operation input, size_t repetitions, string mod = __MODULE__, size_t line = __LINE__) 370 { 371 auto flat = input.reshape([input.volume]); 372 auto r = flat.repeat([repetitions], mod, line); 373 374 return r.reshape([repetitions] ~ input.shape, mod, line); 375 } 376 377 /// 378 unittest 379 { 380 import dopt.core : evaluate; 381 382 auto r1 = float32([2], [1.0f, 2.0f]).repeat(3); 383 384 assert(r1.evaluate().as!float == [ 385 1.0f, 2.0f, 386 1.0f, 2.0f, 387 1.0f, 2.0f 388 ]); 389 } 390 391 /** 392 Creates a variable with the given type. 393 394 If no default value is provided, then the variable will have a default value of all zeros. The default value is 395 stored in the attributes["default"] field of the returned operation. 396 397 Params: 398 type = The type of the variable 399 defaultVal = The default value of the variable. The array should store the elements in row major order. 400 401 Returns: 402 The newly created variable 403 */ 404 Operation variable(TensorType type, void[] defaultVal = null, string mod = __MODULE__, size_t line = __LINE__) 405 { 406 auto bufSize = type.volume * sizeOf(type.elementType); 407 408 if(defaultVal is null) 409 { 410 defaultVal = new ubyte[bufSize]; 411 } 412 else 413 { 414 enforce(defaultVal.length == bufSize, "The length of defaultVal does not match type.volume."); 415 } 416 417 return createOperation("variable", [], ["type": Variant(type), "default": Variant(Buffer(defaultVal))], mod, line); 418 } 419 420 /** 421 Creates a variable with the given shape and float32 elements. 422 423 If no default value is provided, then the variable will have a default value of all zeros. The default value is 424 stored in the attributes["default"] field of the returned operation. 425 426 Params: 427 size = The shape of the variable 428 defaultVal = The default value of the variable. The array should store the elements in row major order. 429 430 Returns: 431 The newly created variable 432 */ 433 Operation float32(size_t[] size = [], float[] defaultVal = null, string mod = __MODULE__, size_t line = __LINE__) 434 { 435 return variable(TensorType(DataType.float32, size), defaultVal, mod, line); 436 } 437 438 /** 439 Creates a variable with the given shape and int32 elements. 440 441 If no default value is provided, then the variable will have a default value of all zeros. The default value is 442 stored in the attributes["default"] field of the returned operation. 443 444 Params: 445 size = The shape of the variable 446 defaultVal = The default value of the variable. The array should store the elements in row major order. 447 448 Returns: 449 The newly created variable 450 */ 451 Operation int32(size_t[] size = [], int[] defaultVal = null, string mod = __MODULE__, size_t line = __LINE__) 452 { 453 return variable(TensorType(DataType.int32, size), defaultVal, mod, line); 454 } 455 456 /** 457 Creates a constant with the given type. 458 459 Params: 460 type = The type of the constant 461 val = The value of the constant. The array should store the elements in row major order. 462 463 Returns: 464 The newly created constant 465 */ 466 Operation constant(TensorType type, void[] val, string mod = __MODULE__, size_t line = __LINE__) 467 { 468 auto bufSize = type.volume * sizeOf(type.elementType); 469 470 if(val is null) 471 { 472 val = new ubyte[bufSize]; 473 } 474 else 475 { 476 enforce(val.length == bufSize, "The length of val does not match type.volume."); 477 } 478 479 return createOperation("constant", [], ["type": Variant(type), "default": Variant(Buffer(val))], mod, line); 480 } 481 482 /** 483 Creates a constant with the given shape and float32 values. 484 485 Params: 486 size = The shape of the constant 487 val = The value of the constant. The array should store the elements in row major order. 488 489 Returns: 490 The newly created constant 491 */ 492 Operation float32Constant(size_t[] size, float[] val, string mod = __MODULE__, size_t line = __LINE__) 493 { 494 return constant(TensorType(DataType.float32, size), val, mod, line); 495 } 496 497 /** 498 Creates a constant with the given shape and int32 values. 499 500 Params: 501 size = The shape of the constant 502 val = The value of the constant. The array should store the elements in row major order. 503 504 Returns: 505 The newly created constant 506 */ 507 Operation int32Constant(size_t[] size, int[] val, string mod = __MODULE__, size_t line = __LINE__) 508 { 509 return constant(TensorType(DataType.int32, size), val, mod, line); 510 } 511 }