Example
In this section we will take a look at one way of changing a parameter during a transient simulation. For this, we'll use the following circuit.
// Build a circuit
var ckt = new Circuit(
new Resistor("R1", "in", "out", 1.0e3),
new Resistor("R2", "out", "0", 1.0e3),
new Capacitor("C1", "out", "0", 0.5e-9),
new VoltageSource("V1", "in", "0", new Pulse(0, 5, 1e-6, 1e-6, 1e-6, 1e-5, 2e-5))
);
We also create our transient simulation as we normally would.
// Create the transient analysis and exports
var tran = new Transient("tran", 1e-6, 10e-5);
var outputExport = new RealVoltageExport(tran, "out");
So far so good. Nothing has really changed from before. We will now subscribe to the necessary events to modify the value of R2. We want it to change linearly with time from (1k\Omega) to (11k\Omega).
The biasing behavior of a resistor
The resistance of a resistor can be changed using the Parameters of that resistor. But we cannot change the parameters directly from the entity! In other words, the following will fail to change the resistance for the simulation.
ckt["R1"].SetParameter(newResistance);
This is because the entity is only in charge of creating behaviors for the simulation. After these behaviors are created, the entity has no say anymore in what happens.
The reason why it won't work, is that the resistor's Temperature behavior will derive a conductance property that is used by the biasing behavior. So to have any effect, we:
- change the resistance property in the Parameters.
- call the Temperature method of the Temperature behavior.
If you need to do something similar to other components, it is recommended to take a look at the source code to find out where the parameter is being used.
Requesting the necessary parameters and behaviors
Parameters can potentially be cloned to make them independent of behaviors running in other simulations, so we prefer to ask our simulation for the parameters instead of the original entity. All behaviors will be created during setup, so we can use the AfterSetup
code to retrieve our parameters.
// Now we need to make sure we have a reference to both the base parameters and temperature behavior
// of the resistor
SpiceSharp.Components.Resistors.Parameters bp = null;
SpiceSharp.Behaviors.ITemperatureBehavior tb = null;
Updating the parameters
We need to update the resistance every time the simulation is getting ready to load the Y-matrix and RHS-vector. In other words, by registering to the BeforeLoad event, we can be sure that the resistance is always updated with the latest value.
Note
The
BeforeLoad
andAfterLoad
events are used instead of yielding a code to theforeach
loop because of performance reasons. These events are called in very tight loops.
// Before loading the resistor, let's change its value first!
tran.BeforeLoad += (sender, args) =>
{
// First we need to figure out the timepoint that will be loaded
double time = tran.GetState<IIntegrationMethod>().Time;
// Then we need to calculate the resistance for "R2"
double resistance = 1.0e3 * (1 + time * 1.0e5);
// Now let's update the parameter
bp.Resistance = resistance;
tb.Temperature();
};
// Run the simulation
foreach (int status in tran.Run(ckt, Simulation.AfterSetup | Simulation.Exports))
{
switch (status)
{
case Simulation.AfterSetup:
var eb = tran.EntityBehaviors["R2"];
eb.TryGetValue(out tb);
eb.TryGetParameterSet(out bp);
break;
default:
double time = tran.Time;
double output = outputExport.Value;
break;
}
}
Combining all these code snippets finally results in the following simulation output.