internal override (NodeDataMap MfpIn, NodeDataMap MfpOut) _Solve(Func <FlowNode, ISet <VariableDescriptor>, ISet <VariableDescriptor> > transferFunction)
            {
                // Custom solver is necessary because of the side-effects of the merge function
                var worklist    = new Queue <FlowNode>(Nodes);
                var mfpIn       = Nodes.ToDictionary(node => node, node => _emptySet);
                var mfpOut      = Nodes.ToDictionary(node => node, node => _emptySet);
                var transitions = _ConstructTransitions();

                while (worklist.Count > 0)
                {
                    var node        = worklist.Dequeue();
                    var merged      = ExtremalNodes.Contains(node) ? ExtremalValues : _Merge(transitions.Predecessors[node].SelectMany(predecessor => mfpOut[predecessor]).ToImmutableHashSet());
                    var transferred = transferFunction(node, merged);

                    // Merging may have changed the data, thus it is always updated (not necessary to check for changes).
                    // But only for cases wherethe data after transfer changed make it necessary to notify the successors about the change.
                    mfpIn[node] = merged;
                    if (!IncreasesAnalysisKnowledge(transferred, mfpOut[node]))
                    {
                        continue;
                    }

                    mfpOut[node] = transferred;
                    worklist.EnqueueAll(transitions.Successors[node]);
                }

                return(mfpIn, mfpOut);
            }
        internal virtual (IDictionary <FlowNode, ISet <TData> > MfpIn, IDictionary <FlowNode, ISet <TData> > MfpOut) _Solve(Func <FlowNode, ISet <TData>, ISet <TData> > transferFunction)
        {
            var worklist = new Queue <FlowTransition>(Flow);
            var analysis = Nodes.ToDictionary(node => node, node => ExtremalNodes.Contains(node) ? ExtremalValues : LeastValues);

            while (worklist.Count > 0)
            {
                var transition  = worklist.Dequeue();
                var transferred = transferFunction(transition.First, analysis[transition.First]);

                if (!IncreasesAnalysisKnowledge(transferred, analysis[transition.Second]))
                {
                    continue;
                }

                analysis[transition.Second] = Merge(transferred, analysis[transition.Second]);
                //worklist.EnqueueAll(Flow.Where(flow => flow.First.Equals(transition.Second)));
                worklist.EnqueueAll(Flow.Where(flow => flow.First == transition.Second));
            }

            return(analysis, analysis.ToDictionary(entry => entry.Key, entry => Transfer(entry.Key, entry.Value)));
        }