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 ((<map>)) 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 ((<map>)) 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 "<<forward>>"
p p,q,r
puts "<<inverse>>"
p *mp.inverse_map(p,q,r)
puts "<<map grid>>"
x = y = NArray[0.0, 1.0]
z = NArray[0.0,1.0,2.0]
p *mp.map_grid(x,y,z)
end
syntax highlighted by Code2HTML, v. 0.9.1