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

Information and Network Architectures #903

Open
wants to merge 125 commits into
base: main
Choose a base branch
from
Open

Conversation

orosoman-dstl
Copy link
Contributor

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.

  • A network architecture models how data is passed around between nodes.
  • An information architecture is models how data is fused together within a network. The information architecture is therefore dependent on the network architecture to operate, as without it there would be no data to fuse.

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.

orosoman-dstl and others added 30 commits November 29, 2023 08:59
Copy link
Contributor

@csherman-dstl csherman-dstl left a 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.

# Introduction
# ------------
#
# Following on from Tutorial 01: Introduction to Architectures in Stone Soup, this tutorial
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
# 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
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
# 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
Copy link
Contributor

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`
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
# 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
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
# - 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
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
# 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
Copy link
Contributor

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 As

# 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.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
# 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).
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
# 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.

Copy link
Member

@sdhiscocks sdhiscocks left a 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.

Comment on lines 1 to 17
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
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
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(),
Copy link
Member

Choose a reason for hiding this comment

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

Default should be None.

Copy link
Contributor Author

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()
Copy link
Member

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?

Copy link
Contributor Author

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.

Suggested change
g = nx.DiGraph()
g = self.di_graph

Copy link
Contributor

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

Comment on lines +306 to +311
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
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
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'.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Returns 'True' if the :class:`Architecture` is hierarchical, otherwise 'False'.
Returns 'True' if the :class:`Architecture` is centralised, otherwise 'False'.

stonesoup/feeder/track.py Outdated Show resolved Hide resolved
@@ -0,0 +1,74 @@
from abc import ABC
Copy link
Member

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

line 12

Comment on lines +45 to +51
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)

Copy link
Member

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?

Comment on lines +104 to +107
def __hash__(self):
return hash((self.probability, self.prediction, self.measurement,
self.measurement_prediction))

Copy link
Member

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.

stonesoup/updater/wrapper.py Show resolved Hide resolved
Copy link
Contributor Author

@orosoman-dstl orosoman-dstl left a 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.

stonesoup/architecture/node.py Outdated Show resolved Hide resolved
stonesoup/architecture/node.py Outdated Show resolved Hide resolved
stonesoup/architecture/node.py Show resolved Hide resolved
stonesoup/feeder/track.py Outdated Show resolved Hide resolved
@@ -0,0 +1,74 @@
from abc import ABC
Copy link
Contributor Author

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.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
# Initiate a new DiGraph as self.digraph isn't necessarily directed.

stonesoup/architecture/__init__.py Show resolved Hide resolved
Comment on lines +373 to +377
if isinstance(self, InformationArchitecture):
for node in self.all_nodes:
if isinstance(node, RepeaterNode):
raise TypeError("Information architecture should not contain any repeater "
"nodes")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
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")

Comment on lines +530 to +531
# for node in self.processing_nodes:
# node.process() # This should happen when a new message is received
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
# for node in self.processing_nodes:
# node.process() # This should happen when a new message is received

Comment on lines +492 to +494
def propagate(self, time_increment: float, failed_edges: Collection = None):
"""Performs the propagation of the measurements through the network"""

Copy link
Contributor Author

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.

orosoman-dstl and others added 2 commits April 23, 2024 17:53
Co-authored-by: Christopher Sherman <146717651+csherman-dstl@users.noreply.github.com>
Co-authored-by: Steven Hiscocks <sdhiscocks@dstl.gov.uk>
@spike-dstl spike-dstl force-pushed the architecture_repr branch 6 times, most recently from 188f4a4 to 99b8687 Compare April 25, 2024 11:33
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

5 participants