Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Algorithm For The Splines From Edges Node. #922

Merged
merged 23 commits into from Jul 14, 2018
Merged

New Algorithm For The Splines From Edges Node. #922

merged 23 commits into from Jul 14, 2018

Conversation

OmarEmaraDev
Copy link
Collaborator

This new algorithm create as much continuous splines from edges as possible, resulting in more workable output for processing and rendering.

return splines

def splinesFromEdges(Vector3DList vertices, EdgeIndicesList edges, VirtualDoubleList radii,
bint isVertexRadius):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would prefer a str input here for isVertexRadius because "how are the radii used?" is not really a yes-no question.
There could be more than two options in theory.


algorithmItems = [
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just by convention either this should be called algorithmTypeItems or the property in the node should just be called algorithm.

cdef int *neighboursAmounts = <int*>PyMem_Malloc(verticesAmount * sizeof(int))
memset(neighboursAmounts, 0, verticesAmount * sizeof(int))
for i in range(edgesAmount):
neighboursAmounts[edges.data[i].v1] += 1
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could raise an segmentation fault if there aren't enough vertices.
The node itself should check wether the inpu is valid (max vertex index of an edge is smaller than the number of vertices, like in the original code)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the node itself have error handling then?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I think the node should do the check. "cutils" usually should not have to make expensive checks, they can expect the data to valid.
Nodes on the other hand can not expect that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright.

cdef int verticesAmount = len(vertices)

# Compute how many neighbour each vertex have.
cdef int *neighboursAmounts = <int*>PyMem_Malloc(verticesAmount * sizeof(int))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in general it would be nice to use the Lists AN provides instead of doing memory allocations yourself if appropriate. The performance impact should be negilible.

splineVertices = Vector3DList.__new__(Vector3DList, capacity = verticesAmount)
splineRadii = FloatList.__new__(FloatList, capacity = verticesAmount)

splineVertices.append(vertices[nonBipolarVertex])
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using the append_LowLevel function here might result in a noticable performance improvement

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering if it would be better to do:

splineRadii = FloatList.__new__(FloatList, length = verticesAmount, capacity = verticesAmount)
splineRadii[0] = radii.get(nonBipolarVertex)
splineRadii[1] = radii.get(nextVertex)
count = 2
for _ in range():
    ...
    splineRadii[count] = radii.get(nextVertex)
    count += 1

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that would be even faster, but not sure if it is worth the extra variable

btw, when you specify the length of a list in the constructor, you don't need to specify the capacity (if it is the same)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could reuse the variable j, then we won't have to create any extra variables. And it will be used in a narrow vertical scope. Should we do that?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reusing the same variable for completely different things is a very bad practice. Let's not do that :D

I'm not even sure why you need the extra variable if you already have variables for last/next and current vertex.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant to use it instead of count, I was responding to your comment:

but not sure if it is worth the extra variable.

But yah, I will no longer reuse variables, will have to edit some stuff in account with this. :D

filledSpaces[nextVertex] -= 1

currentVertex = nonBipolarVertex
for _ in range(verticesAmount):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe a better range(...) can be found so that this does not feel like a very inefficient inner loop?
Also by just looking at the way you use the loop it might be more appropriate to use a while loop instead?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know that the number of iterations won't exceed verticesAmount and is likely to be much less, so I though a for loop with a break condition would be safer here.
What do you mean by a better range(...)?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just ment a better upper bound could be used in range(...).

Anyway, looks like a for loop shouldn't be used here at all. Can you try to convert it into a while loop?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, that would be my first while loop 😄.


currentVertex = nonBipolarVertex
while neighboursAmounts[nextVertex] == 2:
i = currentVertex
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you actually want to change i here? Changing it might interfere with the outer loop

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am temporarily storing the value of currentVertex in it. The variable i--- As an index of the for loop--- is not used after that line, so I though it would be OK to overwrite it.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the thing is this: it is not used now but you never know if it will be used in the future. By reusing it you sign up for trouble.

Also if anyone wants to convert the code to c, you can't simply reuse the variable, because you would probably end up in an endless loop or so.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, will make sure not to do that again.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about this form?

while neighboursAmounts[nextVertex] == 2:
    if neighbours[neighboursStarts[nextVertex]] == currentVertex:
        currentVertex = nextVertex
        nextVertex = neighbours[neighboursStarts[nextVertex] + 1]
    else:
        currentVertex = nextVertex
        nextVertex = neighbours[neighboursStarts[nextVertex]]

currentVertex = nextVertex will be written twice, but we will avoid creating any temporary variables or extra lines of code.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think such inline expression will also work. Which one should we use?

currentVertex, nextVertex = nextVertex, neighbours[neighboursStarts[nextVertex] + neighbours[neighboursStarts[nextVertex]] == currentVertex]

start += neighboursAmounts[i]

# Keep track of how many index is in each group of neighbours at each iteration.
cdef int *filledSpaces = <int*>PyMem_Malloc(verticesAmount * sizeof(int))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think you should use an IntegerList instead of allocating it yourself.
IntegerList.fromValue(0, length = verticesAmount)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, so, in general, we shouldn't use malloc if there is a list alternative.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, by using lists the code is safer, we can't forget to free and it is cleaner.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything now uses lists.

# Compute how many neighbour each vertex have.
cdef IntegerList neighboursAmounts = IntegerList.fromValue(0, length = verticesAmount)
for i in range(edgesAmount):
neighboursAmounts[edges.data[i].v1] += 1
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for performance reasons you should use .data when accessing list elements from cython code.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was just going to ask you about this. Will do.

# Compute the indices of neighbouring vertices of each vertex.
cdef IntegerList neighbours = IntegerList(length = edgesAmount * 2)
for i in range(edgesAmount):
neighbours.data[neighboursStarts.data[edges.data[i].v1] +
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest extra v1 and v2 variables of type unsigned int to reduce line length and improve readability.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it a good programming practice to do:

v1, v2 = edges.data[i].v1, edges.data[i].v2

or should I separate them into two lines.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in this case it is fine. You could just check if cython generates efficient code for it by checking the generated c code.
(You can create an annotated html version of the file with this command: cython -a path/to/file.pyx)

nextVertex = neighbours.data[neighboursStarts.data[nonBipolarVertex] + j]
if (filledSpaces.data[nextVertex] == neighboursAmounts.data[nextVertex] or
neighboursAmounts.data[nextVertex] != 2):
splineVertices = Vector3DList.__new__(Vector3DList, length = verticesAmount)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these lists are wayy to large in many cases. Better use the append_LowLevel function instead of allocating huge arrays for every spline.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What should the initial capacity of those lists be?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just don't give them an initial capacity, or maybe 2.


# Generate Splines.
cdef list splines = []
cdef PolySpline spline
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this used?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, my bad.

@JacquesLucke JacquesLucke merged commit ecce0e7 into JacquesLucke:v2.1 Jul 14, 2018
@OmarEmaraDev OmarEmaraDev deleted the edges_to_splines branch August 1, 2018 17:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants