Graph Representation#

Module defining the nodes and edges of the graph representing the lymphatic system.

Anything related to the network of nodes and edges is defined here. This includes the nodes themselves (either Tumor or LymphNodeLevel), the edges (Edge), and the graph (Representation).

The nodes and edges are used to define the structure of the graph, which may then be accessed via the Representation class. This in turn is then used to compute e.g. the transition matrix of the model.

class lymph.graph.AbstractNode(name: str, state: int, allowed_states: list[int] | None = None)[source]#

Bases: object

Abstract base class for nodes in the graph reprsenting the lymphatic system.

__init__(name: str, state: int, allowed_states: list[int] | None = None) None[source]#

Make a new node.

Upon initialization, the name and state of the node must be provided. The state must be one of the allowed_states. The constructor makes sure that the allowed_states are a list of ints, even when, e.g., a tuple of floats is provided.

property name: str#

Return the name of the node.

property state: int#

Return the state of the node.

comp_obs_prob(obs: int, obs_table: ndarray, log: bool = False) float[source]#

Compute the probability of the diagnosis obs, given the current state.

The obs_table is a 2D array with the rows corresponding to the states and the columns corresponding to the observations. It encodes for each state and diagnosis the corresponding probability.

class lymph.graph.Tumor(name: str, state: int = 1)[source]#

Bases: AbstractNode

A tumor in the graph representation of the lymphatic system.

__init__(name: str, state: int = 1) None[source]#

Create a new tumor node.

It can only ever be in one state, which is implemented such that the allowed_states are set to [state].

class lymph.graph.LymphNodeLevel(name: str, state: int = 0, allowed_states: list[int] | None = None)[source]#

Bases: AbstractNode

A lymph node level (LNL) in the graph representation of the lymphatic system.

__init__(name: str, state: int = 0, allowed_states: list[int] | None = None) None[source]#

Create a new lymph node level.

classmethod binary(name: str, state: int = 0) LymphNodeLevel[source]#

Create a new binary LNL.

classmethod trinary(name: str, state: int = 0) LymphNodeLevel[source]#

Create a new trinary LNL.

property is_binary: bool#

Return whether the node is binary.

property is_trinary: bool#

Return whether the node is trinary.

comp_bayes_net_prob(log: bool = False) float[source]#

Compute the Bayesian network’s probability for the current state.

comp_trans_prob(new_state: int) float[source]#

Compute the hidden Markov model’s transition probability to a new_state.

class lymph.graph.Edge(parent: Tumor | LymphNodeLevel, child: LymphNodeLevel, spread_prob: float = 0.0, micro_mod: float = 1.0, callbacks: list[callable] | None = None)[source]#

Bases: object

This class represents an arc in the graph representation of the lymph system.

__init__(parent: Tumor | LymphNodeLevel, child: LymphNodeLevel, spread_prob: float = 0.0, micro_mod: float = 1.0, callbacks: list[callable] | None = None) None[source]#

Create a new edge between two nodes.

The parent node must be a Tumor or a LymphNodeLevel, and the child node must be a LymphNodeLevel.

The spread_prob parameter is the probability of a tumor or involved LNL to spread to the next LNL. The micro_mod parameter is a modifier for the spread probability in case of only a microscopic node involvement.

property parent: Tumor | LymphNodeLevel#

Return the parent node that drains lymphatically via the edge.

property child: LymphNodeLevel#

Return the child node of the edge, receiving lymphatic drainage.

get_name(middle='to') str[source]#

Return the name of the edge.

An edge’s name is simply the name of the parent node and the child node, connected by the string provided via the middle argument.

This is used to identify and assign spread probabilities to it e.g. in the set_params() method and elsewhere.

>>> lnl_II = LymphNodeLevel("II")
>>> lnl_III = LymphNodeLevel("III")
>>> edge = Edge(lnl_II, lnl_III)
>>> edge.get_name()
'IItoIII'
>>> edge.get_name(middle='->')
'II->III'
property is_growth: bool#

Check if this edge represents a node’s growth.

property is_tumor_spread: bool#

Check if this edge represents spread from a tumor to an LNL.

get_micro_mod() float[source]#

Return the spread probability.

set_micro_mod(new_micro_mod: float | None) None[source]#

Set the spread modifier for LNLs with microscopic involvement.

property micro_mod: float#

Parameter modifying spread probability in case of macroscopic involvement

get_spread_prob() float[source]#

Return the spread probability.

set_spread_prob(new_spread_prob: float | None) None[source]#

Set the spread probability of the edge.

property spread_prob: float#

Spread probability of the edge

get_params(as_dict: bool = True, **_kwargs) types.ParamsType[source]#

Return the value of the parameter param or all params in a dict.

set_params(*args, **kwargs) tuple[float][source]#

Set the values of the edge’s parameters.

If provided as positional arguments, the edge connects to a trinary node, and is not a growth node, the first argument is the spread probability and the second argument is the microscopic spread modifier. Otherwise it only consumes one argument, which is the growth or spread probability.

Keyword arguments (i.e., "growth", "spread", and "micro") override positional arguments. Unused args are returned.

>>> edge = Edge(LymphNodeLevel("II", allowed_states=[0, 1, 2]), LymphNodeLevel("III"))
>>> _ = edge.set_params(0.1, 0.2)
>>> edge.spread_prob
0.1
>>> edge.micro_mod
0.2
>>> _ = edge.set_params(spread=0.3, micro=0.4)
>>> edge.spread_prob
0.3
>>> edge.micro_mod
0.4
property transition_tensor: ndarray#

Return the transition tensor of the edge.

See also

lymph.helper.comp_transition_tensor()

class lymph.graph.Representation(graph_dict: dict[tuple[str], list[str]], tumor_state: int | None = None, allowed_states: list[int] | None = None, on_edge_change: list[callable] | None = None)[source]#

Bases: object

Class holding the graph structure of the model.

This class allows accessing the connected nodes (Tumor and LymphNodeLevel) and edges (Edge) of the models.

__init__(graph_dict: dict[tuple[str], list[str]], tumor_state: int | None = None, allowed_states: list[int] | None = None, on_edge_change: list[callable] | None = None) None[source]#

Create a new graph representation of nodes and edges.

The graph_dict is a dictionary that defines which nodes are created and with what edges they are connected. The keys of the dictionary are tuples of the form (node_type, node_name). The node_type can be either "tumor" or "lnl". The node_name is a string that uniquely identifies the node. The values of the dictionary are lists of node names to which the key node should be connected.

property nodes: dict[str, Tumor | LymphNodeLevel]#

List of both Tumor and LymphNodeLevel instances.

property tumors: dict[str, Tumor]#

List of all Tumor nodes in the graph.

property lnls: dict[str, LymphNodeLevel]#

List of all LymphNodeLevel nodes in the graph.

property allowed_states: list[int]#

Return the list of allowed states for each LymphNodeLevel.

property is_binary: bool#

Indicate if the model is binary.

Returns True if all LymphNodeLevel instances are binary, False otherwise.

property is_trinary: bool#

Returns True if the graph is trinary, False otherwise.

Similar to is_binary().

property edges: dict[str, Edge]#

Iterable of all edges in the graph.

property tumor_edges: dict[str, Edge]#

List of all tumor Edge instances in the graph.

This contains all edges who’s parents are instances of Tumor and who’s children are instances of LymphNodeLevel.

property lnl_edges: dict[str, Edge]#

List of all LNL Edge instances in the graph.

This contains all edges who’s parents and children are instances of LymphNodeLevel, including growth edges (if the graph is trinary).

property growth_edges: dict[str, Edge]#

List of all growth Edge instances in the graph.

Growth edges are only present in trinary models and are arcs where the parent and child are the same LymphNodeLevel instance. They facilitate the change from a micsoscopically positive to a macroscopically positive LNL.

to_dict() dict[tuple[str, str], set[str]][source]#

Returns graph representing this instance’s nodes and egdes as dictionary.

>>> graph_dict = {
...    ('tumor', 'T'): ['II', 'III'],
...    ('lnl', 'II'): ['III'],
...    ('lnl', 'III'): [],
... }
>>> graph = Representation(graph_dict)
>>> graph.to_dict() == graph_dict
True
get_mermaid() str[source]#

Prints the graph in mermaid format.

>>> graph_dict = {
...    ('tumor', 'T'): ['II', 'III'],
...    ('lnl', 'II'): ['III'],
...    ('lnl', 'III'): [],
... }
>>> graph = Representation(graph_dict)
>>> graph.edges["TtoII"].spread_prob = 0.1
>>> graph.edges["TtoIII"].spread_prob = 0.2
>>> graph.edges["IItoIII"].spread_prob = 0.3
>>> print(graph.get_mermaid())  
flowchart TD
    T-->|10%| II
    T-->|20%| III
    II-->|30%| III
get_mermaid_url() str[source]#

Returns the URL to the rendered graph.

get_state(as_dict: bool = False) dict[str, int] | list[int][source]#

Return the states of the system’s LNLs.

If as_dict is True, the result is a dictionary with the names of the LNLs as keys and their states as values. Otherwise, the result is a list of the states of the LNLs in the order they appear in the graph.

set_state(*new_states_args, **new_states_kwargs) None[source]#

Assign a new state to the system’s LNLs.

The state can either be provided with positional arguments or as keyword arguments. In case of positional arguments, the order must be the same as the order of the LNLs in the graph. If keyword arguments are used, the keys must be the names of the LNLs. The order of the keyword arguments does not matter.

The keyword arguments override the positional arguments.

property state_list#

Return list of all possible hidden states.

E.g., for three binary LNLs I, II, III, the first state would be where all LNLs are in state 0. The second state would be where LNL III is in state 1 and all others are in state 0, etc. The third represents the case where LNL II is in state 1 and all others are in state 0, etc. Essentially, it looks like binary counting:

>>> graph = Representation(graph_dict={
...     ("tumor", "T"): ["I", "II" , "III"],
...     ("lnl", "I"): [],
...     ("lnl", "II"): ["I", "III"],
...     ("lnl", "III"): [],
... })
>>> graph.state_list
array([[0, 0, 0],
       [0, 0, 1],
       [0, 1, 0],
       [0, 1, 1],
       [1, 0, 0],
       [1, 0, 1],
       [1, 1, 0],
       [1, 1, 1]])
get_params(as_dict: bool = True, as_flat: bool = True) types.ParamsType[source]#

Return the parameters of the edges in the graph.

If as_dict is False, return an iterable of all parameter values. If as_dict is True, return a nested dictionary with the edges’ names as keys and the edges’ parameter dicts as values.

If as_flat is True, return a flat dictionary with the T-stages and parameters as keys and values, respectively. This is the result of passing the nested dictionary to flatten().

set_params(*args, **kwargs) tuple[float][source]#

Set the parameters of the edges in the graph.

The arguments are passed to the set_params() method of the edges. Global keyword arguments (e.g. "spread") are passed to each edge’s set_params method. Unused args are returned.

Specific keyword arguments take precedence over global ones which in turn take precedence over positional arguments.

>>> graph = Representation(graph_dict={
...     ("tumor", "T"): ["II" , "III"],
...     ("lnl", "II"): ["III"],
...     ("lnl", "III"): [],
... })
>>> _ = graph.set_params(0.1, 0.2, 0.3, spread=0.4, TtoII_spread=0.5)
>>> graph.get_params(as_dict=True)   
{'TtoII_spread': 0.5,
 'TtoIII_spread': 0.4,
 'IItoIII_spread': 0.4}