UNPKG

algebrite

Version:

Computer Algebra System in Coffeescript

554 lines (399 loc) 10.2 kB
#(docs are generated from top-level comments, keep an eye on the formatting!) ### tensor ===================================================================== Tags ---- scripting, JS, internal, treenode, general concept General description ------------------- Tensors are a strange in-between of matrices and "computer" rectangular data structures. Tensors, unlike matrices, and like rectangular data structures, can have an arbitrary number of dimensions (rank), although a tensor with rank zero is just a scalar. Tensors, like matrices and unlike many computer rectangular data structures, must be "contiguous" i.e. have no empty spaces within its size, and "uniform", i.e. each element must have the same shape and hence the same rank. Also tensors have necessarily to make a distinction between row vectors, column vectors (which have a rank of 2) and uni-dimensional vectors (rank 1). They look very similar but they are fundamentally different. Tensors with elements that are also tensors get promoted to a higher rank , this is so we can represent and get the rank of a matrix correctly. Example: Start with a tensor of rank 1 with 2 elements (i.e. shape: 2) if you put in both its elements another 2 tensors of rank 1 with 2 elements (i.e. shape: 2) then the result is a tensor of rank 2 with shape 2,2 i.e. the dimension of a tensor at all times must be the number of nested tensors in it. Also, all tensors must be "uniform" i.e. they must be accessed uniformly, which means that all existing elements of a tensor must be contiguous and have the same shape. Implication of it all is that you can't put arbitrary tensors inside tensors (like you would do to represent block matrices) Rather, all tensors inside tensors must have same shape (and hence, rank) Limitations ----------- n.a. Implementation info ------------------- Tensors are implemented... ### # Called from the "eval" module to evaluate tensor elements. # p1 points to the tensor operand. Eval_tensor = -> i = 0 ndim = 0 nelem = 0 #U **a, **b #--------------------------------------------------------------------- # # create a new tensor for the result # #--------------------------------------------------------------------- check_tensor_dimensions p1 nelem = p1.tensor.nelem ndim = p1.tensor.ndim p2 = alloc_tensor(nelem) p2.tensor.ndim = ndim for i in [0...ndim] p2.tensor.dim[i] = p1.tensor.dim[i] #--------------------------------------------------------------------- # # b = Eval(a) # #--------------------------------------------------------------------- a = p1.tensor.elem b = p2.tensor.elem check_tensor_dimensions p2 for i in [0...nelem] #console.log "push/pop: pushing element a of " + i push(a[i]) Eval() #console.log "push/pop: popping into element b of " + i b[i] = pop() check_tensor_dimensions p1 check_tensor_dimensions p2 #--------------------------------------------------------------------- # # push the result # #--------------------------------------------------------------------- push(p2) promote_tensor() #----------------------------------------------------------------------------- # # Add tensors # # Input: Operands on stack # # Output: Result on stack # #----------------------------------------------------------------------------- tensor_plus_tensor = -> i = 0 ndim = 0 nelem = 0 #U **a, **b, **c save() p2 = pop() p1 = pop() # are the dimension lists equal? ndim = p1.tensor.ndim if (ndim != p2.tensor.ndim) push(symbol(NIL)) restore() return for i in [0...ndim] if (p1.tensor.dim[i] != p2.tensor.dim[i]) push(symbol(NIL)) restore() return # create a new tensor for the result nelem = p1.tensor.nelem p3 = alloc_tensor(nelem) p3.tensor.ndim = ndim for i in [0...ndim] p3.tensor.dim[i] = p1.tensor.dim[i] # c = a + b a = p1.tensor.elem b = p2.tensor.elem c = p3.tensor.elem for i in [0...nelem] push(a[i]) push(b[i]) add() c[i] = pop() # push the result push(p3) restore() #----------------------------------------------------------------------------- # # careful not to reorder factors # #----------------------------------------------------------------------------- tensor_times_scalar = -> i = 0 ndim = 0 nelem = 0 #U **a, **b save() p2 = pop() p1 = pop() ndim = p1.tensor.ndim nelem = p1.tensor.nelem p3 = alloc_tensor(nelem) p3.tensor.ndim = ndim for i in [0...ndim] p3.tensor.dim[i] = p1.tensor.dim[i] a = p1.tensor.elem b = p3.tensor.elem for i in [0...nelem] push(a[i]) push(p2) multiply() b[i] = pop() push(p3) restore() scalar_times_tensor = -> i = 0 ndim = 0 nelem = 0 #U **a, **b save() p2 = pop() p1 = pop() ndim = p2.tensor.ndim nelem = p2.tensor.nelem p3 = alloc_tensor(nelem) p3.tensor.ndim = ndim for i in [0...ndim] p3.tensor.dim[i] = p2.tensor.dim[i] a = p2.tensor.elem b = p3.tensor.elem for i in [0...nelem] push(p1) push(a[i]) multiply() b[i] = pop() push(p3) restore() check_tensor_dimensions = (p) -> if p.tensor.nelem != p.tensor.elem.length console.log "something wrong in tensor dimensions" debugger is_square_matrix = (p) -> if (istensor(p) && p.tensor.ndim == 2 && p.tensor.dim[0] == p.tensor.dim[1]) return 1 else return 0 #----------------------------------------------------------------------------- # # gradient of tensor # #----------------------------------------------------------------------------- d_tensor_tensor = -> i = 0 j = 0 ndim = 0 nelem = 0 #U **a, **b, **c ndim = p1.tensor.ndim nelem = p1.tensor.nelem if (ndim + 1 >= MAXDIM) push_symbol(DERIVATIVE) push(p1) push(p2) list(3) return p3 = alloc_tensor(nelem * p2.tensor.nelem) p3.tensor.ndim = ndim + 1 for i in [0...ndim] p3.tensor.dim[i] = p1.tensor.dim[i] p3.tensor.dim[ndim] = p2.tensor.dim[0] a = p1.tensor.elem b = p2.tensor.elem c = p3.tensor.elem for i in [0...nelem] for j in [0...p2.tensor.nelem] push(a[i]) push(b[j]) derivative() c[i * p2.tensor.nelem + j] = pop() push(p3) #----------------------------------------------------------------------------- # # gradient of scalar # #----------------------------------------------------------------------------- d_scalar_tensor = -> #U **a, **b p3 = alloc_tensor(p2.tensor.nelem) p3.tensor.ndim = 1 p3.tensor.dim[0] = p2.tensor.dim[0] a = p2.tensor.elem b = p3.tensor.elem for i in [0...p2.tensor.nelem] push(p1) push(a[i]) derivative() b[i] = pop() push(p3) #----------------------------------------------------------------------------- # # Derivative of tensor # #----------------------------------------------------------------------------- d_tensor_scalar = -> i = 0 #U **a, **b p3 = alloc_tensor(p1.tensor.nelem) p3.tensor.ndim = p1.tensor.ndim for i in [0...p1.tensor.ndim] p3.tensor.dim[i] = p1.tensor.dim[i] a = p1.tensor.elem b = p3.tensor.elem for i in [0...p1.tensor.nelem] push(a[i]) push(p2) derivative() b[i] = pop() push(p3) compare_tensors = (p1, p2) -> i = 0 if (p1.tensor.ndim < p2.tensor.ndim) return -1 if (p1.tensor.ndim > p2.tensor.ndim) return 1 for i in [0...p1.tensor.ndim] if (p1.tensor.dim[i] < p2.tensor.dim[i]) return -1 if (p1.tensor.dim[i] > p2.tensor.dim[i]) return 1 for i in [0...p1.tensor.nelem] if (equal(p1.tensor.elem[i], p2.tensor.elem[i])) continue if (lessp(p1.tensor.elem[i], p2.tensor.elem[i])) return -1 else return 1 return 0 #----------------------------------------------------------------------------- # # Raise a tensor to a power # # Input: p1 tensor # # p2 exponent # # Output: Result on stack # #----------------------------------------------------------------------------- power_tensor = -> i = 0 k = 0 n = 0 # first and last dims must be equal k = p1.tensor.ndim - 1 if (p1.tensor.dim[0] != p1.tensor.dim[k]) push_symbol(POWER) push(p1) push(p2) list(3) return push(p2) n = pop_integer() if (isNaN(n)) push_symbol(POWER) push(p1) push(p2) list(3) return if (n == 0) if (p1.tensor.ndim != 2) stop("power(tensor,0) with tensor rank not equal to 2") n = p1.tensor.dim[0] p1 = alloc_tensor(n * n) p1.tensor.ndim = 2 p1.tensor.dim[0] = n p1.tensor.dim[1] = n for i in [0...n] p1.tensor.elem[n * i + i] = one check_tensor_dimensions p1 push(p1) return if (n < 0) n = -n push(p1) inv() p1 = pop() push(p1) for i in [1...n] push(p1) inner() if (iszero(stack[tos - 1])) break copy_tensor = -> i = 0 save() p1 = pop() p2 = alloc_tensor(p1.tensor.nelem) p2.tensor.ndim = p1.tensor.ndim for i in [0...p1.tensor.ndim] p2.tensor.dim[i] = p1.tensor.dim[i] for i in [0...p1.tensor.nelem] p2.tensor.elem[i] = p1.tensor.elem[i] check_tensor_dimensions p1 check_tensor_dimensions p2 push(p2) restore() # Tensors with elements that are also tensors get promoted to a higher rank. promote_tensor = -> i = 0 j = 0 k = 0 nelem = 0 ndim = 0 save() p1 = pop() if (!istensor(p1)) push(p1) restore() return p2 = p1.tensor.elem[0] for i in [1...p1.tensor.nelem] if (!compatible(p2, p1.tensor.elem[i])) stop("Cannot promote tensor due to inconsistent tensor components.") if (!istensor(p2)) push(p1) restore() return ndim = p1.tensor.ndim + p2.tensor.ndim if (ndim > MAXDIM) stop("tensor rank > " + MAXDIM) nelem = p1.tensor.nelem * p2.tensor.nelem p3 = alloc_tensor(nelem) p3.tensor.ndim = ndim for i in [0...p1.tensor.ndim] p3.tensor.dim[i] = p1.tensor.dim[i] for j in [0...p2.tensor.ndim] p3.tensor.dim[i + j] = p2.tensor.dim[j] k = 0 for i in [0...p1.tensor.nelem] p2 = p1.tensor.elem[i] for j in [0...p2.tensor.nelem] p3.tensor.elem[k++] = p2.tensor.elem[j] check_tensor_dimensions p2 check_tensor_dimensions p3 push(p3) restore() compatible = (p,q) -> if (!istensor(p) && !istensor(q)) return 1 if (!istensor(p) || !istensor(q)) return 0 if (p.tensor.ndim != q.tensor.ndim) return 0 for i in [0...p.tensor.ndim] if (p.tensor.dim[i] != q.tensor.dim[i]) return 0 return 1