Added start of wire harness workbench. Shortest path algorithm.

This commit is contained in:
Mike Molinari 2022-08-17 00:54:52 +00:00 committed by Val Erastov
parent 0cd501b8cc
commit dde86e056a
23 changed files with 1774 additions and 2 deletions

View file

@ -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,
]

View file

@ -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>&nbsp;";
//document.getElementById(timingElementId).innerHTML = timing;
if (brief) {
pointPairs = pointPairs.map(pair => pair.briefClone())
}
return pointPairs;
}

View file

@ -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;
}

View file

@ -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>&nbsp;";
} 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>&nbsp;";
} 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>&nbsp;";
//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;
}
}

View file

@ -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);
}
}

View file

@ -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;
}

View file

@ -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);
}
}

View file

@ -0,0 +1,37 @@
export function singlePathToHTML(pathInformation) {
return pathInformation.feasible ?
"Shortest path '" + pathInformation.startId + "' &#10132; '" + pathInformation.endId + "': "
+ pathInformationToHTML(pathInformation) :
"No path '" + pathInformation.startId + "' &#10132; '" + 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 += "&nbsp;&nbsp;&nbsp;&nbsp;[#" + (count + 1) + "] ...&#10132; '"
+ 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(/-&gt;/g, "&#8594;");
return s;
}
function toHtmlEntities(s) {
return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}

View file

@ -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;
}
}

View file

@ -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]]
}
}

View file

@ -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
}
}

View file

@ -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>

View file

@ -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";
}

View file

@ -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);
}

View file

@ -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));
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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>

View file

@ -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>

View file

@ -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>&nbsp;</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>

View file

@ -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>
&nbsp;&nbsp;&nbsp;&nbsp;[#1] ...➔ '1': {"distance":105,"startId":"0","endId":"1","segments":["0→4","4→5","5→1"],"nodes":["0","4","5","1"],"feasible":true}<br>
&nbsp;&nbsp;&nbsp;&nbsp;[#2] ...➔ '2': {"distance":26,"startId":"0","endId":"2","segments":["0→2"],"nodes":["0","2"],"feasible":true}<br>
&nbsp;&nbsp;&nbsp;&nbsp;[#3] ...➔ '3': {"distance":99,"startId":"0","endId":"3","segments":["0→2","2→7","7→3"],"nodes":["0","2","7","3"],"feasible":true}<br>
&nbsp;&nbsp;&nbsp;&nbsp;[#4] ...➔ '4': {"distance":38,"startId":"0","endId":"4","segments":["0→4"],"nodes":["0","4"],"feasible":true}<br>
&nbsp;&nbsp;&nbsp;&nbsp;[#5] ...➔ '5': {"distance":73,"startId":"0","endId":"5","segments":["0→4","4→5"],"nodes":["0","4","5"],"feasible":true}<br>
&nbsp;&nbsp;&nbsp;&nbsp;[#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>
&nbsp;&nbsp;&nbsp;&nbsp;[#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>

View 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
}