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

Add Random topology class #155

Merged
merged 2 commits into from
Jul 14, 2018
Merged

Add Random topology class #155

merged 2 commits into from
Jul 14, 2018

Conversation

whzup
Copy link
Collaborator

@whzup whzup commented Jul 5, 2018

Added a new topology with random neighbours. Added documentation and
a test file for it and reworked the documentation of the
GeneralOptimizer class to incorporate all available topologies.
Simplified the fixture function for the GeneralOptimizer class.
Cited the relevant paper for the algorithm implemented.

I implemented the algorithm to compute the neighbours in a separate, private method inside the Random class because I thought it's cleaner that way. We need such a complicated algorithm because it's important that the "graph" we create when assigning neighbours is fully connected.

@whzup
Copy link
Collaborator Author

whzup commented Jul 5, 2018

@ljvmiranda921 the static/dynamic feature is already in the pipeline and almost ready for PR 😄. Just gonna wait for you to approve this one so I can work from a clean branch.
Please don't feel stressed by my many PRs and take as much time as needed to review them!

@whzup whzup added enhancement Feature requests v0.3.0 labels Jul 6, 2018
@ljvmiranda921
Copy link
Owner

ljvmiranda921 commented Jul 6, 2018

The static/dynamic feature is already in the pipeline and almost ready for PR

Wow, you're an MVP of this version haha. Thanks a lot for all your help!

Please don't feel stressed by my many PRs and take as much time as needed to review them!

Haha! No problem. Will probably review them next week, @whzup , after my final defense (wish me luck)!

@whzup
Copy link
Collaborator Author

whzup commented Jul 7, 2018

Hahaha, I thought I'm going to push out some PRs before I go work 😄. Sure do! Wish you success! Is the thesis going to be available online? Might take a glance at it if so 😋.

@ljvmiranda921
Copy link
Owner

ljvmiranda921 commented Jul 7, 2018

I thought I'm going to push out some PRs before I go work

Sure @whzup ! Just push the changes and I'll take care of this next week.

Is the thesis going to be available online? Might take a glance at it if so.

Of course! The link can be found here

Added a new topology with random neighbors. Added documentation and
a test file for it and reworked the documentation of the
GeneralOptimizer class to incorporate all available topologies.
Simplified the fixture function for the GeneralOptimizer class.
Cited the relevant paper for the algorithm implemented.
@whzup
Copy link
Collaborator Author

whzup commented Jul 9, 2018

Just updated the documentation for the Random topology to fit the changes I made in the other topology documentations 👍.

Copy link
Owner

@ljvmiranda921 ljvmiranda921 left a comment

Choose a reason for hiding this comment

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

Overall, awesome work!

I have some comments regarding documentation (perhaps an explanation on how dijkstra plays into the Topology and some dev explanations on the adj_*, aux_* and dist_* matrices). There's also a comment on reducing the need for nested for-loops via itertools, please check it out.

As for tests, I'd recommend you test the __compute_neighbors() method so that we know it works as expected. The other tests were great! I love that you parametrized the topologies for the GeneralOptimizer.

Sorry this took too long. Final defense was done yesterday and it went well!

"""
A Random Network Topology

This class implements a random topology. All particles are connected in a random fashion.
Copy link
Owner

Choose a reason for hiding this comment

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

It would be nice if we add a short explanation on why dijkstra is used and how it relates to the Random topology. 😄

a Swarm instance
k : int
number of neighbors to be considered. Must be a
positive integer less than :code:`n_particles-1`
Copy link
Owner

Choose a reason for hiding this comment

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

A bit nitpicky but an empty line before Returns will make everything consistent 👍

for j in range(swarm.n_particles):
if dist_matrix[i][j] == 0:
adj_matrix[i][j] = 1

Copy link
Owner

Choose a reason for hiding this comment

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

Wanna try itertools.product for this nested for-loop? Here's the documentation. Sample usage:

import itertools

for i, j in itertools.product(range(swarm.n_particles), repeat=2):
    # Not sure if this ternary operator works, if not, just use the normal one
    adj_matrix[i][j] = 1 if dist_matrix[i][j] == 0

for j in range(swarm.n_particles):
if dist_matrix[i][j] == 0:
adj_matrix[i][j] = 1

Copy link
Owner

Choose a reason for hiding this comment

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

From my understanding, the elements in adj_matrix are initially set to 1. Then we have a for-loop that traverses the dist_matrix and updates the adj_matrix to 1 if the corresponding element in the dist_matrix is equal to 0.

Does this change anything? Perhaps it's connected_components that makes the difference but maybe you can explain it more clearly? 👍

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 was wondering about this part as well. Only when I tested the algorithm without the condition that you test it for connectivity it became clear to me. If you don't have a connected graph it is quite possible that you can't extract the best position from the swarm. It might not be a problem with larger swarm sizes but in smaller ones, it occurred very often. I think this happens because the compute_gbest() method can't handle multiple unconnected swarm clusters. Just wanted to calrify this because it's hard to stuff all this information in a 3 line comment 😄. I'll go ahead and change these things in the evening 👍.

Copy link
Owner

Choose a reason for hiding this comment

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

Just wanted to calrify this because it's hard to stuff all this information in a 3 line comment smile

Understood. Yes it does look cluttered. I suggest we put this on the docstring instead with a note markup. Just revert this back to # Generate connected graph
and put the explanation you've added as a note on __compute_neighbors()'s docstring. 👍

Copy link
Owner

Choose a reason for hiding this comment

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

So what I'm thinking is that inside the __compute_neighbors()'s docstring, we have an explanation for all three matrices:

def __compute_neighbors():
    """Single-line comment

    etc. etc.

    - adj_matrix:  does this and this and contains this
    - dist_matrix: contains this, relates to this, etc.
    - aux_matrix: performs this, etc.
    """

This method computes the adjacency matrix of the topology using
the randomized algorithm proposed in [TSWJ2013]. The resulting
topology is a connected graph.

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 we can add a short explanation between the adj_matrix, aux_matrix, and dist_matrix here in order to guide other developers who may someday work with this code. 👍


# Import from package
from pyswarms.backend.topology import Random

Copy link
Owner

Choose a reason for hiding this comment

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

Please add a test for __compute_neighbors 👍
This seems to be an integral part of the topology and it would be nice if we can make sure that it will always work as expected. 💯

You can construct a "smaller" swarm in conftest.py to make it more manageable.

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 do you think is the best way to test it? The elements of the output matrix are random so we can't check if it returns the "right" elements. The only things we could test are the shape and the symmetry. We could use small swarms but then it would just be lucky if we get the right answers or not.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Implemented a test for the shape and the symmetry, I hope it is sufficient for you 🙈.

Copy link
Owner

Choose a reason for hiding this comment

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

I really wonder why my earlier reviews didn't show up 😕 So you can probably try to set a random seed (not sure if we set the numpy random seed or the standard library's random seed) to some value so that it will always return the correct values whenever the __compute_neighbors() is called.

Copy link
Owner

Choose a reason for hiding this comment

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

So yes, just check if setting a random seed will work so that we always have an expected behaviour

Random()
])
def general_opt_reset(top):
"""Returns a GeneralOptimizerPSO instance that has been run and reset to check
default value"""
Copy link
Owner

Choose a reason for hiding this comment

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

Awesome! 🥇

@@ -52,6 +67,21 @@ def test_invalid_k_or_p_values(options):
GeneralOptimizerPSO(5, 2, options, Ring())


Copy link
Owner

Choose a reason for hiding this comment

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

Great tests! To be honest, the GeneralOptimizer can make everything DRY. The reason we will still keep the GlobalBest and LocalBest is for tradition— they're the commonly-known PSO algorithms, and having them exposed to a first-time user is good.

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 agree. They're also very easy to handle if you want to make a quick optimization. The GeneralOptimizer class will surely grow and so might the complexity of its handling. I guess it's a good decision to leave the others in for simplicity's sake.

@whzup
Copy link
Collaborator Author

whzup commented Jul 11, 2018

@ljvmiranda921, nice! Thanks for the good inputs! Surely went well the thesis looks pretty good 👍. Done with all changes except the testing for the compute_neighbors() method. Not sure how to test it, TBH😄.

@ljvmiranda921
Copy link
Owner

ljvmiranda921 commented Jul 12, 2018

Thanks a lot @whzup ! This PR looks good and nicely-structured.

Thanks for the good inputs! Surely went well the thesis looks pretty good +1.

Thank you so much! I appreciate that you liked it!

Done with all changes except the testing for the compute_neighbors() method. Not sure how to test it, TBH

So my idea is that you contrive a small swarm from the Swarm class and add it to conftest.py (or you can use the existing swarm fixture), then, try to pass it to __compute_neighbors and see the return value. That will then be your expected value.

After that, we write a test that checks if it will always give us the expected value whenever the test suite is run. If there is stochasticity (randomness) involved, you can set the random seed (not sure if you will set numpy's random seed or the random library's seed) so that it will always return the same thing. 👍

@whzup
Copy link
Collaborator Author

whzup commented Jul 12, 2018

Done! Hope you like it 😄. I'm already itching to PR the static/dynamic feature 😋.

Copy link
Owner

@ljvmiranda921 ljvmiranda921 left a comment

Choose a reason for hiding this comment

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

Woops let me check. I wonder why my previous review comments didn't show up...

for j in range(swarm.n_particles):
if dist_matrix[i][j] == 0:
adj_matrix[i][j] = 1

Copy link
Owner

Choose a reason for hiding this comment

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

Just wanted to calrify this because it's hard to stuff all this information in a 3 line comment smile

Understood. Yes it does look cluttered. I suggest we put this on the docstring instead with a note markup. Just revert this back to # Generate connected graph
and put the explanation you've added as a note on __compute_neighbors()'s docstring. 👍

for j in range(swarm.n_particles):
if dist_matrix[i][j] == 0:
adj_matrix[i][j] = 1

Copy link
Owner

Choose a reason for hiding this comment

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

So what I'm thinking is that inside the __compute_neighbors()'s docstring, we have an explanation for all three matrices:

def __compute_neighbors():
    """Single-line comment

    etc. etc.

    - adj_matrix:  does this and this and contains this
    - dist_matrix: contains this, relates to this, etc.
    - aux_matrix: performs this, etc.
    """

Copy link
Owner

@ljvmiranda921 ljvmiranda921 left a comment

Choose a reason for hiding this comment

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

Thanks for this @whzup ! Just a few short things to edit (and 1 more test) so that I can merge everything by this week. Very excited to see the static implementations soon!

  1. Less clutter in the code. It would be nice to move all long explanations into the docstring. The idea is to have short explanations in the inline-comments, and have more verbose descriptions in the docstring. I suggest you also put something like:
  • adj_matrix: does this etc. etc. etc.
  • aux_matrix: creates this etc.
  • dist_matrix: contains this etc.

So that everything is manageable and more readable without looking into the code.

  1. Set random seed so we can have an expected behaviour. For the tests, try setting a random seed so that we will always have an expected behaviour whenever __compute_neighbors() is called. Just contrive a very small swarm (or you can use the preset ones in conftest), and call the required method.

# best position within the swarm. If the element (i,j) of the
# distance matrix is 0, that means that there is no path
# from node i to node j, thus there are still unconnected
# pieces of the graph (i.e. topology)
Copy link
Owner

Choose a reason for hiding this comment

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

Hi @whzup , one last thing regarding documentation. I'm not sure if you've seen my earlier comment but I suggested to put this long text on the docstring itself. You can just put a # Generate connected graph on the docstring. The idea is to make the docstring a bit longer while the in-line comments shorter (less clutter) and can be viewed by the developer using help() without resorting to look at the source code.


# Import from package
from pyswarms.backend.topology import Random

Copy link
Owner

Choose a reason for hiding this comment

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

I really wonder why my earlier reviews didn't show up 😕 So you can probably try to set a random seed (not sure if we set the numpy random seed or the standard library's random seed) to some value so that it will always return the correct values whenever the __compute_neighbors() is called.


# Import from package
from pyswarms.backend.topology import Random

Copy link
Owner

Choose a reason for hiding this comment

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

So yes, just check if setting a random seed will work so that we always have an expected behaviour

Updated the documentation of the Random class. Especially the __compute_neighbor() method. Added the comments inside the method to the docstring and deleted irrelevant comments.
Changed the nested for-loops to one loop with the itertools library.
Added a new test for the return value of the __compute_neighbor() method, which checks the shape and the symmetry.
Added a new test for the return value of the __compute_neighbors() method, which compares the returned matrix with a preset comparison matrix using a seed.
Copy link
Owner

@ljvmiranda921 ljvmiranda921 left a comment

Choose a reason for hiding this comment

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

LGTM! Will merge in a bit

* dist_matrix : The distance matrix computed with Dijkstra's
algorithm. It is used to determine where the
graph needs edges to change it to a connected
graph.
Copy link
Owner

Choose a reason for hiding this comment

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

Awesome! This is good @whzup !

@ljvmiranda921 ljvmiranda921 merged commit cca8d3c into ljvmiranda921:development Jul 14, 2018
@whzup whzup deleted the random_topology branch July 14, 2018 05:22
@ljvmiranda921 ljvmiranda921 mentioned this pull request Jul 31, 2018
12 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Feature requests
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants