M0: compilable skeleton — Kigi 0.1.0 fork surgery
Hard fork of xai-org/grok-build (Apache-2.0) re-targeted as Kigi, an
unofficial Kimi Code CLI community build.
Rename & identity
- 72 xai-*/xai-grok-* crates -> kigi-* (explicit: xai-grok-pager-bin ->
kigi-bin [binary `kigi`], xai-grok-pager -> kigi-tui; rest mechanical);
ptyctl, ptyctl-cli, third_party/ unchanged; proto package
xai.grok.tools.v1 -> kigi.tools.v1
- Config home ~/.kigi (KIGI_SHARE_DIR override), env prefix GROK_* ->
KIGI_*, `kigi --version` carries the unofficial-community-build notice
- clap identity, help text, startup banner, prompt templates rebranded
(templates re-encrypted)
Deletions (PRD removal list #5/#6/#7/#9/#10)
- voice input (xai-grok-voice) and all TUI wiring
- telemetry: Mixpanel client, external OTel stream, Sentry, OTLP layers,
trace/GCS/S3 upload queues (kigi-file-utils halved), workspace upload
module & dc_log, heap-profile uploader, auth-diagnostics uploader,
session-analytics halves of feedback; local zero-egress observability
preserved in new kigi-log crate (unified log, --debug firehose,
subsystem file logs, opt-in instrumentation)
- announcements (crate, remote-settings fields, TUI surfaces)
- plugin marketplace (crate, sources/browse/CTA/extensions-modal tab);
direct plugin install/uninstall/update via kigi-agent git_install kept
- relay/gateway/assets endpoints and features (agent relay, headless
relay transport, gateway bridge, LeaderEnvUrls); leader IPC socket now
~/.kigi/leader.sock + KIGI_LEADER_SOCKET, no ws-url derivation
- functional types rehomed instead of deleted: PermissionMode ->
kigi-config-types, McpInitStrategy -> kigi-mcp, PrCreationSource ->
session signals, TerminalDiagnostics -> kigi-pager-render, agent_id ->
shell util
Endpoints
- kigi-env rewritten: single production KigiEndpoints {coding_api_base_url
https://api.kimi.com/coding/v1 (KIGI_CODE_BASE_URL), oauth_host
https://auth.kimi.com (KIGI_OAUTH_HOST), update_base_url (GitHub
Releases API), upgrade_page_url}; GrokBuildEnvironment enum deleted
Toolchain & workspace hygiene
- Rust 1.97.0 pinned; edition 2024; full cargo update; git2 hoisted to
workspace at 0.21 (Option->Result API migration), quick-xml 0.41
- Root Cargo.toml hand-maintained (PRD §8.1): version 0.1.0 inherited by
all members, members sorted, unused deps pruned
- cargo-deny advisories gate (deny.toml with documented transitive
exceptions); CI workflow (check/clippy/fmt/deny/test, macOS+Linux)
- cross-crate test seams re-gated behind `test-support` cargo feature;
insta snapshot baselines renamed to the kigi_tui prefix
- clippy --workspace --all-targets: zero warnings; fmt clean
Fixes surfaced by the port
- updater probe/installer divergence (bin/kigi vs bin/grok symlink set)
- idle model-metadata refresh dead under KIGI_CODE_BASE_URL override
(new is_effective_coding_endpoint_url, loopback+override aware)
- macOS symlinked-TMPDIR fixture canonicalization (foreign_sessions,
fast-worktree); RSS measurement tests serialized via serial_test
Docs & legal (Apache §4)
- NOTICE added (upstream attribution + change statement); THIRD-PARTY
notices sustained; kigi-tools ported-code notices extended; README,
CONTRIBUTING, SECURITY, AGENTS.md rewritten
Out of scope for M0 (tracked): Kimi auth/inference (M1), search/fetch,
command parity, config import (M2), Computer Hub excision & final
brand-token sweep (M2), distribution & self-update rewrite (M3).
This commit is contained in:
commit
d6c20fc13f
2612 files changed
+1353757
No files matched your search
@@ -0,0 +1,137 @@
|
||||
use crate::layout::rank::util::slack;
|
||||
use crate::{GraphConfig, GraphEdge, GraphNode};
|
||||
use graphlib_rust::{Edge, Graph, GraphOption};
|
||||
|
||||
/*
|
||||
* Constructs a spanning tree with tight edges and adjusted the input node's
|
||||
* ranks to achieve this. A tight edge is one that is has a length that matches
|
||||
* its "minlen" attribute.
|
||||
*
|
||||
* The basic structure for this function is derived from Gansner, et al., "A
|
||||
* Technique for Drawing Directed Graphs."
|
||||
*
|
||||
* Pre-conditions:
|
||||
*
|
||||
* 1. Graph must be a DAG.
|
||||
* 2. Graph must be connected.
|
||||
* 3. Graph must have at least one node.
|
||||
* 5. Graph nodes must have been previously assigned a "rank" property that
|
||||
* respects the "minlen" property of incident edges.
|
||||
* 6. Graph edges must have a "minlen" property.
|
||||
*
|
||||
* Post-conditions:
|
||||
*
|
||||
* - Graph nodes will have their rank adjusted to ensure that all edges are
|
||||
* tight.
|
||||
*
|
||||
* Returns a tree (undirected graph) that is constructed using only "tight"
|
||||
* edges.
|
||||
*/
|
||||
|
||||
pub fn feasible_tree(
|
||||
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
) -> Graph<GraphConfig, GraphNode, GraphEdge> {
|
||||
let mut t: Graph<GraphConfig, GraphNode, GraphEdge> = Graph::new(Some(GraphOption {
|
||||
directed: Some(false),
|
||||
multigraph: Some(false),
|
||||
compound: Some(false),
|
||||
}));
|
||||
|
||||
// Choose arbitrary node from which to start our tree
|
||||
let start = g.nodes().first().cloned().unwrap_or("".to_string());
|
||||
let size = g.node_count();
|
||||
t.set_node(start, Some(GraphNode::default()));
|
||||
|
||||
while tight_tree(&mut t, g) < size {
|
||||
if let Some(edge) = find_min_stack_edge(&t, g) {
|
||||
let delta = if t.has_node(&edge.v) {
|
||||
slack(g, &edge)
|
||||
} else {
|
||||
-1 * slack(g, &edge)
|
||||
};
|
||||
shift_ranks(&t, g, delta);
|
||||
}
|
||||
}
|
||||
|
||||
t
|
||||
}
|
||||
|
||||
/*
|
||||
* Finds a maximal tree of tight edges and returns the number of nodes in the
|
||||
* tree.
|
||||
*/
|
||||
fn tight_tree(
|
||||
t: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
) -> usize {
|
||||
fn dfs(
|
||||
v: &String,
|
||||
t: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
) {
|
||||
let node_edges = g.node_edges(v, None).unwrap_or(vec![]);
|
||||
for node_edge in node_edges {
|
||||
let edge_v = node_edge.v.clone();
|
||||
let mut _w: Option<&String> = None;
|
||||
if v == &edge_v {
|
||||
_w = Some(&node_edge.w);
|
||||
} else {
|
||||
_w = Some(&edge_v);
|
||||
}
|
||||
let w = _w.unwrap().clone();
|
||||
if !t.has_node(&w) && slack(g, &node_edge) == 0 {
|
||||
t.set_node(w.clone(), Some(GraphNode::default()));
|
||||
let _ = t.set_edge(&v, &w, Some(GraphEdge::default()), None);
|
||||
dfs(&w, t, g);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let nodes = t.nodes();
|
||||
for node_id in nodes.into_iter() {
|
||||
dfs(&node_id, t, g);
|
||||
}
|
||||
return t.node_count();
|
||||
}
|
||||
|
||||
/*
|
||||
* Finds the edge with the smallest slack that is incident on tree and returns
|
||||
* it.
|
||||
*/
|
||||
fn find_min_stack_edge(
|
||||
t: &Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
) -> Option<Edge> {
|
||||
let edges = g.edges();
|
||||
let result = edges
|
||||
.iter()
|
||||
.map(|e| {
|
||||
let mut e_: Option<i32> = None;
|
||||
if t.has_node(&e.v) != t.has_node(&e.w) {
|
||||
e_ = Some(slack(g, e));
|
||||
}
|
||||
(e, e_)
|
||||
})
|
||||
.filter(|(_, e_)| e_.is_some())
|
||||
.min_by(|(_, e1_), (_, e2_)| e1_.unwrap().cmp(&e2_.unwrap()));
|
||||
|
||||
if result.is_some() {
|
||||
Some(result.unwrap().0.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn shift_ranks(
|
||||
t: &Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
delta: i32,
|
||||
) {
|
||||
let nodes = t.nodes();
|
||||
for node_id in nodes.into_iter() {
|
||||
let node_ = g.node_mut(&node_id);
|
||||
if let Some(node) = node_ {
|
||||
node.rank = Some(node.rank.unwrap_or(0) + delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
pub mod feasible_tree;
|
||||
pub mod network_simplex;
|
||||
pub mod util;
|
||||
|
||||
use crate::layout::rank::feasible_tree::feasible_tree;
|
||||
use crate::layout::rank::network_simplex::network_simplex;
|
||||
use crate::layout::rank::util::longest_path;
|
||||
use crate::{GraphConfig, GraphEdge, GraphNode};
|
||||
use graphlib_rust::Graph;
|
||||
|
||||
/*
|
||||
* Assigns a rank to each node in the input graph that respects the "minlen"
|
||||
* constraint specified on edges between nodes.
|
||||
*
|
||||
* This basic structure is derived from Gansner, et al., "A Technique for
|
||||
* Drawing Directed Graphs."
|
||||
*
|
||||
* Pre-conditions:
|
||||
*
|
||||
* 1. Graph must be a connected DAG
|
||||
* 2. Graph nodes must be objects
|
||||
* 3. Graph edges must have "weight" and "minlen" attributes
|
||||
*
|
||||
* Post-conditions:
|
||||
*
|
||||
* 1. Graph nodes will have a "rank" attribute based on the results of the
|
||||
* algorithm. Ranks can start at any index (including negative), we'll
|
||||
* fix them up later.
|
||||
*/
|
||||
|
||||
pub fn rank(g: &mut Graph<GraphConfig, GraphNode, GraphEdge>) {
|
||||
let _ranker = g.graph().ranker.clone();
|
||||
match _ranker {
|
||||
Some(ranker) => {
|
||||
let ranker_str = &*ranker;
|
||||
if ranker_str == "network-simplex" {
|
||||
network_simplex(g);
|
||||
} else if ranker_str == "tight-tree" {
|
||||
tight_tree_ranker(g);
|
||||
} else if ranker_str == "longest-path" {
|
||||
longest_path(g);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
network_simplex(g);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn tight_tree_ranker(g: &mut Graph<GraphConfig, GraphNode, GraphEdge>) {
|
||||
longest_path(g);
|
||||
feasible_tree(g);
|
||||
}
|
||||
@@ -0,0 +1,365 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::layout::rank::feasible_tree::feasible_tree;
|
||||
use crate::layout::rank::util::{longest_path, slack};
|
||||
use crate::layout::util;
|
||||
use crate::{GraphConfig, GraphEdge, GraphNode};
|
||||
use graphlib_rust::algo::postorder::postorder;
|
||||
use graphlib_rust::{Edge, Graph};
|
||||
use ordered_hashmap::OrderedHashMap;
|
||||
|
||||
/*
|
||||
* The network simplex algorithm assigns ranks to each node in the input graph
|
||||
* and iteratively improves the ranking to reduce the length of edges.
|
||||
*
|
||||
* Preconditions:
|
||||
*
|
||||
* 1. The input graph must be a DAG.
|
||||
* 2. All nodes in the graph must have an object value.
|
||||
* 3. All edges in the graph must have "minlen" and "weight" attributes.
|
||||
*
|
||||
* Postconditions:
|
||||
*
|
||||
* 1. All nodes in the graph will have an assigned "rank" attribute that has
|
||||
* been optimized by the network simplex algorithm. Ranks start at 0.
|
||||
*
|
||||
*
|
||||
* A rough sketch of the algorithm is as follows:
|
||||
*
|
||||
* 1. Assign initial ranks to each node. We use the longest path algorithm,
|
||||
* which assigns ranks to the lowest position possible. In general this
|
||||
* leads to very wide bottom ranks and unnecessarily long edges.
|
||||
* 2. Construct a feasible tight tree. A tight tree is one such that all
|
||||
* edges in the tree have no slack (difference between length of edge
|
||||
* and minlen for the edge). This by itself greatly improves the assigned
|
||||
* rankings by shorting edges.
|
||||
* 3. Iteratively find edges that have negative cut values. Generally a
|
||||
* negative cut value indicates that the edge could be removed and a new
|
||||
* tree edge could be added to produce a more compact graph.
|
||||
*
|
||||
* Much of the algorithms here are derived from Gansner, et al., "A Technique
|
||||
* for Drawing Directed Graphs." The structure of the file roughly follows the
|
||||
* structure of the overall algorithm.
|
||||
*/
|
||||
|
||||
pub fn network_simplex(g: &mut Graph<GraphConfig, GraphNode, GraphEdge>) {
|
||||
let mut simplified = util::simplify(g);
|
||||
|
||||
longest_path(&mut simplified);
|
||||
|
||||
let mut t: Graph<GraphConfig, GraphNode, GraphEdge> = feasible_tree(&mut simplified);
|
||||
init_low_lim_values(&mut t, None);
|
||||
init_cut_values(&mut t, &mut simplified);
|
||||
|
||||
while let Some(e) = leave_edge(&t) {
|
||||
let Some(f) = enter_edge(&t, &simplified, &e) else {
|
||||
break;
|
||||
};
|
||||
exchange_edges(&mut t, &mut simplified, &e, f);
|
||||
}
|
||||
|
||||
for v in g.nodes() {
|
||||
let Some(node) = g.node_mut(&v) else {
|
||||
continue;
|
||||
};
|
||||
let Some(simple_node) = simplified.node(&v) else {
|
||||
continue;
|
||||
};
|
||||
node.rank = simple_node.rank;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Initializes cut values for all edges in the tree.
|
||||
*/
|
||||
fn init_cut_values(
|
||||
t: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
) {
|
||||
let node_ids = t.nodes();
|
||||
let mut vs = postorder(t, &node_ids);
|
||||
vs.pop();
|
||||
for node_id in vs {
|
||||
assign_cut_value(t, g, &node_id);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Given the tight tree, its graph, and a child in the graph calculate and
|
||||
* return the cut value for the edge between the child and its parent.
|
||||
*/
|
||||
fn assign_cut_value(
|
||||
t: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
child: &String,
|
||||
) {
|
||||
let cutvalue = calc_cut_value(t, g, child);
|
||||
let child_lab_ = t.node_mut(child);
|
||||
if let Some(child_lab) = child_lab_ {
|
||||
let parent = child_lab.parent.clone().unwrap_or("".to_string());
|
||||
let edge_label_ = t.edge_mut(&child, &parent, None);
|
||||
if let Some(edge_label) = edge_label_ {
|
||||
edge_label.cutvalue = Some(cutvalue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Given the tight tree, its graph, and a child in the graph calculate and
|
||||
* return the cut value for the edge between the child and its parent.
|
||||
*/
|
||||
fn calc_cut_value(
|
||||
t: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
child: &String,
|
||||
) -> f32 {
|
||||
// The accumulated cut value for the edge between this node and its parent
|
||||
let mut cut_value = 0.0;
|
||||
let child_lab_ = t.node_mut(child);
|
||||
if let Some(child_lab) = child_lab_ {
|
||||
let parent = child_lab.parent.clone().unwrap_or("".to_string());
|
||||
// True if the child is on the tail end of the edge in the directed graph
|
||||
let mut child_is_tail = true;
|
||||
// The graph's view of the tree edge we're inspecting
|
||||
let mut graph_edge = g.edge_mut(child, &parent, None);
|
||||
|
||||
if graph_edge.is_none() {
|
||||
child_is_tail = false;
|
||||
graph_edge = g.edge_mut(&parent, &child, None);
|
||||
}
|
||||
|
||||
cut_value = graph_edge
|
||||
.cloned()
|
||||
.unwrap_or(GraphEdge::default())
|
||||
.weight
|
||||
.unwrap_or(0.0);
|
||||
let edge_objs_ = g.node_edges(child, None);
|
||||
if let Some(edge_objs) = edge_objs_ {
|
||||
for e in edge_objs {
|
||||
let is_out_edge = &e.v == child;
|
||||
let other = if is_out_edge {
|
||||
e.w.clone()
|
||||
} else {
|
||||
e.v.clone()
|
||||
};
|
||||
|
||||
if other != parent {
|
||||
let points_to_head = is_out_edge == child_is_tail;
|
||||
let other_weight = g
|
||||
.edge_with_obj(&e)
|
||||
.unwrap_or(&GraphEdge::default())
|
||||
.weight
|
||||
.unwrap_or(0.0);
|
||||
|
||||
cut_value += if points_to_head {
|
||||
other_weight
|
||||
} else {
|
||||
-other_weight
|
||||
};
|
||||
|
||||
if is_tree_edge(t, child, &other) {
|
||||
let out_cut_value = t
|
||||
.edge(&child, &other, None)
|
||||
.unwrap_or(&GraphEdge::default())
|
||||
.cutvalue
|
||||
.unwrap_or(0.0);
|
||||
cut_value += if points_to_head {
|
||||
-out_cut_value
|
||||
} else {
|
||||
out_cut_value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cut_value
|
||||
}
|
||||
|
||||
fn init_low_lim_values(tree: &mut Graph<GraphConfig, GraphNode, GraphEdge>, root_: Option<String>) {
|
||||
let mut root = tree.nodes().first().cloned().unwrap_or("".to_string());
|
||||
if root_.is_some() {
|
||||
root = root_.unwrap();
|
||||
}
|
||||
let mut visited: OrderedHashMap<String, bool> = OrderedHashMap::new();
|
||||
dfs_assign_low_lim(tree, &mut visited, 1, &root, None);
|
||||
}
|
||||
|
||||
fn dfs_assign_low_lim(
|
||||
tree: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
visited: &mut OrderedHashMap<String, bool>,
|
||||
next_lim_: usize,
|
||||
v: &String,
|
||||
parent: Option<&String>,
|
||||
) -> usize {
|
||||
let low = next_lim_.clone();
|
||||
let mut next_lim = next_lim_.clone();
|
||||
|
||||
visited.entry(v.clone()).or_insert(true);
|
||||
let neighbors_ = tree.neighbors(v);
|
||||
if let Some(neighbors) = neighbors_ {
|
||||
for w in neighbors.into_iter() {
|
||||
if !visited.contains_key(&w) {
|
||||
next_lim = dfs_assign_low_lim(tree, visited, next_lim.clone(), &w, Some(v));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let label_ = tree.node_mut(v);
|
||||
if let Some(label) = label_ {
|
||||
label.low = Some(low);
|
||||
label.lim = Some(next_lim.clone());
|
||||
next_lim += 1;
|
||||
|
||||
if parent.is_some() {
|
||||
label.parent = Some(parent.cloned().unwrap());
|
||||
} else {
|
||||
// TODO should be able to remove this when we incrementally update low lim
|
||||
label.parent = None;
|
||||
}
|
||||
}
|
||||
|
||||
next_lim
|
||||
}
|
||||
|
||||
fn leave_edge(tree: &Graph<GraphConfig, GraphNode, GraphEdge>) -> Option<Edge> {
|
||||
let edge_objs = tree.edges();
|
||||
edge_objs
|
||||
.iter()
|
||||
.find(|edge_obj| {
|
||||
tree.edge_with_obj(edge_obj)
|
||||
.unwrap_or(&GraphEdge::default())
|
||||
.cutvalue
|
||||
.unwrap_or(0.0)
|
||||
< 0.0
|
||||
})
|
||||
.cloned()
|
||||
}
|
||||
|
||||
fn enter_edge(
|
||||
t: &Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
g: &Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
edge: &Edge,
|
||||
) -> Option<Edge> {
|
||||
let mut v = edge.v.clone();
|
||||
let mut w = edge.w.clone();
|
||||
|
||||
// For the rest of this function we assume that v is the tail and w is the
|
||||
// head, so if we don't have this edge in the graph we should flip it to
|
||||
// match the correct orientation.
|
||||
if !g.has_edge(&v, &w, None) {
|
||||
v = edge.w.clone();
|
||||
w = edge.v.clone();
|
||||
}
|
||||
|
||||
let v_label = t.node(&v).cloned().unwrap_or(GraphNode::default());
|
||||
let w_label = t.node(&w).cloned().unwrap_or(GraphNode::default());
|
||||
let mut tail_label = &v_label;
|
||||
let mut flip = false;
|
||||
|
||||
// If the root is in the tail of the edge then we need to flip the logic that
|
||||
// checks for the head and tail nodes in the candidates function below.
|
||||
if v_label.lim.clone().unwrap_or(0) > w_label.lim.clone().unwrap_or(0) {
|
||||
tail_label = &w_label;
|
||||
flip = true;
|
||||
}
|
||||
|
||||
let edge_objs = g.edges();
|
||||
let candidates = edge_objs.iter().filter(|edge_obj| {
|
||||
let v_node = t.node(&edge_obj.v).cloned().unwrap_or(GraphNode::default());
|
||||
let w_node = t.node(&edge_obj.w).cloned().unwrap_or(GraphNode::default());
|
||||
flip == is_descendant(&v_node, tail_label) && flip != is_descendant(&w_node, tail_label)
|
||||
});
|
||||
|
||||
candidates
|
||||
.min_by(|e1, e2| slack(g, e1).cmp(&slack(g, e2)))
|
||||
.cloned()
|
||||
}
|
||||
|
||||
fn exchange_edges(
|
||||
t: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
e: &Edge,
|
||||
f: Edge,
|
||||
) {
|
||||
let v = e.v.clone();
|
||||
let w = e.w.clone();
|
||||
t.remove_edge(&v, &w, None);
|
||||
let _ = t.set_edge(&f.v, &f.w, Some(GraphEdge::default()), None);
|
||||
init_low_lim_values(t, None);
|
||||
init_cut_values(t, g);
|
||||
update_ranks(t, g);
|
||||
}
|
||||
|
||||
fn update_ranks(
|
||||
t: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
) {
|
||||
let Some(root) = t.nodes().first().cloned() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut children: HashMap<String, Vec<String>> = HashMap::new();
|
||||
for v in t.nodes() {
|
||||
let parent = t.node(&v).unwrap_or(&GraphNode::default()).parent.clone();
|
||||
let Some(parent) = parent else {
|
||||
continue;
|
||||
};
|
||||
|
||||
children.entry(parent).or_default().push(v);
|
||||
}
|
||||
|
||||
let mut stack: Vec<String> = vec![root.clone()];
|
||||
while let Some(parent) = stack.pop() {
|
||||
let Some(vs) = children.get(&parent).cloned() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
for v in vs {
|
||||
let mut edge = g.edge(&v, &parent, None);
|
||||
let mut flipped = false;
|
||||
if edge.is_none() {
|
||||
edge = g.edge(&parent, &v, None);
|
||||
flipped = true;
|
||||
}
|
||||
|
||||
let minlen = edge.unwrap_or(&GraphEdge::default()).minlen.unwrap_or(0.0);
|
||||
let parent_rank = g
|
||||
.node(&parent)
|
||||
.unwrap_or(&GraphNode::default())
|
||||
.rank
|
||||
.unwrap_or(0);
|
||||
if let Some(v_node) = g.node_mut(&v) {
|
||||
v_node.rank = Some(
|
||||
parent_rank
|
||||
+ if flipped {
|
||||
minlen as i32
|
||||
} else {
|
||||
-(minlen as i32)
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
stack.push(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if the edge is in the tree.
|
||||
*/
|
||||
fn is_tree_edge(tree: &Graph<GraphConfig, GraphNode, GraphEdge>, u: &String, v: &String) -> bool {
|
||||
tree.has_edge(&u, &v, None)
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if the specified node is descendant of the root node per the
|
||||
* assigned low and lim attributes in the tree.
|
||||
*/
|
||||
fn is_descendant(v_label: &GraphNode, root_label: &GraphNode) -> bool {
|
||||
let low = root_label.low.clone().unwrap_or(0);
|
||||
let v_lim = v_label.lim.clone().unwrap_or(0);
|
||||
let root_lim = root_label.lim.clone().unwrap_or(0);
|
||||
low <= v_lim && v_lim <= root_lim
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
use crate::{GraphConfig, GraphEdge, GraphNode};
|
||||
use graphlib_rust::{Edge, Graph};
|
||||
use ordered_hashmap::OrderedHashMap;
|
||||
|
||||
/*
|
||||
* Initializes ranks for the input graph using the longest path algorithm. This
|
||||
* algorithm scales well and is fast in practice, it yields rather poor
|
||||
* solutions. Nodes are pushed to the lowest layer possible, leaving the bottom
|
||||
* ranks wide and leaving edges longer than necessary. However, due to its
|
||||
* speed, this algorithm is good for getting an initial ranking that can be fed
|
||||
* into other algorithms.
|
||||
*
|
||||
* This algorithm does not normalize layers because it will be used by other
|
||||
* algorithms in most cases. If using this algorithm directly, be sure to
|
||||
* run normalize at the end.
|
||||
*
|
||||
* Pre-conditions:
|
||||
*
|
||||
* 1. Input graph is a DAG.
|
||||
* 2. Input graph node labels can be assigned properties.
|
||||
*
|
||||
* Post-conditions:
|
||||
*
|
||||
* 1. Each node will be assign an (unnormalized) "rank" property.
|
||||
*/
|
||||
|
||||
pub fn longest_path(g: &mut Graph<GraphConfig, GraphNode, GraphEdge>) {
|
||||
let mut visited: OrderedHashMap<String, bool> = OrderedHashMap::new();
|
||||
|
||||
fn dfs(
|
||||
v: &String,
|
||||
g: &mut Graph<GraphConfig, GraphNode, GraphEdge>,
|
||||
visited: &mut OrderedHashMap<String, bool>,
|
||||
) -> i32 {
|
||||
let node_label = g.node(v);
|
||||
if visited.contains_key(v) {
|
||||
return node_label
|
||||
.cloned()
|
||||
.unwrap_or(GraphNode::default())
|
||||
.rank
|
||||
.unwrap_or(0);
|
||||
}
|
||||
visited.insert(v.clone(), true);
|
||||
|
||||
let ranks: Vec<i32> = g
|
||||
.out_edges(v, None)
|
||||
.unwrap_or(vec![])
|
||||
.iter()
|
||||
.map(|e| {
|
||||
dfs(&e.w, g, visited)
|
||||
- (g.edge_with_obj(&e)
|
||||
.cloned()
|
||||
.unwrap_or(GraphEdge::default())
|
||||
.minlen
|
||||
.unwrap_or(0.0)
|
||||
.round() as i32)
|
||||
})
|
||||
.collect();
|
||||
let rank: i32 = ranks.iter().min().cloned().unwrap_or(0) as i32;
|
||||
{
|
||||
let _node_label = g.node_mut(v);
|
||||
if let Some(node_label) = _node_label {
|
||||
node_label.rank = Some(rank.clone());
|
||||
}
|
||||
}
|
||||
return rank;
|
||||
}
|
||||
|
||||
for node_id in g.sources().into_iter() {
|
||||
dfs(&node_id, g, &mut visited);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the amount of slack for the given edge. The slack is defined as the
|
||||
* difference between the length of the edge and its minimum length.
|
||||
*/
|
||||
pub fn slack(g: &Graph<GraphConfig, GraphNode, GraphEdge>, e: &Edge) -> i32 {
|
||||
let w_rank = g
|
||||
.node(&e.w)
|
||||
.cloned()
|
||||
.unwrap_or(GraphNode::default())
|
||||
.rank
|
||||
.unwrap_or(0);
|
||||
let v_rank = g
|
||||
.node(&e.v)
|
||||
.cloned()
|
||||
.unwrap_or(GraphNode::default())
|
||||
.rank
|
||||
.unwrap_or(0);
|
||||
let minlen = g
|
||||
.edge_with_obj(e)
|
||||
.cloned()
|
||||
.unwrap_or(GraphEdge::default())
|
||||
.minlen
|
||||
.unwrap_or(10.0)
|
||||
.round() as i32;
|
||||
return w_rank - v_rank - minlen;
|
||||
}
|
||||
Reference in new issue
Block a user