rsencu / pyeuclid

Automatically exported from code.google.com/p/pyeuclid
0 stars 0 forks source link

AttributeError: Line has zero-length vector for vertical unit line segment. #19

Open GoogleCodeExporter opened 8 years ago

GoogleCodeExporter commented 8 years ago
What steps will reproduce the problem?
1. >>> LineSegment2(Point2(0,1), Point2(0,0)).connect(Point2(0, 0.7))
2.
3.

What is the expected output? What do you see instead?

Expect the usual line segment.

See:
 File "<stdin>", line 1, in <module>
  File "pyeuclid.py", line 1798, in connect
    return other._connect_line2(self)
  File "pyeuclid.py", line 1728, in _connect_line2
    c = _connect_point2_line2(self, other)
  File "pyeuclid.py", line 1652, in _connect_point2_line2
    L.p.y + u * L.v.y))
  File "pyeuclid.py", line 1766, in __init__
    raise AttributeError, 'Line has zero-length vector'
AttributeError: Line has zero-length vector

What version of the product are you using? On what operating system?

Latest source cut and pasted from the home page.
Ubuntu 11.04

Please provide any additional information below.

Seems to work for horizontal line segments, this one is vertical.

Original issue reported on code.google.com by boo...@nc.rr.com on 20 May 2012 at 11:18

GoogleCodeExporter commented 8 years ago
The issue is NOT that the line is vertical (that was just my bad guess for a 
chance occurrence) but that the point is ON the line.

If the module is designed with the precondition for the Line2(Point2, Point2) 
constructor: "the points are not identical so that the line has non-zero 
length", then the module should catch the exception at certain places, for 
example so that Point2.distance(Line2) does not raise the exception when the 
point is on the line, but instead returns 0, which is a valid scalar distance.

On the other hand, a set interpretation of Line2 might allow the points 
defining the line to be the same (having an empty interior set and a boundary 
set of cardinality one.)  Possibly the module documentation should make clear 
that the module does not comply to such a set interpretation.  I am not an 
expert, my comment is flavored by my recent reading of the Shapely/GEOS module.

Also AttributeError is not the correct exception to raise.  AttributeError 
means the attribute could not be found by the Python interpreter.  ValueError 
should be raised, ("Raised when a built-in operation or function receives an 
argument that has the right type but an inappropriate value, and the situation 
is not described by a more precise exception such as IndexError.") or the 
module should define its own exception (since the module is not really 
"built-in".)

Also, if it is a precondition that the points of the Line2() constructor can 
not be the same point, an assertion could check that earlier (and would raise a 
more understandable message.)  Without suffering performance since assertions 
can be turned off.

Original comment by boo...@nc.rr.com on 21 May 2012 at 11:53

GoogleCodeExporter commented 8 years ago
This discusses a proposed fix.

An invariant is that Lines and Rays have infinite length, or equivalently, 
length() is undefined.

LineSegments have a finite length, but it can be zero.

Lines have no bounding points.  LineSegments and Rays have a set of bounding 
points.  A Ray has one bounding point and a LineSegment has two.

Another invariant of Line and Ray is that they have a non-zero vector in the 
implementation.  The problem is that this assertion is also made for 
LineSegment, when it inherits Line.__init__().

A fix might be to breakout most of the __init__ constructor for Line(), all 
except the assertion at the end that the vector is non-zero.  Define an 
__init__ for LineSegment and Ray that calls said constructor.  Line.__init__ 
would also call the constructor.  Only Line and Ray would also make said 
assertion in their __init___.

I haven't tested this proposed fix.

Another simpler, but hacky, fix would be to define nearest(), which instead of 
returning a LineSegment as connect() does, returns a Point.  connect() would 
continue to raise an exception when it returns a zero-length LineSegment.  
nearest() would catch that exception and return the point of intersection.  I 
haven't fully thought out the implications.  It might be better if connect() 
always returns a LineSegment, even if zero length.  Then you might interpret 
such a condition as tangency?  For example, some references "properly count" 
the points of tangency of a line to a convex polygon vertex (or circle?) as a 
set of cardinality two.

Original comment by boo...@nc.rr.com on 21 May 2012 at 8:40

GoogleCodeExporter commented 8 years ago
I tested the proposed fix.  It works in that no exception is raised for the 
problem case.  But the fix is not entirely satisfactory because a zero-length 
line segment does not intersect() with the line it supposedly connects a point 
to.

foo = LineSegment2(Point2(0,1), Point2(0,0))
foo.connect(Point2(0, 0.7)).intersect(foo)

returns None

Original comment by boo...@nc.rr.com on 21 May 2012 at 10:31

GoogleCodeExporter commented 8 years ago
I tried a different fix:

1. catch the exception in connect() and return a Point2 instead of a 
LineSegment2

2. define Point2._swap() and .length()

3. define Line2._intersect_point2() to return given point if the point is on 
the line.
(Currently, it is not implemented, which seems like another deficiency.)

Seems to work for:
>>> from pyeuclid import *
>>> foo = LineSegment2(Point2(0,1), Point2(0,0))
>>> bar = foo.connect(Point2(0, 0.7))
>>> bar.intersect(foo)
Point2(0.00, 0.70)

I am not sure it is the best possible fix, but it seems intuitive:
LineSegment has a finite length greater than 0
connect() returns either a LineSeqment or a Point
Point.length() is always 0
Line.intersect(Point) returns the point if it is on the line, else None

Original comment by boo...@nc.rr.com on 22 May 2012 at 1:32

GoogleCodeExporter commented 8 years ago
Is this a duplicate of bug #12 ?

Original comment by rodrigo....@gmail.com on 6 Aug 2014 at 9:31