Commit 65bbad6a authored by Sean's avatar Sean
Browse files

Fix bug with deepcopy

When deepcopying a circuit, the nodes were also deepcopied. This is a problem because
nodes are supposed to be singletons. When the user wanted to analyse a deepcopied
circuit that had previously been analysed, the input component would be added with
different nodes even though those nodes would exist in the circuit already (same
name, but different memory locations), leading to singular matrices when solving.

The fix is to implement special deepcopy behaviour for circuits using __deepcopy__.
This is not ideal; it might be worth removing the singleton property of nodes and
make them circuit-specific.
parent b71adcda
Pipeline #99434 passed with stage
in 24 minutes and 46 seconds
......@@ -2,9 +2,12 @@
from unittest import TestCase
from itertools import permutations
from copy import deepcopy
import numpy as np
from zero import Circuit
from zero.components import Resistor, Capacitor, Inductor, OpAmp, Node
from zero.analysis import AcSignalAnalysis
from ..data import ZeroDataTestCase
......@@ -233,3 +236,24 @@ class TestComponentReplacement(ZeroDataTestCase):
self.assertRaises(ValueError, circuit.replace_component, passive, opamp)
# Test that the circuit still has the passive.
self.assertTrue(circuit.has_component(passive.name))
class TestCircuitCopying(ZeroDataTestCase):
def test_deep_copy(self):
"""Test deep copying of a circuit."""
circuit1 = Circuit()
circuit1.add_resistor(name="R1", value=50, node1="n1", node2="n2")
circuit1.add_resistor(name="R2", value=50, node1="n2", node2="gnd")
frequencies = np.logspace(0, 3, 11)
analysis1 = AcSignalAnalysis(circuit=circuit1)
sol1 = analysis1.calculate(frequencies=frequencies, input_type="voltage", node="n1")
# Copy the circuit.
circuit2 = deepcopy(circuit1)
analysis2 = AcSignalAnalysis(circuit=circuit2)
sol2 = analysis2.calculate(frequencies=frequencies, input_type="voltage", node="n1")
self.assertTrue(sol1.equivalent_to(sol2))
......@@ -2,6 +2,7 @@
on which simulations can be performed."""
import logging
from copy import deepcopy
import numpy as np
from .config import ZeroConfig, OpAmpLibrary
......@@ -57,6 +58,38 @@ class Circuit:
new_instance.nodes = set(self.nodes)
return new_instance
def __deepcopy__(self, memo):
"""Deep copy circuit.
Creates a new circuit containing new copies of the components in this one. The new
components have identical properties but are stored in memory as new objects and so any
references to components from the old circuit in user code will not point to the new
versions.
This is useful when you want to create a "master" circuit and modify and analyse several
independent versions of it.
Notes
-----
:class:`Nodes <Node>` are by design singletons, existing in the global state of the Python
kernel, and so the new circuit references the same node objects as the old circuit.
"""
new_instance = self.__class__()
new_instance.nodes = set()
# Shallow copy the nodes, and update the memo to make deepcopy think we already deep copied
# the nodes.
for node in self.nodes:
memo[id(node)] = node
new_instance.nodes.add(node)
# Deep copy the components. deepcopy() should use the already-copied nodes from above.
new_instance.components = deepcopy(self.components, memo)
return new_instance
@property
def non_gnd_nodes(self):
"""Circuit nodes, excluding ground.
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment