require "narray" =begin = class CoordMapping == Overview Mapping of a coordinate to another. It supports analytic and grid-point-based mapping in subclasses. Here in this root CoordMapping class only the invariant unity mapping (or no mapping) is defined. == Class methods ---CoordMapping.new Constructor. One or more arguments can be needed in subclasses == Methods ---map(x,y,z,...) Maps data point(s) ARGUMENTS * x,y,z,... (one or more Numeric or NArray) : data points. Mapping is made of [x,y,z,..] (if Numeric) or [x[0],y[0],z[0],..], [x[1],y[1],z[1],..], ..(if NArray). Thus, the number of arguments must be equal to the rank of the mapping. Also, their lengths must agree with each other. RETURN VALUE * Array of p,q,r,... (Numeric or NArray) : mapping result ---map_grid(x,y,z,...) Same as (()) but for a regular grid. ARGUMENTS * x,y,z,... (one or more 1D NArray) : coordinate values of a regular grid [x_i, y_j, z_k,..]. The shape of the grid is thus [x.length, y.length, z.length,..]. This method needs no redefinition, since it calls (()) inside. ---inverse_map(p,q,r,...) Inversely maps data point(s). ARGUMENTS * p,q,r,... (one or more Numeric or NArray) : data points. Inverse mapping is made of [p,q,r,..] (if Numeric) or [p[0],q[0],r[0],..], [p[1],q[1],r[1],..], ..(if NArray). Thus, the number of arguments must be equal to the rank of the mapping. Also, if NArray, their lengths must agree with each other. RETURN VALUE * Array of x,y,z,... (Numeric or NArray) : inverse mapping result ---inverse Returns the inverse mapping. RETURN VALUE * a CoordMapping ---inversion_rigorous? Whether the inversion is rigorous (analytical) RETURN VALUE * true or false = class LinearCoordMapping < CoordMapping == Overview Linear coordinate mapping expressed as offset + factor*x, where offset is a vector (NVect) and factor is a matrix (NMatrix). Methods listed below are only those newly defined or those whose arguments are changed. == Class methods ---LinearCoordMapping.new(offset=nil, factor=nil) Constructor. If one of offset and factor is not specified (nil), a zero vector / a unit matrix is used (at least one of them must be given). ARGUMENTS * offset (NVector or nil) : the offset. Its length represents the rank of mapping. (if nil a zero vector is assumed) * factor (NMatrix or nil) : the factor. For consistency, ( offset.length == factor.shape[0] == factor.shape[1] ) is required. == Methods ---offset Returns the internally stored offset. ---factor Returns the internally stored factor. =end module NumRu class CoordMapping def initialize @rank = nil # nil means any (set a positive integer to specify) @inversion_rigorous = true end attr_reader :rank def inversion_rigorous? @inversion_rigorous end def map(*args) __check_args_m(*args) args.collect{|v| v.dup} end def inverse_map __check_args_m(*args) args.collect{|v| v.dup} end def map_grid(*args) args.each{|v| raise ArgumentError,"all args must be 1D" if v.rank!=1} shape = args.collect{|v| v.length} rank = shape.length expanded=Array.new (0...args.length).each{|i| to_insert = (0...i).collect{|j| 0} + ((i+1)...rank).collect{|j| 1} x = args[i] if to_insert.length > 0 x = x.newdim(*to_insert) x += NArray.float(*shape) end expanded.push(x) } map(*expanded) end def inverse self.clone end private def __check_args_m(*args) if @rank if args.length != @rank raise ArgementError, "# of the arguments must agree with the rank #{@rank}" end else if args.length == 0 raise ArgementError,"# of the arguments must be 1 or greater" end end if args[0].is_a?(Numeric) for i in 1...args.length if !args[i].is_a?(Numeric) raise ArgumentError, "If the first arg is a numeric, the remaing must be so." end end else for i in 1...args.length if args[i-1].length != args[i].length raise ArgumentError,"lengths of the args must be the same" end end end end end ############################ class LinearCoordMapping < CoordMapping def initialize(offset=nil, factor=nil) #< argument check & set rank, offset, factor > if !offset && !factor raise ArgumentError,"One of offset and factor must be specified" elsif !factor raise ArgumentError,"offset is not a NVector" if !offset.is_a?(NVector) @rank = offset.length @factor = NMatrix.float(@rank,@rank) @factor.diagonal!(1) else raise ArgumentError,"factor is not a NMAtrix" if !factor.is_a?(NMatrix) nx,ny = factor.shape raise ArgumentError,"factor must be a square matrix" if nx != ny @rank = nx if offset raise ArgumentError,"offset is not a NVector" if !offset.is_a?(NVector) if offset.length != @rank raise ArgumentError,"inconsistent dimensionarity between "+ "offset #{offset.length} and factor #{factor.shape}" end @offset = offset else @offset = NVector.float(@rank) end @factor = factor end #< other parameters > @inversion_rigorous = true @inv_factor = nil # deferred until needed (might not be invertible) end attr_reader :factor, :offset def map(*args) __check_args_m(*args) x = __to_NVector(*args) p = @offset + @factor*x (0...@rank).collect{|i| p[i,false]} end def inverse_map(*args) __check_args_m(*args) p = __to_NVector(*args) __set_inv_factor if !@inv_factor x = @inv_factor * ( p - @offset ) (0...@rank).collect{|i| x[i,false]} end def inverse __set_inv_factor if !@inv_factor LinearCoordMapping.new( -@inv_factor*@offset, @inv_factor ) # Here, LinearCoordMapping.new is better than class.new, # since the constructor may change in subclasses. end private def __to_NVector(*args) if args[0].is_a?(Numeric) NVector[*args] else v = NVector.float(@rank,*args[0].shape) for i in 0...@rank v[i,false] = args[i] end v end end def __set_inv_factor begin @inv_factor = @factor.inverse rescue raise $!,"mapping factor (which is a Matrix) is not invertible" end end end end ################################################################ if $0 == __FILE__ include NumRu puts "\n** The default unity mapping class CoordMapping **\n\n" mp = CoordMapping.new x = y = z = NArray.float(10).indgen! p,q,r = mp.map(x,y,z) p p,q,r puts "\n** LinearCoordMapping **\n\n" include Math offset = NVector[ 100.0, 0.0, 100.0 ] theta = PI/6 factor = NMatrix[ [cos(theta), -sin(theta), 0], [sin(theta), cos(theta) , 0], [0 , 0 , 1] ] mp = LinearCoordMapping.new(offset, factor) y = z = NArray.float(10) p,q,r = mp.map(x,y,z) puts "<>" p p,q,r puts "<>" p *mp.inverse_map(p,q,r) puts "<>" x = y = NArray[0.0, 1.0] z = NArray[0.0,1.0,2.0] p *mp.map_grid(x,y,z) end