Generate all paths that consists of specified number of visits of nodes / edges
AnsweredIn a graph/chain there are 3 different states: ST, GRC_i and GRC_j. The following edges between the states exists:
EDGES = [
# source, target, name
('ST', 'GRC_i', 'TDL_i'),
('ST', 'GRC_j', 'TDL_j'),
('GRC_i', 'GRC_j', 'RVL_j'),
('GRC_j', 'GRC_i', 'RVL_i'),
('GRC_j', 'ST', 'SUL_i'),
('GRC_i', 'ST', 'SUL_j'),
]

The values for TDL_i, TDL_i, RVL_i and RVL_j are known. The chain always starts in ST and the final state is always known.
I want to infer SUL_i and SUL_j based on possible paths that satisfy the known information.
For example if we have the following information:
The following relationships are evident:
- The number of visits to
STis equal toSUL_i + SUL_j + 1 - The number of visits to
GRC_iis equal toTDL_i + RVL_i - The number of visits to
GRC_jis equal toTDL_j + RVL_j - The upper-bound of
SUL_iis the number of visits toGRC_j - The upper-bound of
SUL_jis the number of visits toGRC_i - The maximum total number of steps is
2 * (TDL_i + TDL_j + RVL_i + RVL_i)
I write the following function:
import networkx as nx
import gurobipy as grb
from gurobipy import GRB
from typing import Literal
def get_SUL(TDL_i: int, TDL_j: int, RVL_i: int, RVL_j: int, final_state: Literal['ST', 'GRC_i', 'GRC_j']):
G = nx.DiGraph()
G.add_edges_from([
('ST', 'GRC_i'),
('ST', 'GRC_j'),
('GRC_i', 'GRC_j'),
('GRC_j', 'GRC_i'),
('GRC_j', 'ST'),
('GRC_i', 'ST')
])
n_actions = len(list(G.edges()))
n_states = len(list(G.nodes()))
min_N = TDL_i + TDL_j + RVL_i + RVL_i
max_N = 2 * (TDL_i + TDL_j + RVL_i + RVL_i)
for N in range(min_N, max_N + 1):
m = grb.Model()
SUL_i = m.addVar(lb=0, ub=TDL_j + RVL_j)
SUL_j = m.addVar(lb=0, ub=TDL_i + RVL_i)
# actions
actions = m.addMVar((n_actions, N), vtype=GRB.BINARY)
m.addConstr(actions[0,:].sum() == TDL_i)
m.addConstr(actions[1,:].sum() == TDL_j)
m.addConstr(actions[2,:].sum() == RVL_i)
m.addConstr(actions[3,:].sum() == RVL_j)
m.addConstr(actions[4,:].sum() == SUL_i)
m.addConstr(actions[5,:].sum() == SUL_j)
m.addConstrs(actions[:,n].sum() == 1 for n in range(N))
# states
states = m.addMVar((n_states, N), vtype=GRB.BINARY)
m.addConstr(states[0,:].sum() == SUL_i + SUL_j + 1)
m.addConstr(states[0,:].sum() == TDL_i + RVL_i)
m.addConstr(states[0,:].sum() == TDL_j + RVL_j)
m.addConstr(states[0,0] == 1)
if final_state == 'ST':
m.addConstr(states[0,-1] == 1)
m.addConstr(states[1,-1] == 0)
m.addConstr(states[2,-1] == 0)
elif final_state == 'GRC_i':
m.addConstr(states[0,-1] == 0)
m.addConstr(states[1,-1] == 1)
m.addConstr(states[2,-1] == 0)
else:
m.addConstr(states[0,-1] == 0)
m.addConstr(states[1,-1] == 0)
m.addConstr(states[2,-1] == 1)
m.addConstrs(actions[:,n].sum() == 1 for n in range(N))
# additional constraints
How do I impose that the action- and states variables are in agreement with each other? For example, the first action can only be TDL_i or TDL_j because we start in ST. Similar, if the chain ends in ST, the last action must be either SUL_i or SUL_j.
Your help is much appreciated
-
Hi Huib,
You could add two sets of constraints for each (node, i) combination:
- Incoming: The sum of all actions[edge, i] that point towards node should equal states[node, i]
- Outgoing: The sum of all actions[edge, i+1] that leave from node should equal states[node, i]
Kind regards,
Ronald0 -
Just a few additional thoughts:
- You can calculate SUL_i and SUL_j without generating paths. Their values only depend on the values of GRC_i, GRB_j, RVL_i, RVL_j and the desired end state.
- Once you know these, your model knows the exact number of states, removing the need for min_N and max_N.
- Generating all possible path can lead to an exponentially large number of solutions. You can try our solution pool feature. Are you really looking for all of them?
Kind regards,
Ronald0 -
Hi Ronald, thank you for your insights. I am mainly interested in finding the value of `SUL_i` and `SUL_j`.
You stated that its possible to retrieve these values without the paths. Is the following function correct
from typing import Literal, Tuple
def get_SUL(TDL_i: int, TDL_j: int, RVL_i: int, RVL_j: int, final_state: Literal['ST', 'GRC_i', 'GRC_j']) -> Tuple[int, int]:
SUL_i = TDL_j + RVL_j - RVL_i
SUL_j = TDL_i + RVL_i - RVL_j
if final_state == 'GRC_i':
SUL_j -= 1
elif final_state == 'GRC_j':
SUL_i -= 1
else:
pass
return SUL_i, SUL_j0 -
The approach is roughly what I had in mind. The best way to verify if it's 100% correct, would be by testing it on several datasets.
Kind regards,
Ronald0
Please sign in to leave a comment.
Comments
4 comments