8 from networkx.readwrite
import json_graph
13 import matplotlib.pylab
as plt
14 from itertools
import islice
18 """Header function to create a graph based on a DFN
25 Option for what graph representation of the DFN is requested. Currently supported are fracture, intersection, and bipartitie
27 Name of inflow boundary (connect to source)
29 Name of outflow boundary (connect to target)
41 if graph_type ==
"fracture":
43 elif graph_type ==
"intersection":
45 elif graph_type ==
"bipartite":
48 print(
"ERROR! Unknown graph type")
55 topology_file="connectivity.dat",
56 fracture_info="fracture_info.dat"):
57 """ Create a graph based on topology of network. Fractures
58 are represented as nodes and if two fractures intersect
59 there is an edge between them in the graph.
61 Source and Target node are added to the graph.
66 Name of inflow boundary (connect to source)
68 Name of outflow boundary (connect to target)
69 topology_file : string
70 Name of adjacency matrix file for a DFN default=connectivity.dat
72 filename for fracture information
77 NetworkX Graph where vertices in the graph correspond to fractures and edges indicated two fractures intersect
82 print(
"--> Loading Graph based on topology in " + topology_file)
83 G = nx.Graph(representation=
"fracture")
84 with open(topology_file,
"r")
as infile:
85 for i, line
in enumerate(infile):
86 conn = [int(n)
for n
in line.split()]
90 inflow_filename = inflow +
".dat"
91 outflow_filename = outflow +
".dat"
92 inflow = np.genfromtxt(inflow_filename).astype(int)
93 outflow = np.genfromtxt(outflow_filename).astype(int)
99 inflow = [inflow.tolist()]
103 outflow = list(outflow)
105 outflow = [outflow.tolist()]
109 G.add_edges_from(zip([
's'] * (len(inflow)), inflow))
110 G.add_edges_from(zip(outflow, [
't'] * (len(outflow))))
112 print(
"--> Graph loaded")
117 """Determines boundary index in intersections_list.dat from name
122 Boundary condition name
127 integer indexing of cube faces
147 return bc_dict[bc_name]
149 error =
"Unknown boundary condition: %s\nExiting\n" % bc
150 sys.stderr.write(error)
156 intersection_file="intersection_list.dat",
157 fracture_info="fracture_info.dat"):
158 """ Create a graph based on topology of network.
159 Edges are represented as nodes and if two intersections
160 are on the same fracture, there is an edge between them in the graph.
162 Source and Target node are added to the graph.
167 Name of inflow boundary
169 Name of outflow boundary
170 intersection_file : string
171 File containing intersection information
173 fracture 1, fracture 2, x center, y center, z center, intersection length
176 filename for fracture information
180 Vertices have attributes x,y,z location and length. Edges has attribute length
184 Aperture and Perm on edges can be added using add_app and add_perm functions
187 print(
"Creating Graph Based on DFN")
188 print(
"Intersections being mapped to nodes and fractures to edges")
192 f = open(intersection_file)
196 frac_edges.append(line.rstrip().split())
200 G = nx.Graph(representation=
"intersection")
204 for i
in range(len(frac_edges)):
205 f1 = int(frac_edges[i][0])
207 if frac_edges[i][1] ==
's' or frac_edges[i][1] ==
't':
208 f2 = frac_edges[i][1]
209 elif int(frac_edges[i][1]) > 0:
210 f2 = int(frac_edges[i][1])
211 elif int(frac_edges[i][1]) == inflow_index:
213 elif int(frac_edges[i][1]) == outflow_index:
215 elif int(frac_edges[i][1]) < 0:
220 G.add_node(i, frac=(f1, f2))
222 G.nodes[i][
'x'] = float(frac_edges[i][2])
223 G.nodes[i][
'y'] = float(frac_edges[i][3])
224 G.nodes[i][
'z'] = float(frac_edges[i][4])
225 G.nodes[i][
'length'] = float(frac_edges[i][5])
227 nodes = list(nx.nodes(G))
228 f1 = nx.get_node_attributes(G,
'frac')
235 x = e.intersection(tmp)
242 if x !=
's' and x !=
't':
251 distance = np.sqrt((xi - xj)**2 + (yi - yj)**2 +
253 G.add_edge(i, j, frac=x, length=distance)
261 if len(e.intersection(set(
's'))) > 0
or len(e.intersection(set(
263 G.add_edge(i,
's', frac=
's', length=0.0)
264 if len(e.intersection(set(
't'))) > 0
or len(e.intersection(set(
266 G.add_edge(i,
't', frac=
't', length=0.0)
268 print(
"Graph Construction Complete")
274 intersection_list='intersection_list.dat',
275 fracture_info='fracture_info.dat'):
276 """Creates a bipartite graph of the DFN.
277 Nodes are in two sets, fractures and intersections, with edges connecting them.
283 name of inflow boundary
285 name of outflow boundary
286 intersection_list: str
287 filename of intersections generated from DFN
289 filename for fracture information
297 See Hyman et al. 2018 "Identifying Backbones in Three-Dimensional Discrete Fracture Networks: A Bipartite Graph-Based Approach" SIAM Multiscale Modeling and Simulation for more details
300 print(
"--> Creating Bipartite Graph")
304 from itertools
import product
306 def intersection_id_generator(length=5):
307 chars =
'abcdefghijklmnopqrstuvwxyz'
308 for p
in product(chars, repeat=length):
311 B = nx.Graph(representation=
"bipartite")
314 B.intersections = set()
315 intersection_id = intersection_id_generator()
320 with open(intersection_list)
as f:
321 header = f.readline()
322 data = f.read().strip()
323 for line
in data.split(
'\n'):
324 fracture1, fracture2, x, y, z, length = line.split(
' ')
325 fracture1 = int(fracture1)
326 fracture2 = int(fracture2)
328 if fracture2 == inflow_index:
330 elif fracture2 == outflow_index:
332 intersection = next(intersection_id)
334 B.add_node(intersection,
338 length=float(length))
339 B.intersections.add(intersection)
341 B.add_edge(intersection, fracture1, frac=fracture1)
342 B.fractures.add(fracture1)
343 if fracture2 > 0
or fracture2 ==
's' or fracture2 ==
't':
344 B.add_edge(intersection, fracture2, frac=fracture2)
345 B.fractures.add(fracture2)
348 B.add_edge(
'intersection_s',
's')
349 B.add_edge(
'intersection_t',
't')
352 with open(fracture_info)
as f:
353 header = f.readline()
354 data = f.read().strip()
355 for fracture, line
in enumerate(data.split(
'\n'), 1):
356 c, perm, aperture = line.split(
' ')
357 B.nodes[fracture][
'perm'] = float(perm)
358 B.nodes[fracture][
'aperture'] = float(aperture)
360 print(
"--> Complete")
366 """Returns the k shortest paths in a graph
371 NetworkX Graph based on a DFN
373 list of integers corresponding to fracture numbers
374 remove_old_source: bool
375 remove old source from the graph
383 bipartite graph not supported
387 if not type(source) == list:
390 print(
"--> Adding new source connections")
391 print(
"--> Warning old source will be removed!!!")
393 if G.graph[
'representation'] ==
"fracture":
399 G.nodes[
's'][
'perm'] = 1.0
400 G.nodes[
's'][
'iperm'] = 1.0
405 elif G.graph[
'representation'] ==
"intersection":
407 nodes_to_remove = [
's']
408 for u, d
in G.nodes(data=
True):
409 if u !=
's' and u !=
't':
413 nodes_to_remove.append(u)
415 print(
"--> Removing nodes: ", nodes_to_remove)
416 G.remove_nodes_from(nodes_to_remove)
420 for u, d
in G.nodes(data=
True):
421 if u !=
's' and u !=
't':
426 "--> Adding edge between {0} and new source / fracture {1}"
428 G.add_edge(u,
's', frac=f1, length=0., perm=1., iperm=1.)
431 "--> Adding edge between {0} and new source / fracture {1}"
433 G.add_edge(u,
's', frac=f2, length=0., perm=1., iperm=1.)
435 elif G.graph[
'representation'] ==
"bipartite":
436 print(
"--> Not supported for bipartite graph")
437 print(
"--> Returning unchanged graph")
442 """Returns the k shortest paths in a graph
447 NetworkX Graph based on a DFN
449 list of integers corresponding to fracture numbers
456 bipartite graph not supported
460 if not type(target) == list:
463 print(
"--> Adding new target connections")
464 print(
"--> Warning old target will be removed!!!")
466 if G.graph[
'representation'] ==
"fracture":
472 G.nodes[
't'][
'perm'] = 1.0
473 G.nodes[
't'][
'iperm'] = 1.0
478 elif G.graph[
'representation'] ==
"intersection":
480 nodes_to_remove = [
't']
481 for u, d
in G.nodes(data=
True):
482 if u !=
's' and u !=
't':
486 nodes_to_remove.append(u)
488 print(
"--> Removing nodes: ", nodes_to_remove)
489 G.remove_nodes_from(nodes_to_remove)
493 for u, d
in G.nodes(data=
True):
494 if u !=
's' and u !=
't':
499 "--> Adding edge between {0} and new target / fracture {1}"
501 G.add_edge(u,
't', frac=f1, length=0., perm=1., iperm=1.)
504 "--> Adding edge between {0} and new target / fracture {1}"
506 G.add_edge(u,
't', frac=f2, length=0., perm=1., iperm=1.)
508 elif G.graph[
'representation'] ==
"bipartite":
509 print(
"--> Not supported for bipartite graph")
510 print(
"--> Returning unchanged graph")
515 """Returns the k shortest paths in a graph
520 NetworkX Graph based on a DFN
522 Number of requested paths
528 Edge weight used for finding the shortest path
532 paths : sets of nodes
533 a list of lists of nodes in the k shortest paths
537 Edge weights must be numerical and non-negative
540 islice(nx.shortest_simple_paths(G, source, target, weight=weight), k))
544 """Returns the subgraph made up of the k shortest paths in a graph
549 NetworkX Graph based on a DFN
551 Number of requested paths
557 Edge weight used for finding the shortest path
562 Subgraph of G made up of the k shortest paths
566 See Hyman et al. 2017 "Predictions of first passage times in sparse discrete fracture networks using graph-based reductions" Physical Review E for more details
569 print(
"\n--> Determining %d shortest paths in the network" % k)
573 k_shortest |= set(path)
574 k_shortest.remove(
's')
575 k_shortest.remove(
't')
576 path_nodes = sorted(list(k_shortest))
577 path_nodes.append(
's')
578 path_nodes.append(
't')
579 nodes = list(G.nodes())
580 secondary = list(set(nodes) - set(path_nodes))
584 print(
"--> Complete\n")
588 """Removes source and target from list of nodes, useful for dumping subnetworks to file for remeshing
593 List of nodes in the graph
601 List of nodes with source and target nodes removed
607 for node
in [source, target]:
616 """Write fracture numbers assocaited with the graph G out into an ASCII file inputs
623 NetworkX Graph based on the DFN
634 if G.graph[
'representation'] ==
"fracture":
635 nodes = list(G.nodes())
636 elif G.graph[
'representation'] ==
"intersection":
638 for u, v, d
in G.edges(data=
True):
639 nodes.append(G[u][v][
'frac'])
640 nodes = list(set(nodes))
641 elif G.graph[
'representation'] ==
"bipartite":
643 for u, v, d
in G.edges(data=
True):
644 nodes.append(G[u][v][
'frac'])
645 nodes = list(set(nodes))
648 fractures = [int(i)
for i
in nodes]
649 fractures = sorted(fractures)
650 print(
"--> Dumping %s" % filename)
651 np.savetxt(filename, fractures, fmt=
"%d")
656 Greedy Algorithm to find edge disjoint subgraph from s to t.
657 See Hyman et al. 2018 SIAM MMS
664 NetworkX Graph based on the DFN
670 Edge weight used for finding the shortest path
672 Number of edge disjoint paths requested
677 Subgraph of G made up of the k shortest of all edge-disjoint paths from source to target
681 1. Edge weights must be numerical and non-negative.
682 2. See Hyman et al. 2018 "Identifying Backbones in Three-Dimensional Discrete Fracture Networks: A Bipartite Graph-Based Approach" SIAM Multiscale Modeling and Simulation for more details
685 print(
"--> Identifying edge disjoint paths")
686 if G.graph[
'representation'] !=
"intersection":
688 "--> ERROR!!! Wrong type of DFN graph representation\nRepresentation must be intersection\nReturning Empty Graph\n"
693 Hprime.graph[
'representation'] = G.graph[
'representation']
697 min_cut = len(nx.minimum_edge_cut(G,
's',
't'))
698 if k ==
'' or k > min_cut:
701 while nx.has_path(Gprime, source, target):
702 path = nx.shortest_path(Gprime, source, target, weight=weight)
703 H = Gprime.subgraph(path)
704 Hprime.add_edges_from(H.edges(data=
True))
705 Gprime.remove_edges_from(list(H.edges()))
710 print(
"--> Complete")
714 def plot_graph(self, G, source='s', target='t', output_name="dfn_graph"):
715 """ Create a png of a graph with source nodes colored blue, target red, and all over nodes black
720 NetworkX Graph based on the DFN
726 Name of output file (no .png)
733 Image is written to output_name.png
736 print(
"\n--> Plotting Graph")
737 print(
"--> Output file: %s.png" % output_name)
739 pos = nx.spring_layout(G)
740 nodes = list(G.nodes)
742 nx.draw_networkx_nodes(G,
748 nx.draw_networkx_nodes(G,
754 nx.draw_networkx_nodes(G,
761 nx.draw_networkx_edges(G, pos, width=1.0, alpha=0.5)
764 plt.savefig(output_name +
".png")
766 print(
"--> Plotting Graph Complete\n")
770 """Write graph out in json format
777 NetworkX Graph based on the DFN
779 Name of output file (no .json)
788 print(
"--> Dumping Graph into file: " + name +
".json")
789 jsondata = json_graph.node_link_data(G)
790 with open(name +
'.json',
'w')
as fp:
791 json.dump(jsondata, fp)
792 print(
"--> Complete")
796 """ Read in graph from json format
803 Name of input file (no .json)
808 NetworkX Graph based on the DFN
811 print(
"Loading Graph in file: " + name +
".json")
812 fp = open(name +
'.json')
813 G = json_graph.node_link_graph(json.load(fp))
818 def add_perm(G, fracture_info="fracture_info.dat"):
819 """ Add fracture permeability to Graph. If Graph representation is
820 fracture, then permeability is a node attribute. If graph representation
821 is intersection, then permeability is an edge attribute
827 NetworkX Graph based on the DFN
830 filename for fracture information
839 perm = np.genfromtxt(fracture_info, skip_header=1)[:, 1]
840 if G.graph[
'representation'] ==
"fracture":
841 nodes = list(nx.nodes(G))
843 if n !=
's' and n !=
't':
844 G.nodes[n][
'perm'] = perm[n - 1]
845 G.nodes[n][
'iperm'] = 1.0 / perm[n - 1]
847 G.nodes[n][
'perm'] = 1.0
848 G.nodes[n][
'iperm'] = 1.0
850 elif G.graph[
'representation'] ==
"intersection":
851 edges = list(nx.edges(G))
854 if x !=
's' and x !=
't':
855 G[u][v][
'perm'] = perm[x - 1]
856 G[u][v][
'iperm'] = 1.0 / perm[x - 1]
858 G[u][v][
'perm'] = 1.0
859 G[u][v][
'iperm'] = 1.0
860 elif G.graph[
'representation'] ==
"bipartite":
862 with open(fracture_info)
as f:
863 header = f.readline()
864 data = f.read().strip()
865 for fracture, line
in enumerate(data.split(
'\n'), 1):
866 c, perm, aperture = line.split(
' ')
867 G.nodes[fracture][
'perm'] = float(perm)
868 G.nodes[fracture][
'iperm'] = 1.0 / float(perm)
869 G.nodes[fracture][
'aperture'] = float(aperture)
872 def add_area(G, fracture_info="fracture_info.dat"):
873 ''' Read Fracture aperture from fracture_info.dat and
874 load on the edges in the graph. Graph must be intersection to node
882 filename for fracture information
889 aperture = np.genfromtxt(fracture_info, skip_header=1)[:, 2]
890 edges = list(nx.edges(G))
892 x = G.edges[u, v][
'frac']
893 if x !=
's' and x !=
't':
895 v][
'area'] = aperture[x - 1] * (G.nodes[u][
'length'] +
896 G.nodes[v][
'length']) / 2.0
898 G.edges[u, v][
'area'] = 1.0
903 '''Compute weight w = K*A/L associated with each edge
913 edges = list(nx.edges(G))
915 if G.edges[u, v][
'length'] > 0:
916 G.edges[u, v][
'weight'] = G.edges[u, v][
'perm'] * G.edges[
917 u, v][
'area'] / G.edges[u, v][
'length']
def add_fracture_source(self, G, source)
def k_shortest_paths_backbone(self, G, k, source='s', target='t', weight=None)
def create_graph(self, graph_type, inflow, outflow)
def dump_json_graph(self, G, name)
def k_shortest_paths(G, k, source, target, weight)
def dump_fractures(self, G, filename)
def pull_source_and_target(nodes, source='s', target='t')
def add_perm(G, fracture_info="fracture_info.dat")
def create_intersection_graph(inflow, outflow, intersection_file="intersection_list.dat", fracture_info="fracture_info.dat")
def add_area(G, fracture_info="fracture_info.dat")
def create_fracture_graph(inflow, outflow, topology_file="connectivity.dat", fracture_info="fracture_info.dat")
def load_json_graph(self, name)
def boundary_index(bc_name)
def plot_graph(self, G, source='s', target='t', output_name="dfn_graph")
def create_bipartite_graph(inflow, outflow, intersection_list='intersection_list.dat', fracture_info='fracture_info.dat')
def greedy_edge_disjoint(self, G, source='s', target='t', weight='None', k='')
def add_fracture_target(self, G, target)