From 355c9f2b491a185b9150c795c99447881b41739a Mon Sep 17 00:00:00 2001 From: Bane Stojkovic Date: Fri, 25 Apr 2025 22:50:07 +0200 Subject: [PATCH] netlist fixes --- .../netlist/AbstractNetlistAnalyzer.java | 2 +- .../main/java/org/diylc/netlist/Group.java | 6 +- .../org/diylc/netlist/NetlistAnalyzer.java | 2 +- .../org/diylc/netlist/NetlistBuilder.java | 4 +- .../src/main/java/org/diylc/netlist/Node.java | 38 +-- .../org/diylc/netlist/SpiceSumarizer.java | 2 +- .../chatbot/service/AiProjectBuilder.java | 41 ++- .../chatbot/service/NetlistBuilder.java | 262 +++++++++--------- .../chatbot/service/NetlistBuilderTest.java | 188 +++++++++++++ 9 files changed, 358 insertions(+), 187 deletions(-) create mode 100644 diylc/diylc-core/src/test/java/org/diylc/plugins/chatbot/service/NetlistBuilderTest.java diff --git a/diylc/diylc-core/src/main/java/org/diylc/netlist/AbstractNetlistAnalyzer.java b/diylc/diylc-core/src/main/java/org/diylc/netlist/AbstractNetlistAnalyzer.java index c7c53192..5e41416b 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/netlist/AbstractNetlistAnalyzer.java +++ b/diylc/diylc-core/src/main/java/org/diylc/netlist/AbstractNetlistAnalyzer.java @@ -92,7 +92,7 @@ public abstract class AbstractNetlistAnalyzer { for (int i = 0; i < c.getControlPointCount(); i++) { if (i != n.getPointIndex() && c.getInternalLinkName(i, n.getPointIndex()) != null) { TreeLeaf l = new TreeLeaf(c, i, n.getPointIndex()); - Node newNodeA = new Node(c, i, n.getZIndex()); + Node newNodeA = new Node(c, i); if (!visited.contains(newNodeA)) { // visited.add(n); Tree newCurrentPath = null; diff --git a/diylc/diylc-core/src/main/java/org/diylc/netlist/Group.java b/diylc/diylc-core/src/main/java/org/diylc/netlist/Group.java index 88e4afd8..69191cd9 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/netlist/Group.java +++ b/diylc/diylc-core/src/main/java/org/diylc/netlist/Group.java @@ -53,11 +53,7 @@ public class Group implements Comparable { } public Group connect(IDIYComponent component, int index) { - return connect(component, index, 0); - } - - public Group connect(IDIYComponent component, int index, int zOrder) { - getNodes().add(new Node(component, index, zOrder)); + getNodes().add(new Node(component, index)); return this; } diff --git a/diylc/diylc-core/src/main/java/org/diylc/netlist/NetlistAnalyzer.java b/diylc/diylc-core/src/main/java/org/diylc/netlist/NetlistAnalyzer.java index 64fd22f3..a1abf33b 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/netlist/NetlistAnalyzer.java +++ b/diylc/diylc-core/src/main/java/org/diylc/netlist/NetlistAnalyzer.java @@ -92,7 +92,7 @@ public abstract class NetlistAnalyzer { continue; if (c.getInternalLinkName(targetPoint, sourcePoint) != null) { - Node newNodeA = new Node(c, targetPoint, n.getZIndex()); + Node newNodeA = new Node(c, targetPoint); if (pathVisited.contains(newNodeA)) continue; diff --git a/diylc/diylc-core/src/main/java/org/diylc/netlist/NetlistBuilder.java b/diylc/diylc-core/src/main/java/org/diylc/netlist/NetlistBuilder.java index 25963e26..2e8e79aa 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/netlist/NetlistBuilder.java +++ b/diylc/diylc-core/src/main/java/org/diylc/netlist/NetlistBuilder.java @@ -75,7 +75,7 @@ public class NetlistBuilder { String nodeName = c.getControlPointNodeName(i); if (c.isControlPointSticky(i) && nodeName != null && (!includeSwitches || !ISwitch.class.isAssignableFrom(type.getInstanceClass()))) { - nodes.add(new Node(c, i, j)); + nodes.add(new Node(c, i)); } } } @@ -86,7 +86,7 @@ public class NetlistBuilder { String label = ((ICommonNode) c).getCommonNodeLabel(); if (!commonNodes.contains(label)) { commonNodes.add(label); - nodes.add(new Node(c, 0, j)); + nodes.add(new Node(c, 0)); } } diff --git a/diylc/diylc-core/src/main/java/org/diylc/netlist/Node.java b/diylc/diylc-core/src/main/java/org/diylc/netlist/Node.java index bd4b24a0..47bf74c1 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/netlist/Node.java +++ b/diylc/diylc-core/src/main/java/org/diylc/netlist/Node.java @@ -22,6 +22,7 @@ package org.diylc.netlist; import java.awt.geom.Point2D; +import java.util.Objects; import org.diylc.core.ICommonNode; import org.diylc.core.IDIYComponent; @@ -35,13 +36,11 @@ public class Node implements Comparable { private IDIYComponent component; private int pointIndex; - private int zIndex; - public Node(IDIYComponent component, int pointIndex, int zIndex) { + public Node(IDIYComponent component, int pointIndex) { super(); this.component = component; this.pointIndex = pointIndex; - this.zIndex = zIndex; } public IDIYComponent getComponent() { @@ -52,10 +51,6 @@ public class Node implements Comparable { return pointIndex; } - public int getZIndex() { - return zIndex; - } - public String getDisplayName() { String[] section = component.getSectionNames(pointIndex); return (section == null || section.length > 1 ? "" : section[0] + ".") + component.getControlPointNodeName(pointIndex); @@ -66,31 +61,16 @@ public class Node implements Comparable { } @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((component == null) ? 0 : component.hashCode()); - result = prime * result + pointIndex; - return result; + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof Node)) return false; + Node other = (Node) obj; + return Objects.equals(component, other.component) && pointIndex == other.pointIndex; } @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - Node other = (Node) obj; - if (component == null) { - if (other.component != null) - return false; - } else if (!component.equals(other.component)) - return false; - if (pointIndex != other.pointIndex) - return false; - return true; + public int hashCode() { + return Objects.hash(component, pointIndex); } @Override diff --git a/diylc/diylc-core/src/main/java/org/diylc/netlist/SpiceSumarizer.java b/diylc/diylc-core/src/main/java/org/diylc/netlist/SpiceSumarizer.java index 286ae295..42e5598b 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/netlist/SpiceSumarizer.java +++ b/diylc/diylc-core/src/main/java/org/diylc/netlist/SpiceSumarizer.java @@ -59,7 +59,7 @@ public class SpiceSumarizer { if (c instanceof ISpiceMapper) pointIndex = ((ISpiceMapper)c).mapToSpiceNode(pointIndex); - int nodeIndex = find(new Node(c, pointIndex, 0), groups); + int nodeIndex = find(new Node(c, pointIndex), groups); if (nodeIndex < 0) nodeIndex = unconnectedIndex++; diff --git a/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/service/AiProjectBuilder.java b/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/service/AiProjectBuilder.java index bbdc2c0f..7deb66f0 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/service/AiProjectBuilder.java +++ b/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/service/AiProjectBuilder.java @@ -14,6 +14,10 @@ import org.diylc.common.PropertyWrapper; import org.diylc.core.IDIYComponent; import org.diylc.core.ISwitch; import org.diylc.core.Project; +import org.diylc.netlist.Netlist; +import org.diylc.netlist.NetlistBuilder; +import org.diylc.netlist.NetlistException; +import org.diylc.netlist.Node; import org.diylc.plugins.chatbot.model.*; import org.diylc.presenter.ComponentProcessor; import org.diylc.presenter.ContinuityArea; @@ -27,16 +31,31 @@ public class AiProjectBuilder { List components = project.getComponents().stream().map(AiProjectBuilder::mapComponent).toList(); - List> sets = - NetlistBuilder.buildNets(project.getComponents(), continuityAreas); +// List> sets = + List netlists = null; + try { + netlists = NetlistBuilder.extractNetlists(false, project, continuityAreas); + } catch (NetlistException e) { + throw new RuntimeException(e); + } + Map> nets; + if (netlists == null || netlists.isEmpty()) { + nets = new HashMap<>(); + } else { + AtomicInteger counter = new AtomicInteger(); + nets = netlists.get(0).getGroups().stream().map(s -> s.getNodes().stream() + .map(AiProjectBuilder::mapTerminal) + .collect(Collectors.toSet())) + .collect(Collectors.toMap(x -> "N" + String.format("%03d", counter.incrementAndGet()), + Function.identity())); + } - AtomicInteger counter = new AtomicInteger(); - Map> nets = - sets.stream().map(s -> s.stream() - .map(AiProjectBuilder::mapTerminal) - .collect(Collectors.toSet())) - .collect(Collectors.toMap(x -> "N" + String.format("%03d", counter.incrementAndGet()), - Function.identity())); +// Map> nets = +// sets.stream().map(s -> s.stream() +// .map(AiProjectBuilder::mapTerminal) +// .collect(Collectors.toSet())) +// .collect(Collectors.toMap(x -> "N" + String.format("%03d", counter.incrementAndGet()), +// Function.identity())); List switches = project.getComponents().stream() @@ -161,7 +180,7 @@ public class AiProjectBuilder { fromPos, toPos, pos, terminals.isEmpty() ? null : terminals); } - static String mapTerminal(NetlistBuilder.TerminalRef terminalRef) { - return terminalRef.component.getName() + "." + terminalRef.terminalIndex; + static String mapTerminal(Node terminalRef) { + return terminalRef.getComponent().getName() + "." + terminalRef.getPointIndex(); } } diff --git a/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/service/NetlistBuilder.java b/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/service/NetlistBuilder.java index cb4ac1fa..412c7874 100644 --- a/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/service/NetlistBuilder.java +++ b/diylc/diylc-core/src/main/java/org/diylc/plugins/chatbot/service/NetlistBuilder.java @@ -1,137 +1,125 @@ -package org.diylc.plugins.chatbot.service; - -import org.diylc.core.IContinuity; -import org.diylc.core.IDIYComponent; -import org.diylc.core.ISwitch; -import org.diylc.presenter.ContinuityArea; - -import java.awt.geom.Area; -import java.awt.geom.Point2D; -import java.util.*; - -public class NetlistBuilder { - - public static class TerminalRef { - public final IDIYComponent component; - public final int terminalIndex; - - public TerminalRef(IDIYComponent component, int terminalIndex) { - this.component = component; - this.terminalIndex = terminalIndex; - } - - public Point2D getPosition() { - return component.getControlPoint(terminalIndex); - } - - @Override - public String toString() { - return component.getName() + "." + terminalIndex; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof TerminalRef)) return false; - TerminalRef other = (TerminalRef) obj; - return Objects.equals(component, other.component) && terminalIndex == other.terminalIndex; - } - - @Override - public int hashCode() { - return Objects.hash(component, terminalIndex); - } - } - - public static List> buildNets(List> components, List continuityAreas) { - Map terminalIndex = new HashMap<>(); - List allTerminals = new ArrayList<>(); - int idx = 0; - - for (IDIYComponent c : components) { - for (int i = 0; i < c.getControlPointCount(); i++) { - if (c.isControlPointSticky(i)) { - TerminalRef ref = new TerminalRef(c, i); - terminalIndex.put(ref, idx++); - allTerminals.add(ref); - } - } - } - - UnionFind uf = new UnionFind(allTerminals.size()); - - // Step 1: connect conductive components - for (IDIYComponent c : components) { - if (c instanceof IContinuity continuity && !(c instanceof ISwitch)) { - int count = c.getControlPointCount(); - for (int i = 0; i < count; i++) { - for (int j = i + 1; j < count; j++) { - if (continuity.arePointsConnected(i, j)) { - TerminalRef a = new TerminalRef(c, i); - TerminalRef b = new TerminalRef(c, j); - uf.union(terminalIndex.get(a), terminalIndex.get(b)); - } - } - } - } - } - - // Step 2: connect terminals inside continuity areas - for (ContinuityArea continuity : continuityAreas) { - Area area = continuity.getSimplifiedArea(); - List inside = new ArrayList<>(); - - for (TerminalRef t : allTerminals) { - Point2D p = t.getPosition(); - if (area.contains(p)) { - inside.add(t); - } - } - - for (int i = 0; i < inside.size(); i++) { - for (int j = i + 1; j < inside.size(); j++) { - uf.union(terminalIndex.get(inside.get(i)), terminalIndex.get(inside.get(j))); - } - } - } - - Map> netMap = new HashMap<>(); - for (TerminalRef t : allTerminals) { - int root = uf.find(terminalIndex.get(t)); - netMap.computeIfAbsent(root, k -> new HashSet<>()).add(t); - } - - // Filter out nets with only one terminal and remove exact duplicates - List> nets = new ArrayList<>(); - Set> seen = new HashSet<>(); - - for (Set net : netMap.values()) { - if (net.size() > 1 && seen.add(net)) { - nets.add(net); - } - } - return nets; - - } - - // Union-Find with path compression - static class UnionFind { - int[] parent; - - UnionFind(int n) { - parent = new int[n]; - for (int i = 0; i < n; i++) parent[i] = i; - } - - int find(int x) { - if (parent[x] != x) - parent[x] = find(parent[x]); - return parent[x]; - } - - void union(int x, int y) { - parent[find(x)] = find(y); - } - } -} - +//package org.diylc.plugins.chatbot.service; +// +//import org.diylc.core.IContinuity; +//import org.diylc.core.IDIYComponent; +//import org.diylc.core.ISwitch; +//import org.diylc.netlist.Node; +//import org.diylc.presenter.ContinuityArea; +// +//import java.awt.geom.Area; +//import java.awt.geom.Point2D; +//import java.util.*; +// +//public class NetlistBuilder { +// private static final double eps = 1e-6; +// +// public static List> buildNets(List> components, List continuityAreas) { +// Map terminalIndex = new HashMap<>(); +// List allTerminals = new ArrayList<>(); +// int idx = 0; +// +// for (IDIYComponent c : components) { +// for (int i = 0; i < c.getControlPointCount(); i++) { +// if (c.isControlPointSticky(i)) { +// Node ref = new Node(c, i); +// terminalIndex.put(ref, idx++); +// allTerminals.add(ref); +// } +// } +// } +// +// UnionFind uf = new UnionFind(allTerminals.size()); +// +// // Step 1: connect conductive components +// for (IDIYComponent c : components) { +// if (c instanceof IContinuity continuity && !(c instanceof ISwitch)) { +// int count = c.getControlPointCount(); +// for (int i = 0; i < count; i++) { +// for (int j = i + 1; j < count; j++) { +// if (continuity.arePointsConnected(i, j)) { +// Node a = new Node(c, i); +// Node b = new Node(c, j); +// uf.union(terminalIndex.get(a), terminalIndex.get(b)); +// } +// } +// } +// } +// } +// +// // Step 2: connect terminals inside continuity areas +// for (ContinuityArea continuity : continuityAreas) { +// Area area = continuity.getSimplifiedArea(); +// List inside = new ArrayList<>(); +// +// for (Node t : allTerminals) { +// Point2D p = t.getPoint2D(); +// if (area.contains(p) || area.intersects(p.getX() - eps/2, p.getY() - eps/2, eps, eps)) { +// inside.add(t); +// } +// } +// +// for (int i = 0; i < inside.size(); i++) { +// for (int j = i + 1; j < inside.size(); j++) { +// uf.union(terminalIndex.get(inside.get(i)), terminalIndex.get(inside.get(j))); +// } +// } +// } +// +// Map> netMap = new HashMap<>(); +// for (Node t : allTerminals) { +// int root = uf.find(terminalIndex.get(t)); +// netMap.computeIfAbsent(root, k -> new HashSet<>()).add(t); +// } +// +// // Filter out nets with only one terminal and remove exact duplicates +// List> nets = new ArrayList<>(); +// Set> seen = new HashSet<>(); +// +// for (Set net : netMap.values()) { +// if (net.size() > 1 && seen.add(net)) { +// nets.add(net); +// } +// } +// return nets; +// +// } +// +// // Union-Find with path compression and union by size +// static class UnionFind { +// private final int[] parent; +// private final int[] size; +// +// UnionFind(int n) { +// parent = new int[n]; +// size = new int[n]; +// for (int i = 0; i < n; i++) { +// parent[i] = i; +// size[i] = 1; +// } +// } +// +// int find(int x) { +// if (parent[x] != x) { +// parent[x] = find(parent[x]); // Path compression +// } +// return parent[x]; +// } +// +// void union(int x, int y) { +// int rootX = find(x); +// int rootY = find(y); +// +// if (rootX == rootY) return; +// +// // Union by size: attach smaller tree to larger tree +// if (size[rootX] < size[rootY]) { +// parent[rootX] = rootY; +// size[rootY] += size[rootX]; +// } else { +// parent[rootY] = rootX; +// size[rootX] += size[rootY]; +// } +// } +// } +//} +// diff --git a/diylc/diylc-core/src/test/java/org/diylc/plugins/chatbot/service/NetlistBuilderTest.java b/diylc/diylc-core/src/test/java/org/diylc/plugins/chatbot/service/NetlistBuilderTest.java new file mode 100644 index 00000000..10ad11c6 --- /dev/null +++ b/diylc/diylc-core/src/test/java/org/diylc/plugins/chatbot/service/NetlistBuilderTest.java @@ -0,0 +1,188 @@ +//package org.diylc.plugins.chatbot.service; +// +//import org.diylc.core.IContinuity; +//import org.diylc.core.IDIYComponent; +//import org.diylc.core.ComponentState; +//import org.diylc.core.IDrawingObserver; +//import org.diylc.core.Project; +//import org.diylc.core.VisibilityPolicy; +//import org.diylc.netlist.Node; +//import org.diylc.presenter.ContinuityArea; +//import org.junit.Test; +//import org.diylc.components.AbstractComponent; +// +//import java.awt.Graphics2D; +//import java.awt.geom.Area; +//import java.awt.geom.Point2D; +//import java.util.*; +// +//import static org.junit.Assert.*; +// +//public class NetlistBuilderTest { +// +// // Mock component that implements IContinuity +// private static class MockConductiveComponent extends AbstractComponent implements IContinuity { +// private final Point2D[] points; +// private final boolean[][] connections; +// +// public MockConductiveComponent(Point2D[] points, boolean[][] connections) { +// this.points = points; +// this.connections = connections; +// } +// +// @Override +// public int getControlPointCount() { +// return points.length; +// } +// +// @Override +// public Point2D getControlPoint(int index) { +// return points[index]; +// } +// +// @Override +// public boolean isControlPointSticky(int index) { +// return true; +// } +// +// @Override +// public boolean arePointsConnected(int index1, int index2) { +// return connections[index1][index2]; +// } +// +// @Override +// public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode, Project project, IDrawingObserver drawingObserver) { +// // Not needed for testing +// } +// +// @Override +// public void drawIcon(Graphics2D g2d, int width, int height) { +// // Not needed for testing +// } +// +// @Override +// public VisibilityPolicy getControlPointVisibilityPolicy(int index) { +// return VisibilityPolicy.ALWAYS; +// } +// +// @Override +// public void setControlPoint(Point2D point, int index) { +// // Not needed for testing +// } +// +// @Override +// public Void getValue() { +// return null; +// } +// +// @Override +// public void setValue(Void value) { +// // Not needed for testing +// } +// } +// +// // Mock component that doesn't implement IContinuity +// private static class MockNonConductiveComponent extends AbstractComponent { +// private final Point2D[] points; +// +// public MockNonConductiveComponent(Point2D[] points) { +// this.points = points; +// } +// +// @Override +// public int getControlPointCount() { +// return points.length; +// } +// +// @Override +// public Point2D getControlPoint(int index) { +// return points[index]; +// } +// +// @Override +// public boolean isControlPointSticky(int index) { +// return true; +// } +// +// @Override +// public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode, Project project, IDrawingObserver drawingObserver) { +// // Not needed for testing +// } +// +// @Override +// public void drawIcon(Graphics2D g2d, int width, int height) { +// // Not needed for testing +// } +// +// @Override +// public VisibilityPolicy getControlPointVisibilityPolicy(int index) { +// return VisibilityPolicy.ALWAYS; +// } +// +// @Override +// public void setControlPoint(Point2D point, int index) { +// // Not needed for testing +// } +// +// @Override +// public Void getValue() { +// return null; +// } +// +// @Override +// public void setValue(Void value) { +// // Not needed for testing +// } +// } +// +// @Test +// public void testBuildNets() { +// // Create test components +// Point2D[] points1 = { +// new Point2D.Double(0, 0), +// new Point2D.Double(10, 0), +// new Point2D.Double(20, 0) +// }; +// boolean[][] connections1 = { +// {false, true, false}, +// {true, false, true}, +// {false, true, false} +// }; +// MockConductiveComponent comp1 = new MockConductiveComponent(points1, connections1); +// +// Point2D[] points2 = { +// new Point2D.Double(30, 0), +// new Point2D.Double(40, 0) +// }; +// MockNonConductiveComponent comp2 = new MockNonConductiveComponent(points2); +// +// // Create a continuity area that connects comp1 and comp2 +// Area area = new Area(); +// area.add(new Area(new java.awt.geom.Rectangle2D.Double(15, -5, 20, 10))); +// ContinuityArea continuityArea = new ContinuityArea(0, area); +// +// // Build nets +// List> components = Arrays.asList(comp1, comp2); +// List continuityAreas = Collections.singletonList(continuityArea); +// List> nets = NetlistBuilder.buildNets(components, continuityAreas); +// +// // Verify results +// assertEquals("Should have exactly one net", 1, nets.size()); +// Set net = nets.get(0); +// assertEquals("Net should contain all terminals", 5, net.size()); +// +// // Verify that all terminals are connected +// Set terminalIndices = new HashSet<>(); +// for (Node node : net) { +// terminalIndices.add(node.getPointIndex()); +// System.out.println("Found terminal: " + node.getPointIndex() + " at " + node.getPoint2D()); +// } +// System.out.println("All terminals: " + terminalIndices); +// assertEquals("Should contain all terminal indices", 5, terminalIndices.size()); +// assertTrue("Should contain terminal 0", terminalIndices.contains(0)); +// assertTrue("Should contain terminal 1", terminalIndices.contains(1)); +// assertTrue("Should contain terminal 2", terminalIndices.contains(2)); +// assertTrue("Should contain terminal 3", terminalIndices.contains(3)); +// assertTrue("Should contain terminal 4", terminalIndices.contains(4)); +// } +//}