blob: df0d882e7241d7780242a4b893238dca69389bd7 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math' show Random;
import 'evaluation_order.dart';
import 'node.dart';
// Computational graph.
//
// This framework allows users to first specify a set of operations, and then run
// those operations in different orders. ComputationalGraph is used to specify
// the order between those operations, and to get correct orders of execution.
//
class ComputationalGraph {
final List<Node> nodes = <Node>[];
final List<List<int>> _allChoices = <List<int>>[];
final Random random = Random(0);
void addNode(Node v) {
// TODO: remove O(n) part.
// TODO: check that there is no insertions of different Nodes with the same
// nodeId.
if (nodes.contains(v)) {
return;
}
nodes.add(v);
}
// Specifies that [parent] would be executed before [child].
void addRelation(Node parent, Node child) {
child.parentsCount += 1;
parent.childs.add(child);
}
// Appends zeros to [choices] to contain [nodes.length] elements.
void _padLength(List<int> choices) {
while (choices.length < nodes.length) {
choices.add(0);
}
}
// Generates lexicographically next list of choices.
// Returns true if generation was successful and false otherwise.
bool _moveNextChoiceList(List<int> choices) {
if (choices.isEmpty) {
_padLength(choices);
return true;
}
while (choices.isNotEmpty) {
choices.last += 1;
if (_getOrder(choices) == null) {
choices.removeLast();
} else {
_padLength(choices);
return true;
}
}
return false;
}
int _countOrders() {
_allChoices.clear();
List<int> choices = <int>[];
while (_moveNextChoiceList(choices)) {
_allChoices.add(List<int>.from(choices));
}
return _allChoices.length;
}
// To generate a correct topological order, the following algorithm is used:
//
// Keep a list of nodes with zero input degree - [ready].
// On each step:
// Choose an arbitrary [node] from [ready].
// Add [node] to order, and remove [node] from graph.
//
// List [choices] specifies which node to take from list on each step. Each
// [choices[i]] should be greater or equal to 0 and less than the length of [ready]
// at the begining of the step [i].
// Returns [this] ordered topologically based on [choices].
// Returns null if [choices] do not correspond to correct topological order.
//
// If [completeWithRandomChoices] is false, the first node will be used as a
// choice when [choices] is over. Otherwise, nodes will be chosen randomly.
EvaluationOrder _getOrder(List<int> choices,
{bool completeWithRandomChoices = false}) {
List<Node> order = <Node>[];
List<Node> ready = <Node>[];
for (final node in nodes) {
if (node.parentsCount == 0) {
ready.add(node);
}
}
Map<Node, int> known = <Node, int>{};
for (int i = 0; i < nodes.length; i++) {
if (ready.isEmpty) {
throw StateError('Computational graph should be acyclic.');
}
int curChoice = 0;
if (i < choices.length) {
curChoice = choices[i];
} else if (completeWithRandomChoices) {
curChoice = random.nextInt(ready.length);
}
if (curChoice >= ready.length) {
return null;
}
Node cur = ready.removeAt(curChoice);
order.add(cur);
for (final next in cur.childs) {
known.putIfAbsent(next, () => next.parentsCount);
}
// Done in separate cycle to correctly manage situation when there are
// duplicates in [cur.childs].
for (final next in cur.childs) {
known[next] -= 1;
if (known[next] == 0) {
ready.add(next);
}
}
}
return EvaluationOrder(order);
}
EvaluationOrder _getNthOrder(int index) {
if (index < 0 || index >= _allChoices.length) {
throw ArgumentError.value(index, 'index', 'Index is out of range.');
}
return _getOrder(_allChoices[index]);
}
EvaluationOrder getRandomOrder() =>
_getOrder([], completeWithRandomChoices: true);
// Returns all correct topological orders of this graph.
Iterable<EvaluationOrder> get orders =>
Iterable<EvaluationOrder>.generate(_countOrders(), _getNthOrder);
}