RC -Example model equations

I tried the example ‘Acausal Component – RC-Circuit’ in ModelingToolkit.jl. I have a question regarding accessing equations that describe the system. I see that I can access the differential equations with equations(expand_connections(rc_model)), but how can I get the algebraic equations? I would expect something like this for the example:

resistor.R * capacitor.i + capacitor.v = source.v

The algebraic equation seem to be somehow hidden in observed. Is it possible to retrieve the equation directly?

Thanks for any hint!

What is your intended usage of this? Equations that have been moved to observed are not required to simulate the system, they are available as observed equations such that you can compute the value of any variable in the model given the chosen state variables.

Is it really so? I know that my system is described by the equation: duc/dt=i(t)/C​ but for the simulation, I need to know what i(t) looks like. In this case, i(t)=(u−uc)/R​​. It depends on uc​, R, and u, which are known functions or constants.

To answer your question, I’m trying to understand how it works :blush:. I find the method of generating equations by connecting different models brilliant. It allows me to follow a real circuit diagram. In my specific case, I don’t necessarily need to solve the ODEs because my circuit doesn’t contain any inductors or capacitors. It’s just a huge R−u(t) network. I thought that I could use the MTK to generate linear algebraic equations for my resistor network, and then solve them symbolically or numerically. Is it possible to get a minimal set of equations from MTK that adequately describe my system?

When you call structural_simplify, MTK will figure out the smallest possible set of equations to use for the simulation, and all other equations are moved to be observed. What it means for a variable/equation to be observed is that it can be trivially computed from the minimal set of chosen variables. If you have no differential equations at all, the “simulation” will be empty, but I do believe that the solution object that is returned when you call solve will contain the info you need to get the value for any variable.

Try

sol(0, idxs=resistor.i)

or maybe it would even work to do

prob[resistor.i]

Thank you for your reply!
Unfortunately, it doesn’t work. I tried to solve it like an ODE:

prob = ODEProblem(rnet_model, u0, (0, 10.0))
sol = solve(prob)

However, when I call, for example, sol(0, idxs=rnet_model.R1.i), it returns NaN, and prob[rnet_model.R2.i] returns 0. It’s not so bad for me, I’m interested in the feature of MTK - building symbolic equations from a circuit diagram.

I’ve played with the following circuit.

I would expect that for this specific case, the minimal set of equations is n, but it’s not so. There is an example with a circuit where n = 3.

using ModelingToolkit, Plots, DifferentialEquations
using ModelingToolkit: t_nounits as t, D_nounits as D
using Symbolics

@connector Pin begin
    v(t)
    i(t), [connect = Flow]
end

@mtkmodel Ground begin
    @components begin
        g = Pin()
    end
    @equations begin
        g.v ~ 0
    end
end

@mtkmodel OnePort begin
    @components begin
        p = Pin()
        n = Pin()
    end
    @variables begin
        v(t)
        i(t)
    end
    @equations begin
        v ~ p.v - n.v
        0 ~ p.i + n.i
        i ~ p.i
    end
end

@mtkmodel Resistor begin
    @extend OnePort()
    @parameters begin
        R = 1.0 # Sets the default resistance
    end
    @equations begin
        v ~ i * R
    end
end


@mtkmodel ConstantVoltage begin
    @extend OnePort()
    @parameters begin
        V = 1.0
    end
    @equations begin
        V ~ v
    end
end

@mtkmodel RNetModel begin
    @components begin
		R1 = Resistor(R = 0.16373498241757348)
		R2 = Resistor(R = 0.16373498241757348)
		R3 = Resistor(R = 0.7409669292221475)
		R4 = Resistor(R = 0.7409669292221475)
		R5 = Resistor(R = 0.25021155859159183)
		R6 = Resistor(R = 0.25021155859159183)
		source = ConstantVoltage(V = 1.0)
		ground = Ground()
	end
	@equations begin
		connect(source.p, R1.p)
		connect(R1.n, R2.p)
		connect(source.n, R2.n)
		connect(source.n, ground.g)
		connect(R1.n,R3.p)
		connect(R3.n,R4.p)
		connect(R4.n,R2.n)
		connect(R3.n,R5.p)
		connect(R5.n,R6.p)
		connect(R6.n,R4.n)
	end
end


#include("GeneratedCircuit.jl")

@mtkbuild rnet_model = RNetModel()

println("equations: ", equations(expand_connections(rnet_model) ))
println("unknowns: ", size(unknowns(rnet_model)) )
println("observed: ", size(observed(rnet_model) ))



#u0 = [0]
#prob = ODEProblem(rnet_model, u0, (0, 10.0))
#sol = solve(prob)

unknown_var = unknowns(rnet_model)
observed_var = observed(rnet_model)
observed_dict = Dict()
[observed_dict[x.lhs]=x.rhs for x in observed_var]

sol_eqs=equations(expand_connections(rnet_model))
sol_eqs_sub=Symbolics.fixpoint_sub(sol_eqs,observed_dict)

sym_sol=Symbolics.solve_for(sol_eqs_sub,unknown_var)

If I create the model with @mtkbuild, I see that there are only 2 unknowns, and the others are observed. I’ve tried different configurations, n=5, n=10; for all cases, n is not equal to the number of unknowns. Why is it so? Is there something wrong with my construct, or am I missing something?

My goal is to extract from the circuit the equation in the form of Ax=b. The vector x is x= (i2, i4, …, i2n) or x = (u2, u4, …, u2n), and the matrix A should be n * n.

Try thisi if you want to see all the equations

@named rnet_model = RNetModel()
rnet_model = complete(rnet_model)
julia> equations(expand_connections(rnet_model) )
44-element Vector{Equation}:
 R1₊v(t) ~ R1₊p₊v(t) - R1₊n₊v(t)
 0 ~ R1₊n₊i(t) + R1₊p₊i(t)
 R1₊i(t) ~ R1₊p₊i(t)
 R1₊v(t) ~ R1₊R*R1₊i(t)
 R2₊v(t) ~ R2₊p₊v(t) - R2₊n₊v(t)
 0 ~ R2₊n₊i(t) + R2₊p₊i(t)
 R2₊i(t) ~ R2₊p₊i(t)
 R2₊v(t) ~ R2₊R*R2₊i(t)
 R3₊v(t) ~ -R3₊n₊v(t) + R3₊p₊v(t)
 0 ~ R3₊n₊i(t) + R3₊p₊i(t)
 R3₊i(t) ~ R3₊p₊i(t)
 R3₊v(t) ~ R3₊R*R3₊i(t)
 R4₊v(t) ~ -R4₊n₊v(t) + R4₊p₊v(t)
 0 ~ R4₊p₊i(t) + R4₊n₊i(t)
 R4₊i(t) ~ R4₊p₊i(t)
 R4₊v(t) ~ R4₊R*R4₊i(t)
 R5₊v(t) ~ R5₊p₊v(t) - R5₊n₊v(t)
 0 ~ R5₊p₊i(t) + R5₊n₊i(t)
 R5₊i(t) ~ R5₊p₊i(t)
 R5₊v(t) ~ R5₊R*R5₊i(t)
 R6₊v(t) ~ -R6₊n₊v(t) + R6₊p₊v(t)
 0 ~ R6₊p₊i(t) + R6₊n₊i(t)
 R6₊i(t) ~ R6₊p₊i(t)
 R6₊v(t) ~ R6₊R*R6₊i(t)
 source₊v(t) ~ -source₊n₊v(t) + source₊p₊v(t)
 0 ~ source₊p₊i(t) + source₊n₊i(t)
 source₊i(t) ~ source₊p₊i(t)
 source₊V ~ source₊v(t)
 ground₊g₊v(t) ~ 0
 source₊p₊v(t) ~ R1₊p₊v(t)
 0 ~ source₊p₊i(t) + R1₊p₊i(t)
 R1₊n₊v(t) ~ R2₊p₊v(t)
 R1₊n₊v(t) ~ R3₊p₊v(t)
 0 ~ R1₊n₊i(t) + R3₊p₊i(t) + R2₊p₊i(t)
 ground₊g₊v(t) ~ R6₊n₊v(t)
 ground₊g₊v(t) ~ R2₊n₊v(t)
 ground₊g₊v(t) ~ source₊n₊v(t)
 ground₊g₊v(t) ~ R4₊n₊v(t)
 0 ~ source₊n₊i(t) + R2₊n₊i(t) + ground₊g₊i(t) + R6₊n₊i(t) + R4₊n₊i(t)
 R5₊p₊v(t) ~ R3₊n₊v(t)
 R5₊p₊v(t) ~ R4₊p₊v(t)
 0 ~ R3₊n₊i(t) + R5₊p₊i(t) + R4₊p₊i(t)
 R5₊n₊v(t) ~ R6₊p₊v(t)
 0 ~ R6₊p₊i(t) + R5₊n₊i(t)

ModelingToolkit can solve linear equations analytically and I think this is performed automatically by @mtkbuild. You’ll notice that the equations in observed are all on assignment form, i.e., the LHS is a single term. Once you have solved the two equations in equations(rnet_model), the rest of the equations appear solvable with simple substitution.

1 Like