2

I am trying to check two normal vectors for equality. My normal vector is represented as a three-element list, one element for each spacial coordinate (X, Y, and Z). All coordinates are rounded to 4 decimal places. I want to check if two surfaces have the same normal, so I have something like the following:

if (surface1.normal in [surface2.normal, self.NegatedNormal(surface2.normal)]):
    # do stuff here

The problem is my normals look like this:

surface1.normal: [0.9947, 0.0155, 0.1015]
surface2.normal: [0.9947, 0.0155, 0.1014]

Note that the z-coordinate is off by 0.0001. So is there a way I can override the equals operator to accept answers within 0.0001 from each other that is compatible with comparisons in other data structures (like a list in my case)? I have a feeling I'd have to write my own __eq__ method but I'm not quite sure how I would do this.

Also, if this is not the best course of action, is their a better way to compare two lists of floats within a given tolerance?

sschilli
  • 2,021
  • 1
  • 13
  • 33
  • 1
    You could write a custom float object that inherits from regular float but write your own functions for its operators (eg. `__ge__() is >=`) – SuperBiasedMan Jul 15 '15 at 16:34
  • @SuperBiasedMan That is an interesting approach. Unfortunately it would require refactoring a lot of code so I'm hesitant to try it but I did not know you could inherit from primitive types, so that's a good thing to keep in mind for the future. – sschilli Jul 15 '15 at 16:39
  • Yes, you can essentially make a subclass of the primitive and then overwrite only the functions that you want to behave differently. – SuperBiasedMan Jul 15 '15 at 16:41
  • Pretty much what @SuperBiasedMan suggests is the best option. Just create your custom `normal` class and override comparison methods. Examples [here](http://stackoverflow.com/a/1061350/764322). – Imanol Luengo Jul 15 '15 at 16:45

2 Answers2

3

You can write a custom function to do this. For example:

def comp_floats(x, y, delta):
    return abs(x - y) < delta

You can obviously do whatever type of error correction you want in the function itself, but this is just one example.

Morgan Thrapp
  • 9,748
  • 3
  • 46
  • 67
  • I am aware I could do this but I am looking for something a bit more versatile that would work with other programming logic. Such as the boolean in the original post returning true for a certain tolerance or surface1.normal == surface2.normal returning true. Basically I'm looking for a good way to overload comparison operators, but I am not sure how to do it in Python or if it is even possible given the effect I am going for. – sschilli Jul 15 '15 at 16:28
  • You can't override keywords/symbols in Python. This is the way you can do it. – Morgan Thrapp Jul 15 '15 at 16:28
  • I would say the proper way of comparing 2 floats would be: `abs(x - y) < epsilon`. – Imanol Luengo Jul 16 '15 at 11:14
  • @imaluengo Thanks, I can never remember the correct algorithm. :P – Morgan Thrapp Jul 16 '15 at 13:02
2

You can't alter inbuilt List properties, but you can override those by extending to your own list sub class.

class CustomList(list):
    deviation = None

    def deviation_check(self, val1, val2):
       if self.deviation:
           return min(val1, val2) + self.deviation >= max(val1, val2)
       return val1 == val2

    def __eq__(self, other):
        is_equal = (self.deviation_check(self[0], other[0]) and
                   self.deviation_check(self[1], other[1]) and
                   self.deviation_check(self[2], other[2]))

        return is_equal

l = CustomList()
l.deviation = 0.0001 # Note: Added deviation as a property
l.extend([0.9947, 0.0155, 0.1015])

l1 = CustomList()
l1.extend([0.9947, 0.0155, 0.1014])

print l == l1

Now the we've two CustomList objects l and l1, when ever you try to check equality between lists l == l1, python check it's equality in this way l.__eq__(l1), so that's the reason we've overriden __eq__ magic method to work as we like.

If you don't add l.deviation it checks for equality. also you've to be clear which side you've to add deviation value.

As I said l == l1 gets converted into l.__eq__(l1) in python, so you've to add deviation property to list object which is in left side to ==.

If you run the above script..

# Output ---------------
True

This is because we've defined l.deviation == 0.0001, this is making the difference in checking equality.

Hope this resolves your problem..

gsb-eng
  • 1,211
  • 1
  • 9
  • 16
  • `def __eq__(self, other): if len(self) != len(other): return False return all([self.deviation_check(item, other[index]) for index, item in enumerate(self)])` That will work for any length list instead of just the 3 length one that your above answer works with. – Morgan Thrapp Jul 15 '15 at 19:39
  • @gsb-eng You should override the list construction to add the deviation parameter in there. Something like `__init__(self, *args, deviation=1e-3)` with the appropiate `super(CustomList, self).__init__(*args)` should work to allow you creating `CustomList(1,2,3,4,5, deviation=1e-6)` (not tested, so it might have typos). – Imanol Luengo Jul 16 '15 at 11:11