Repository: KLayout/klayout (language: python)

Issue:
Netlist device combination / simplify requests
This is a takeaway of a number of discussions regarding extraction of C elements.

1. Netlist#simplify could eliminate shorted elements such as R, L or C

Example:

```
netlist = pya.Netlist()

circuit = pya.Circuit()
circuit.name = "TOP"

netlist.add(circuit)

cap_device = pya.DeviceClassCapacitor()
cap_device.name = "model_name"

netlist.add(cap_device)

n1 = circuit.create_net("n1")
n2 = circuit.create_net("n2")

c1 = circuit.create_device(cap_device, "c1")
c1.set_parameter("C", 1e-15)

c2 = circuit.create_device(cap_device, "c2")
c2.set_parameter("C", 2e-15)

# shorted cap
c1.connect_terminal("A", n1)
c1.connect_terminal("B", n1)

# non-shorted one
c2.connect_terminal("A", n1)
c2.connect_terminal("B", n2)

netlist.simplify()

print(netlist)
```

Current output:

```
circuit TOP (n1=n1,n2=n2);
  device model_name c1 (A=n1,B=n1) (C=1e-15,A=0,P=0);
  device model_name c2 (A=n1,B=n2) (C=2e-15,A=0,P=0);
end;
```

Proposed output:


```
circuit TOP (n1=n1,n2=n2);
  device model_name c2 (A=n1,B=n2) (C=2e-15,A=0,P=0);
end;
```

2. Netlist#combine_devices does not work properly on small (typical cap values):

Example:

```

netlist = pya.Netlist()

circuit = pya.Circuit()
circuit.name = "TOP"

netlist.add(circuit)

cap_device = pya.DeviceClassCapacitor()
cap_device.name = "model_name"

netlist.add(cap_device)

n1 = circuit.create_net("n1")
n2 = circuit.create_net("n2")
n3 = circuit.create_net("n3")

p1 = circuit.create_pin("p1")
p2 = circuit.create_pin("p2")

circuit.connect_pin(p1, n1)
circuit.connect_pin(p2, n3)

c1 = circuit.create_device(cap_device, "c1")
c1.set_parameter("C", 1e-15)

c2 = circuit.create_device(cap_device, "c2")
c2.set_parameter("C", 2e-15)

c3 = circuit.create_device(cap_device, "c3")
c3.set_parameter("C", 3e-15)

# C1 and C2 are series caps
c1.connect_terminal("A", n1)
c1.connect_terminal("B", n2)

c2.connect_terminal("A", n2)
c2.connect_terminal("B", n3)

# C3 connects across
c3.connect_terminal("A", n1)
c3.connect_terminal("B", n3)

print(netlist)

netlist.combine_devices()

print(netlist)
```

Output:

```
circuit TOP (p1=n1,p2=n3);
  device model_name c1 (A=n1,B=n2) (C=1e-15,A=0,P=0);
  device model_name c2 (A=n2,B=n3) (C=2e-15,A=0,P=0);
  device model_name c3 (A=n1,B=n3) (C=3e-15,A=0,P=0);
end;

circuit TOP (p1=n1,p2=n3);
  device model_name c1 (A=n1,B=n3) (C=3e-15,A=0,P=0);
end;
```

Expected output for combined-device netlist

```
circuit TOP (p1=n1,p2=n3);
  device model_name c1 (A=n1,B=n3) (C=3.66666666667e-15,A=0,P=0);
end;
```


Discussion / hints:

Fix patch:
diff --git a/src/db/db/dbCircuit.cc b/src/db/db/dbCircuit.cc
index c9c4d2153..49d3e91d5 100644
--- a/src/db/db/dbCircuit.cc
+++ b/src/db/db/dbCircuit.cc
@@ -825,6 +825,41 @@ void Circuit::do_purge_nets (bool keep_pins)
   }
 }
 
+static bool can_purge_device (const db::Device &device)
+{
+  if (! device.device_class ()) {
+    return false;
+  }
+
+  const std::vector<db::DeviceTerminalDefinition> &tdefs = device.device_class ()->terminal_definitions ();
+  if (tdefs.size () <= 1) {
+    return false;
+  }
+
+  const db::Net *net = device.net_for_terminal (tdefs.front ().id ());
+  for (auto t = tdefs.begin () + 1; t != tdefs.end (); ++t) {
+    if (net != device.net_for_terminal (t->id ())) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+void Circuit::purge_devices ()
+{
+  std::vector<db::Device *> devices_to_be_purged;
+  for (device_iterator d = begin_devices (); d != end_devices (); ++d) {
+    if (can_purge_device (*d)) {
+      devices_to_be_purged.push_back (d.operator-> ());
+    }
+  }
+
+  for (auto d = devices_to_be_purged.begin (); d != devices_to_be_purged.end (); ++d) {
+    remove_device (*d);
+  }
+}
+
 /**
  *  @brief Sanity check for device to be removed
  */
diff --git a/src/db/db/dbCircuit.h b/src/db/db/dbCircuit.h
index abcb74274..9ebc5f83d 100644
--- a/src/db/db/dbCircuit.h
+++ b/src/db/db/dbCircuit.h
@@ -732,6 +732,14 @@ class DB_PUBLIC Circuit
    */
   void purge_nets_keep_pins ();
 
+  /**
+   *  @brief Purges invalid devices
+   *
+   *  This method will purge all invalid devices, i.e. those
+   *  whose terminals are all connected to the same net.
+   */
+  void purge_devices ();
+
   /**
    *  @brief Combine devices
    *
diff --git a/src/db/db/dbNetlist.cc b/src/db/db/dbNetlist.cc
index e5e801863..3c038eb77 100644
--- a/src/db/db/dbNetlist.cc
+++ b/src/db/db/dbNetlist.cc
@@ -619,6 +619,13 @@ void Netlist::purge_nets ()
   }
 }
 
+void Netlist::purge_devices ()
+{
+  for (bottom_up_circuit_iterator c = begin_bottom_up (); c != end_bottom_up (); ++c) {
+    c->purge_devices ();
+  }
+}
+
 void Netlist::make_top_level_pins ()
 {
   size_t ntop = top_circuit_count ();
@@ -684,6 +691,8 @@ void Netlist::simplify ()
 {
   make_top_level_pins ();
   purge ();
+
+  //  combine devices are purge nets that are created in that step
   combine_devices ();
   purge_nets ();
 }
diff --git a/src/db/db/dbNetlist.h b/src/db/db/dbNetlist.h
index 0bff649e1..d21115838 100644
--- a/src/db/db/dbNetlist.h
+++ b/src/db/db/dbNetlist.h
@@ -508,6 +508,14 @@ class DB_PUBLIC Netlist
    */
   void purge_nets ();
 
+  /**
+   *  @brief Purges invalid devices
+   *
+   *  This method will purge all invalid devices, i.e. those
+   *  whose terminals are all connected to the same net.
+   */
+  void purge_devices ();
+
   /**
    *  @brief Creates pins for top-level circuits
    *
diff --git a/src/db/db/dbNetlistDeviceClasses.cc b/src/db/db/dbNetlistDeviceClasses.cc
index 2a1fa9573..5252557d0 100644
--- a/src/db/db/dbNetlistDeviceClasses.cc
+++ b/src/db/db/dbNetlistDeviceClasses.cc
@@ -116,7 +116,7 @@ class ResistorDeviceCombiner
   {
     double va = a->parameter_value (0);
     double vb = b->parameter_value (0);
-    a->set_parameter_value (0, va + vb < 1e-10 ? 0.0 : va * vb / (va + vb));
+    a->set_parameter_value (0, va + vb < 1e-30 ? 0.0 : va * vb / (va + vb));
 
     //  parallel width is sum of both, length is the one that gives the same value of resistance
     //    R = 1/(1/R1 + 1/R2)
@@ -204,7 +204,7 @@ class CapacitorDeviceCombiner
   {
     double va = a->parameter_value (0);
     double vb = b->parameter_value (0);
-    a->set_parameter_value (0, va + vb < 1e-10 ? 0.0 : va * vb / (va + vb));
+    a->set_parameter_value (0, va + vb < 1e-30 ? 0.0 : va * vb / (va + vb));
 
     //  TODO: does this implementation make sense?
     double aa = a->parameter_value (1);
@@ -259,7 +259,7 @@ class InductorDeviceCombiner
   {
     double va = a->parameter_value (0);
     double vb = b->parameter_value (0);
-    a->set_parameter_value (0, va + vb < 1e-10 ? 0.0 : va * vb / (va + vb));
+    a->set_parameter_value (0, va + vb < 1e-30 ? 0.0 : va * vb / (va + vb));
   }
 
   void serial (Device *a, Device *b) const
diff --git a/src/db/db/gsiDeclDbNetlist.cc b/src/db/db/gsiDeclDbNetlist.cc
index 1573631dc..2687fec45 100644
--- a/src/db/db/gsiDeclDbNetlist.cc
+++ b/src/db/db/gsiDeclDbNetlist.cc
@@ -1809,6 +1809,12 @@ Class<db::Circuit> decl_dbCircuit (decl_dbNetlistObject, "db", "Circuit",
     "For example, serial or parallel resistors can be combined into "
     "a single resistor.\n"
   ) +
+  gsi::method ("purge_devices", &db::Circuit::purge_devices,
+    "@brief Purges invalid devices.\n"
+    "Purges devices which are considered invalid. Such devices are for example those whose terminals are all connected to a single net.\n"
+    "\n"
+    "This method has been added in version 0.29.7."
+  ) +
   gsi::method ("purge_nets", &db::Circuit::purge_nets,
     "@brief Purges floating nets.\n"
     "Floating nets are nets with no device or subcircuit attached to. Such floating "
@@ -2176,6 +2182,12 @@ Class<db::Netlist> decl_dbNetlist ("db", "Netlist",
     "Floating nets can be created as effect of reconnections of devices or pins. "
     "This method will eliminate all nets that make less than two connections."
   ) +
+  gsi::method ("purge_devices", &db::Netlist::purge_devices,
+    "@brief Purges invalid devices.\n"
+    "Purges devices which are considered invalid. Such devices are for example those whose terminals are all connected to a single net.\n"
+    "\n"
+    "This method has been added in version 0.29.7."
+  ) +
   gsi::method ("simplify", &db::Netlist::simplify,
     "@brief Convenience method that combines the simplification.\n"
     "This method is a convenience method that runs \\make_top_level_pins, \\purge, \\combine_devices and \\purge_nets."


Test patch:
diff --git a/src/db/unit_tests/dbNetlistTests.cc b/src/db/unit_tests/dbNetlistTests.cc
index 6cb051c86..5a992d7f0 100644
--- a/src/db/unit_tests/dbNetlistTests.cc
+++ b/src/db/unit_tests/dbNetlistTests.cc
@@ -1792,3 +1792,253 @@ TEST(26_JoinNets)
     "end;\n"
   );
 }
+
+//  Issue #1832 - small caps are not combined properly
+TEST(27_CombineSmallC)
+{
+  db::Netlist nl;
+
+  db::Circuit *circuit = new db::Circuit ();
+  circuit->set_name ("TOP");
+  nl.add_circuit (circuit);
+
+  db::DeviceClass *device = new db::DeviceClassCapacitor ();
+  device->set_name ("model_name");
+  nl.add_device_class (device);
+
+  db::Net *n1 = new db::Net ("n1");
+  circuit->add_net (n1);
+
+  db::Net *n2 = new db::Net ("n2");
+  circuit->add_net (n2);
+
+  db::Net *n3 = new db::Net ("n3");
+  circuit->add_net (n3);
+
+  auto p1 = circuit->add_pin ("p1");
+  auto p2 = circuit->add_pin ("p2");
+
+  circuit->connect_pin (p1.id (), n1);
+  circuit->connect_pin (p2.id (), n3);
+
+  db::Device *c1 = new db::Device (device, "c1");
+  circuit->add_device (c1);
+  c1->set_parameter_value (db::DeviceClassCapacitor::param_id_C, 1e-15);
+
+  db::Device *c2 = new db::Device (device, "c2");
+  circuit->add_device (c2);
+  c2->set_parameter_value (db::DeviceClassCapacitor::param_id_C, 2e-15);
+
+  db::Device *c3 = new db::Device (device, "c3");
+  circuit->add_device (c3);
+  c3->set_parameter_value (db::DeviceClassCapacitor::param_id_C, 3e-15);
+
+  c1->connect_terminal (db::DeviceClassCapacitor::terminal_id_A, n1);
+  c1->connect_terminal (db::DeviceClassCapacitor::terminal_id_B, n2);
+
+  c2->connect_terminal (db::DeviceClassCapacitor::terminal_id_A, n2);
+  c2->connect_terminal (db::DeviceClassCapacitor::terminal_id_B, n3);
+
+  c3->connect_terminal (db::DeviceClassCapacitor::terminal_id_A, n1);
+  c3->connect_terminal (db::DeviceClassCapacitor::terminal_id_B, n3);
+
+  EXPECT_EQ (nl.to_string (),
+    "circuit TOP (p1=n1,p2=n3);\n"
+    "  device model_name c1 (A=n1,B=n2) (C=1e-15,A=0,P=0);\n"
+    "  device model_name c2 (A=n2,B=n3) (C=2e-15,A=0,P=0);\n"
+    "  device model_name c3 (A=n1,B=n3) (C=3e-15,A=0,P=0);\n"
+    "end;\n"
+  );
+
+  nl.combine_devices ();
+
+  EXPECT_EQ (nl.to_string (),
+    "circuit TOP (p1=n1,p2=n3);\n"
+    "  device model_name c1 (A=n1,B=n3) (C=3.66666666667e-15,A=0,P=0);\n"
+    "end;\n"
+  );
+}
+
+TEST(27_CombineSmallR)
+{
+  db::Netlist nl;
+
+  db::Circuit *circuit = new db::Circuit ();
+  circuit->set_name ("TOP");
+  nl.add_circuit (circuit);
+
+  db::DeviceClass *device = new db::DeviceClassResistor ();
+  device->set_name ("model_name");
+  nl.add_device_class (device);
+
+  db::Net *n1 = new db::Net ("n1");
+  circuit->add_net (n1);
+
+  db::Net *n2 = new db::Net ("n2");
+  circuit->add_net (n2);
+
+  db::Net *n3 = new db::Net ("n3");
+  circuit->add_net (n3);
+
+  auto p1 = circuit->add_pin ("p1");
+  auto p2 = circuit->add_pin ("p2");
+
+  circuit->connect_pin (p1.id (), n1);
+  circuit->connect_pin (p2.id (), n3);
+
+  db::Device *c1 = new db::Device (device, "c1");
+  circuit->add_device (c1);
+  c1->set_parameter_value (db::DeviceClassResistor::param_id_R, 1e-15);
+
+  db::Device *c2 = new db::Device (device, "c2");
+  circuit->add_device (c2);
+  c2->set_parameter_value (db::DeviceClassResistor::param_id_R, 2e-15);
+
+  db::Device *c3 = new db::Device (device, "c3");
+  circuit->add_device (c3);
+  c3->set_parameter_value (db::DeviceClassResistor::param_id_R, 3e-15);
+
+  c1->connect_terminal (db::DeviceClassResistor::terminal_id_A, n1);
+  c1->connect_terminal (db::DeviceClassResistor::terminal_id_B, n2);
+
+  c2->connect_terminal (db::DeviceClassResistor::terminal_id_A, n2);
+  c2->connect_terminal (db::DeviceClassResistor::terminal_id_B, n3);
+
+  c3->connect_terminal (db::DeviceClassResistor::terminal_id_A, n1);
+  c3->connect_terminal (db::DeviceClassResistor::terminal_id_B, n3);
+
+  EXPECT_EQ (nl.to_string (),
+    "circuit TOP (p1=n1,p2=n3);\n"
+    "  device model_name c1 (A=n1,B=n2) (R=1e-15,L=0,W=0,A=0,P=0);\n"
+    "  device model_name c2 (A=n2,B=n3) (R=2e-15,L=0,W=0,A=0,P=0);\n"
+    "  device model_name c3 (A=n1,B=n3) (R=3e-15,L=0,W=0,A=0,P=0);\n"
+    "end;\n"
+  );
+
+  nl.combine_devices ();
+
+  EXPECT_EQ (nl.to_string (),
+    "circuit TOP (p1=n1,p2=n3);\n"
+    "  device model_name c1 (A=n1,B=n3) (R=1.5e-15,L=0,W=0,A=0,P=0);\n"
+    "end;\n"
+  );
+}
+
+TEST(27_CombineSmallL)
+{
+  db::Netlist nl;
+
+  db::Circuit *circuit = new db::Circuit ();
+  circuit->set_name ("TOP");
+  nl.add_circuit (circuit);
+
+  db::DeviceClass *device = new db::DeviceClassInductor ();
+  device->set_name ("model_name");
+  nl.add_device_class (device);
+
+  db::Net *n1 = new db::Net ("n1");
+  circuit->add_net (n1);
+
+  db::Net *n2 = new db::Net ("n2");
+  circuit->add_net (n2);
+
+  db::Net *n3 = new db::Net ("n3");
+  circuit->add_net (n3);
+
+  auto p1 = circuit->add_pin ("p1");
+  auto p2 = circuit->add_pin ("p2");
+
+  circuit->connect_pin (p1.id (), n1);
+  circuit->connect_pin (p2.id (), n3);
+
+  db::Device *c1 = new db::Device (device, "c1");
+  circuit->add_device (c1);
+  c1->set_parameter_value (db::DeviceClassInductor::param_id_L, 1e-15);
+
+  db::Device *c2 = new db::Device (device, "c2");
+  circuit->add_device (c2);
+  c2->set_parameter_value (db::DeviceClassInductor::param_id_L, 2e-15);
+
+  db::Device *c3 = new db::Device (device, "c3");
+  circuit->add_device (c3);
+  c3->set_parameter_value (db::DeviceClassInductor::param_id_L, 3e-15);
+
+  c1->connect_terminal (db::DeviceClassInductor::terminal_id_A, n1);
+  c1->connect_terminal (db::DeviceClassInductor::terminal_id_B, n2);
+
+  c2->connect_terminal (db::DeviceClassInductor::terminal_id_A, n2);
+  c2->connect_terminal (db::DeviceClassInductor::terminal_id_B, n3);
+
+  c3->connect_terminal (db::DeviceClassInductor::terminal_id_A, n1);
+  c3->connect_terminal (db::DeviceClassInductor::terminal_id_B, n3);
+
+  EXPECT_EQ (nl.to_string (),
+    "circuit TOP (p1=n1,p2=n3);\n"
+    "  device model_name c1 (A=n1,B=n2) (L=1e-15);\n"
+    "  device model_name c2 (A=n2,B=n3) (L=2e-15);\n"
+    "  device model_name c3 (A=n1,B=n3) (L=3e-15);\n"
+    "end;\n"
+  );
+
+  nl.combine_devices ();
+
+  EXPECT_EQ (nl.to_string (),
+    "circuit TOP (p1=n1,p2=n3);\n"
+    "  device model_name c1 (A=n1,B=n3) (L=1.5e-15);\n"
+    "end;\n"
+  );
+}
+
+TEST(28_EliminateShortedDevices)
+{
+  db::Netlist nl;
+
+  db::Circuit *circuit = new db::Circuit ();
+  circuit->set_name ("TOP");
+  nl.add_circuit (circuit);
+
+  db::DeviceClass *device = new db::DeviceClassCapacitor ();
+  device->set_name ("model_name");
+  nl.add_device_class (device);
+
+  db::Net *n1 = new db::Net ("n1");
+  circuit->add_net (n1);
+
+  db::Net *n2 = new db::Net ("n2");
+  circuit->add_net (n2);
+
+  auto p1 = circuit->add_pin ("p1");
+  auto p2 = circuit->add_pin ("p2");
+
+  circuit->connect_pin (p1.id (), n1);
+  circuit->connect_pin (p2.id (), n2);
+
+  db::Device *c1 = new db::Device (device, "c1");
+  circuit->add_device (c1);
+  c1->set_parameter_value (db::DeviceClassInductor::param_id_L, 1e-15);
+
+  db::Device *c2 = new db::Device (device, "c2");
+  circuit->add_device (c2);
+  c2->set_parameter_value (db::DeviceClassInductor::param_id_L, 2e-15);
+
+  c1->connect_terminal (db::DeviceClassInductor::terminal_id_A, n1);
+  c1->connect_terminal (db::DeviceClassInductor::terminal_id_B, n1);
+
+  c2->connect_terminal (db::DeviceClassInductor::terminal_id_A, n1);
+  c2->connect_terminal (db::DeviceClassInductor::terminal_id_B, n2);
+
+  EXPECT_EQ (nl.to_string (),
+    "circuit TOP (p1=n1,p2=n2);\n"
+    "  device model_name c1 (A=n1,B=n1) (C=1e-15,A=0,P=0);\n"
+    "  device model_name c2 (A=n1,B=n2) (C=2e-15,A=0,P=0);\n"
+    "end;\n"
+  );
+
+  nl.purge_devices ();
+
+  EXPECT_EQ (nl.to_string (),
+    "circuit TOP (p1=n1,p2=n2);\n"
+    "  device model_name c2 (A=n1,B=n2) (C=2e-15,A=0,P=0);\n"
+    "end;\n"
+  );
+}
diff --git a/src/lvs/unit_tests/lvsTests.cc b/src/lvs/unit_tests/lvsTests.cc
index 001df63da..d34b0fb15 100644
--- a/src/lvs/unit_tests/lvsTests.cc
+++ b/src/lvs/unit_tests/lvsTests.cc
@@ -121,6 +121,12 @@ TEST(12_private)
   run_test (_this, "test_12.lvs", "test_12b.cir.gz", "test_12.gds.gz", true);
 }
 
+TEST(121_private)
+{
+  // test_is_long_runner ();
+  run_test (_this, "test_121.lvs", "test_121.cir.gz", "test_121.gds.gz", true);
+}
+
 TEST(13_private)
 {
   // test_is_long_runner ();