feat(networks): expose networkx helpers and plotting#233
feat(networks): expose networkx helpers and plotting#233nicola-bastianello merged 21 commits intoteam-decent:mainfrom
Conversation
Introduce a shared Network base with common message settings and accessors, refactor P2PNetwork to subclass it, and add FedNetwork with server/client helpers plus star-topology validation and a federated factory.
Organize imports and remove extraneous whitespace in networks.py to satisfy ruff. Update user guide to reference Network.active_agents so Sphinx can find the target.
Merge main into feat/fl-setup and confirm dev tox targets (mypy, pytest, ruff, sphinx) all pass.
Adjust kind to return the concrete class and simplify FL send_all validation.
Unify send/receive with optional None/iterables and keep aliases in P2P; enforce role-aware Fed send/receive and use client-only agents; remove unused accessors/kind, add doc notes on graph mutability and Fed agent semantics; update docs accordingly. closes team-decent#192
Iterate receiver iterable directly in Network.send/receive
…nt#229) Centralize sender/receiver connectivity validation in Network.send/receive and expose connected_agents for all network types. Simplify FedNetwork send/receive to rely on the base checks while keeping FL-specific errors and fan-out guards. Remove the duplicated output section in user.rst.
Add degrees/edges accessors and plotting helper to networks. Use networkx to build adjacency for P2P networks and allow auto-plot via benchmark creation flags. Document plotting options in user guide. closes team-decent#206
Add sequence-based send/receive handling while preserving behavior. Report invalid agent ids in connection errors.
Adjust Network.plot to import matplotlib at call time and accept typed kwargs copied into the draw call. Cast adjacency conversion for agent nodes. Update docstring wrapping and Sphinx intersphinx/Axes alias to keep ruff, mypy, and sphinx clean.
…nto feat/networkx-extras
Explain supported layouts and common draw kwargs passed via plot_network_kwargs and net.plot so users know which values to set.
nicola-bastianello
left a comment
There was a problem hiding this comment.
this is great! just a few comments
There was a problem hiding this comment.
Nice features, network plotting is really needed. I have some minor refactoring comments/discussions and a comment regarding our lovely friend Sphinx autodoc... I spent many hours figuring stuff out, which is terribly documented, so if you ever need help feel free to reach out regarding Sphinx
decent_bench/networks.py
Outdated
| """Agents directly connected to ``agent`` in the underlying graph.""" | ||
| return list(self.graph.neighbors(agent)) | ||
|
|
||
| def plot(self, ax: Axes | None = None, layout: str = "spring", **draw_kwargs: Mapping[str, object]) -> Axes: |
There was a problem hiding this comment.
-
Would be nice if you could make
layouta Literal so typechecking can catch incorrect values. Or you could make a Union of the actual networkx layouts and remove your_LAYOUT_FUNCS(should work but I am not sure). This would also link the layouts in the docs -
When you are using
TYPE_CHECKINGimport sphinx wont see this import (as TYPE_CHECKING is False when sphinx autodocs) so move the import outside of type checking. Then you can remove the type alias and nitpick ignore indocs/source/conf.pyso that the parameter is correctly linked. This is fine as matplotlib already is a requirement for this project. There are some other solutions if you want to keep the type checking import, seeutils/types.py - ArrayLike, but this one is the easiest. Theautodoc_type_aliasessetting inconf.pydoes not work as one might want/expect to... It is meant to be used ontyping.TypeAlias, its really confusing and strange (and unnecessarily restrictive). See https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#confval-autodoc_type_aliases -
Maybe we should move this away from the network object and into a networks utilities module? I think that the network object should only deal with network related tasks and this is more of a visualization/utility task. This would also allow us to remove the
plot_networkandplot_network_kwargsparameters from benchmark problem as these are not really benchmark problem related. These would instead be parameters ofcreate_network_...orbenchmarkfunctions. Any thoughts @adrianardv @nicola-bastianello
There was a problem hiding this comment.
regarding 3.: I like @Simpag suggestion, it makes sense to keep plotting as a separate util, and avoid having additional arguments when creating the benchmark problem. this way the users don't need to worry about network plotting unless they are interested.
I'm now thinking that we could even do the following: we don't implement the plotting interface in decent-bench, rather we show in the user guide how to apply networkx plotting to the network.graph attribute. I think the benefit of this is that we don't introduce any dependence on the specific plotting functions in networkx. this means that we don't need to go back and update code if networkx changes the way plotting is done/the args or kwargs of the plotting functions. also, in a sense any documenting we do about plotting is redundant with networkx docs
we could have the section in the user guide be something like "additional network utils" and describe there 1) how to plot, 2) anything else we think is good to mention on how to use networkx (we can add in the future, so for now 1) is enough)
There was a problem hiding this comment.
I think we must decide what decent bench should be. If its a pure benchmarking framework then we should not include plotting in my opinion. But if we are going to implement other optimization strategies like hyperparameter tuning then it might be nice to have it since decent bench becomes more of a utility than a pure benchmarking framework
There was a problem hiding this comment.
that's a good point. I do want to go beyond just benchmarking, but we need to be careful how we do that. in any case, for now we can keep the graph plotting functionality as a separate util, and we can come back to this discussion to decide what is the best way to expand functionality without creating an un-maintanable code base
There was a problem hiding this comment.
Okey, for now, I pulled plotting out of Network and the benchmark flags, and added network_utils.plot_network as an optional helper.
There was a problem hiding this comment.
looks great, thank you!
|
looking back at the communication methods in FedNetwork, I'm a bit worried that they are more confusing than beneficial; we have 7 methods, all with similar names (send_to_all_clients, send_from_client, send_from_all_clients, receive_at_client, receive_at_all_clients, receive_from_client, receive_from_all_clients). my worry is that users will be overwhelmed by these, and that it would be easier to have fewer methods and leave the users to read the docs of those. I think having fewer communication functions would also help making algorithms' implementations more readable any thoughts? |
|
I personally like to have helper functions. However the names are very similar and could be mistaken at a glance. Might also be overwhelming at first. Could change the ”send to all” functions to broadcast instead in order to distinguish them more easily. I have no ideas currently about the receive methods |
Remove Network.plot and plot flags from BenchmarkProblem, add network_utils.plot_network helper, and shift plotting guidance to a standalone doc section with intersphinx links.
|
I agree on shrinking the comm methods. I'm thinking:
def collect_from_all_clients(self, msgs: Mapping[Agent, Array]) -> None:
"""
Send messages from each client to the server (synchronous FL push) and receive at server.
"""
clients = set(self.clients)
senders = set(msgs)
invalid = senders - clients
if invalid:
raise ValueError("All senders must be clients")
missing = clients - senders
if missing:
raise ValueError("Messages must be provided for all clients")
for client, msg in msgs.items():
self.send(sender=client, receiver=self.server, msg=msg)
self.receive(receiver=self.server, sender=None)If this sounds good, I’ll make the changes. |
|
thanks Adriana for the changes! see below for my comments
I agree
I'm thinking that we could call them
I'm not sure this method would be needed; the reason is that all algorithms will be written from the perspective of agent |
|
Maybe move network_utils to the utils folder |
|
About this:
Happy to align names with P2P. I’m thinking:
Is this the behaviour you want, or did you have something else in mind @nicola-bastianello ? I’m now thinking broadcast(msg) for server → all clients, and receive_all() for the server draining all client messages; the rest of the comms can use the base send/receive, with broadcast/receive_all as convenience aliases for those server behaviours. Then, the comm. methods would be reduced to: the canonical send/receive; and broadcast and receive_all aliases, in both P2P and FL. |
…t#233) Move network_utils into utils namespace. Simplify FedNetwork messaging helpers. Update API docs and user guide imports for the new utils path.
|
very good points. I like your plan of having send/receive for the clients, and broadcast/receive_all for the server. let's go for that, thank you! |
nicola-bastianello
left a comment
There was a problem hiding this comment.
just a couple of comments on the user guide, and then it's ready to be merged. Thanks!
docs/source/user.rst
Outdated
| from decent_bench import benchmark, benchmark_problem | ||
| from decent_bench.utils import network_utils | ||
| from decent_bench.costs import LinearRegressionCost | ||
| from decent_bench.distributed_algorithms import ADMM, DGD, ED |
There was a problem hiding this comment.
this import is not needed in this code
docs/source/user.rst
Outdated
| benchmark_problem=problem, | ||
| ) | ||
|
|
||
| Network utilities |
There was a problem hiding this comment.
in the docs "Network utilities" is a section with subsections "Create problems using existing resources" and "Create problems from scratch". I think a better order would be to place "Network utilities" just after "Create problems from scratch"
Remove unused imports in the network utilities example and reorder the section.
Summary