Self contained circuits in a class

Trying to self contain a whole cirucit+sim in a single class but keep getting ValueError: Can't attach a part to a net in different circuits (, )! have been reading the section Circuit Objects in the docs but no luck. code that I am using is:

from skidl.pyspice import *

reset()

class cir_4_1:
    def __init__(self, voltage=12):
        reset()
        self.voltage=voltage
        
        #create local self continted cirutc
        #need to know how to add title
        self.circuit=Circuit()
        #constuct cirucit
        self.circuit_gen(self.voltage)
        #gen the netlist
        self.circ=self.circuit.generate_netlist()
        print(self.circ)
        
    
    def circuit_gen(self, V1_dc=12):
        """
        cirucit construction
        """
        
        #also tried using `Net( name, circuit=self.circuit)` and nope
        net_1=Net('N1'); net_2=Net('N2')
        net_3=Net('N3'); net_4=Net('N4')
        net_5=Net('N5'); 
        
        #is there a better way to create ground here
        ground=Net('0')
        
        #think issue is here
        self.circuit+=net_1, net_2, net_3, net_4, net_5, ground
        
        #also need to set these into self.circuit and tried 
        #using circuit=self.circuit and nope
        v1=V(dc_value=V1_dc@u_V); v1['p', 'n']+=net_4, ground
        r1=R(ref='R1', value=6@u_Ohm); r1[1, 2]+=net_1, ground
        r2=R(ref='R2', value=2@u_Ohm); r2[1, 2]+=net_1, net_2
        r3=R(ref='R3', value=4@u_Ohm); r3[1, 2]+=net_2, net_4
        r4=R(ref='R4', value=8@u_Ohm); r4[1, 2]+=net_2, net_3
        r5=R(ref='R5', value=4@u_Ohm); r5[1, 2]+=net_3, net_5

        vcvs=E(gain=3); vcvs['ip', 'in']+=net_1, net_2 ;vcvs['op', 'on']+=ground, net_5
    
    def node_sim(self):
        """
        Use standard dc_operating point and then self.circ.node_names 
        to then get all the node voltages
        """
        pass
    
    def i_internal_sim(self):
        """
        Use standard dc_operating point in conjuction with save_internal_parameters
        and self.cric.element to get `@?[i]` to get currents
        """
        pass
        
    
cir_4_1()
    

In SKiDL there is the default_circuit which is a global circuit where parts and nets get placed. Unless you explicitly specify a circuit when you create nets and parts, they will be assigned to default_circuit. So one way to solve your problem is to change something like:

net_1 = Net('N1')

to

net_1 = Net('N1', circuit=self.circuit)

But you need to do this for everything as shown here:

    def circuit_gen(self, V1_dc=12):
        """
        cirucit construction
        """
        
        # with self.circuit:
        #also tried using `Net( name, circuit=self.circuit)` and nope
        net_1=Net('N1', circuit=self.circuit); net_2=Net('N2', circuit=self.circuit)
        net_3=Net('N3', circuit=self.circuit); net_4=Net('N4', circuit=self.circuit)
        net_5=Net('N5', circuit=self.circuit); 
        
        #is there a better way to create ground here
        ground=Net('0', circuit=self.circuit)
        
        # The nets are already in self.circuit, so no need for this
        #think issue is here
        # self.circuit+=net_1, net_2, net_3, net_4, net_5, ground
        
        #also need to set these into self.circuit and tried 
        #using circuit=self.circuit and nope
        v1=V(dc_value=V1_dc@u_V, circuit=self.circuit); v1['p', 'n']+=net_4, ground
        r1=R(ref='R1', value=6@u_Ohm, circuit=self.circuit); r1[1, 2]+=net_1, ground
        r2=R(ref='R2', value=2@u_Ohm, circuit=self.circuit); r2[1, 2]+=net_1, net_2
        r3=R(ref='R3', value=4@u_Ohm, circuit=self.circuit); r3[1, 2]+=net_2, net_4
        r4=R(ref='R4', value=8@u_Ohm, circuit=self.circuit); r4[1, 2]+=net_2, net_3
        r5=R(ref='R5', value=4@u_Ohm, circuit=self.circuit); r5[1, 2]+=net_3, net_5

        vcvs=E(gain=3, circuit=self.circuit); vcvs['ip', 'in']+=net_1, net_2 ;vcvs['op', 'on']+=ground, net_5

Naturally, this is a bit tedious. Instead of doing it for each statement, you can use a with statement to override the default_circuit with another circuit. (This is in the development branch on github. It will be in the next release.) For your example, it would be done like this:

    def circuit_gen(self, V1_dc=12):
        """
        cirucit construction
        """
        
        #  Override the default_circuit with self.circuit...
        with self.circuit:

            #also tried using `Net( name, circuit=self.circuit)` and nope
            net_1=Net('N1'); net_2=Net('N2')
            net_3=Net('N3'); net_4=Net('N4')
            net_5=Net('N5'); 
            
            #is there a better way to create ground here
            ground=Net('0')
            
            # No need to do this, but it doesn't cause an error.
            #think issue is here
            self.circuit+=net_1, net_2, net_3, net_4, net_5, ground
            
            #also need to set these into self.circuit and tried 
            #using circuit=self.circuit and nope
            v1=V(dc_value=V1_dc@u_V); v1['p', 'n']+=net_4, ground
            r1=R(ref='R1', value=6@u_Ohm); r1[1, 2]+=net_1, ground
            r2=R(ref='R2', value=2@u_Ohm); r2[1, 2]+=net_1, net_2
            r3=R(ref='R3', value=4@u_Ohm); r3[1, 2]+=net_2, net_4
            r4=R(ref='R4', value=8@u_Ohm); r4[1, 2]+=net_2, net_3
            r5=R(ref='R5', value=4@u_Ohm); r5[1, 2]+=net_3, net_5

            vcvs=E(gain=3); vcvs['ip', 'in']+=net_1, net_2 ;vcvs['op', 'on']+=ground, net_5

        # Once you leave the with... block, the original default circuit is restored.

Thanks, Dave below is the finished results. Yeah, that with statement will make this so much easier.

from skidl.pyspice import *
from IPython.display import display
import pandas as pd

reset()

class cir_4_1:
    def __init__(self, voltage=12):
        """
        setup and run
        Args:
            voltage (float; 12; volts): dc voltage to set `Vs` to 
        Returns:
            self.node_voltages (pd.Dataframe): static DC operating node voltages
            self.dev_currents (pd.Dataframe): static DC operating device currents
        """
        reset()
        self.voltage=voltage
        
        #create local self continted cirutc
        #need to know how to add title
        self.circuit=Circuit()
        #constuct cirucit
        self.circuit_gen(self.voltage)
        #gen the netlist
        self.circ=self.circuit.generate_netlist()
        print('circuit netlist')
        print(self.circ)
        
        #perform sims and show results in Dataframes
        self.node_sim()
        print('node voltages')
        display(self.node_voltages)
        
        self.i_internal_sim()
        print('device currents')
        display(self.dev_currents)
        
    
    def circuit_gen(self, V1_dc=12):
        """
        cirucit construction
        
        """
        
        #using the `circuit=self.circuit` will use `with` in skidl next release
        net_1=Net('N1', circuit=self.circuit); net_2=Net('N2', circuit=self.circuit)
        net_3=Net('N3', circuit=self.circuit); net_4=Net('N4', circuit=self.circuit)
        net_5=Net('N5', circuit=self.circuit); 
        
        ground=Net('0', circuit=self.circuit)
        
        
        v1=V(ref='Vs', dc_value=V1_dc@u_V, circuit=self.circuit); v1['p', 'n']+=net_4, ground
        r1=R(ref='R1', value=6@u_Ohm, circuit=self.circuit); r1[1, 2]+=net_1, ground
        r2=R(ref='R2', value=2@u_Ohm, circuit=self.circuit); r2[1, 2]+=net_1, net_2
        r3=R(ref='R3', value=4@u_Ohm, circuit=self.circuit); r3[1, 2]+=net_2, net_4
        r4=R(ref='R4', value=8@u_Ohm, circuit=self.circuit); r4[1, 2]+=net_2, net_3
        r5=R(ref='R5', value=4@u_Ohm, circuit=self.circuit); r5[1, 2]+=net_3, net_5

        vcvs=E(gain=3, circuit=self.circuit); vcvs['ip', 'in']+=net_1, net_2 ;vcvs['op', 'on']+=ground, net_5
    
    def node_sim(self):
        """
        Use standard dc_operating point and then self.circ.node_names 
        to then get all the node voltages
        """
        #create sim connection
        sim=self.circ.simulator()
        #run sim
        self.node_sim_res=sim.operating_point()
        
        #get results and throw into pandas
        self.node_voltages={n:self.node_sim_res[n].as_ndarray() for n in self.circ.node_names if n !='0'}
        self.node_voltages=pd.DataFrame.from_dict(self.node_voltages).T
        self.node_voltages.rename(columns={0:'value'}, inplace=True)
        self.node_voltages['units']='[V]'
    
    def i_internal_sim(self):
        """
        Use standard dc_operating point in conjuction with save_internal_parameters
        and self.cric.element to get `@?[i]` to get currents
        """
        
        #this is bacsic, and for demo only need to be checked aginest
        #ngspice docs for what elements have internal current meassurments
        #see "Model and Device Parameters" chapter in the ngspice manual
        saves=[f'@{e}[i]' for e in self.circ.element_names]
        
        #create sim connection
        sim=self.circ.simulator()
        #set to save internals
        sim.save_internal_parameters(*saves)
        #run the sim
        self.intcur_sim_res=sim.operating_point()

        #get results and throw into pandas
        self.dev_currents={e:self.intcur_sim_res[e].as_ndarray() for e in saves}
        self.dev_currents=pd.DataFrame.from_dict(self.dev_currents).T
        self.dev_currents.rename(columns={0:'value'}, inplace=True)
        self.dev_currents['units']='[A]'
        
    
cir_4_1(12)