-
Notifications
You must be signed in to change notification settings - Fork 121
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
Information and Network Architectures #903
base: main
Are you sure you want to change the base?
Conversation
…tyle, background colour
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've left a few suggestions based on the tutorials pages, there are also a number of instances of under indentation or line lengths being too long.
docs/tutorials/architecture/01_Introduction_to_Architectures.py
Outdated
Show resolved
Hide resolved
# Introduction | ||
# ------------ | ||
# | ||
# Following on from Tutorial 01: Introduction to Architectures in Stone Soup, this tutorial |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# Following on from Tutorial 01: Introduction to Architectures in Stone Soup, this tutorial | |
# Following on from :doc:`01_Introduction_to_Architectures`, this tutorial |
Potentially link to the previous tutorial, although not sure how this line would look with "1 - Introduction to Architectures" there.
Alternatively it could just be "Following on from the previous tutorial, "
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
# | ||
# Firstly, we set up a set of Nodes that will feature in our information architecture. As the set | ||
# of nodes will include FusionNodes. As a prerequisite of building a FusionNode, we must first |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# of nodes will include FusionNodes. As a prerequisite of building a FusionNode, we must first | |
# of nodes will include FusionNodes, we must first |
I think this reads slightly better.
# sphinx_gallery_thumbnail_path = '_static/sphinx_gallery/ArchTutorial_2.png' | ||
|
||
information_architecture = InformationArchitecture(edges, current_time=start_time) | ||
information_architecture |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Theres an issue in the image provided here that there are two Nodes labelled SensorNode A.
# Much of the time, we only care about the information architecture and not the actual mechanisms behind delivery of the | ||
# email, which is similar in nature to the network architecture. | ||
# Some nodes have the sole purpose of receiving and re-transmitting data on to other nodes in the network. We call | ||
# these :class:`~.RepeaterNode`s. Additionally, any :class:`~.Node` present in the :class:`~.InformationArchitecture` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# these :class:`~.RepeaterNode`s. Additionally, any :class:`~.Node` present in the :class:`~.InformationArchitecture` | |
# these :class:`~.RepeaterNode`\ s. Additionally, any :class:`~.Node` present in the :class:`~.InformationArchitecture` |
Following the class reference with an s causes the referencing to break.
# architecture (an Edges object), and a pre-fabricated InformationArchitecture object, which must | ||
# be provided as property `information_arch`. | ||
# | ||
# - Secondly, by providing the NetworkArchitecture with two `edge_list` values: one for the network |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# - Secondly, by providing the NetworkArchitecture with two `edge_list` values: one for the network | |
# - Secondly, by providing the :class:`~.NetworkArchitecture` with two `edge_list` values: one for the network |
# architecture and one for the information architecture. | ||
# | ||
# - Thirdly, by providing just a set of edges for the network architecture. In this case, | ||
# the NetworkArchitecture class will infer which nodes in the network architecture are also |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# the NetworkArchitecture class will infer which nodes in the network architecture are also | |
# the :class:`~.NetworkArchitecture` class will infer which nodes in the network architecture are also |
# The plot below displays the Network Architecture we have built. This includes all Nodes, | ||
# including those that do not feature in the Information Architecture (the repeater nodes). | ||
|
||
network_architecture |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same issue as before with multiple SensorNode A
s
# We then build a third "bad" sensor with high measurement noise (low measurement accuracy). | ||
# This will enable us to design an architecture and observe how the "bad" measurements are | ||
# propagated and fused with the "good" measurements. The bad sensor has its noise covariance | ||
# scaled by a factor of `sf` over the noise of the good sensors. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# scaled by a factor of `sf` over the noise of the good sensors. | |
# scaled by a factor of ``sf`` over the noise of the good sensors. |
good_sensor.timestamp = start_time | ||
|
||
# %% | ||
# We then build a third "bad" sensor with high measurement noise (low measurement accuracy). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# We then build a third "bad" sensor with high measurement noise (low measurement accuracy). | |
# We then build a "bad" sensor with high measurement noise (low measurement accuracy). |
As far as I can tell this is the second sensor at this point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent new feature. Mainly minor comments but a couple of discussion points as well.
Also, doc strings format should be NumPy, which need updating/adding in number of places.
stonesoup/architecture/__init__.py
Outdated
from abc import abstractmethod | ||
from operator import attrgetter | ||
|
||
import pydot | ||
|
||
from ..base import Base, Property | ||
from .node import Node, SensorNode, RepeaterNode, FusionNode | ||
from .edge import Edges, DataPiece, Edge | ||
from ..types.groundtruth import GroundTruthPath | ||
from ..types.detection import TrueDetection, Clutter | ||
from ._functions import _default_label | ||
|
||
from typing import List, Tuple, Collection, Set, Union, Dict | ||
import numpy as np | ||
import networkx as nx | ||
import graphviz | ||
from datetime import datetime, timedelta |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from abc import abstractmethod | |
from operator import attrgetter | |
import pydot | |
from ..base import Base, Property | |
from .node import Node, SensorNode, RepeaterNode, FusionNode | |
from .edge import Edges, DataPiece, Edge | |
from ..types.groundtruth import GroundTruthPath | |
from ..types.detection import TrueDetection, Clutter | |
from ._functions import _default_label | |
from typing import List, Tuple, Collection, Set, Union, Dict | |
import numpy as np | |
import networkx as nx | |
import graphviz | |
from datetime import datetime, timedelta | |
from abc import abstractmethod | |
from datetime import datetime, timedelta | |
from operator import attrgetter | |
from typing import List, Tuple, Collection, Set, Union, Dict | |
import graphviz | |
import numpy as np | |
import networkx as nx | |
import pydot | |
from .edge import Edges, DataPiece, Edge | |
from .node import Node, SensorNode, RepeaterNode, FusionNode | |
from ._functions import _default_label | |
from ..base import Base, Property | |
from ..types.detection import TrueDetection, Clutter | |
from ..types.groundtruth import GroundTruthPath |
doc="An Edges object containing all edges. For A to be connected to B we would have an " | ||
"Edge with edge_pair=(A, B) in this object.") | ||
current_time: datetime = Property( | ||
default=datetime.now(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Default should be None
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be replaced in the __init__
with datetime.now()
? It would seem inconvenient to me otherwise to have to specify a start, when this is unimportant to the majority of likely use cases.
a KeyError is raised. | ||
""" | ||
# Initiate a new DiGraph as self.digraph isn't necessarily directed. | ||
g = nx.DiGraph() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you use self.di_graph
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, perhaps 98 is an old comment, from before we decided what an edge represented precisely.
g = nx.DiGraph() | |
g = self.di_graph |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think my reasoning for creating a new DiGraph was that self.di_graph is created in the init, and not updated if the edge set is changed.
Creating a new graph forces use of the current edge set and doesn't risk out of date results. They may still be a better way around this though
if not len(self.top_level_nodes) == 1: | ||
return False | ||
for node in self.all_nodes: | ||
if node not in self.top_level_nodes and len(self.recipients(node)) != 1: | ||
return False | ||
return True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if not len(self.top_level_nodes) == 1: | |
return False | |
for node in self.all_nodes: | |
if node not in self.top_level_nodes and len(self.recipients(node)) != 1: | |
return False | |
return True | |
top_nodes = self.top_level_nodes | |
if len(top_nodes) != 1: | |
return False | |
for node in self.all_nodes: | |
if node not in top_nodes and len(self.recipients(node)) != 1: | |
return False | |
return True |
@property | ||
def is_centralised(self): | ||
""" | ||
Returns 'True' if the :class:`Architecture` is hierarchical, otherwise 'False'. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returns 'True' if the :class:`Architecture` is hierarchical, otherwise 'False'. | |
Returns 'True' if the :class:`Architecture` is centralised, otherwise 'False'. |
@@ -0,0 +1,74 @@ | |||
from abc import ABC |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this file used amywhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
line 12
def available_at_time(self, time: datetime): | ||
new_path = [] | ||
for state in self.states: | ||
if state.timestamp <= time: | ||
new_path.append(state) | ||
return GroundTruthPath(new_path, self.id) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this use existing slicing interface?
def __hash__(self): | ||
return hash((self.probability, self.prediction, self.measurement, | ||
self.measurement_prediction)) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this required? What about other hypothesies types if so.
…n of duplicate architectures
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looked at the simpler non-documentation comments to answer. Will review the others soon.
@@ -0,0 +1,74 @@ | |||
from abc import ABC |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
line 12
from node1 to node2 if key1=node1 and key2=node2. If no path exists from node1 to node2, | ||
a KeyError is raised. | ||
""" | ||
# Initiate a new DiGraph as self.digraph isn't necessarily directed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# Initiate a new DiGraph as self.digraph isn't necessarily directed. |
if isinstance(self, InformationArchitecture): | ||
for node in self.all_nodes: | ||
if isinstance(node, RepeaterNode): | ||
raise TypeError("Information architecture should not contain any repeater " | ||
"nodes") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if isinstance(self, InformationArchitecture): | |
for node in self.all_nodes: | |
if isinstance(node, RepeaterNode): | |
raise TypeError("Information architecture should not contain any repeater " | |
"nodes") | |
if any([isinstance(node, RepeaterNode) for node in self.all_nodes]): | |
raise TypeError("Information architecture should not contain any repeater " | |
"nodes") |
# for node in self.processing_nodes: | ||
# node.process() # This should happen when a new message is received |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# for node in self.processing_nodes: | |
# node.process() # This should happen when a new message is received |
def propagate(self, time_increment: float, failed_edges: Collection = None): | ||
"""Performs the propagation of the measurements through the network""" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the current idea is that the network architecture will be used to test whether or not data can be sent as intended by the information architecture. I found this the most intuitive way. It should hopefully not cause any limitations that I can foresee for now.
Co-authored-by: Christopher Sherman <146717651+csherman-dstl@users.noreply.github.com> Co-authored-by: Steven Hiscocks <sdhiscocks@dstl.gov.uk>
188f4a4
to
99b8687
Compare
…into architecture_repr
99b8687
to
fed2f5a
Compare
Introducing the long-awaited architecture module! Thus far, multi-sensor tracking in Stone Soup has operated on the assumption that data from multiple sensors can be fused together seamlessly, but this addition allows experimentation with different architectures.
As there is a fair chunk of new components, we've provided documentation in the form of three tutorials, which I'd recommend starting with to aid your understanding.