private static List <WorkflowComponent> ClusterReversedModel(string name, List <Data> allData, List <WorkflowComponent> matchedComponents, List <WorkflowComponent> reversedComponents, ReversalMode mode, List <WorkflowComponent> components) { //// Identify Reversed Components //foreach (var component in matchedComponents) // if (component is IReversableWorkflow reversable && reversable.ReversedInputs.Count > 0) // reversedComponents.Add(component); // 3.1. Graph only with reversed links of non-reversible variables only, both forward and backward (can be thought as undirected) var reversedLinksGraph = new Graph(); foreach (Data data in reversedComponents.GetAllData()) { reversedLinksGraph.AddVertex(data.Id); } foreach (WorkflowComponent component in reversedComponents) { AddComponentAndEdgesBoth(reversedLinksGraph, component, mode); } // 3.2. Forward graph - To trace forward dependencies var forwardGraph = new Graph(); foreach (Data data in allData) { forwardGraph.AddVertex(data.Id); } foreach (WorkflowComponent component in matchedComponents) { AddComponentAndEdgesForward(forwardGraph, component); } //forwardGraph = GraphBuilder.FromTwoSets(matchedComponents, allData, name1: c => CS(c.Id), from1to2: c => c.ModelDataOutputs, to1from2: c => c.ModelDataInputs); // 3.3. Reversed graph - To trace backward dependencies var reversedGraph = new Graph(); foreach (Data data in allData) { reversedGraph.AddVertex(data.Id); } foreach (WorkflowComponent component in matchedComponents) { AddComponentAndEdgesBackWard(reversedGraph, component); } // 3.4 Group reversed components var visitedComponents = new HashSet <string>(); var reversedComponentsHash = new HashSet <string>(reversedComponents.Select(c => CS(c.Id))); var componentsHash = new HashSet <string>(matchedComponents.Select(c => CS(c.Id))); var componentsUnderStudy = new Stack <string>(); var groups = new List <HashSet <string> >(); HashSet <string> groupForwardHash = null; // Used to reduce unnecesary visits, if node is visited for one component in the group it shouldn't be visited more HashSet <string> groupBackwardHash = null; // Used to reduce unnecesary visits, if node is visited for one component in the group it shouldn't be visited more foreach (WorkflowComponent component in reversedComponents) { // 3.4.1. If the component has not been studied start studie and prepare a gropup for the component if (!visitedComponents.Contains(CS(component.Id))) { groups.Add(new HashSet <string>()); groupForwardHash = new HashSet <string>(); groupBackwardHash = new HashSet <string>(); componentsUnderStudy.Push(CS(component.Id)); } while (componentsUnderStudy.Count > 0) { // 3.4.2. Get the component under study to investigate its dependencies string componentUnderStudy = componentsUnderStudy.Pop(); if (visitedComponents.Contains(componentUnderStudy)) { continue; } // 3.4.3. Group with adajacent reversed components HashSet <string> adjacent = reversedLinksGraph.DepthFirstSearch(componentUnderStudy); foreach (string adj in adjacent) { visitedComponents.Add(adj); } // If there is more than one component joint by non-reversible variables if (adjacent.Where(a => reversedComponentsHash.Contains(a)).Count() > 1) { // 3.4.4. Extend with others dependencies HashSet <string> affectedForward = forwardGraph.DepthFirstSearch(adjacent, groupForwardHash); HashSet <string> affectedBackward = reversedGraph.DepthFirstSearch(adjacent, groupBackwardHash); affectedForward.IntersectWith(affectedBackward); adjacent.UnionWith(affectedForward); } // 3.4.5. If dependencies include a new reversed model push to the stack to be studied foreach (string adj in adjacent) { // If a component is a reversed component and hasn't been added yet if (!visitedComponents.Contains(adj) && reversedComponentsHash.Contains(adj)) { componentsUnderStudy.Push(adj); } else { visitedComponents.Add(adj); } // Add to the group of dependent model from the first reversed model under study if (componentsHash.Contains(adj)) { groups.Last().Add(adj); } } } } // 3.5 Create Globally Reversed Workflows for each group var globalReversedComponents = new List <WorkflowComponent>(); var componentsDict = matchedComponents.ToDictionary(c => CS(c.Id)); foreach (HashSet <string> group in groups) { if (group.Count > 1) { var groupComponents = group.Select(c => componentsDict[c]).ToList(); // new List<WorkflowComponent>(); List <Data> groupData = groupComponents.GetAllData(); var(groupInputs, groupOutputs, _) = groupComponents.GetInputsOutputsStatus(groupData); GlobalReversalMode globalMode = (mode == ReversalMode.GroupNonReversibleOnly) ? GlobalReversalMode.ReverseModelsWhenReversibleVariables : GlobalReversalMode.NoReversedModels; string globalName = WorkflowGlobal.GetGlobalWorkflowName(name, components, groupComponents); Workflow globalWorkflow = ScheduleWorkflowGlobal(globalName, "", groupInputs, groupOutputs, groupComponents, globalMode); globalReversedComponents.Add(globalWorkflow); } else { // If the group consists of one component only, it is already a reversed model and does not have any unfeasible reversal globalReversedComponents.Add(componentsDict[group.First()]); } } // 3.6. Add the rest of components globalReversedComponents.AddRange(matchedComponents.Where(c => !visitedComponents.Contains(CS(c.Id)))); return(globalReversedComponents); }
public static Workflow ScheduleWorkflowGlobal(string name, string description, List <Data> inputs, List <Data> outputs, List <WorkflowComponent> components, GlobalReversalMode mode) { List <WorkflowComponent> workflowComponents = components.GetAllComponents(); var allData = inputs.Concat(outputs).ToList(); // 0. Samity checks, check is not more than 1 output if (mode == GlobalReversalMode.NoReversedModels) { if (!AllSanityCheck(workflowComponents)) { mode = GlobalReversalMode.ReverseModelsWhenReversibleVariables; } } else if (mode == GlobalReversalMode.Global) { if (!AllSanityCheck(workflowComponents)) { throw new ArgumentException($"A default workflow does not exist. Therefore the gloabl workflow cannot be created"); } } if (mode == GlobalReversalMode.ReverseModelsWhenReversibleVariables) { NonReversibleSanityCheck(workflowComponents); } // 1. Find Model-Variable matching. Which variables are inputs, and wich are outputs for each model MatchingDictionary outputsDict = MatchModelsWithVariablesGlobal(allData, components, mode); // 2. Determine which models are reversed, and create reversed Workflows (Model or Global) to cater for them ReverseComponents(name, outputsDict, workflowComponents, out List <WorkflowComponent> matchedComponents, out List <WorkflowComponent> reversedComponents); // 4. Cluster Strongly Connected Components to get an acyclic graph List <WorkflowComponent> clusteredComponents = ClusterStronglyConnectedComponents(name, allData, matchedComponents); // 5. Schedule the acyclic graph to get the final order of the components List <WorkflowComponent> scheduledComponents = ScheduleAcyclic(allData, clusteredComponents); Workflow workflow = null; if (clusteredComponents.Count < matchedComponents.Count) { workflow = ScheduleWorkflowSCC(name.Split(':').First(), description, inputs, outputs, workflowComponents, matchedComponents); } else { workflow = new WorkflowGlobal(name, description, inputs, outputs, components, scheduledComponents, DeafaultSolvers(), mode != GlobalReversalMode.Global, mode.ToString()); } workflow.DependencyAnalysis = new GraphBasedDependencyAnalysis(matchedComponents); return(workflow); }
private static MatchingDictionary MatchModelsWithVariablesGlobal(List <Data> allData, List <WorkflowComponent> components, GlobalReversalMode mode) { if (mode == GlobalReversalMode.NoReversedModels || mode == GlobalReversalMode.Global) { List <WorkflowComponent> originalComponents = components.GetAllComponents(); Dictionary <string, IOStatus> status = originalComponents.GetInputsOutputsStatus(allData, out List <Data> inputs, out List <Data> outputs); foreach (string key in status.Keys) { if (status[key] == IOStatus.Conflict) { throw new ArgumentException($"The following varibale \"{key}\" is of non-reversible type and output of more than one model. Therefore the workflow cannot be created"); } } var inputsHash = new HashSet <string>(inputs.Select(d => d.Id)); var bipartite = new BipartiteGraph(); Primes.Reset(); var completeMatching = new MatchingDictionary(originalComponents.Select(c => c.Id)); // 1.1 Add Nodes for the Variables // Filter-out the selected inputs and outputs, as they don't belong to the bipartite graph foreach (Data data in allData.Where(d => !inputsHash.Contains(d.Id))) { bipartite.AddNode(data.Id, GraphNode.Type.Type1); } // 1.2 Add Nodes for the Models, and edges between Variables and Nodes foreach (WorkflowComponent component in originalComponents) { completeMatching.Add(component.Id, new HashSet <string>()); bipartite.AddNode(CS(component.Id), GraphNode.Type.Type2); GraphNode modelNode = bipartite.GetNode(CS(component.Id)); // Filter-out the selected inputs, as they don't belong to the bipartite graph foreach (Data data in component.ModelDataInputs.Where(d => !inputsHash.Contains(d.Id))) { bipartite.AddDirectedEdge(data.Id, modelNode, Primes.Next(), 1); } // Filter-out the selected inputs, as they don't belong to the bipartite graph foreach (Data data in component.ModelDataOutputs.Where(d => !inputsHash.Contains(d.Id))) { bipartite.AddDirectedEdge(data.Id, modelNode, Primes.Next(), 0); } // How many output the model need if (component.ModelDataOutputs.Count > 1) { bipartite.modelAndItsOutputs.Add(CS(component.Id), component.ModelDataOutputs.Count); //signle output models are not stored } } // 1.3 Associate matchings to each model, maping models to set of outputs var matches = new MaximumMatchings2(bipartite, true); if (matches.OverConstrainedModels.Count > 0) { throw new ArgumentException($"The inputs conbination is not valid as the following models are overconstraint:\r\n\t" + matches.OverConstrainedModels.Aggregate((total, last) => total += "\r\n\t" + last) + "\r\n"); } Matching matching = matches.OrderedFilteredMaximumMatchings.FirstOrDefault() ?? throw new NullReferenceException("NO suitable matchings were found"); completeMatching.CompleteWithMatching(matching); return(completeMatching); } else { Dictionary <string, IOStatus> status = components.GetInputsOutputsStatus(allData, out List <Data> inputs, out List <Data> outputs); var inputsHash = new HashSet <string>(inputs.Select(d => d.Id)); var outputsHash = new HashSet <string>(); List <WorkflowComponent> originalComponents = components.GetAllComponents(); Dictionary <string, IOStatus> originalStatus = originalComponents.GetDataStatus(allData); var nonReversibleData = allData.Where(d => !(d is DoubleData)).ToList(); Dictionary <string, IOStatus> nonReversibleStatus = components.GetDataStatus(nonReversibleData); foreach (string data in nonReversibleStatus.Keys) { if (nonReversibleStatus[data] == IOStatus.Input) { inputsHash.Add(data); // Should be already there } else if (nonReversibleStatus[data] == IOStatus.Output || nonReversibleStatus[data] == IOStatus.Both) { outputsHash.Add(data); } } var reversedInputsHash = new HashSet <string>(); var reversedOutputsHash = new HashSet <string>(); foreach (WorkflowComponent component in components) { if (component is IReversableWorkflow rw) { foreach (Data data in rw.ReversedInputs) { reversedInputsHash.Add(data.Id); } foreach (Data data in rw.ReversedOutputs) { reversedOutputsHash.Add(data.Id); } IEnumerable <Data> NonReversableReversedInputs = rw.ReversedInputs.Where(d => !(d is DoubleData)); IEnumerable <Data> NonReversableReversedOutputs = rw.ReversedOutputs.Where(d => !(d is DoubleData)); // If the model id the upper end of a reversal throug non-reversible variables. int difference = NonReversableReversedOutputs.Count() - NonReversableReversedInputs.Count(); while (difference > 0) { // Assign as many reversible variable to the inputs as reversals end in the component IEnumerable <Data> reversableReversedInputs = rw.ReversedInputs.Where(d => d is DoubleData); foreach (Data data in reversableReversedInputs.Take(difference)) { inputsHash.Add(data.Id); } } } } // Relax one input per reversal -> lowerEndOfReversal might have more elements than non-reversible reversal, but those should be already not in inputHash IEnumerable <string> lowerEndOfReversals = reversedOutputsHash.Except(reversedInputsHash); inputsHash.ExceptWith(lowerEndOfReversals); var bipartite = new BipartiteGraph(); Primes.Reset(); var completeMatching = new MatchingDictionary(originalComponents.Select(c => c.Id)); // 1.1 Add Nodes for the Variables // Filter-out the selected inputs and outputs, as they don't belong to the bipartite graph foreach (Data data in allData.Where(d => !inputsHash.Contains(d.Id) && !outputsHash.Contains(d.Id))) { bipartite.AddNode(data.Id, GraphNode.Type.Type1); } // 1.2 Add Nodes for the Models, and edges between Variables and Nodes foreach (WorkflowComponent component in originalComponents) { completeMatching.Add(component.Id, new HashSet <string>()); // if the component has all its inputs and outputs determined do not add to the graph bool addModel = component.ModelDataOutputs.Count > component.ModelDataOutputs.Where(d => inputsHash.Count > -1 && outputsHash.Contains(d.Id)).Count(); if (addModel) { bipartite.AddNode(component.Id, GraphNode.Type.Type2); } GraphNode modelNode = bipartite.GetNode(component.Id); int uniqueNonReversibleOutputs = 0; // Filter-out the selected inputs, as they don't belong to the bipartite graph foreach (Data data in component.ModelDataInputs.Where(d => !inputsHash.Contains(d.Id) && !outputsHash.Contains(d.Id))) { bipartite.AddDirectedEdge(data.Id, modelNode, Primes.Next(), 1); } // Filter-out the selected inputs, as they don't belong to the bipartite graph foreach (Data data in component.ModelDataOutputs.Where(d => !inputsHash.Contains(d.Id))) { if (outputsHash.Contains(data.Id)) { uniqueNonReversibleOutputs++; completeMatching[component.Id].Add(data.Id); } else { bipartite.AddDirectedEdge(data.Id, modelNode, Primes.Next(), 0); } } // How many output the model need if (component.ModelDataOutputs.Count - uniqueNonReversibleOutputs > 1 && addModel) { bipartite.modelAndItsOutputs.Add(component.Id, component.ModelDataOutputs.Count - uniqueNonReversibleOutputs); //signle output models are not stored } } // 1.3 Associate matchings to each model, maping models to set of outputs var matches = new MaximumMatchings2(bipartite, true); if (matches.OverConstrainedModels.Count > 0) { throw new ArgumentException($"The inputs conbination is not valid as the following models are overconstraint:\r\n\t" + matches.OverConstrainedModels.Aggregate((total, last) => total += "\r\n\t" + last) + "\r\n"); } Matching matching = matches.OrderedFilteredMaximumMatchings.FirstOrDefault() ?? throw new NullReferenceException("NO suitable matchings were found"); completeMatching.CompleteWithMatching(matching); return(completeMatching); } }