frw: HTML/Javascript implementation of floating random walk cap extraction - #11179
frw: HTML/Javascript implementation of floating random walk cap extraction#11179QuantamHD wants to merge 1 commit into
Conversation
…ction Signed-off-by: Ethan Mahintorabi <ethanmoon@google.com>
There was a problem hiding this comment.
Code Review
This pull request introduces three interactive HTML-based 2D Floating Random Walk (FRW) capacitance extractors: a rectangular simulator, a circular simulator, and an infinite-space circular simulator. While the interactive drag-and-drop features and real-time path rendering are excellent additions, several critical issues need to be addressed. In the infinite-space simulator, the relative error calculation incorrectly uses total walks instead of completed walks, and the lack of an outer boundary for the infinite domain can cause browser freezes and selection bias due to walks hitting the safety brake. Additionally, both the rectangular and circular simulators lack overlap prevention when dragging conductors, which can result in physically impossible overlapping states and incorrect capacitance calculations.
| let relativeError = Infinity; | ||
|
|
||
| if (hitsNeighbor > 50) { | ||
| relativeError = Math.sqrt((1 - p) / (totalWalks * p)); |
There was a problem hiding this comment.
The relative error calculation is incorrect because it uses totalWalks in the denominator. In this infinite space simulation, walks that hit the safety brake are discarded and do not increment hitsNeighbor or hitsTarget. Therefore, totalWalks is larger than the number of completed walks, leading to an underestimation of the relative error. This can cause the simulation to converge prematurely before reaching the actual target error limit. The relative error should be calculated using only the completed walks (hitsNeighbor + hitsTarget), which simplifies to Math.sqrt((1 - p) / hitsNeighbor).
| relativeError = Math.sqrt((1 - p) / (totalWalks * p)); | |
| relativeError = Math.sqrt((1 - p) / hitsNeighbor); |
| let safetyBrake = 0; | ||
|
|
||
| // Massive safety brake for walks that go into deep space | ||
| while (isWalking && safetyBrake < 500000) { |
There was a problem hiding this comment.
In a 2D infinite domain without ground boundaries, a random walk is recurrent but has an infinite expected number of steps to return or hit a compact target. Consequently, a significant portion of walks will wander extremely far and hit the safety brake of 500000. Discarding these walks introduces a severe selection bias (over-representing walks that terminate quickly). Furthermore, running up to 500,000 steps per walk for 1,000 walks on the main thread in a single animation frame will completely freeze the browser UI. To resolve this, consider implementing an outer bounding boundary (e.g., a large circle) with an analytical absorption/return probability, which is the standard way to handle infinite domains in FRW.
| NEIGHBOR.x = mouseX - dragOffsetX; | ||
| NEIGHBOR.y = mouseY - dragOffsetY; | ||
|
|
||
| // Keep the circle inside the canvas | ||
| NEIGHBOR.x = Math.max(NEIGHBOR.r, Math.min(canvas.width - NEIGHBOR.r, NEIGHBOR.x)); | ||
| NEIGHBOR.y = Math.max(NEIGHBOR.r, Math.min(canvas.height - NEIGHBOR.r, NEIGHBOR.y)); |
There was a problem hiding this comment.
There is no overlap prevention when dragging the neighbor circle. If the user drags the neighbor circle over the target circle, they can overlap, which results in negative distances and physically impossible/incorrect capacitance values. Consider adding the same radial overlap prevention logic that was implemented in frw_two_circles_infinite_bound.html.
| NEIGHBOR.x = mouseX - dragOffsetX; | |
| NEIGHBOR.y = mouseY - dragOffsetY; | |
| // Keep the circle inside the canvas | |
| NEIGHBOR.x = Math.max(NEIGHBOR.r, Math.min(canvas.width - NEIGHBOR.r, NEIGHBOR.x)); | |
| NEIGHBOR.y = Math.max(NEIGHBOR.r, Math.min(canvas.height - NEIGHBOR.r, NEIGHBOR.y)); | |
| NEIGHBOR.x = mouseX - dragOffsetX; | |
| NEIGHBOR.y = mouseY - dragOffsetY; | |
| // Ensure circles don't overlap | |
| let distBetweenCenters = Math.sqrt(Math.pow(NEIGHBOR.x - TARGET.x, 2) + Math.pow(NEIGHBOR.y - TARGET.y, 2)); | |
| if (distBetweenCenters < (TARGET.r + NEIGHBOR.r + 5)) { | |
| let angle = Math.atan2(NEIGHBOR.y - TARGET.y, NEIGHBOR.x - TARGET.x); | |
| NEIGHBOR.x = TARGET.x + Math.cos(angle) * (TARGET.r + NEIGHBOR.r + 5); | |
| NEIGHBOR.y = TARGET.y + Math.sin(angle) * (TARGET.r + NEIGHBOR.r + 5); | |
| } | |
| // Keep the circle inside the canvas | |
| NEIGHBOR.x = Math.max(NEIGHBOR.r, Math.min(canvas.width - NEIGHBOR.r, NEIGHBOR.x)); | |
| NEIGHBOR.y = Math.max(NEIGHBOR.r, Math.min(canvas.height - NEIGHBOR.r, NEIGHBOR.y)); |
| NEIGHBOR.x = mouseX - dragOffsetX; | ||
| NEIGHBOR.y = mouseY - dragOffsetY; | ||
|
|
||
| // Optional: Keep the box inside the canvas | ||
| NEIGHBOR.x = Math.max(0, Math.min(canvas.width - NEIGHBOR.w, NEIGHBOR.x)); | ||
| NEIGHBOR.y = Math.max(0, Math.min(canvas.height - NEIGHBOR.h, NEIGHBOR.y)); |
There was a problem hiding this comment.
There is no overlap prevention when dragging the neighbor rectangle. If the user drags the neighbor rectangle over the target rectangle, they can overlap, which results in immediate absorption (hits) during the simulation and incorrect capacitance values. Consider adding a simple AABB overlap check to prevent dragging the neighbor box into the target box.
| NEIGHBOR.x = mouseX - dragOffsetX; | |
| NEIGHBOR.y = mouseY - dragOffsetY; | |
| // Optional: Keep the box inside the canvas | |
| NEIGHBOR.x = Math.max(0, Math.min(canvas.width - NEIGHBOR.w, NEIGHBOR.x)); | |
| NEIGHBOR.y = Math.max(0, Math.min(canvas.height - NEIGHBOR.h, NEIGHBOR.y)); | |
| let newX = mouseX - dragOffsetX; | |
| let newY = mouseY - dragOffsetY; | |
| // Keep the box inside the canvas | |
| newX = Math.max(0, Math.min(canvas.width - NEIGHBOR.w, newX)); | |
| newY = Math.max(0, Math.min(canvas.height - NEIGHBOR.h, newY)); | |
| // Prevent overlap with TARGET (with 5px padding) | |
| const padding = 5; | |
| const overlaps = ( | |
| newX < TARGET.x + TARGET.w + padding && | |
| newX + NEIGHBOR.w > TARGET.x - padding && | |
| newY < TARGET.y + TARGET.h + padding && | |
| newY + NEIGHBOR.h > TARGET.y - padding | |
| ); | |
| if (!overlaps) { | |
| NEIGHBOR.x = newX; | |
| NEIGHBOR.y = newY; | |
| } |
|
I don't plan to merge it so I've closed it but I'll examine it further. |
To test it open the html page with vscode and right click on the html file "View with Live Server", otherwise you will get CORS exception.