Bug ? calling .inverse on a non invertible transformation return the original transformation, without any warning

It seems calling inverse on any non invertible transformation return the original transform without any warning.
In my code, a chaining of computation creates a non-invertible transformation, then the inverse is called.
During testing, I realized that multiplying one by its inverse doesn’t produce the identity.
I was expecting either nil or an exception, as testing the validity of the inverse this way is undocumented and bothersome.

this is my test code ?

  log "offset_matrix :\n #{offset_matrix}"  
  log "offset_matrix inverse:\n #{offset_matrix.inverse}"  
  log "id = offset_matrix x offset_matrix.inverse:\n #{offset_matrix * offset_matrix.inverse}"

this is the result I get :

offset_matrix :
    0.500     0.612    -0.612     0.000
  -0.612     0.250    -0.250     0.000
   0.612    -0.250     0.250     0.000
   0.000     0.000     0.000     1.000

offset_matrix inverse:
    0.500     0.612    -0.612     0.000
  -0.612     0.250    -0.250     0.000
   0.612    -0.250     0.250     0.000
   0.000     0.000     0.000     1.000

id = offset_matrix x offset_matrix.inverse:
   -0.500     0.612    -0.612     0.000
  -0.612    -0.250     0.250     0.000
   0.612     0.250    -0.250     0.000
   0.000     0.000     0.000     1.000

Just an FYI, I seem to recall a bug last cycle (or the one before, ie recently,) in regard to an issue with the .identity? method.

I think that after creating a new identity transform, that if any math operations were done, the method would no longer return true even if the transform was still an identity transform.

@tt_su


Suggestion: Get ThomThom’s Transformation Inspector extension. It’s nifty!:
http://extensions.sketchup.com/en/content/transformation-inspector


Actually, that is logging code. Test code would create the transform to be tested.

  offset_matrix = Geom::Transformation::new(
    [ 0.500,  0.612, -0.612,  0.000,
     -0.612,  0.250, -0.250,  0.000,
      0.612, -0.250,  0.250,  0.000,
      0.000,  0.000,  0.000,  1.000
    ]
  )

For the multiplication I get:

 [ -0.499088,  0.612,    -0.612, 0.0, 
   -0.612, -0.249544,  0.249544, 0.0, 
    0.612,  0.249544, -0.249544, 0.0,
    0.0,         0.0,       0.0, 1.0 ]

Another FYI.

The Standard Ruby library has a Matrix class:
http://ruby-doc.org/stdlib-2.0.0/libdoc/matrix/rdoc/Matrix.html

(You may find it’s methods more to your liking.)

require "matrix"

def array_to_matrix(arg)
  if arg.is_a?(Geom::Transformation)
    a = arg.to_a 
  else
    a = arg.flatten
  end
  Matrix[ a[0..3], a[4..7], a[8..11], a[12..15] ]
end

Hi Dan,
thank you for the plugin and tips, it will help a lot.

It alrteady does ; it helped me confirm the Transformation.inverse method is wrong.

We both get the same absurd result for the inverse (and hence the multiplication) while the standard builtin matrix does raise an exception.

log 'with builtin matrix'
>> with builtin matrix
offset_matrix2 = array_to_matrix(offset_matrix)
log "is matrix non invertible? {offset_matrix2.singular?}"  
>> is matrix non invertible? true
log offset_matrix2.det 
>> 0.0.
log offset_matrix2.inv
>> ( raise ExceptionForMatrix::ErrNotRegular )

IMHO, Transformation.inverse should either raise an exception or return nil. To have a new method similar to Matrix.singular? would also help.

On my end, I’ll try to find a workaround (likely with Matrix). On yours, I hope this bug can be squeezed into the next (api) release. :slight_smile:

I have noticed the same behavior, but assumed that it was a “feature”. (The reason the transformation above is invalid is because the internal Z axis is parallel to the Y axis. Actually, it is in the opposite direction of the internal Y axis, as the Z axis values are the negative of the Y axis ones).

I have a utility that checks for transformation validity. It performs the validity check by multiplying a transformation by its inverse and determines if it is close to identity (using my own identity check method). It makes use of the fact that an exception is not raised. If Sketchup were to fix this problem by raising an exception, then they would break my utility. I would have to modify my code to handle the new exception.

Now by design, Ruby does not raise an exception when a divide by zero occurs with floats (following IEEE 754 standard). Should a matrix inverse cause an exception if floating point division by zero doesn’t?

Unless I’m mistaken (which is entirely possible) IEEE arithmetic returns a special value called NAN (not a number) on division by zero. Could there be a similar special matrix for inverse of a singular matrix?

@slbaumgartner there is no need for a special value for the absence of an inverse. We already have nil to this kind of situation. It precisely means the absence of something and breaks any computation that doesn’t take it into account.

In Ruby, NAN is defined as a constant within the Float class, ie: Float::NAN, along with Float::INFINITY and several others.
http://ruby-doc.org/core-2.0.0/Float.html

Ruby also defines a global FloatDomainError exception class:
FloatDomainError < RangeError < StandardError < Exception
http://ruby-doc.org/core-2.0.0/FloatDomainError.html
which says:

Raised when attempting to convert special float values (in particular infinite or NaN) to numerical classes which don’t support them.

ADD: Found another math domain exception:

``Math::DomainError
http://ruby-doc.org/core-2.0.0/Math/DomainError.html
which says:

Raised when a mathematical function is evaluated outside of its domain of definition.

For example, since cos returns values in the range -1..1, its inverse function acos is only defined on that interval:

Math.acos(42)

produces:

Error: #<Math::DomainError: Numerical argument is out of domain - "acos">

So if an exception were to be raised it’d either be one of these, or the API should define a subclass of it, within the Geom module.
Just examples:
Geom::TransformInvertError < FloatDomainError
Geom::TransformParallelAxesError

nil is not the correct object. It will raise the wrong exception type, ie:
Error: #<TypeError: nil can't be coerced into Float>

There is no way the API core team can try not to break everyone’s code. That is just a nightmare. The API should move forward, and fix things that need fixing. IMO.

Ruby has rescue which can be used as a blocks clause, or in modifier poistion:
tran_inv = tran.inverse rescue tran

At the Ruby console, floating point division by zero returns “Infinity”, not “NaN”.

In MATLAB, taking the inverse of the matrix above results in:

inv( offset_matrix )
warning: matrix singular to machine precision
ans =
       Inf   Inf   Inf   Inf
       Inf   Inf   Inf   Inf
       Inf   Inf   Inf   Inf
       Inf   Inf   Inf   Inf

Note: MATLAB results, not Ruby console.

 @DanRathbun Ruby has rescue. . .

Yes, it wouldn’t be difficult to handle the exception. I’m not worried about the Sketchup development team changing the inverse function, there seems to be a lot more higher priority fixes and new implementations than this.

I just thought I would document that someone does use the existing behavior.

After a bit of thought, I think there is only few options that don’t break existing code :

  • add a method is_invertible? to the transformation class
  • add an optional parameter to the inverse method which would become inverse(strict=false). if strict=true, the inverse can be nil or raise an exception.
  • plan for a deprecation of the non strict option, and add warning.

Anything else would break existing code.

1 Like