I keep track of nodes added to a circuit or subcircuit. This is challenging when it comes to models, because each Ngspice model type calls for a different number of nodes in the device lines of all devices that use it.

nodeSets

The nodeSets dict contains all the active observer sets. Each set contains the nodes connected at a particular level of the circuit. The dict is keyed by a unique ID for each level.

A "level" is found within the setup method of an Include or Subcircuit. The very top level at which observation begins, which is automatically begun when you call an instance of Netlist, has an integer ID of 1.

If you start constructing your circuit inside a nodeCheck context, for example, and then construct two Include objects from there, one after the other, the observer sets for nodes constructed by their setup methods will have IDs of 2 and 3.

Now, note that any nodes added to each of their observer sets will automatically be added to the top-level observer set for ID=1, because Include objects aren't really nested. All nodes connected by their setup methods appear in the circuit in which they were constructed. So, all nodes added to set ID=2 will automatically be added to set ID=1 (but not to set ID=3, because it is a peer).

It's different for a Subcircuit, because nodes connected inside its setup do not appear in the higher-level circuit. For example, suppose the second of your Include objects (ID=2) constructs a Subcircuit, with its observer set assigned ID=4. None of the nodes added to set ID=4 are added to any other set. We do need to map what the subcircuit calls its external nodes vs what the higher-level circuit calls them; that's the job of the subNodes dict, discussed below.

nested

The nested dict contains bools, keyed by ID, indictating whether each active observation is a nested one, i.e., nodes are connected inside a subcircuit.

Before an Include.setup or Subcircuit.setup method begins, its observer ID gets added to this set, with False for an include (because its "internal" nodes are really just part of the higher-level circuit), or True for a subcircuit because its internal nodes are not shared with the higher-level circuit except through the "X1234 a b c foo" subcircuit call. See netlist.Subcircuit.__init__, where NodeStuff._nodecheck gets called with nest=True.

In either case, when the setup method ends and observe is called again with the ID, the ID is removed from this dict.

subNodes

To accurately determine whether all nodes are actually being connected, we need to keep track of what those nodes are called inside a Subcircuit vs what they are called outside of it. That's the job of subNodes, a dict that is keyed by subcircuit name (they are unique). Each entry is a 2-tuple with (1) the observer set ID for the subcircuit's setup and (2) the nodes list with which the Subcircuit was constructed.

Whenever...

Class Variable modelNames A set of all model names defined thus far, global to the entire circuit. (Unfortunately, that's how Ngspice works.)
Class Variable nodeSets A dict, keyed by ID, of active observer sets that each contain the nodes connected at that level of the circuit.
Class Variable nested A dict of bools, keyed by ID, indictating whether each active observation is a nested one, i.e., nodes are connected inside a subcircuit.
Class Method reset Sometimes (e.g., unit testing) you have to clear all my class-wide stuff and make all instances of me act like nothing ever got done with them.
Method otherSets Iterates over the IDs and sets of any IDs other than the specified ID.
Method observe Constructs a set that has nodes automatically added to it as I connect them anywhere, keeping track of it in a dict nodeSets of such sets, keyed by a unique integer ID.
Method parseNodes Parses the supplied text for nodes, adding any that are found.
Method addNodes Call with one or more nodes to add to any appropriate observer sets.
Method addModel Call with the name of an Ngspice model and I will look for that in future non-MODEL calls to determine which args are nodes.
Method registerSubcircuit Call with the unique name of an instance of Subcircuit, the observer ID for its internal nodes, and a sequence of its nodes that appear on the .SUBCKT line of the netlist.
Method __call__ Call with the uppercase reference designator refDes of a device and the args/params parsed from a f.X(...) call.
modelNames =
A set of all model names defined thus far, global to the entire circuit. (Unfortunately, that's how Ngspice works.)
nodeSets =
A dict, keyed by ID, of active observer sets that each contain the nodes connected at that level of the circuit.
nested =
A dict of bools, keyed by ID, indictating whether each active observation is a nested one, i.e., nodes are connected inside a subcircuit.
@classmethod
def reset(cls):

Sometimes (e.g., unit testing) you have to clear all my class-wide stuff and make all instances of me act like nothing ever got done with them.

TODO: It really is a mess using class-wide attributes, even when you don't consider the dangers of a multi-threaded program trying to construct multiple circuits at once.

def otherSets(self, ID):

Iterates over the IDs and sets of any IDs other than the specified ID.

def observe(self, ID=None, nest=False):

Constructs a set that has nodes automatically added to it as I connect them anywhere, keeping track of it in a dict nodeSets of such sets, keyed by a unique integer ID.

Returns the ID of the new set.

Call again with the set ID to de-activate observation; it will no longer be added to. The completed set will be returned.

Called by Elements.observe.

def parseNodes(self, text):

Parses the supplied text for nodes, adding any that are found.

def addNodes(self, *nodes):

Call with one or more nodes to add to any appropriate observer sets.

Unless the current observation is nested, the nodes will be added to all active observer sets. If it's nested, the nodes will only be added to its observer set.

def addModel(self, model):

Call with the name of an Ngspice model and I will look for that in future non-MODEL calls to determine which args are nodes.

def registerSubcircuit(self, name, ID, nodes):

Call with the unique name of an instance of Subcircuit, the observer ID for its internal nodes, and a sequence of its nodes that appear on the .SUBCKT line of the netlist.

def __call__(self, refDes, args, params={}):

Call with the uppercase reference designator refDes of a device and the args/params parsed from a f.X(...) call.

Tracks the nodes with any sets added via calls to observe.

Returns the model name, if any, specified for the device. Otherwise returns None.

API Documentation for pingspice, generated by pydoctor at 2021-09-18 08:41:11.