From 7c6196d563363c9849a9e0a1f4e2f4229abccd07 Mon Sep 17 00:00:00 2001 From: AmyangXYZ Date: Wed, 27 Dec 2023 23:48:52 -0500 Subject: [PATCH] find neighbors using kdtree --- src/hooks/useDrawTopology.ts | 2 +- src/networks/5G/network.ts | 4 +- src/networks/TSCH/network.ts | 8 +- src/networks/TSN/kdtree.ts | 83 +++++++++++++++++ src/networks/TSN/network.ts | 68 ++++---------- src/networks/TSN/node.ts | 4 +- src/networks/common.ts | 174 ++++++++++++++++++----------------- 7 files changed, 198 insertions(+), 145 deletions(-) create mode 100644 src/networks/TSN/kdtree.ts diff --git a/src/hooks/useDrawTopology.ts b/src/hooks/useDrawTopology.ts index 6c7ea75..a35c755 100644 --- a/src/hooks/useDrawTopology.ts +++ b/src/hooks/useDrawTopology.ts @@ -435,7 +435,7 @@ export function useDrawTopology(dom: HTMLElement) { let drawnLinks: { [uid: number]: any } = {} const drawLinks = () => { - for (const l of Network.Links.value) { + for (const l of Object.values(Network.Links.value)) { if (drawnLinks[l.uid] == undefined) { drawLink(l.uid, l.v1, l.v2) } diff --git a/src/networks/5G/network.ts b/src/networks/5G/network.ts index cafeed4..ee2d650 100644 --- a/src/networks/5G/network.ts +++ b/src/networks/5G/network.ts @@ -1,5 +1,5 @@ import { ref } from 'vue' -import { Network, NETWORK_TYPE, NODE_TYPE, type LinkMeta } from '../common' +import { Network, NETWORK_TYPE, NODE_TYPE } from '../common' import type { ScheduleConfig, FiveGNodeMeta } from './typedefs' import { SeededRandom } from '@/hooks/useSeed' @@ -54,7 +54,7 @@ export class FiveGNetwork extends Network { w: new Worker(new URL('@/networks/5G/node.ts', import.meta.url), { type: 'module' }) } // add links - this.Links.value.push({ uid: i * 3, v1: 0, v2: i }) + super.addLink(0, i) // send init msg // n.w!.postMessage({ diff --git a/src/networks/TSCH/network.ts b/src/networks/TSCH/network.ts index aee6315..71696e2 100644 --- a/src/networks/TSCH/network.ts +++ b/src/networks/TSCH/network.ts @@ -10,7 +10,7 @@ import type { import { ADDR, MSG_TYPES, PKT_TYPES, CELL_TYPES } from './typedefs' import { SeededRandom } from '@/hooks/useSeed' import { Network, NETWORK_TYPE, NODE_TYPE } from '../common' -import type { Packet, Message, MsgHandler, LinkMeta } from '../common' +import type { Packet, Message, MsgHandler } from '../common' export class TSCHNetwork extends Network { doneCnt = 0 @@ -80,11 +80,7 @@ export class TSCHNetwork extends Network { this.Nodes.value[node].parent = payload.parent this.Nodes.value[node].neighbors.push(payload.parent) this.Nodes.value[payload.parent].neighbors.push(node) - this.Links.value.push({ - uid: node * 2 + payload.parent * 3, - v1: node, - v2: payload.parent - }) + super.addLink(node, payload.parent) } this.Nodes.value[node].queueLen = payload.queue.length diff --git a/src/networks/TSN/kdtree.ts b/src/networks/TSN/kdtree.ts new file mode 100644 index 0000000..24cd324 --- /dev/null +++ b/src/networks/TSN/kdtree.ts @@ -0,0 +1,83 @@ +export class KDNode { + id: number + pos: [number, number] + left: KDNode | undefined + right: KDNode | undefined + constructor(id: number, pos: [number, number]) { + this.id = id + this.pos = pos + this.left = undefined + this.right = undefined + } +} + +export class KDTree { + root: KDNode | undefined + constructor() { + this.root = undefined + } + + Insert(node: KDNode): void { + this.root = this._insertRecur(this.root, node, 0) + } + private _insertRecur(current: KDNode | undefined, node: KDNode, depth: number): KDNode { + if (current == undefined) { + return node + } + const cd = depth % 2 // two dimension only + if (node.pos[cd] < current.pos[cd]) { + current.left = this._insertRecur(current.left, node, depth + 1) + } else { + current.right = this._insertRecur(current.right, node, depth + 1) + } + return current + } + + FindKNearest(pos: [number, number], k: number, range: number): number[] { + const nearestNodes: KDNode[] = [] + this._findKNearestRecur(this.root, pos, 0, k, range * range, nearestNodes) + return nearestNodes.map(({ id }) => id) + } + private _findKNearestRecur( + current: KDNode | undefined, + pos: [number, number], + depth: number, + k: number, + range: number, + nearestNodes: KDNode[] + ): void { + if (current == undefined) return + + const d = this._distanceSquared(current.pos, pos) + if (current.pos != pos && d <= range) { + if (nearestNodes.length < k) { + nearestNodes.push(current) + } else if (d < this._distanceSquared(pos, nearestNodes[k - 1].pos)) { + nearestNodes[k - 1] = current + } + nearestNodes.sort( + (a: KDNode, b: KDNode) => + this._distanceSquared(pos, a.pos) - this._distanceSquared(pos, b.pos) + ) + } + + const cd = depth % 2 + const diff = pos[cd] - current.pos[cd] + const closer = diff < 0 ? current.left : current.right + const farther = diff < 0 ? current.right : current.left + this._findKNearestRecur(closer, pos, depth + 1, k, range, nearestNodes) + + if ( + diff * diff <= range && + (nearestNodes.length < k || diff * diff < this._distanceSquared(pos, nearestNodes[k - 1].pos)) + ) { + this._findKNearestRecur(farther, pos, depth + 1, k, range, nearestNodes) + } + } + + private _distanceSquared(v1: [number, number], v2: [number, number]): number { + const dx = v2[0] - v1[0] + const dy = v2[1] - v1[1] + return dx * dx + dy * dy + } +} diff --git a/src/networks/TSN/network.ts b/src/networks/TSN/network.ts index ea268f0..4c99150 100644 --- a/src/networks/TSN/network.ts +++ b/src/networks/TSN/network.ts @@ -2,6 +2,7 @@ import { ref, toRaw } from 'vue' import { Network, NETWORK_TYPE, NODE_TYPE, type Message } from '../common' import { MSG_TYPES, type INIT_MSG_PAYLOAD, type ScheduleConfig, type TSNNodeMeta } from './typedefs' import { SeededRandom } from '@/hooks/useSeed' +import { KDNode, KDTree } from './kdtree' export class TSNNetwork extends Network { InPorts: any @@ -41,6 +42,9 @@ export class TSNNetwork extends Network { } ] // placeholder + // to find neighbors + const tree = new KDTree() + for (let i = 1; i <= this.TopoConfig.value.num_nodes; i++) { const n = { id: i, @@ -66,60 +70,20 @@ export class TSNNetwork extends Network { sch_config: toRaw(this.SchConfig.value) } }) - // handle msg/pkt from nodes - // n.w!.onmessage = (e: any) => { - // if ('ch' in e.data == false) { - // const msg: Message = e.data - // if (this.msgHandlers[msg.type] != undefined) { - // this.msgHandlers[msg.type](msg) - // } else { - // console.log('!! undefined message type:', msg.type) - // } - // } else { - // const pkt: Packet = e.data - // // check channel interference, only one packet can be transmitted on each channel in a slot - // if ( - // this.PacketsCurrent.value.filter((p) => p.ch == pkt.ch).length == 0 || - // pkt.type == PKT_TYPES.ACK - // ) { - // // must use this format for the detailedView function of el-table-v2 - // pkt.id = this.Packets.value.length - // pkt.children = [ - // { - // id: `${this.Packets.value.length}-detail-content`, - // detail: JSON.stringify(pkt.payload).replace(/"/g, '') - // } - // ] - - // this.Packets.value.push(pkt) - // this.PacketsCurrent.value.push(pkt) - // if (pkt.dst == ADDR.BROADCAST) { - // for (const nn of this.Nodes.value) { - // // check if in tx_range - // const distance = Math.sqrt( - // Math.pow(n.pos[0] - nn.pos[0], 2) + Math.pow(n.pos[1] - nn.pos[1], 2) - // ) - // if (nn.id > 0 && nn.id != n.id && distance <= this.TopoConfig.value.tx_range) { - // nn.w!.postMessage(pkt) - // } - // } - // } else { - // const nn = this.Nodes.value[pkt.dst] - // if (nn != undefined) { - // // check if in tx_range - // const distance = Math.sqrt( - // Math.pow(n.pos[0] - nn.pos[0], 2) + Math.pow(n.pos[1] - nn.pos[1], 2) - // ) - // if (distance <= this.TopoConfig.value.tx_range) { - // nn.w!.postMessage(pkt) - // } - // } - // } - // } - // } - // } this.Nodes.value.push(n) + tree.Insert(new KDNode(i, this.Nodes.value[i].pos)) + } + + for (let i = 1; i <= this.TopoConfig.value.num_nodes; i++) { + this.Nodes.value[i].neighbors = tree.FindKNearest( + this.Nodes.value[i].pos, + 5, + this.TopoConfig.value.tx_range + ) + this.Nodes.value[i].neighbors.forEach((n: number) => { + super.addLink(i, n) + }) } } } diff --git a/src/networks/TSN/node.ts b/src/networks/TSN/node.ts index 8fc400a..c865dc9 100644 --- a/src/networks/TSN/node.ts +++ b/src/networks/TSN/node.ts @@ -21,7 +21,7 @@ class TSNNode { if (this.msgHandlers[msg.type] != undefined) { this.msgHandlers[msg.type](msg) } else { - console.log('!! undefined message type:', msg.type) + // console.log('!! undefined message type:', msg.type) } } else { const pkt: Packet = e.data @@ -29,7 +29,7 @@ class TSNNode { if (this.pktHandlers[pkt.type] != undefined) { this.pktHandlers[pkt.type](pkt) } else { - console.log('!! undefined packet type:', pkt.type) + // console.log('!! undefined packet type:', pkt.type) } } } diff --git a/src/networks/common.ts b/src/networks/common.ts index c91f475..24b47da 100644 --- a/src/networks/common.ts +++ b/src/networks/common.ts @@ -1,86 +1,6 @@ import { ref, type Ref } from 'vue' import { SeededRandom } from '@/hooks/useSeed' -export class Network { - ID: number - Type: number - Nodes: any - Links = ref([]) - EndSystems: any - TopoConfig: Ref - SchConfig: any - Schedule: any - Packets = ref([]) - ASN = ref(0) - asnTimer: any - PacketsCurrent = ref([]) - - SignalReset = ref(0) - SlotDone = ref(true) - Running = ref(false) - SlotDuration = ref(750) - - constructor() { - this.ID = 1 - this.Type = -1 - this.TopoConfig = ref({ - seed: 1, - num_nodes: 5, - num_es: 4, - grid_size: 80, - tx_range: 25 - }) - this.createEndSystems() - } - createEndSystems = () => { - // initialize ref array if it does not already exist - this.EndSystems = ref([]) - const rand = new SeededRandom(this.TopoConfig.value.seed) - - this.EndSystems.value = [] // clear any old end systems - - for (let i = 1; i <= this.TopoConfig.value.num_es; i++) { - const es = { - id: i, - type: Math.floor( - rand.next() * Object.keys(END_SYSTEM_TYPE).filter((key) => isNaN(Number(key))).length - ), // Object.keys(...).filter(...) is used to count # of elements in enum - pos: [ - Math.floor(rand.next() * this.TopoConfig.value.grid_size) - - this.TopoConfig.value.grid_size / 2, - Math.floor(rand.next() * this.TopoConfig.value.grid_size) - - this.TopoConfig.value.grid_size / 2 - ], - neighbor: 1 + Math.floor(rand.next() * this.TopoConfig.value.num_nodes) // range from 1 to num_nodes inclusive - } - - this.EndSystems.value.push(es) - } - } - - Run = () => { - this.Step() - this.Running.value = true - this.asnTimer = setInterval(() => { - this.ASN.value++ - this.SlotDone.value = false - }, this.SlotDuration.value) - } - Step = () => { - this.ASN.value++ - this.SlotDone.value = false - } - Pause = () => { - this.Running.value = false - clearInterval(this.asnTimer) - } - Reset = () => { - this.Running.value = false - clearInterval(this.asnTimer) - this.SignalReset.value++ - } -} - export enum NETWORK_TYPE { TSCH, TSN, @@ -98,7 +18,7 @@ export enum NODE_TYPE { export interface NodeMeta { id: number type: number - pos: number[] + pos: [number, number] neighbors: number[] tx_cnt: number rx_cnt: number @@ -107,7 +27,7 @@ export interface NodeMeta { export interface LinkMeta { // undirected link for visualization - uid: number // uid=v1*2+v2*3 + uid: number v1: number v2: number } @@ -161,3 +81,93 @@ export interface TopologyConfig { grid_size: number tx_range: number } + +export class Network { + ID: number + Type: number + Nodes: any + Links = ref<{ [uid: number]: LinkMeta }>([]) + EndSystems: any + TopoConfig: Ref + SchConfig: any + Schedule: any + Packets = ref([]) + ASN = ref(0) + asnTimer: any + PacketsCurrent = ref([]) + + SignalReset = ref(0) + SlotDone = ref(true) + Running = ref(false) + SlotDuration = ref(750) + + constructor() { + this.ID = 1 + this.Type = -1 + this.TopoConfig = ref({ + seed: 1, + num_nodes: 10, + num_es: 4, + grid_size: 80, + tx_range: 25 + }) + this.createEndSystems() + } + createEndSystems = () => { + // initialize ref array if it does not already exist + this.EndSystems = ref([]) + const rand = new SeededRandom(this.TopoConfig.value.seed) + + this.EndSystems.value = [] // clear any old end systems + + for (let i = 1; i <= this.TopoConfig.value.num_es; i++) { + const es = { + id: i, + type: Math.floor( + rand.next() * Object.keys(END_SYSTEM_TYPE).filter((key) => isNaN(Number(key))).length + ), // Object.keys(...).filter(...) is used to count # of elements in enum + pos: [ + Math.floor(rand.next() * this.TopoConfig.value.grid_size) - + this.TopoConfig.value.grid_size / 2, + Math.floor(rand.next() * this.TopoConfig.value.grid_size) - + this.TopoConfig.value.grid_size / 2 + ], + neighbor: 1 + Math.floor(rand.next() * this.TopoConfig.value.num_nodes) // range from 1 to num_nodes inclusive + } + + this.EndSystems.value.push(es) + } + } + addLink(v1: number, v2: number) { + if (v1 > v2) { + ;[v1, v2] = [v2, v1] + } + // Cantor pairing + const uid = 0.5 * (v1 + v2) * (v1 + v2 + 1) + v2 + if (this.Links.value[uid] == undefined) { + this.Links.value[uid] = { uid, v1, v2 } + } + } + + Run = () => { + this.Step() + this.Running.value = true + this.asnTimer = setInterval(() => { + this.ASN.value++ + this.SlotDone.value = false + }, this.SlotDuration.value) + } + Step = () => { + this.ASN.value++ + this.SlotDone.value = false + } + Pause = () => { + this.Running.value = false + clearInterval(this.asnTimer) + } + Reset = () => { + this.Running.value = false + clearInterval(this.asnTimer) + this.SignalReset.value++ + } +}