mirror of
https://github.com/xibyte/jsketcher
synced 2026-01-17 05:34:32 +01:00
Added start of wire harness workbench. Shortest path algorithm.
This commit is contained in:
parent
0cd501b8cc
commit
dde86e056a
23 changed files with 1774 additions and 2 deletions
|
|
@ -1,8 +1,11 @@
|
|||
import {ModelerWorkspace} from "workbenches/modeler";
|
||||
import { SheetMetalWorkspace } from "workbenches/sheetMetal";
|
||||
import {SheetMetalWorkspace} from "workbenches/sheetMetal";
|
||||
// import { RoutingElectricalWorkspace } from "./routingElectrical";
|
||||
import {WorkbenchConfig} from "cad/workbench/workbenchService";
|
||||
|
||||
|
||||
export const WorkbenchRegistry: WorkbenchConfig[] = [
|
||||
ModelerWorkspace, SheetMetalWorkspace
|
||||
ModelerWorkspace,
|
||||
SheetMetalWorkspace,
|
||||
//RoutingElectricalWorkspace,
|
||||
]
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
import {roundValueForPresentation as r} from 'cad/craft/operationHelper';
|
||||
import {ApplicationContext} from "cad/context";
|
||||
import {OperationDescriptor} from "cad/craft/operationBundle";
|
||||
import {abSegmentsToDigraph} from "./pathFinderLogic/js/sided_ab_graphs/digraph_ab_builder"
|
||||
import {
|
||||
findShortestPathForAllPairsAsync,
|
||||
simpleArrayToPointPairs
|
||||
} from "./pathFinderLogic/js/sided_ab_graphs/digraph_ab_pairs_analyser"
|
||||
|
||||
|
||||
interface autoRouteParams {
|
||||
thickness: number;
|
||||
}
|
||||
|
||||
|
||||
export const AutoRouteOperation: OperationDescriptor<autoRouteParams> = {
|
||||
id: 'RE_AUTO_ROUTE',
|
||||
label: 'AUTO ROUTE',
|
||||
icon: 'img/cad/autoRoute',
|
||||
info: 'Auto Routing tool',
|
||||
path: __dirname,
|
||||
paramsInfo: ({ thickness, }) => `(${r(thickness)} )`,
|
||||
run: async (params: autoRouteParams, ctx: ApplicationContext) => {
|
||||
|
||||
const occ = ctx.occService;
|
||||
const oci = occ.commandInterface;
|
||||
|
||||
|
||||
//place code here to retrieve list of roughing splines from the model.
|
||||
const segments = [
|
||||
{ "id": "seg1", "firstPoint": "p1", "firstSide": "A", "secondPoint": "p2", "secondSide": "A", "weight": 8 },
|
||||
{ "id": "seg2", "firstPoint": "p1", "firstSide": "B", "secondPoint": "p3", "secondSide": "A", "weight": 2 },
|
||||
{ "id": "seg3", "firstPoint": "p1", "firstSide": "B", "secondPoint": "p2", "secondSide": "A", "weight": 3 },
|
||||
{ "id": "seg11", "firstPoint": "p1", "firstSide": "A", "secondPoint": "p5", "secondSide": "A", "weight": 10.1 },
|
||||
{ "id": "seg12", "firstPoint": "p1", "firstSide": "A", "secondPoint": "p5", "secondSide": "B", "weight": 10.1 },
|
||||
{ "id": "seg21", "firstPoint": "p2", "firstSide": "B", "secondPoint": "p3", "secondSide": "B", "weight": 15 },
|
||||
{ "id": "otherSeg1", "firstPoint": "other1", "firstSide": "A", "secondPoint": "other2", "secondSide": "B", "weight": 115 }
|
||||
];
|
||||
|
||||
|
||||
const connections = [
|
||||
{ id: "WIRE1", startPoint: "p1", endPoint: "p2", wireInfo: { diameter: 2 } },
|
||||
{ id: "WIRE2", startPoint: "p2", endPoint: "p3", wireInfo: { diameter: 5 } },
|
||||
{ id: "WIRE4", startPoint: "p2", endPoint: "p3", wireInfo: { diameter: 5 } },
|
||||
{ id: "WIRE6", startPoint: "p2", endPoint: "p3", wireInfo: { diameter: 5 } },
|
||||
];
|
||||
|
||||
|
||||
const report = await sidedAdPairsResolve(segments, connections, false, "error", "timing", "");
|
||||
|
||||
|
||||
const segmentResults = [];
|
||||
|
||||
for (const wire of report) {
|
||||
// ...use `element`...
|
||||
//console.log(wire);
|
||||
|
||||
for (const segment of wire.route.segments) {
|
||||
// ...use `element`...
|
||||
//console.log(segment);
|
||||
if (segmentResults[segment] == undefined) segmentResults[segment] = [];
|
||||
segmentResults[segment].push(wire);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(report, segmentResults);
|
||||
//alert(JSON.stringify(report, null, 2));
|
||||
|
||||
return {
|
||||
created: [],
|
||||
consumed: []
|
||||
};
|
||||
|
||||
},
|
||||
|
||||
|
||||
form: [
|
||||
{
|
||||
type: 'number',
|
||||
label: 'Thickness',
|
||||
name: 'thickness',
|
||||
defaultValue: 1,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function sidedAdPairsResolve(segmentsJson, connections, brief, errorElementId, timingElementId, progressBarId) {
|
||||
const t0 = performance.now();
|
||||
const digraph = abSegmentsToDigraph(segmentsJson);
|
||||
let pointPairs = simpleArrayToPointPairs(connections);
|
||||
console.log(pointPairs);
|
||||
const t1 = performance.now();
|
||||
await findShortestPathForAllPairsAsync(digraph, pointPairs, progressBarId == null ? null : part => {
|
||||
//document.getElementById(progressBarId).value = Math.round(part * 100)
|
||||
});
|
||||
// - note: without "await" we will have now non-processed pointPairs!
|
||||
const t2 = performance.now();
|
||||
const timing = "Executing: " + (t2 - t0) + " ms = "
|
||||
+ (t1 - t0) + " + " + (t2 - t1) + " for "
|
||||
+ pointPairs.length + " pairs from " + digraph.getNumberOfNodes() + " nodes, "
|
||||
+ ((t2 - t1) / pointPairs.length) + " ms/pair<br> ";
|
||||
//document.getElementById(timingElementId).innerHTML = timing;
|
||||
if (brief) {
|
||||
pointPairs = pointPairs.map(pair => pair.briefClone())
|
||||
}
|
||||
return pointPairs;
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
body {
|
||||
background-color: white;
|
||||
color: black;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
.comment {
|
||||
color: #888;
|
||||
}
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
import {ShortestPathTree} from "./js/digraphs/spt.js"
|
||||
import {allPathsToHTML, pathInformationToHTML, singlePathToHTML} from "./js/digraphs/spt_html.js"
|
||||
import {simpleObjectToDigraph} from "./js/digraphs/digraph_simple_builder.js"
|
||||
import {abSegmentsToDigraph} from "./js/sided_ab_graphs/digraph_ab_builder.js"
|
||||
import {DigraphABShortestPathFinder} from "./js/sided_ab_graphs/digraph_ab_shortest_path_finder.js"
|
||||
import {
|
||||
findShortestPathForAllPairsAsync,
|
||||
simpleArrayToPointPairs
|
||||
} from "./js/sided_ab_graphs/digraph_ab_pairs_analyser.js"
|
||||
|
||||
export {makeTestGridGraph, makeTestGridAbGraph, makeTestGridAbPointPairs} from "./js/test_digraph_builders.js"
|
||||
|
||||
export function simpleResolve(graphJson, startVertex, endVertex, errorElementId) {
|
||||
document.getElementById(errorElementId).innerHTML = "";
|
||||
let tree = null;
|
||||
let timing = "";
|
||||
try {
|
||||
const t0 = performance.now();
|
||||
// tree = new ShortestPathTree(graphJson); // old-style
|
||||
const digraph = simpleObjectToDigraph(graphJson);
|
||||
const t1 = performance.now();
|
||||
tree = new ShortestPathTree(digraph);
|
||||
tree.build(startVertex);
|
||||
const t2 = performance.now();
|
||||
timing = "Timing: " + (t2 - t0) + " ms = "
|
||||
+ (t1 - t0) + " + " + (t2 - t1) + " for " + tree.numberOfNodes + " nodes, "
|
||||
+ ((t2 - t1) * 1e3 / tree.numberOfNodes) + " mcs/vertex<br> ";
|
||||
} catch (e) {
|
||||
document.getElementById(errorElementId).innerHTML = e;
|
||||
throw e;
|
||||
}
|
||||
const result = timing + "<br><br>" + singleHTML(tree, endVertex)
|
||||
+ "<br><br>"
|
||||
+ allPathsToHTML(tree, 250)
|
||||
+ "<br>"
|
||||
+ "<span class=\"comment\">Processed directed graph:<br>"
|
||||
+ tree.digraph.toJsonString(50).replace(/\},/g, "},<br>")
|
||||
+ "</span>";
|
||||
return result;
|
||||
}
|
||||
|
||||
export function sidedAdResolve(segmentsJson, startPoint, endPoint, errorElementId) {
|
||||
document.getElementById(errorElementId).innerHTML = "";
|
||||
let finder = null;
|
||||
let timing = "";
|
||||
try {
|
||||
const t0 = performance.now();
|
||||
// tree = new ShortestPathTree(graphJson); // old-style
|
||||
const digraph = abSegmentsToDigraph(segmentsJson);
|
||||
const t1 = performance.now();
|
||||
finder = new DigraphABShortestPathFinder(digraph);
|
||||
finder.build(startPoint);
|
||||
const t2 = performance.now();
|
||||
timing = "Timing: " + (t2 - t0) + " ms = "
|
||||
+ (t1 - t0) + " + " + (t2 - t1) + ", "
|
||||
+ ((t2 - t1) * 1e3 / digraph.getNumberOfNodes()) + " mcs/vertex<br> ";
|
||||
} catch (e) {
|
||||
document.getElementById(errorElementId).innerHTML = e;
|
||||
throw e;
|
||||
}
|
||||
const result = timing + "<br><br>" + singleHTML(finder, endPoint)
|
||||
+ "<br>"
|
||||
+ forBothHTML(finder, endPoint)
|
||||
+ "<br><br>"
|
||||
+ allPathsToHTML(finder.treeA, 150)
|
||||
+ "<br>"
|
||||
+ allPathsToHTML(finder.treeB, 150)
|
||||
+ "<br>"
|
||||
+ "<span class=\"comment\">Processed directed graph:<br>"
|
||||
+ finder.treeA.digraph.toJsonString(50).replace(/\},/g, "},<br>")
|
||||
+ "</span>";
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function sidedAdPairsResolve(segmentsJson, pairsJson, brief, errorElementId, timingElementId, progressBarId) {
|
||||
//document.getElementById(errorElementId).innerHTML = "";
|
||||
//document.getElementById(timingElementId).innerHTML = "Executing...";
|
||||
let pointPairs = null;
|
||||
try {
|
||||
const t0 = performance.now();
|
||||
const digraph = abSegmentsToDigraph(segmentsJson);
|
||||
pointPairs = simpleArrayToPointPairs(pairsJson);
|
||||
const t1 = performance.now();
|
||||
await findShortestPathForAllPairsAsync(digraph, pointPairs, progressBarId == null ? null : part => {
|
||||
//document.getElementById(progressBarId).value = Math.round(part * 100)
|
||||
});
|
||||
// - note: without "await" we will have now non-processed pointPairs!
|
||||
const t2 = performance.now();
|
||||
const timing = "Executing: " + (t2 - t0) + " ms = "
|
||||
+ (t1 - t0) + " + " + (t2 - t1) + " for "
|
||||
+ pointPairs.length + " pairs from " + digraph.getNumberOfNodes() + " nodes, "
|
||||
+ ((t2 - t1) / pointPairs.length) + " ms/pair<br> ";
|
||||
//document.getElementById(timingElementId).innerHTML = timing;
|
||||
} catch (e) {
|
||||
document.getElementById(errorElementId).innerHTML = e;
|
||||
throw e;
|
||||
}
|
||||
if (brief) {
|
||||
pointPairs = pointPairs.map(pair => pair.briefClone())
|
||||
}
|
||||
return JSON.stringify(pointPairs, null, 2);
|
||||
}
|
||||
|
||||
|
||||
function singleHTML(treeOrFinder, endVertex) {
|
||||
try {
|
||||
return singlePathToHTML(treeOrFinder.getShortestPathInformation(endVertex, true));
|
||||
} catch (e) {
|
||||
return String(e);
|
||||
}
|
||||
}
|
||||
|
||||
function forBothHTML(finder, endPoint) {
|
||||
try {
|
||||
const forBoth = finder.getShortestPathInformationForBothSides(endPoint);
|
||||
return forBoth.length + " checked paths:<br>"
|
||||
+ (forBoth.length == 0 ? "" : pathInformationToHTML(forBoth).replace(/\},/g, "},<br>"));
|
||||
} catch (e) {
|
||||
return String(e);
|
||||
}
|
||||
}
|
||||
|
||||
export function parseJsonAndPrintError(jsonText, errorElementId) {
|
||||
try {
|
||||
return JSON.parse(jsonText);
|
||||
} catch (e) {
|
||||
document.getElementById(errorElementId).innerHTML = "JSON problem: " + e;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
export class GraphNode {
|
||||
constructor(id) {
|
||||
if (id == null) {
|
||||
throw "Null id";
|
||||
}
|
||||
this.id = id;
|
||||
this.weights = [];
|
||||
this.neighbourIds = [];
|
||||
this.edgeIds = [];
|
||||
// Note: edgeIds is an optional additional information about edges, not important for processing
|
||||
this.numberOfEdges = 0;
|
||||
}
|
||||
|
||||
addEdge(weight, otherNodeId, edgeId) {
|
||||
this.weights.push(weight);
|
||||
this.neighbourIds.push(otherNodeId);
|
||||
this.edgeIds.push(edgeId ?? this.id + "->" + otherNodeId);
|
||||
this.numberOfEdges++;
|
||||
}
|
||||
|
||||
getWeight(k) {
|
||||
return this.weights[k];
|
||||
}
|
||||
|
||||
getNeighbourId(k) {
|
||||
return this.neighbourIds[k];
|
||||
}
|
||||
|
||||
getEdgeId(k) {
|
||||
return this.edgeIds[k];
|
||||
}
|
||||
}
|
||||
|
||||
export class Digraph {
|
||||
constructor() {
|
||||
this.nodeMap = new Map();
|
||||
}
|
||||
|
||||
addNode(node) {
|
||||
const id = node.id;
|
||||
if (id == null) {
|
||||
throw "Null id in " + node + ": probably it is not a graph node";
|
||||
}
|
||||
this.nodeMap.set(id, node);
|
||||
}
|
||||
|
||||
addNodes(nodes) {
|
||||
for (const node of nodes) {
|
||||
this.addNode(node);
|
||||
}
|
||||
}
|
||||
|
||||
getNode(id) {
|
||||
return this.nodeMap.get(id);
|
||||
}
|
||||
|
||||
getAllNodeIds() {
|
||||
return this.nodeMap.keys();
|
||||
}
|
||||
|
||||
getNumberOfNodes() {
|
||||
return this.nodeMap.size;
|
||||
}
|
||||
|
||||
toJson(limitOfNodes) {
|
||||
const json = {};
|
||||
let count = 0;
|
||||
for (const [id, node] of this.nodeMap) {
|
||||
const nodeJson = {};
|
||||
for (let k = 0; k < node.numberOfEdges; k++) {
|
||||
nodeJson[node.getNeighbourId(k)] = node.getWeight(k)
|
||||
}
|
||||
json[id] = nodeJson;
|
||||
++count;
|
||||
if (limitOfNodes && count > limitOfNodes) {
|
||||
json["other_nodes"] = "cannot be shown (more than " + limitOfNodes + " nodes)";
|
||||
break;
|
||||
}
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
toJsonString(limitOfNodes, space) {
|
||||
return JSON.stringify(this.toJson(limitOfNodes), null, space);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import {Digraph, GraphNode} from "./digraph.js"
|
||||
|
||||
// For example:
|
||||
// id= "A"
|
||||
// object= { "B": 5.3, "C": 2.1 },
|
||||
// object is a map of other nodes with their weights
|
||||
export function simpleObjectToGraphNode(id, object) {
|
||||
const result = new GraphNode(id);
|
||||
for (const propertyName of Object.keys(object)) {
|
||||
result.addEdge(object[propertyName], propertyName);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function simpleObjectToDigraph(object) {
|
||||
const result = new Digraph();
|
||||
for (const id of Object.keys(object)) {
|
||||
const node = simpleObjectToGraphNode(id, object[id]);
|
||||
result.addNode(node);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
@ -0,0 +1,229 @@
|
|||
import {MinHeap} from "../heap/min_heap.js"
|
||||
|
||||
class NodeAndDistance {
|
||||
// counter allows to provide stable behaviour order, identical to SimpleDistanceHeap
|
||||
constructor(nodeId, distance, globalCounter) {
|
||||
this.nodeId = nodeId;
|
||||
this.distance = distance;
|
||||
this.counter = globalCounter ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
let _USE_MIN_HEAP = true;
|
||||
|
||||
// For debugging needs; usually should be true
|
||||
export function _setUseMinHeap(value) {
|
||||
_USE_MIN_HEAP = value;
|
||||
}
|
||||
|
||||
function nodeAndDistanceLess(a, b) {
|
||||
const result = a.distance < b.distance || (a.distance == b.distance && a.counter < b.counter);
|
||||
// console.log(JSON.stringify(a) + " and " + JSON.stringify(b) + ": " + result);
|
||||
return result;
|
||||
}
|
||||
|
||||
class DistanceHeap {
|
||||
constructor() {
|
||||
this.heap = new MinHeap(nodeAndDistanceLess);
|
||||
}
|
||||
|
||||
isEmpty() {
|
||||
return this.heap.isEmpty();
|
||||
}
|
||||
|
||||
removeShortestDistanceNode() {
|
||||
return this.heap.remove();
|
||||
}
|
||||
|
||||
addNode(nodeAndDistance) {
|
||||
this.heap.insert(nodeAndDistance);
|
||||
}
|
||||
|
||||
checkIntegrity() {
|
||||
this.heap.checkIntegrity();
|
||||
}
|
||||
|
||||
toString() {
|
||||
return JSON.stringify(this.heap.heap);
|
||||
}
|
||||
}
|
||||
|
||||
// Preserving this class for comparison/debugging
|
||||
class SimpleDistanceHeap {
|
||||
constructor() {
|
||||
this.heap = new Map();
|
||||
}
|
||||
|
||||
isEmpty() {
|
||||
return this.heap.size == 0;
|
||||
}
|
||||
|
||||
removeShortestDistanceNode() {
|
||||
let shortestId = null;
|
||||
let shortestDistance = Number.POSITIVE_INFINITY;
|
||||
for (const [id, distance] of this.heap) {
|
||||
// actually this.heap.keys is a set of "interesting" vertices
|
||||
if (shortestId === null || distance < shortestDistance) {
|
||||
shortestId = id;
|
||||
shortestDistance = distance;
|
||||
}
|
||||
}
|
||||
if (shortestId == null) {
|
||||
return null;
|
||||
}
|
||||
this.heap.delete(shortestId);
|
||||
return new NodeAndDistance(shortestId, shortestDistance);
|
||||
}
|
||||
|
||||
addNode(nodeAndDistance) {
|
||||
if (this.heap.has(nodeAndDistance.nodeId)) {
|
||||
throw "Assertion! " + nodeAndDistance.nodeId + " already exists!";
|
||||
}
|
||||
this.heap.set(nodeAndDistance.nodeId, nodeAndDistance.distance);
|
||||
}
|
||||
|
||||
checkIntegrity() {
|
||||
}
|
||||
|
||||
toString() {
|
||||
const result = [];
|
||||
for (const [id, distance] of this.heap) {
|
||||
result.push(new NodeAndDistance(id, distance));
|
||||
}
|
||||
return JSON.stringify(result);
|
||||
}
|
||||
}
|
||||
|
||||
// ShortestPathTree is created on the base of an instance of the class Digraph; see digraph.js
|
||||
export class ShortestPathTree {
|
||||
constructor(digraph) {
|
||||
if (!digraph)
|
||||
throw "No digraph passed: " + digraph;
|
||||
if (!digraph.getNode) {
|
||||
throw "No getNode method in digraph " + digraph + ": probably it is not a Digraph instance";
|
||||
}
|
||||
this.digraph = digraph;
|
||||
this.startNode = null;
|
||||
this.parentNodeIds = new Map();
|
||||
this.parentEdgeIds = new Map();
|
||||
// Note: parentEdgeIds is a map nodeId => parentEdgeID, not edgeId => parentEdgeId!
|
||||
// We have no any guarantee that edge IDs are different, they are additional information
|
||||
// and do not affect the algorithm.
|
||||
this.distances = new Map();
|
||||
this.numberOfNodes = digraph.getNumberOfNodes();
|
||||
}
|
||||
|
||||
build(startNodeId) {
|
||||
const startNode = this.digraph.getNode(startNodeId);
|
||||
if (!startNode) {
|
||||
throw "No start node '" + startNodeId + "'";
|
||||
}
|
||||
this.startNode = startNode;
|
||||
this.distances = new Map();
|
||||
// - unlike distancesHeap, this is both a temporary memory AND the result of algorithm;
|
||||
// in other language like C/Java in would be an array, not map
|
||||
this.parentNodeIds = new Map();
|
||||
this.parentEdgeIds = new Map();
|
||||
const distancesHeap = _USE_MIN_HEAP ? new DistanceHeap() : new SimpleDistanceHeap();
|
||||
const visited = new Set();
|
||||
let counter = 0;
|
||||
distancesHeap.addNode(new NodeAndDistance(startNodeId, 0, counter++));
|
||||
|
||||
while (!distancesHeap.isEmpty()) {
|
||||
const nodeAndDistance = distancesHeap.removeShortestDistanceNode();
|
||||
// console.log("!!! --> " + nodeAndDistance.nodeId);
|
||||
// distancesHeap.checkIntegrity(); // - uncomment this to test/debug MinHeap
|
||||
const nodeId = nodeAndDistance.nodeId;
|
||||
const distance = nodeAndDistance.distance;
|
||||
const nodeWithChildren = this.digraph.getNode(nodeId);
|
||||
for (let k = 0; k < nodeWithChildren.numberOfEdges; k++) {
|
||||
const childId = nodeWithChildren.getNeighbourId(k);
|
||||
if (visited.has(childId)) {
|
||||
continue;
|
||||
}
|
||||
const edgeId = nodeWithChildren.getEdgeId(k);
|
||||
const childWeight = nodeWithChildren.getWeight(k);
|
||||
const childDistance = this.distances.get(childId);
|
||||
const newDistance = distance + childWeight;
|
||||
if (childDistance == null || childDistance > newDistance) {
|
||||
// console.log("!!! " + childId + " := " + newDistance + ", from " + nodeId + ", " + counter);
|
||||
this.distances.set(childId, newDistance);
|
||||
distancesHeap.addNode(new NodeAndDistance(childId, newDistance, counter++));
|
||||
this.parentNodeIds.set(childId, nodeId);
|
||||
this.parentEdgeIds.set(childId, edgeId);
|
||||
}
|
||||
}
|
||||
this.distances.set(nodeId, distance);
|
||||
visited.add(nodeId);
|
||||
}
|
||||
}
|
||||
|
||||
getDistance(endNodeId) {
|
||||
return this.distances.get(endNodeId);
|
||||
}
|
||||
|
||||
getShortestPath(endNodeId, resultSegmentsArray, resultNodesArray) {
|
||||
const endNode = this.digraph.getNode(endNodeId);
|
||||
resultSegmentsArray.length = 0;
|
||||
resultNodesArray.length = 0;
|
||||
if (!endNode) {
|
||||
return false;
|
||||
// - it is more simple in usage than throwing exception
|
||||
}
|
||||
let parentNodeId = this.parentNodeIds.get(endNodeId);
|
||||
let parentEdgeId = this.parentEdgeIds.get(endNodeId);
|
||||
if (parentNodeId == null) {
|
||||
return false;
|
||||
}
|
||||
resultNodesArray.push(endNodeId);
|
||||
while (parentNodeId != null) {
|
||||
resultSegmentsArray.push(parentEdgeId);
|
||||
resultNodesArray.push(parentNodeId);
|
||||
parentEdgeId = this.parentEdgeIds.get(parentNodeId);
|
||||
// - warning: must be BEFORE the next operator (changing parentNodeId)
|
||||
parentNodeId = this.parentNodeIds.get(parentNodeId);
|
||||
}
|
||||
resultSegmentsArray.reverse();
|
||||
resultNodesArray.reverse();
|
||||
return true;
|
||||
}
|
||||
|
||||
getShortestPathInformation(endNodeId) {
|
||||
const pathSegments = [];
|
||||
const pathNodes = [];
|
||||
const feasible = this.getShortestPath(endNodeId, pathSegments, pathNodes);
|
||||
return feasible ?
|
||||
new PathInformation(this.startNode.id, endNodeId, pathSegments, pathNodes, this.getDistance(endNodeId)) :
|
||||
new PathInformation(this.startNode.id, endNodeId);
|
||||
}
|
||||
|
||||
getAllShortestPathMap() {
|
||||
const results = new Map();
|
||||
for (const endNodeId of this.digraph.getAllNodeIds()) {
|
||||
const pathInformation = this.getShortestPathInformation(endNodeId);
|
||||
if (pathInformation.feasible) {
|
||||
results.set(endNodeId, pathInformation);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
export class PathInformation {
|
||||
constructor(startId, endId, pathSegments, pathNodes, distance) {
|
||||
this.distance = distance;
|
||||
// - we assign it first to make this attribute the first in JSON
|
||||
this.startId = startId;
|
||||
this.endId = endId;
|
||||
if (pathSegments != null) {
|
||||
this.segments = pathSegments;
|
||||
this.nodes = pathNodes;
|
||||
}
|
||||
this.feasible = pathSegments != null;
|
||||
}
|
||||
|
||||
briefClone() {
|
||||
return new PathInformation(this.startId, this.endId, null, null, this.distance);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
export function singlePathToHTML(pathInformation) {
|
||||
return pathInformation.feasible ?
|
||||
"Shortest path '" + pathInformation.startId + "' ➔ '" + pathInformation.endId + "': "
|
||||
+ pathInformationToHTML(pathInformation) :
|
||||
"No path '" + pathInformation.startId + "' ➔ '" + pathInformation.endId + "'";
|
||||
}
|
||||
|
||||
export function allPathsToHTML(tree, limitOfNodes) {
|
||||
try {
|
||||
const pathMap = tree.getAllShortestPathMap();
|
||||
let report = "All " + pathMap.size + " shortest paths from '" + tree.startNode.id + "':<br>\n";
|
||||
let count = 0;
|
||||
for (const [key, path] of pathMap) {
|
||||
report += " [#" + (count + 1) + "] ...➔ '"
|
||||
+ key + "': " + pathInformationToHTML(path) + "<br>\n";
|
||||
++count;
|
||||
if (limitOfNodes && count > limitOfNodes) {
|
||||
report += "...<br>\n"
|
||||
break;
|
||||
}
|
||||
}
|
||||
return report;
|
||||
} catch (e) {
|
||||
return String(e);
|
||||
}
|
||||
}
|
||||
|
||||
export function pathInformationToHTML(path) {
|
||||
let s = JSON.stringify(path);
|
||||
s = toHtmlEntities(s);
|
||||
s = s.replace(/->/g, "→");
|
||||
return s;
|
||||
}
|
||||
|
||||
function toHtmlEntities(s) {
|
||||
return s.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
// Created on the base of the project:
|
||||
// https://github.com/noamsauerutley/shortest-path
|
||||
|
||||
// MIT License
|
||||
|
||||
// Copyright 2020 © Noam Sauer-Utley. https://www.noamsauerutley.com/
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
|
||||
class ShortestPathTree {
|
||||
constructor(graph) {
|
||||
if (!graph)
|
||||
throw "No graph passed: " + graph;
|
||||
this.graph = graph;
|
||||
this.startNode = null;
|
||||
this.parents = {};
|
||||
this.distances = {};
|
||||
this.numberOfNodes = Object.entries(graph).length;
|
||||
}
|
||||
|
||||
build(startNode) {
|
||||
if (!this.graph[startNode])
|
||||
throw "No start node '" + startNode + "'";
|
||||
this.startNode = startNode;
|
||||
this.distances = {};
|
||||
this.distances = Object.assign(this.distances, this.graph[startNode]);
|
||||
|
||||
this.parents = {};
|
||||
// track paths
|
||||
for (const child in this.graph[startNode]) {
|
||||
this.parents[child] = startNode;
|
||||
}
|
||||
|
||||
// track nodes that have already been visited
|
||||
const visited = [];
|
||||
|
||||
// find the nearest node
|
||||
let node = this.shortestDistanceNode(visited);
|
||||
|
||||
// for that node
|
||||
while (node) {
|
||||
// find its distance from the start node & its child nodes
|
||||
const distance = this.distances[node];
|
||||
const children = this.graph[node];
|
||||
// for each of those child nodes
|
||||
for (const child in children) {
|
||||
// make sure each child node is not the start node
|
||||
if (String(child) === String(startNode)) {
|
||||
continue;
|
||||
}
|
||||
// save the distance from the start node to the child node
|
||||
const newdistance = distance + children[child];
|
||||
// if there's no recorded distance from the start node to the child node in the distances object
|
||||
// or if the recorded distance is shorter than the previously stored distance from the start node to the child node
|
||||
// save the distance to the object
|
||||
// record the path
|
||||
if (!this.distances[child] || this.distances[child] > newdistance) {
|
||||
this.distances[child] = newdistance;
|
||||
this.parents[child] = node;
|
||||
}
|
||||
}
|
||||
// move the node to the visited set
|
||||
visited.push(node);
|
||||
// move to the nearest neighbor node
|
||||
node = this.shortestDistanceNode(visited);
|
||||
}
|
||||
}
|
||||
|
||||
getShortestPath(endNode) {
|
||||
if (!this.graph[endNode])
|
||||
throw "No end node '" + endNode + "'";
|
||||
let parent = this.parents[endNode];
|
||||
if (parent == null) {
|
||||
return null;
|
||||
}
|
||||
const shortestPath = [endNode];
|
||||
while (parent) {
|
||||
shortestPath.push(parent);
|
||||
parent = this.parents[parent];
|
||||
}
|
||||
shortestPath.reverse();
|
||||
return shortestPath;
|
||||
}
|
||||
|
||||
// private
|
||||
shortestDistanceNode(visited) {
|
||||
let shortest = null;
|
||||
|
||||
for (const node in this.distances) {
|
||||
const currentIsShortest =
|
||||
shortest === null || this.distances[node] < this.distances[shortest];
|
||||
if (currentIsShortest && !visited.includes(node)) {
|
||||
shortest = node;
|
||||
}
|
||||
}
|
||||
return shortest;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
export class MinHeap {
|
||||
|
||||
constructor(nodeLessFunction) {
|
||||
// Initialing the array heap and adding a dummy element at index 0
|
||||
this.heap = [null];
|
||||
this.nodeLessFunction = nodeLessFunction ?? function (a, b) {
|
||||
return a < b
|
||||
};
|
||||
}
|
||||
|
||||
size() {
|
||||
return this.heap.length - 1;
|
||||
}
|
||||
|
||||
isEmpty() {
|
||||
return this.size() <= 0;
|
||||
}
|
||||
|
||||
getMin() {
|
||||
return this.heap[1];
|
||||
}
|
||||
|
||||
checkIntegrity() {
|
||||
for (let k = 2; k < this.heap.length; k++) {
|
||||
// Start from k = 2: element #1 has no parent
|
||||
if (this.nodeLessFunction(this.heap[k], this.heap[k >> 1])) {
|
||||
throw "Heap damaged: element #" + k + " < #" + (k >> 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
insert(node) {
|
||||
this.heap.push(node);
|
||||
|
||||
if (this.heap.length > 1) {
|
||||
let current = this.heap.length - 1;
|
||||
|
||||
let parent = current >> 1;
|
||||
while (current > 1 && this.nodeLessFunction(this.heap[current], this.heap[parent])) {
|
||||
this.swap(parent, current);
|
||||
current = parent;
|
||||
parent = current >> 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
remove() {
|
||||
const heapSize = this.size();
|
||||
if (heapSize < 0) {
|
||||
throw "Assertion: negative heap size " + heapSize;
|
||||
}
|
||||
if (this.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
// Smallest element is at the index 1 in the heap array
|
||||
const smallest = this.heap[1];
|
||||
// console.log("smallest: " + smallest);
|
||||
|
||||
// When there are more than two elements in the array, we put the right most element at the first position
|
||||
// and start comparing nodes with the child nodes
|
||||
if (this.heap.length > 2) {
|
||||
this.heap[1] = this.heap[this.heap.length - 1];
|
||||
// - 1st element should be removing; replace it with the last
|
||||
this.heap.splice(heapSize);
|
||||
// - remove the last element (duplicate)
|
||||
|
||||
if (this.heap.length == 3) {
|
||||
if (this.nodeLessFunction(this.heap[2], this.heap[1])) {
|
||||
this.swap(1, 2);
|
||||
}
|
||||
return smallest;
|
||||
}
|
||||
|
||||
let current = 1;
|
||||
let leftChildIndex = current * 2;
|
||||
let rightChildIndex = leftChildIndex + 1;
|
||||
|
||||
// Note: in original version there was a bug: it checked
|
||||
// this.heap[...ChildIndex]
|
||||
// (will be "false" for number element 0!) instead of correct
|
||||
// ...ChildIndex] <= heapSize
|
||||
// Moreover, it is possible that only left index is inside heapSide!
|
||||
// Note: < , not <= ! heapSize is the size BEFORE removing the last element
|
||||
for (; ;) {
|
||||
let newCurrent = -1;
|
||||
if (this.existsAndLess(rightChildIndex, current)) {
|
||||
// So, also leftChildIndex < this.heap.length!
|
||||
// No sense to compare leftChildIndex and current:
|
||||
// in any case, we will swap with smallest from leftChildIndex and rightChildIndex
|
||||
newCurrent = this.nodeLessFunction(this.heap[leftChildIndex], this.heap[rightChildIndex]) ?
|
||||
leftChildIndex :
|
||||
rightChildIndex;
|
||||
} else {
|
||||
if (this.existsAndLess(leftChildIndex, current)) {
|
||||
newCurrent = leftChildIndex;
|
||||
} else {
|
||||
// Heap is restored
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.swap(current, newCurrent);
|
||||
current = newCurrent;
|
||||
leftChildIndex = current * 2;
|
||||
rightChildIndex = leftChildIndex + 1;
|
||||
}
|
||||
} else {
|
||||
// If there are only two elements in the array (in other words, only 1 element in the heap),
|
||||
// we directly splice out the first element
|
||||
this.heap.splice(1, 1);
|
||||
}
|
||||
return smallest;
|
||||
}
|
||||
|
||||
// private
|
||||
existsAndLess(checked, other) {
|
||||
return checked < this.heap.length && this.nodeLessFunction(this.heap[checked], this.heap[other]);
|
||||
}
|
||||
|
||||
// private
|
||||
swap(i, j) {
|
||||
// Swapping the two nodes by using the ES6 destructuring syntax:
|
||||
// works little faster than usual exchange via "temp" variable
|
||||
[this.heap[i], this.heap[j]] = [this.heap[j], this.heap[i]]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
// See https://blog.bitsrc.io/implementing-heaps-in-javascript-c3fbf1cb2e65
|
||||
class MinHeap {
|
||||
|
||||
constructor() {
|
||||
/* Initialing the array heap and adding a dummy element at index 0 */
|
||||
this.heap = [null]
|
||||
}
|
||||
|
||||
getMin() {
|
||||
/* Accessing the min element at index 1 in the heap array */
|
||||
return this.heap[1]
|
||||
}
|
||||
|
||||
insert(node) {
|
||||
|
||||
/* Inserting the new node at the end of the heap array */
|
||||
this.heap.push(node)
|
||||
|
||||
/* Finding the correct position for the new node */
|
||||
|
||||
if (this.heap.length > 1) {
|
||||
let current = this.heap.length - 1
|
||||
|
||||
/* Traversing up the parent node until the current node (current) is greater than the parent (current/2)*/
|
||||
while (current > 1 && this.heap[Math.floor(current / 2)] > this.heap[current]) {
|
||||
|
||||
/* Swapping the two nodes by using the ES6 destructuring syntax*/
|
||||
[this.heap[Math.floor(current / 2)], this.heap[current]] = [this.heap[current], this.heap[Math.floor(current / 2)]]
|
||||
current = Math.floor(current / 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
remove() {
|
||||
/* Smallest element is at the index 1 in the heap array */
|
||||
const smallest = this.heap[1]
|
||||
|
||||
/* When there are more than two elements in the array, we put the right most element at the first position
|
||||
and start comparing nodes with the child nodes
|
||||
*/
|
||||
if (this.heap.length > 2) {
|
||||
this.heap[1] = this.heap[this.heap.length - 1]
|
||||
this.heap.splice(this.heap.length - 1)
|
||||
|
||||
if (this.heap.length === 3) {
|
||||
if (this.heap[1] > this.heap[2]) {
|
||||
[this.heap[1], this.heap[2]] = [this.heap[2], this.heap[1]]
|
||||
}
|
||||
return smallest
|
||||
}
|
||||
|
||||
let current = 1
|
||||
let leftChildIndex = current * 2
|
||||
let rightChildIndex = current * 2 + 1
|
||||
|
||||
while (this.heap[leftChildIndex] &&
|
||||
this.heap[rightChildIndex] &&
|
||||
(this.heap[current] > this.heap[leftChildIndex] ||
|
||||
this.heap[current] > this.heap[rightChildIndex])) {
|
||||
if (this.heap[leftChildIndex] < this.heap[rightChildIndex]) {
|
||||
[this.heap[current], this.heap[leftChildIndex]] = [this.heap[leftChildIndex], this.heap[current]]
|
||||
current = leftChildIndex
|
||||
} else {
|
||||
[this.heap[current], this.heap[rightChildIndex]] = [this.heap[rightChildIndex], this.heap[current]]
|
||||
current = rightChildIndex
|
||||
}
|
||||
|
||||
leftChildIndex = current * 2
|
||||
rightChildIndex = current * 2 + 1
|
||||
}
|
||||
}
|
||||
|
||||
/* If there are only two elements in the array, we directly splice out the first element */
|
||||
|
||||
else if (this.heap.length === 2) {
|
||||
this.heap.splice(1, 1)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
|
||||
return smallest
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Simple Heap Test</title>
|
||||
<meta charset="UTF-8">
|
||||
<script type="module">
|
||||
import {MinHeap} from "./min_heap.js"
|
||||
|
||||
const n = 11;
|
||||
|
||||
function newNode(x, y) {
|
||||
return {x: x, y: y};
|
||||
}
|
||||
|
||||
function less(a, b) {
|
||||
const result = a.x < b.x;
|
||||
// console.log(JSON.stringify(a) + " and " + JSON.stringify(b) + ": " + result);
|
||||
return result;
|
||||
}
|
||||
|
||||
function testHeap() {
|
||||
const heap = new MinHeap(less);
|
||||
let report = "";
|
||||
for (let k = 0; k < n; k++) {
|
||||
heap.insert(newNode(k, k));
|
||||
}
|
||||
report += checkHeap(heap);
|
||||
report += "\n";
|
||||
|
||||
for (let k = 0; k < n; k++) {
|
||||
heap.insert(newNode(-5 * k, null));
|
||||
}
|
||||
report += checkHeap(heap);
|
||||
report += "\n";
|
||||
|
||||
for (let k = 0; k < n; k++) {
|
||||
heap.insert(newNode(Math.random() * 100, 12));
|
||||
}
|
||||
report += checkHeap(heap);
|
||||
report += "\n";
|
||||
document.getElementById("report").innerHTML = "<pre>" + report + "</pre>";
|
||||
}
|
||||
|
||||
function checkHeap(heap) {
|
||||
try {
|
||||
heap.checkIntegrity();
|
||||
let last = null;
|
||||
let report = "";
|
||||
for (let k = 0; k < n; k++) {
|
||||
const a = heap.remove();
|
||||
report += k + ": " + JSON.stringify(a) + "\n";
|
||||
if (last != null && less(a, last)) {
|
||||
throw "Error: " + JSON.stringify(a) + " < " + JSON.stringify(last);
|
||||
}
|
||||
last = a;
|
||||
}
|
||||
return report;
|
||||
} catch (e) {
|
||||
document.getElementById("error").innerHTML = e;
|
||||
throw msg;
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById("buttonTest").onclick = testHeap;
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div style="color:red;font-weight:bold" id="error"></div>
|
||||
<p>
|
||||
<button id="buttonTest">Run</button>
|
||||
</p>
|
||||
<div id="report"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
import {Digraph, GraphNode} from "../digraphs/digraph.js"
|
||||
import {checkSegment} from "./digraph_ab_elements.js"
|
||||
|
||||
// Example of segments argument (probably JSON array):
|
||||
// [
|
||||
// {
|
||||
// "id": "segment1",
|
||||
// "weight": 8,
|
||||
// "firstPoint": "p1",
|
||||
// "firstSide": "A",
|
||||
// "secondPoint": "p2",
|
||||
// "secondSide": "B"
|
||||
// },
|
||||
// {
|
||||
// "id": "segment2",
|
||||
// "weight": 3,
|
||||
// "firstPoint": "p2",
|
||||
// "firstSide": "A",
|
||||
// "secondPoint": "p3",
|
||||
// "secondSide": "B"
|
||||
// }
|
||||
// ...
|
||||
// ]
|
||||
// Every point side (A or B) of a point P is a vertex of undirected graph (P/A or P/B),
|
||||
// BUT path cannot enter to side A and exit from the same side A.
|
||||
// So, every segment, connecting side X of point P with side Y of point Q,
|
||||
// is transformed to a pair of digraph edges: P/X -> Q/inv(Y), Q/Y -> P/inv(X),
|
||||
// where inv(A)=B, inv(B)=A
|
||||
|
||||
export function abSegmentsToDigraph(segments) {
|
||||
const allPointIds = new Set();
|
||||
for (const segment of segments) {
|
||||
checkSegment(segment);
|
||||
addPointPair(allPointIds, segment);
|
||||
}
|
||||
const nodesA = toDigraphNodes(allPointIds, "A");
|
||||
const nodesB = toDigraphNodes(allPointIds, "B");
|
||||
for (const segment of segments) {
|
||||
addFirstToSecondEdge(nodesA, nodesB, segment);
|
||||
addSecondToFirstEdge(nodesA, nodesB, segment);
|
||||
}
|
||||
const result = new Digraph();
|
||||
result.addNodes(nodesA.values());
|
||||
result.addNodes(nodesB.values());
|
||||
return result;
|
||||
}
|
||||
|
||||
export function pointToNode(point, side) {
|
||||
return point + "/" + side;
|
||||
}
|
||||
|
||||
export function nodeToPoint(node) {
|
||||
if (node.endsWith("/A") || node.endsWith("/B")) {
|
||||
return node.substring(0, node.length - 2);
|
||||
} else {
|
||||
throw "'" + node + "' is not a correct node name in AB-digraph";
|
||||
}
|
||||
}
|
||||
|
||||
// private
|
||||
function addPointPair(pointSet, segment) {
|
||||
pointSet.add(String(segment.firstPoint));
|
||||
pointSet.add(String(segment.secondPoint));
|
||||
}
|
||||
|
||||
// private
|
||||
function toDigraphNodes(pointSet, side) {
|
||||
const result = new Map();
|
||||
for (const point of pointSet) {
|
||||
result.set(point, new GraphNode(pointToNode(point, side)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// private
|
||||
function addFirstToSecondEdge(nodesA, nodesB, segment) {
|
||||
const firstNodes = segment.firstSide == "A" ? nodesB : nodesA;
|
||||
// - inversion!
|
||||
const secondNodes = segment.secondSide == "A" ? nodesA : nodesB;
|
||||
const firstNode = firstNodes.get(segment.firstPoint);
|
||||
const secondNode = secondNodes.get(segment.secondPoint);
|
||||
firstNode.addEdge(segment.weight, secondNode.id, segment.id);
|
||||
}
|
||||
|
||||
// private
|
||||
function addSecondToFirstEdge(nodesA, nodesB, segment) {
|
||||
const firstNodes = segment.firstSide == "A" ? nodesA : nodesB;
|
||||
const secondNodes = segment.secondSide == "A" ? nodesB : nodesA;
|
||||
// - inversion!
|
||||
const firstNode = firstNodes.get(segment.firstPoint);
|
||||
const secondNode = secondNodes.get(segment.secondPoint);
|
||||
secondNode.addEdge(segment.weight, firstNode.id, segment.id);
|
||||
}
|
||||
|
||||
// private
|
||||
function otherSide(side) {
|
||||
return side == "A" ? "B" : "A";
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
export class DigraphABSegment {
|
||||
constructor(id, firstPoint, firstSide, secondPoint, secondSide, weight) {
|
||||
this.id = id;
|
||||
this.firstPoint = firstPoint;
|
||||
this.firstSide = firstSide;
|
||||
this.secondPoint = secondPoint;
|
||||
this.secondSide = secondSide;
|
||||
this.weight = weight;
|
||||
checkSegment(this);
|
||||
}
|
||||
}
|
||||
|
||||
export class DigraphABPointPair {
|
||||
constructor(startPoint, endPoint, id, wireInfo = null) {
|
||||
this.id = id; // - optional
|
||||
this.startPoint = startPoint;
|
||||
this.endPoint = endPoint;
|
||||
this.route = null;
|
||||
this.wireInfo = wireInfo;
|
||||
}
|
||||
|
||||
briefClone() {
|
||||
const result = new DigraphABPointPair(this.startPoint, this.endPoint, this.id, this.wireInfo);
|
||||
if (this.route != null) {
|
||||
result.route = this.route.briefClone();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export function checkSegment(segment) {
|
||||
if (!segment.id)
|
||||
throw "No id attribute in segment " + JSON.stringify(segment);
|
||||
if (!segment.weight)
|
||||
throw "No weight attribute in segment " + JSON.stringify(segment);
|
||||
if (!segment.firstPoint)
|
||||
throw "No firstPoint attribute in segment " + JSON.stringify(segment);
|
||||
if (!segment.firstSide)
|
||||
throw "No firstSide attribute in segment " + JSON.stringify(segment);
|
||||
if (segment.firstSide != "A" && segment.firstSide != "B")
|
||||
throw "Invalid firstSide attribute '" + segment.firstSide
|
||||
+ "' (not 'A' or 'B') in segment " + JSON.stringify(segment);
|
||||
if (!segment.secondPoint) throw "No secondPoint attribute in segment " + JSON.stringify(segment);
|
||||
if (!segment.secondSide) throw "No secondSide attribute in segment " + JSON.stringify(segment);
|
||||
if (segment.secondSide != "A" && segment.secondSide != "B")
|
||||
throw "Invalid secondSide attribute '" + segment.secondSide
|
||||
+ "' (not 'A' or 'B') in segment " + JSON.stringify(segment);
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
import {DigraphABPointPair} from "./digraph_ab_elements.js"
|
||||
import {DigraphABShortestPathFinder} from "./digraph_ab_shortest_path_finder.js"
|
||||
|
||||
export function simpleArrayToPointPairs(array) {
|
||||
const result = [];
|
||||
for (const pair of array) {
|
||||
if (pair.startPoint == null)
|
||||
throw "No startPoint in element " + JSON.stringify(pair);
|
||||
if (pair.endPoint == null)
|
||||
throw "No endPoint in element " + JSON.stringify(pair);
|
||||
result.push(new DigraphABPointPair(pair.startPoint, pair.endPoint, pair.id, pair.wireInfo));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function findShortestPathForAllPairs(digraph, pointPairsArray) {
|
||||
sortPointPairArray(pointPairsArray);
|
||||
const finder = new DigraphABShortestPathFinder(digraph);
|
||||
for (const pair of pointPairsArray) {
|
||||
finder.processPair(pair);
|
||||
}
|
||||
return pointPairsArray;
|
||||
}
|
||||
|
||||
// Example:
|
||||
// function showProgressBar(part) {
|
||||
// // part is a number from 0.0 to 1.0
|
||||
// document.getElementById("progressBar").value = Math.round(part * 100)
|
||||
// }
|
||||
// IMPORTANT: this function SHOULD be called with "await" operator
|
||||
// (if you want to get correct pointPairsArray after its execution)
|
||||
export async function findShortestPathForAllPairsAsync(digraph, pointPairsArray, showProgressBar) {
|
||||
let lastTime = performance.now();
|
||||
sortPointPairArray(pointPairsArray);
|
||||
const finder = new DigraphABShortestPathFinder(digraph);
|
||||
for (let i = 0, n = pointPairsArray.length; i < n; i++) {
|
||||
lastTime = await updateProgress(i, n, showProgressBar, lastTime);
|
||||
finder.processPair(pointPairsArray[i]);
|
||||
}
|
||||
return pointPairsArray;
|
||||
}
|
||||
|
||||
export function sortPointPairArray(pairArray) {
|
||||
pairArray.sort(function (a, b) {
|
||||
if (a.startPoint > b.startPoint) {
|
||||
return 1;
|
||||
}
|
||||
if (a.startPoint < b.startPoint) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
// private
|
||||
async function updateProgress(i, n, showProgressBar, lastTime) {
|
||||
if (n <= 1) {
|
||||
return lastTime;
|
||||
}
|
||||
if (i != n - 1) {
|
||||
// n-1 is shown always
|
||||
const t = performance.now();
|
||||
// console.log(lastTime +"; " + (t - lastTime));
|
||||
if (lastTime != null && t - lastTime < 200) {
|
||||
// less than 200 ms from last showing
|
||||
return lastTime;
|
||||
}
|
||||
lastTime = t;
|
||||
}
|
||||
if (showProgressBar != null) {
|
||||
showProgressBar(i / (n - 1));
|
||||
}
|
||||
if (i != n - 1) {
|
||||
await sleep(1);
|
||||
}
|
||||
return lastTime;
|
||||
}
|
||||
|
||||
// private
|
||||
async function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
import {nodeToPoint, pointToNode} from "./digraph_ab_builder.js"
|
||||
import {PathInformation, ShortestPathTree} from "../digraphs/spt.js"
|
||||
|
||||
export class DigraphABShortestPathFinder {
|
||||
constructor(digraph) {
|
||||
this.treeA = new ShortestPathTree(digraph);
|
||||
this.treeB = new ShortestPathTree(digraph);
|
||||
this.startPointId = null;
|
||||
}
|
||||
|
||||
processPair(pointPair) {
|
||||
this.rebuild(pointPair.startPoint);
|
||||
pointPair.route = this.getShortestPathInformation(pointPair.endPoint);
|
||||
}
|
||||
|
||||
rebuild(startPointId) {
|
||||
if (startPointId != this.startPointId) {
|
||||
this.build(startPointId);
|
||||
}
|
||||
}
|
||||
|
||||
build(startPointId) {
|
||||
this.treeA.build(pointToNode(startPointId, "A"));
|
||||
this.treeB.build(pointToNode(startPointId, "B"));
|
||||
this.startPointId = startPointId;
|
||||
}
|
||||
|
||||
// Rarely used
|
||||
getShortestPathInformationForBothSides(endPointId) {
|
||||
const result = [];
|
||||
addIfExists(result, this.treeA.getShortestPathInformation(pointToNode(endPointId, "A")));
|
||||
addIfExists(result, this.treeA.getShortestPathInformation(pointToNode(endPointId, "B")));
|
||||
addIfExists(result, this.treeB.getShortestPathInformation(pointToNode(endPointId, "A")));
|
||||
addIfExists(result, this.treeB.getShortestPathInformation(pointToNode(endPointId, "B")));
|
||||
return result;
|
||||
}
|
||||
|
||||
getShortestPathInformation(endPointId, includePointsWhenNotFound) {
|
||||
const forBothSides = this.getShortestPathInformationForBothSides(endPointId);
|
||||
if (forBothSides.length == 0) {
|
||||
return includePointsWhenNotFound ?
|
||||
new PathInformation(this.startPointId, endPointId) :
|
||||
new PathInformation();
|
||||
}
|
||||
let result = forBothSides[0];
|
||||
for (let k = 1; k < forBothSides.length; k++) {
|
||||
if (forBothSides[k].distance < result.distance) {
|
||||
result = forBothSides[k];
|
||||
}
|
||||
}
|
||||
if (containsDuplicates(result.nodes)) {
|
||||
result.containsDuplicates = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export function containsDuplicates(nodesArray) {
|
||||
const points = new Set();
|
||||
for (const node of nodesArray) {
|
||||
const p = nodeToPoint(node);
|
||||
if (points.has(p)) {
|
||||
return true;
|
||||
}
|
||||
points.add(p);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// private
|
||||
function addIfExists(array, pathInformation) {
|
||||
if (pathInformation.feasible) {
|
||||
array.push(pathInformation);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
import {DigraphABPointPair, DigraphABSegment} from "./sided_ab_graphs/digraph_ab_elements.js"
|
||||
|
||||
export function makeTestGridGraph(width, height, straightWeight, diagonalWeight, includeDiagonals) {
|
||||
straightWeight = parseFloat(straightWeight);
|
||||
diagonalWeight = parseFloat(diagonalWeight);
|
||||
const g = {};
|
||||
for (let i = 0; i < height; i++) {
|
||||
for (let j = 0; j < width; j++) {
|
||||
const node = {};
|
||||
if (i < height - 1) {
|
||||
node[vertex(i + 1, j)] = straightWeight;
|
||||
}
|
||||
if (j < width - 1) {
|
||||
node[vertex(i, j + 1)] = straightWeight;
|
||||
}
|
||||
if (i < height - 1 && j < width - 1 && includeDiagonals) {
|
||||
node[vertex(i + 1, j + 1)] = diagonalWeight;
|
||||
}
|
||||
g[vertex(i, j)] = node;
|
||||
}
|
||||
}
|
||||
return g;
|
||||
}
|
||||
|
||||
export function makeTestGridAbGraph(width, height, straightWeight, diagonalWeight, includeDiagonals) {
|
||||
straightWeight = parseFloat(straightWeight);
|
||||
diagonalWeight = parseFloat(diagonalWeight);
|
||||
const g = [];
|
||||
for (let i = 0; i < height; i++) {
|
||||
for (let j = 0; j < width; j++) {
|
||||
const v = vertex(i, j);
|
||||
if (i < height - 1) {
|
||||
g.push(new DigraphABSegment("Down-" + i + "-" + j, v, "B", vertex(i + 1, j), "A", straightWeight));
|
||||
}
|
||||
if (j < width - 1) {
|
||||
g.push(new DigraphABSegment("Right-" + i + "-" + j, v, "B", vertex(i, j + 1), "A", straightWeight));
|
||||
}
|
||||
if (i < height - 1 && j < width - 1 && includeDiagonals) {
|
||||
g.push(new DigraphABSegment("Diag-" + i + "-" + j, v, "B", vertex(i + 1, j + 1), "A", diagonalWeight));
|
||||
}
|
||||
}
|
||||
}
|
||||
return g;
|
||||
}
|
||||
|
||||
export function makeTestGridAbPointPairs(width, height, numberOfPairs, seed) {
|
||||
const pairs = [];
|
||||
const rnd = mulberryRandom32(seed ?? 157);
|
||||
pairs.push(new DigraphABPointPair(vertex(0, 0), vertex(height - 1, width - 1)));
|
||||
for (let k = 1; k < numberOfPairs; k++) {
|
||||
const i1 = randomInt(0, height, rnd);
|
||||
const i2 = randomInt(0, height, rnd);
|
||||
const j1 = randomInt(0, width, rnd);
|
||||
const j2 = randomInt(0, width, rnd);
|
||||
pairs.push(new DigraphABPointPair(vertex(i1, j1), vertex(i2, j2)));
|
||||
}
|
||||
return pairs;
|
||||
}
|
||||
|
||||
function vertex(y, x) {
|
||||
return "V" + x + "|" + y;
|
||||
}
|
||||
|
||||
function randomInt(min, max, rnd) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
return Math.floor(rnd() * (max - min)) + min;
|
||||
}
|
||||
|
||||
// Allows to specify start seed, unlike Math.random
|
||||
function mulberryRandom32(seed) {
|
||||
return function () {
|
||||
let t = seed += 0x6D2B79F5;
|
||||
t = Math.imul(t ^ t >>> 15, t | 1);
|
||||
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
||||
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Sided-A/B Path Demo</title>
|
||||
<link rel="stylesheet" type="text/css" href="demo.css">
|
||||
<script type="module">
|
||||
import {parseJsonAndPrintError, sidedAdResolve} from "./demo.js"
|
||||
|
||||
function doSimple() {
|
||||
const startVertex = document.querySelector("#simpleStart").value;
|
||||
const endVertex = document.querySelector("#simpleEnd").value;
|
||||
const segments = parseJsonAndPrintError(document.querySelector("#segments").value, "simpleError");
|
||||
const report = sidedAdResolve(segments, startVertex, endVertex, "simpleError");
|
||||
document.querySelector("#simpleResults").innerHTML = report;
|
||||
}
|
||||
|
||||
/*
|
||||
function doGrid() {
|
||||
const startVertex = document.querySelector("#gridStart").value;
|
||||
const endVertex = document.querySelector("#gridEnd").value;
|
||||
const graphJson = makeTestGridGraph(
|
||||
document.querySelector("#gridWidth").value,
|
||||
document.querySelector("#gridHeight").value,
|
||||
document.querySelector("#straightWeight").value,
|
||||
document.querySelector("#diagonalWeight").value,
|
||||
document.querySelector("#includeDiagonals").checked);
|
||||
const report = sidedAdResolve(segments, startVertex, endVertex, "gridError");
|
||||
document.querySelector("#gridResults").innerHTML = report;
|
||||
}
|
||||
*/
|
||||
document.querySelector("#doSimple").onclick = doSimple;
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="container">
|
||||
<h2>A/B sided graphs simple test</h2>
|
||||
<p id="simpleError" class="error"></p>
|
||||
<p>Enter JSON array of segments of some A/B-graph:</p>
|
||||
<textarea id="segments" rows="12" cols="150">
|
||||
[
|
||||
{ "id": "seg1", "firstPoint": "p1", "firstSide": "A", "secondPoint": "p2", "secondSide": "A", "weight": 8},
|
||||
{ "id": "seg2", "firstPoint": "p1", "firstSide": "B", "secondPoint": "p3", "secondSide": "A", "weight": 2},
|
||||
{ "id": "seg3", "firstPoint": "p1", "firstSide": "B", "secondPoint": "p2", "secondSide": "A", "weight": 3},
|
||||
{ "id": "seg11", "firstPoint": "p1", "firstSide": "A", "secondPoint": "p5", "secondSide": "A", "weight": 10.1},
|
||||
{ "id": "seg12", "firstPoint": "p1", "firstSide": "A", "secondPoint": "p5", "secondSide": "B", "weight": 10.1},
|
||||
{ "id": "seg21", "firstPoint": "p2", "firstSide": "B", "secondPoint": "p3", "secondSide": "B", "weight": 15},
|
||||
{ "id": "otherSeg1", "firstPoint": "other1", "firstSide": "A", "secondPoint": "other2", "secondSide": "B", "weight": 115}
|
||||
]
|
||||
</textarea>
|
||||
<p>
|
||||
Start point: <input id="simpleStart" value="p2"><br>
|
||||
End point: <input id="simpleEnd" value="p3">
|
||||
</p>
|
||||
<p>
|
||||
<button id="doSimple">Process graph above</button>
|
||||
</p>
|
||||
<p id="simpleResults">Click button about to see results here
|
||||
</p>
|
||||
|
||||
<!--
|
||||
<h2>Grid graphs</h2>
|
||||
<p>
|
||||
Start vertex: <input id="gridStart" value="V-0-0"><br>
|
||||
End vertex: <input id="gridEnd" value="V-19-9"> (common syntax: 'V-XX-YY')<br>
|
||||
Grid width: <input id="gridWidth" value="20"><br>
|
||||
Grid height: <input id="gridHeight" value="10"><br>
|
||||
Straigh weight: <input id="straightWeight" value="1"><br>
|
||||
Diagonal weight: <input id="diagonalWeight" value="1.41"><br>
|
||||
Include diagonals: <input id="includeDiagonals" type="checkbox">
|
||||
</p>
|
||||
<p><button onclick="doGrid()">Process grid-based graph</button></p>
|
||||
<p id="gridResults">Click button about to see results here</p>
|
||||
-->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Sided-A/B Path Demo</title>
|
||||
<link rel="stylesheet" type="text/css" href="demo.css">
|
||||
<script type="module">
|
||||
import {parseJsonAndPrintError, sidedAdPairsResolve} from "./demo.js"
|
||||
|
||||
async function doAnalyse() {
|
||||
const segments = parseJsonAndPrintError(document.querySelector("#segments").value, "error");
|
||||
const pairs = parseJsonAndPrintError(document.querySelector("#pairs").value, "error");
|
||||
const report = await sidedAdPairsResolve(segments, pairs, false, "error", "timing");
|
||||
document.querySelector("#results").value = report;
|
||||
}
|
||||
|
||||
document.querySelector("#doAnalyse").onclick = doAnalyse;
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="container">
|
||||
<h2>A/B sided graphs main demo</h2>
|
||||
<p id="error" class="error"></p>
|
||||
<p>Enter JSON array of segments of some A/B-graph:</p>
|
||||
<textarea id="segments" rows="12" cols="150">
|
||||
[
|
||||
{ "id": "seg1", "firstPoint": "p1", "firstSide": "A", "secondPoint": "p2", "secondSide": "A", "weight": 8},
|
||||
{ "id": "seg2", "firstPoint": "p1", "firstSide": "B", "secondPoint": "p3", "secondSide": "A", "weight": 2},
|
||||
{ "id": "seg3", "firstPoint": "p1", "firstSide": "B", "secondPoint": "p2", "secondSide": "A", "weight": 3},
|
||||
{ "id": "seg11", "firstPoint": "p1", "firstSide": "A", "secondPoint": "p5", "secondSide": "A", "weight": 10.1},
|
||||
{ "id": "seg12", "firstPoint": "p1", "firstSide": "A", "secondPoint": "p5", "secondSide": "B", "weight": 10.1},
|
||||
{ "id": "seg21", "firstPoint": "p2", "firstSide": "B", "secondPoint": "p3", "secondSide": "B", "weight": 15},
|
||||
{ "id": "otherSeg1", "firstPoint": "other1", "firstSide": "A", "secondPoint": "other2", "secondSide": "B", "weight": 115}
|
||||
]
|
||||
</textarea>
|
||||
<textarea id="pairs" rows="12" cols="150">
|
||||
[
|
||||
{ "id" : "path1", "startPoint": "p1", "endPoint": "p2" },
|
||||
{ "id" : "path2", "startPoint": "p2", "endPoint": "p3" },
|
||||
{ "startPoint": "p3", "endPoint": "p2" },
|
||||
{ "startPoint": "other1", "endPoint": "other2" }
|
||||
]
|
||||
</textarea>
|
||||
<p>
|
||||
<button id="doAnalyse">Process graph above</button>
|
||||
</p>
|
||||
<p id="timing">Click button about to see results below</p>
|
||||
|
||||
<textarea id="results" rows="12" cols="150" readonly>
|
||||
</textarea>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Sided-A/B Path Demo</title>
|
||||
<link rel="stylesheet" type="text/css" href="demo.css">
|
||||
<script type="module">
|
||||
import {makeTestGridAbGraph, makeTestGridAbPointPairs, sidedAdPairsResolve} from "./demo.js"
|
||||
import {_setUseMinHeap} from "./js/digraphs/spt.js"
|
||||
|
||||
async function doGrid() {
|
||||
_setUseMinHeap(document.querySelector("#useMinHeap").checked);
|
||||
// - for debugging needs (measuring speed); normally should be true
|
||||
|
||||
const width = document.querySelector("#gridWidth").value;
|
||||
const height = document.querySelector("#gridHeight").value;
|
||||
const straightWeight = document.querySelector("#straightWeight").value;
|
||||
const diagonalWeight = document.querySelector("#diagonalWeight").value;
|
||||
const includeDiagonals = document.querySelector("#includeDiagonals").checked;
|
||||
const brief = document.querySelector("#brief").checked;
|
||||
const numberOfPairs = parseInt(document.querySelector("#numberOfPairs").value);
|
||||
const randomSeed = parseInt(document.querySelector("#randomSeed").value);
|
||||
const t1 = performance.now();
|
||||
const segments = makeTestGridAbGraph(width, height, straightWeight, diagonalWeight, includeDiagonals);
|
||||
const pairs = makeTestGridAbPointPairs(width, height, numberOfPairs, randomSeed);
|
||||
document.querySelector("#segments").value = JSON.stringify(segments, null, 2);
|
||||
document.querySelector("#pairs").value = JSON.stringify(pairs, null, 2);
|
||||
const t2 = performance.now();
|
||||
document.querySelector("#preparingTiming").innerHTML = "Preparing: " + (t2 - t1) + " ms";
|
||||
|
||||
document.querySelector("#doGrid").disabled = true;
|
||||
// - important to prevent starting new threads of calculations while processing is not finished
|
||||
const report = await sidedAdPairsResolve(segments, pairs, brief, "error", "timing", "progressBar");
|
||||
document.querySelector("#doGrid").disabled = false;
|
||||
|
||||
document.querySelector("#results").value = report;
|
||||
}
|
||||
|
||||
document.querySelector("#doGrid").onclick = doGrid;
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="container">
|
||||
<h2>Grid A/B sided graphs speed test</h2>
|
||||
<p id="error" class="error"></p>
|
||||
<p>
|
||||
Grid width: <input id="gridWidth" value="50"><br>
|
||||
Grid height: <input id="gridHeight" value="50"><br>
|
||||
Straigh weight: <input id="straightWeight" value="1"><br>
|
||||
Diagonal weight: <input id="diagonalWeight" value="1.41"><br>
|
||||
Include diagonals: <input id="includeDiagonals" type="checkbox"><br>
|
||||
Number of random pairs: <input id="numberOfPairs" value="1000"><br>
|
||||
Random seed: <input id="randomSeed" value="0"><br>
|
||||
Brief results: <input id="brief" type="checkbox"><br>
|
||||
Use MinHeap structure (should be true): <input id="useMinHeap" type="checkbox" checked="checked"><br>
|
||||
</p>
|
||||
<p>
|
||||
<button id="doGrid"
|
||||
">Process grid-based A/B graph</button></p>
|
||||
<div id="preparingTiming">Click button to see results below</div>
|
||||
<div id="timing"></div>
|
||||
|
||||
<p>
|
||||
<progress id="progressBar" value="0" max="100" style="width:600px"></progress>
|
||||
</p>
|
||||
|
||||
<div>Results:</div>
|
||||
<textarea id="results" rows="12" cols="150" readonly>
|
||||
</textarea>
|
||||
|
||||
<p> </p>
|
||||
<div>Actually processed segments:</div>
|
||||
<textarea id="segments" rows="12" cols="150" readonly>
|
||||
</textarea>
|
||||
|
||||
<div>Actually processed pairs:</div>
|
||||
<textarea id="pairs" rows="12" cols="150" readonly>
|
||||
</textarea>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Simple Shortest-Path Demo</title>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" type="text/css" href="demo.css">
|
||||
<!--
|
||||
<script src="js/digraphs/digraph.js"></script>
|
||||
<script src="js/digraphs/digraph_simple_builder.js"></script>
|
||||
<script src="js/heap/min_heap.js"></script>
|
||||
<script src="js/digraphs/spt.js"></script>
|
||||
<script src="js/digraphs/spt_html.js"></script>
|
||||
<script src="js/test_digraph_builders.js"></script>
|
||||
<script src="demo.js"></script>
|
||||
-->
|
||||
<script type="module">
|
||||
import {makeTestGridGraph, parseJsonAndPrintError, simpleResolve} from "./demo.js"
|
||||
|
||||
function doSimple() {
|
||||
const startVertex = document.querySelector("#simpleStart").value;
|
||||
const endVertex = document.querySelector("#simpleEnd").value;
|
||||
const graphJson = parseJsonAndPrintError(document.querySelector("#graphJson").value, "simpleError");
|
||||
// let node = simpleObjectToGraphNode("0", new Array(["1", "2"]));
|
||||
// document.querySelector("#simpleResults").innerHTML = JSON.stringify(node);
|
||||
// let g = simpleObjectToDigraph(graphJson);
|
||||
// document.querySelector("#simpleResults").innerHTML = JSON.stringify(Array.from(g.nodeMap.entries()));
|
||||
const report = simpleResolve(graphJson, startVertex, endVertex, "simpleError");
|
||||
document.querySelector("#simpleResults").innerHTML = report;
|
||||
}
|
||||
|
||||
function doGrid() {
|
||||
const startVertex = document.querySelector("#gridStart").value;
|
||||
const endVertex = document.querySelector("#gridEnd").value;
|
||||
const graphJson = makeTestGridGraph(
|
||||
document.querySelector("#gridWidth").value,
|
||||
document.querySelector("#gridHeight").value,
|
||||
document.querySelector("#straightWeight").value,
|
||||
document.querySelector("#diagonalWeight").value,
|
||||
document.querySelector("#includeDiagonals").checked);
|
||||
const report = simpleResolve(graphJson, startVertex, endVertex, "gridError");
|
||||
document.querySelector("#gridResults").innerHTML = report;
|
||||
}
|
||||
|
||||
document.querySelector("#doSimple").onclick = doSimple;
|
||||
document.querySelector("#doGrid").onclick = doGrid;
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="container">
|
||||
<h2>Simple digraph SPT test</h2>
|
||||
<p id="simpleError" class="error"></p>
|
||||
<p>Enter graph:</p>
|
||||
<!--
|
||||
{
|
||||
"start": { "A": 5, "B": 2 },
|
||||
"A": { "start": 1, "C": 4, "D": 2 },
|
||||
"B": { "A": 8, "D": 7 },
|
||||
"C": { "D": 6, "end": 3 },
|
||||
"D": { "end": 1 },
|
||||
"end": {},
|
||||
"OtherA": {"OtherB" : 2 },
|
||||
"OtherB": {}
|
||||
}
|
||||
-->
|
||||
<textarea id="graphJson" rows="12" cols="100">
|
||||
{
|
||||
"0": { "2" : 26, "4": 38 },
|
||||
"1": { "3" : 29 },
|
||||
"2": { "7" : 34 },
|
||||
"3": { "6" : 52 },
|
||||
"4": { "5" : 35, "7": 37 },
|
||||
"5": { "7" : 28, "1": 32 },
|
||||
"6": { "0" : 58, "2": 40, "4": 93 },
|
||||
"7": { "3" : 39, "5": 28 },
|
||||
"OtherA": {"OtherB" : 2 },
|
||||
"OtherB": {}
|
||||
}
|
||||
</textarea>
|
||||
<!-- Tree above is from Sedgewick and Wayne -->
|
||||
<p>
|
||||
Start vertex: <input id="simpleStart" value="0"><br>
|
||||
End vertex: <input id="simpleEnd" value="7">
|
||||
</p>
|
||||
<p>
|
||||
<button id="doSimple">Process graph above</button>
|
||||
</p>
|
||||
<p id="simpleResults">Click button about to see results here<br>
|
||||
<span class="comment">For default graph example above, expected results are the following:<br><br>
|
||||
Shortest path '0' ➔ '7': {"distance":60,"startId":"0","endId":"7","segments":["0→2","2→7"],"nodes":["0","2","7"],"feasible":true}<br><br>All 7 shortest paths from '0':<br>
|
||||
[#1] ...➔ '1': {"distance":105,"startId":"0","endId":"1","segments":["0→4","4→5","5→1"],"nodes":["0","4","5","1"],"feasible":true}<br>
|
||||
[#2] ...➔ '2': {"distance":26,"startId":"0","endId":"2","segments":["0→2"],"nodes":["0","2"],"feasible":true}<br>
|
||||
[#3] ...➔ '3': {"distance":99,"startId":"0","endId":"3","segments":["0→2","2→7","7→3"],"nodes":["0","2","7","3"],"feasible":true}<br>
|
||||
[#4] ...➔ '4': {"distance":38,"startId":"0","endId":"4","segments":["0→4"],"nodes":["0","4"],"feasible":true}<br>
|
||||
[#5] ...➔ '5': {"distance":73,"startId":"0","endId":"5","segments":["0→4","4→5"],"nodes":["0","4","5"],"feasible":true}<br>
|
||||
[#6] ...➔ '6': {"distance":151,"startId":"0","endId":"6","segments":["0→2","2→7","7→3","3→6"],"nodes":["0","2","7","3","6"],"feasible":true}<br>
|
||||
[#7] ...➔ '7': {"distance":60,"startId":"0","endId":"7","segments":["0→2","2→7"],"nodes":["0","2","7"],"feasible":true}<br>
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<h2>Grid graphs</h2>
|
||||
<p id="gridError" class="error"></p>
|
||||
<p>
|
||||
Start vertex: <input id="gridStart" value="V0|0"><br>
|
||||
End vertex: <input id="gridEnd" value="V19|9"> (common syntax: 'Vxx|yy')<br>
|
||||
Grid width: <input id="gridWidth" value="100"><br>
|
||||
Grid height: <input id="gridHeight" value="100"><br>
|
||||
Straigh weight: <input id="straightWeight" value="1"><br>
|
||||
Diagonal weight: <input id="diagonalWeight" value="1.41"><br>
|
||||
Include diagonals: <input id="includeDiagonals" type="checkbox">
|
||||
</p>
|
||||
<p>
|
||||
<button id="doGrid">Process grid-based graph</button>
|
||||
</p>
|
||||
<p id="gridResults">Click button about to see results here</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
27
modules/workbenches/routingElectrical/index.ts
Normal file
27
modules/workbenches/routingElectrical/index.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import {WorkbenchConfig} from "cad/workbench/workbenchService";
|
||||
|
||||
//imports of feature history type commands
|
||||
import {AutoRouteOperation} from "./features/autoRoute/autoRoute.operation";
|
||||
import {GiFoldedPaper} from "react-icons/gi";
|
||||
|
||||
|
||||
//imports of action type commands
|
||||
|
||||
|
||||
|
||||
export const RoutingElectricalWorkspace: WorkbenchConfig = {
|
||||
|
||||
workbenchId: 'RoutingElectrical',
|
||||
features: [
|
||||
AutoRouteOperation,
|
||||
|
||||
],
|
||||
actions: [],
|
||||
ui: {
|
||||
toolbar: [
|
||||
'DATUM_CREATE', 'PLANE', 'EditFace', '-',
|
||||
'RE_AUTO_ROUTE',
|
||||
]
|
||||
},
|
||||
icon: GiFoldedPaper
|
||||
}
|
||||
Loading…
Reference in a new issue