The question is : Given a constraint and a graph, can I find an effective way to verify if I can satisfy it ?
I have graph/trees like theses :
I have constraints specific for each graph like "UUB ?" for A). The constraint ask if there is a path in the graph that pass through each of the letters specified in the constraint.
For the constraint "UUB ?" we want a path that pass through at least two U and one B. For the first graph "UUB ?" is possible with the path U->B->U. "RBB ?" is not possible because one of the B is on the same layer as R and you can't have both at the same time. I didn't found any effective method to check if a constraint can be satisfied..
Constraint precision : The constraint can't include more letters than the number of layers. The order of the constraint is not relevant (UBB is the same as BUB and BBU).
Graph precision : These graphs are oriented and each layer (column) is entirely connected to the next. They can have a maximum of 5 layers (columns) and 5 lines (U,B,R,G,W).
They can be represented like vectors (respectively):
A)
U 1 0 1
B 1 1 0
R 1 0 0
B)
U 1 0 1 0
B 1 1 0 1
R 1 0 1 0
G 1 1 0 0
C)
U 1 0 1 0 0
B 1 1 0 1 0
R 1 0 1 0 0
G 1 1 0 0 0
W 0 0 1 0 1
Precision on the algorithm : This verification will happen at each step of a Monte Carlo simulation of millions of steps, it need to be as fast as possible (this imply that I can't extract all paths and check if they are including the desired nodes..)
I found simple ways to check if a graph can't satisfy a constraint but the opposite seems tough (at least for me, I am far from a graph expert but I can understand key concepts).
Thank you for reading and for your help !
You can use a recursive generator function, building possible paths at each call, checking the letter frequencies of the running combination against those of the possible paths:
from collections import Counter
def check_paths(d, p, c=[], f={}):
if not d and any(all(j in i and i[j] == k for j, k in f.items()) for i in p):
yield c #produce a path if all the layers have been examined and all the letter frequencies from the path match a possible path
elif d:
for x in d[0]:
_c = Counter(c+[x])
#check if the current path, with the addition of a new letter, is below or equals the frequency threshold of any of the target paths
if any(all(j in i and i[j] >= k for j, k in _c.items()) for i in p):
yield from check_paths(d[1:], p, c=c+[x], f=_c)
l = ['U', 'B', 'R', 'G', 'W']
def path_exists(m, p):
v = [[j for j, k in zip(l, i) if k] for i in zip(*m)]
return next(check_paths(v, [Counter(i) for i in p]), None) is not None
print(path_exists([[1, 0, 1], [1, 1, 0], [1, 0, 0]], ['UUB', 'RBB', 'UBR']))
print(path_exists([[1, 0, 1, 0], [1, 1, 0, 1], [1, 0, 1, 0], [1, 1, 0, 0]], ['UBBB', 'RGRR', 'UBRG']))
print(path_exists([[1, 0, 1, 0, 0], [1, 1, 0, 1, 0], [1, 0, 1, 0, 0], [1, 1, 0, 0, 0], [0, 0, 1, 0, 1]], ['UBRGW', 'RGRGG', 'WWRUB']))
Output:
True
True
True