@@ -245,6 +245,7 @@ function graph_view(graph::DependencyGraph, context=nothing; diagnostics::Vector
245245 cyclic, cycle_vec = is_graph_cyclic (graph; warn= false )
246246 cycle_nodes = cyclic ? [_model_node_id (last (pair), process (first (pair))) for pair in cycle_vec] : String[]
247247 cycle_edges = cyclic ? _cycle_edge_ids (edges, cycle_nodes) : String[]
248+ edges = _mark_cycle_edges (edges, cycle_edges)
248249 scales = sort! (unique ([node. scale for node in nodes]); by= string)
249250 return _dependency_graph_view (nodes, edges, scales, cyclic, cycle_nodes, cycle_edges, diagnostics)
250251end
@@ -313,6 +314,33 @@ function _cycle_edge_ids(edges, cycle_nodes::Vector{String})
313314 return unique (ids)
314315end
315316
317+ function _mark_cycle_edges (edges, cycle_edge_ids:: Vector{String} )
318+ isempty (cycle_edge_ids) && return edges
319+ cycle_edge_id_set = Set (cycle_edge_ids)
320+ return [
321+ edge. id in cycle_edge_id_set ? _cycle_edge (edge) : edge
322+ for edge in edges
323+ ]
324+ end
325+
326+ function _cycle_edge (edge:: GraphEdge )
327+ diagnostics = copy (edge. diagnostics)
328+ push! (diagnostics, " Cycle edge: select this edge to break the cycle at the target input with `PreviousTimeStep`." )
329+ return GraphEdge (
330+ edge. id,
331+ edge. source,
332+ edge. target,
333+ edge. source_port,
334+ edge. target_port,
335+ edge. source_variable,
336+ edge. target_variable,
337+ :cycle_dependency ,
338+ edge. scale_relation,
339+ edge. label,
340+ unique (diagnostics),
341+ )
342+ end
343+
316344function _push_edge! (edges, edge_ids:: Set{String} , edge)
317345 edge. id in edge_ids && return edges
318346 push! (edges, edge)
@@ -945,10 +973,105 @@ function _graph_view_from_mapping_only(mapping::ModelMapping, diagnostics)
945973 ))
946974 end
947975 end
976+
977+ edges = GraphEdge[]
978+ edge_ids = Set {String} ()
979+ _add_inferred_mapping_edges! (edges, edge_ids, nodes)
980+ _add_spec_mapped_input_edges! (edges, edge_ids, nodes, mapping)
981+
948982 scales = sort! (unique ([node. scale for node in nodes]); by= string)
949- cyclic = any (occursin .(" Cyclic" , diagnostics))
950- cycle_nodes = cyclic ? [node. id for node in nodes] : String[]
951- return _dependency_graph_view (nodes, GraphEdge[], scales, cyclic, cycle_nodes, String[], diagnostics)
983+ detected_cycle, detected_cycle_nodes, cycle_edges = _cycle_from_rendered_edges (nodes, edges)
984+ cyclic = detected_cycle || any (occursin .(" Cyclic" , diagnostics))
985+ cycle_nodes = detected_cycle ? detected_cycle_nodes : (cyclic ? [node. id for node in nodes] : String[])
986+ edges = _mark_cycle_edges (edges, cycle_edges)
987+ return _dependency_graph_view (nodes, edges, scales, cyclic, cycle_nodes, cycle_edges, diagnostics)
988+ end
989+
990+ function _add_inferred_mapping_edges! (edges, edge_ids:: Set{String} , nodes)
991+ outputs = Dict {Tuple{Symbol,Symbol},Vector{Tuple{GraphNode,GraphPort}}} ()
992+ for node in nodes
993+ for output in node. outputs
994+ push! (get! (outputs, (node. scale, output. name), Tuple{GraphNode,GraphPort}[]), (node, output))
995+ end
996+ end
997+
998+ for node in nodes
999+ for input in node. inputs
1000+ for (producer_node, output) in get (outputs, (node. scale, input. name), Tuple{GraphNode,GraphPort}[])
1001+ producer_node. id == node. id && continue
1002+ edge = GraphEdge (
1003+ " edge:inferred:$(producer_node. id) :$(output. id) :$(node. id) :$(input. id) " ,
1004+ producer_node. id,
1005+ node. id,
1006+ output. id,
1007+ input. id,
1008+ output. name,
1009+ input. name,
1010+ :soft_dependency ,
1011+ :same_scale ,
1012+ string (input. name),
1013+ [" Inferred for visualization after dependency compilation failed." ],
1014+ )
1015+ _push_edge! (edges, edge_ids, edge)
1016+ end
1017+ end
1018+ end
1019+
1020+ return edges
1021+ end
1022+
1023+ function _cycle_from_rendered_edges (nodes, edges)
1024+ node_ids = Set (node. id for node in nodes)
1025+ outgoing = Dict {String,Vector{GraphEdge}} (id => GraphEdge[] for id in node_ids)
1026+ for edge in edges
1027+ haskey (outgoing, edge. source) || continue
1028+ edge. target in node_ids || continue
1029+ push! (outgoing[edge. source], edge)
1030+ end
1031+
1032+ visited = Set {String} ()
1033+ stack = Set {String} ()
1034+ parent_node = Dict {String,String} ()
1035+ parent_edge = Dict {String,String} ()
1036+
1037+ function visit (node_id:: String )
1038+ push! (visited, node_id)
1039+ push! (stack, node_id)
1040+
1041+ for edge in get (outgoing, node_id, GraphEdge[])
1042+ target = edge. target
1043+ if ! (target in visited)
1044+ parent_node[target] = node_id
1045+ parent_edge[target] = edge. id
1046+ found, cycle_nodes, cycle_edges = visit (target)
1047+ found && return true , cycle_nodes, cycle_edges
1048+ elseif target in stack
1049+ cycle_nodes = String[target]
1050+ cycle_edges = String[edge. id]
1051+ cursor = node_id
1052+ while cursor != target && haskey (parent_node, cursor)
1053+ push! (cycle_nodes, cursor)
1054+ push! (cycle_edges, parent_edge[cursor])
1055+ cursor = parent_node[cursor]
1056+ end
1057+ push! (cycle_nodes, target)
1058+ reverse! (cycle_nodes)
1059+ reverse! (cycle_edges)
1060+ return true , cycle_nodes, unique (cycle_edges)
1061+ end
1062+ end
1063+
1064+ delete! (stack, node_id)
1065+ return false , String[], String[]
1066+ end
1067+
1068+ for node in nodes
1069+ node. id in visited && continue
1070+ found, cycle_nodes, cycle_edges = visit (node. id)
1071+ found && return true , cycle_nodes, cycle_edges
1072+ end
1073+
1074+ return false , String[], String[]
9521075end
9531076
9541077function _graph_node (node:: AbstractDependencyNode , id:: String , context, node_ids)
0 commit comments