Ideal diode switching with multiple voltage sources in ModelingToolkit , solver gets stuck at t=0

One additional thing worth mentioning: we noticed that switching to the FBDF() solver allows the system to advance (though only a few steps, Fig1), and when combined with the soft diode model it actually gets further than before (Fig2). However, it still stops prematurely at some point during the simulation.


Edit: Following the SciML FAQ (Frequently Asked Questions Β· DifferentialEquations.jl) we started testing different solvers to see what happened. Using KenCarp4() with a very small fixed timestep the simulation runs to completion and the waveform looks correct:

If anyone could give us more details on why certain solvers handle this type of problem better than others we would really appreciate it, as we are more used to working with commercial software where this kind of solver sensitivity for power electronics circuits is not nearly as apparent. Understanding the reasoning behind it would help us make better choices going forward.

I’ve added this example to ModelingToolkitTolerances.jl examples: ModelingToolkitTolerances.jl/examples/137086.jl at main Β· bradcarman/ModelingToolkitTolerances.jl

When running the tolerance analysis I get the following result…

using ModelingToolkitTolerances
resids = analysis(prob4, Rodas5P())
                             Summary of Max Residuals
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ abstol β”‚ reltol = 0.001 β”‚ reltol = 1.0e-6 β”‚ reltol = 1.0e-9 β”‚ reltol = 1.0e-12 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  0.001 β”‚            N/A β”‚            5.28 β”‚            3.78 β”‚             5.32 β”‚
β”‚ 1.0e-6 β”‚            N/A β”‚           0.305 β”‚           0.127 β”‚           0.0549 β”‚
β”‚ 1.0e-9 β”‚            N/A β”‚           0.585 β”‚          0.0183 β”‚     * 0.000489 * β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Therefore, applying a tighter tolerance for Rodas5P with adaptive solving is able to give a reasonable result…

sol4  = solve(prob4, Rodas5P(); abstol=1e-9, reltol=1e-12) #, dt = 1e-5, adaptive = false)
plot(sol4; idxs=sys4.D3.p.i)

Does this look like the result you were expecting?


Thank you so much for taking the time to add this as an example, that is really helpful. And yes, the results look exactly like what we were expecting. So at least for these cases it is now clear that solver tolerances play a much more important role than we were used to from commercial tools, something we will definitely keep in mind going forward. Thanks a lot :blush:.

By the way, what is the status of derivative discontinuity detection these days in MTK/Dyad. Would it be now possible to implement a piecewise linear diode model like I posted back in late 2023?

If not, perhaps it’s possible to blend the smooth-but-ill-conditioned Shockley with a small-but-not-too-small resistance Ron in series (to limit the di/dv derivative of the I-V curve when conducting) and a large-but-not-too-large resistance in parallel (to limit reverse voltage when blocked). Or as put in Modelica Standard Library, a small conductance Goff.

I feel that using resistive β€œnumerical snubber” is safer than capacitors (unless of course the goal is to model a genuine physical snubber in the circuit), because I find it easier to reason about (how much voltage or current error does it bring for a given forward current or reverse voltage).

And hopefully you see that we also have a very helpful community, in case you do run into problems with OSS :wink:

I had no idea about this ModelingToolkitTolerances.jl package. This is great!

By the way, what is the status of derivative discontinuity detection these days in MTK/Dyad. Would it be now possible to implement a piecewise linear diode model like I posted back in late 2023?

It should work (it may need you to set iflifting on the MTK side which hopefully will become on by default soon)

Absolutely, both my colleagues and I come from a commercial tools background and have relatively little experience with MTK and Julia in general, so the community support throughout this forum has been extremely valuable and we are genuinely grateful for it. On that note, if we manage to scale EMT simulations successfully using ModelingToolkit, we would love to contribute back by putting together a library of predefined electrical components with proper documentation, to make MTK more accessible and appealing for electrical engineering applications. It could even be worth exploring whether some of these models could be incorporated into the ModelingToolkitStandardLibrary directly.

Neither did we until this thread, but it turns out to be extremely useful, well worth keeping in mind for any MTK workflow!

Hi again everyone,

We have been running more tests and managed to implement a three-phase rectifier like the one shown earlier in the thread. The code and load current waveform are attached below so you can get an idea of the results. Note that to get the simulation to start we used ramped voltage sources, as suggested by @langestefan.

begin
	function ShockleyDiode(; name, I_S = 1e-6, n_ideal = 1.0, T_K = 293.15)
	
	    @parameters begin
	        I_s   = I_S
	        n_id  = n_ideal
	        T     = T_K
	        k_B   = 1.380649e-23   
	        q_e   = 1.602e-19     
	    end
	    @variables begin
	        v(t) =0.0
	        i(t) 
	    end
	    systems = @named begin
	        p = Pin()
	        n = Pin()
	    end
	
	    eqs = [
	        v   ~ p.v - n.v,
	        i   ~ I_s * (exp(v / (n_id * k_B * T / q_e)) - 1),
	        p.i ~  i,
	        n.i ~ -i,
	    ]
	    System(eqs, t; name, systems)
	end

	
	function SourceAC3(; name,
	                          RMSVoltage = 220.0,
	                          Phase      = 0.0,
	                          Freq       = 50.0,
	                          Ο„          = 1e-6)
	    @named pin_a = Pin()
	    @named pin_b = Pin()
	    @named pin_c = Pin()
	    @named pin_n = Pin()
	
	    @parameters RMSVoltage=RMSVoltage  Phase=Phase  Freq=Freq  Ο„=Ο„
	
	    Vpeak = RMSVoltage * √2
	    ramp  = 1 - exp(-t / Ο„)
	
	    eqs = [
	        pin_a.v - pin_n.v ~ ramp * Vpeak * sin(2Ο€ * Freq * t + Phase),
	        pin_b.v - pin_n.v ~ ramp * Vpeak * sin(2Ο€ * Freq * t + Phase - 2Ο€/3),
	        pin_c.v - pin_n.v ~ ramp * Vpeak * sin(2Ο€ * Freq * t + Phase - 4Ο€/3),
	
	        pin_a.i + pin_b.i + pin_c.i + pin_n.i ~ 0,
	    ]
	
	    System(eqs, t, [], [RMSVoltage, Phase, Freq, Ο„];
	        systems = [pin_a, pin_b, pin_c, pin_n],
	        name)
	end
	
	function DiodeBridge(; name, I_S = 1e-6, n_ideal = 1.0, T_K = 293.15)
	
	    @named D1    = ShockleyDiode(I_S = I_S, n_ideal = n_ideal, T_K = T_K)
	    @named D2    = ShockleyDiode(I_S = I_S, n_ideal = n_ideal, T_K = T_K)
		@named D3    = ShockleyDiode(I_S = I_S, n_ideal = n_ideal, T_K = T_K)
		@named D4    = ShockleyDiode(I_S = I_S, n_ideal = n_ideal, T_K = T_K)
		@named D5    = ShockleyDiode(I_S = I_S, n_ideal = n_ideal, T_K = T_K)
		@named D6    = ShockleyDiode(I_S = I_S, n_ideal = n_ideal, T_K = T_K)
	
	
		eqs = [
		   
	    connect(D1.p, D2.n),
	    connect(D3.p, D4.n),
	    connect(D5.p, D6.n),		
	    connect(D1.n, D3.n, D5.n),
	    connect(D2.p, D4.p, D6.p),
	
		]
	
	    System(eqs, t; systems = [D1, D2, D3, D4,D5 ,D6], name)
	end

	function CircuitBridge(; name,
                               RMSVoltage = 9.0,
                               Freq       = 50.0,
                               R_load     = 10.0,
                               I_S        = 1e-6,
                               n_ideal    = 1.0,
                               T_K        = 293.15)

    
    @named src = SourceAC3Phase(RMSVoltage = RMSVoltage, Freq = Freq, Phase = 0.0)
	@named bridge = DiodeBridge()
	@named gnd   = Ground()

    @named rload = Resistor(R = R_load)


	eqs = [
	   
    connect(src.pin_a, bridge.D1.p, bridge.D2.n),
    connect(src.pin_b, bridge.D3.p, bridge.D4.n),
    connect(src.pin_c, bridge.D5.p, bridge.D6.n),		
    connect(rload.p, bridge.D1.n, bridge.D3.n, bridge.D5.n),
    connect(rload.n, bridge.D2.p, bridge.D4.p, bridge.D6.p),
    connect(src.pin_n, gnd.g),

	]

    System(eqs, t; systems = [src, bridge ,gnd, rload], name)
	end

	@named circuitbridge =  CircuitBridge(RMSVoltage = 9.0, Freq = 50.0, R_load = 100.0)
	
	sysBridge  = mtkcompile(circuitbridge)
	probBridge = ODEProblem(sysBridge, [], (0.0, 0.1))
	
	solBridge  = solve(probBridge,Rodas5P(),abstol=1e-9, reltol=1e-12)
	plot(solBridge, idxs = [circuitbridge.rload.p.i])
end

When comparing these results against Simulink there were no discrepancies, so we can consider this validated.

At this point we started wondering whether we could simulate the same diode rectifier but fed by a synchronous generator instead of ideal voltage sources. As a first step we implemented the system in Fig. 2, where the generator is driven by a constant torque and feeds resistive loads, just to verify the synchronous generator implementation on its own. Comparing those results with Simulink gave a perfect match, which is great.

begin
	function PMSG(; name,
	        R = 0.05,
	        L = 0.000635,   
	        Ξ¨ = 0.192,
	        J = 0.011,
	        F = 0.001889,
	        p = 4,
	        Tf = 0.0)
	
	    @named input1 = RealInput()   
	    @named pin_a  = Pin()
	    @named pin_b  = Pin()
	    @named pin_c  = Pin()
	    @named pin_n  = Pin()          
	
	    @named output1 = RealOutput()   
	    @named output2 = RealOutput()   
	
	    @parameters R=R L=L Ξ¨=Ξ¨ J=J F=F p=p Tf=Tf
	    @variables  ia(t)=0 ib(t)=0 ic(t)=0 Ο‰β‚˜(t)=0 Θ(t)=0 Tβ‚‘(t)=0
	
	    eqs = [
	        
		    D(ia) ~ (1/L)*( Ξ¨*p*Ο‰β‚˜*sin(p*Θ)        - R*ia - (pin_a.v - pin_n.v) ),
		    D(ib) ~ (1/L)*( Ξ¨*p*Ο‰β‚˜*sin(p*Θ - 2Ο€/3) - R*ib - (pin_b.v - pin_n.v) ),
		    D(ic) ~ (1/L)*( Ξ¨*p*Ο‰β‚˜*sin(p*Θ + 2Ο€/3) - R*ic - (pin_c.v - pin_n.v) ),
		
		    
		    Tβ‚‘ ~ Ξ¨*p*( ia*sin(p*Θ) + ib*sin(p*Θ - 2Ο€/3) + ic*sin(p*Θ + 2Ο€/3) ),
	
	        
	        D(Ο‰β‚˜) ~ (1/J)*( input1.u - Tβ‚‘ - Tf - F*Ο‰β‚˜ ),
	        D(Θ)  ~ Ο‰β‚˜,
	
	      
	        pin_a.i ~ -ia,
	        pin_b.i ~ -ib,
	        pin_c.i ~ -ic,
	        pin_a.i + pin_b.i + pin_c.i + pin_n.i ~ 0,
	
	        
	        output1.u ~ Tβ‚‘,
	        output2.u ~ Ο‰β‚˜,
	    ]
	
	    System(eqs, t,
	           [ia, ib, ic, Ο‰β‚˜, Θ, Tβ‚‘],
	           [R, L, Ξ¨, J, F, p, Tf];
	           systems = [input1, pin_a, pin_b, pin_c, pin_n,
	                      output1, output2],
	           name)
	end
	
	function LoadABC(; name, R_L=10.0)
	
	    @named pin_a = Pin()
	    @named pin_b = Pin()
	    @named pin_c = Pin()
	    @named pin_n = Pin()  
	
	    @parameters R_L=R_L
	
	    eqs = [
	       
	        pin_a.v - pin_n.v ~ R_L * pin_a.i,
	        pin_b.v - pin_n.v ~ R_L * pin_b.i,
	        pin_c.v - pin_n.v ~ R_L * pin_c.i,
	
	        
	        pin_n.i ~ -(pin_a.i + pin_b.i + pin_c.i),
	    ]
	
	    System(eqs, t, [], [R_L];
	           systems = [pin_a, pin_b, pin_c, pin_n],
	           name)
	end
	
	function WindResistive(; name, R_L=10.0, p=4)
	
		@named aero = Constant(k=-97.24)
	    @named gen    = PMSG(p=p)
	    @named Aload  = LoadABC(R_L=R_L)
	    @named gnd    = Ground()   
	
	    eqs = [
	        
	        connect(aero.output, gen.input1),
	
	       
	        connect(gen.pin_a, Aload.pin_a),
	        connect(gen.pin_b, Aload.pin_b),
	        connect(gen.pin_c, Aload.pin_c),
	
	
	        connect(gen.pin_n, gnd.g),
	    ]
	
	    System(eqs, t;
	           systems = [aero, gen, Aload, gnd],
	           name)
	end

	@named SistemWindResistive = WindResistive(R_L=10.0)

	sysWindResistive  = mtkcompile(SistemWindResistive)
	
	probWindResistive  = ODEProblem(sysWindResistive , [], (0.0, 15.0))
	solWindResistive  = solve(probWindResistive ,Rodas5P(),dt=0.00001,adaptive=false,maxiters=Int(1e9))
	plot(solWindResistive , idxs = [SistemWindResistive.Aload.pin_a.i], tspan = (0, 0.1))

However, problems arose when trying to combine the synchronous generator with the full bridge rectifier, we cannot get the system to converge. We have tried different solvers and so far none of them have worked (while the system in Simulink, Fig4, does work). At this point we are not sure whether there is a mistake somewhere in the definition of that last circuit that we are not catching, or whether there is still something about the solvers that we are missing. After spending the whole day on this, we are also willing to admit that fresh eyes might spot something obvious that we have been staring at for too long. Any ideas on how to approach this would be enormously helpful.

begin
	function WindDiodes(; name, R_L=10.0, p=4)
	
		@named aero = Constant(k=-97.24)
	    @named gen    = PMSG(p=p)
		@named zbridge = DiodeBridge()
	    @named rload = Resistor(R = R_L)
	    @named gnd    = Ground()   
	
	    eqs = [
	
	        connect(aero.output, gen.input1),
	
	    
	        connect(gen.pin_a, zbridge.D1.p,zbridge.D2.n),
	        connect(gen.pin_b, zbridge.D3.p,zbridge.D4.n),
	        connect(gen.pin_c, zbridge.D5.p,zbridge.D6.n),
			connect(rload.p,zbridge.D1.n,zbridge.D3.n,zbridge.D5.n),
			connect(rload.n,zbridge.D2.p,zbridge.D4.p,zbridge.D6.p),		
		
	
	   
	        connect(gen.pin_n, gnd.g),
	    ]
	
	    System(eqs, t;
	           systems = [aero, gen, rload,zbridge, gnd],
	           name)
	end
	@named winddiodes= WindDiodes(R_L=10.0)
	sysWindDiodes = mtkcompile(winddiodes)
	probWindDiodes= ODEProblem(sysWindDiodes , [], (0.0, 15.0))
	solWindDiodes = solve(probWindDiodes,FBDF(),abstol=1e-12, reltol=1e-12)
	plot(solWindDiodes, idxs = [winddiodes.gen.pin_c.v])
end

P.S.: You may wonder why in Code 2 the load is called Aload instead of simply load. As an extra curiosity, we noticed that depending on variable names, errors related to cyclic guesses can appear. For example, if load is used instead of Aload in Code 2, the following error shows up:

ERROR: Cyclic guesses detected in the system. Symbolic values were found for the following variables/parameters in the map: 

genβ‚Špin_bβ‚Šv(t)  => 10.0genβ‚Šia(t) - 10.0Initial(genβ‚Šia(t)) - loadβ‚Špin_nβ‚Šv(t) - loadβ‚ŠR_L*loadβ‚Špin_aβ‚Ši(t)

In order to resolve this, please provide additional numeric guesses so that the chain can be resolved to assign numeric values to each variable.    
...

I think this is again an initialization/numerical problem that more mature tools like simulink already have solutions for. That’s what you pay for in the end.

I think reading through this issue and comments from @cryptic.ax may help Initialization Failure when Providing Certain State Guesses (Cyclic Guesses Error) Β· Issue #4039 Β· SciML/ModelingToolkit.jl Β· GitHub and see also Homotopy Initialization Β· Issue #3988 Β· SciML/ModelingToolkit.jl Β· GitHub which also mentions the ramping I suggested.

In the end, it’s about how (pairs of) variables show up in the equations that determine whether initialization is numerically possible or not.

Leak resistors should fix the initialization:

@named leak_p = Resistor(R = 1e9)
@named leak_n = Resistor(R = 1e9)

connect(rload.p, leak_p.p),  connect(leak_p.n, gnd.g),
connect(rload.n, leak_n.p),  connect(leak_n.n, gnd.g),

So that V_bus_pos = V_bus_neg = V_a = V_b = V_c = 0 at t = 0

Thanks for the references, we will look into the cyclic guesses issue in more detail.

As a last attempt we tried several things at once: adding high-value resistors in parallel with the bridge diodes, clamping the diode exponential to make the switching less abrupt, adding a DC-side capacitor to smooth the system, and replacing the constant mechanical torque input to the synchronous machine with a ramp to ease initialization. Unfortunately the system still cannot complete the simulation.

We have to admit this is a bit of a disappointment, as there was quite a lot of excitement in our research group about the possibility of using ModelingToolkit for this kind of system. Hopefully it is just a matter of finding the right combination of tricks and we are not too far off.

Code and results are attached below.

	function ShockleyDiode(; name, I_S = 1e-6, n_ideal = 1.0, T_K = 293.15)
	
	    @parameters begin
	        I_s   = I_S
	        n_id  = n_ideal
	        T     = T_K
	        k_B   = 1.380649e-23   
	        q_e   = 1.602e-19     
	    end
	    @variables begin
	        v(t) =0.0
	        i(t) 
	    end
	    systems = @named begin
	        p = Pin()
	        n = Pin()
	    end
	
	    eqs = [
	        v   ~ p.v - n.v,
	        #i   ~ I_s * (exp(v / (n_id * k_B * T / q_e)) - 1),
			i ~ I_s*(exp(clamp(v/(n_id*k_B*T/q_e),-40,40))-1),
	        p.i ~  i,
	        n.i ~ -i,
	    ]
	    System(eqs, t; name, systems)
	end

	function DiodeBridge(; name, I_S = 1e-6, n_ideal = 1.0, T_K = 293.15)
	
	    @named D1    = ShockleyDiode(I_S = I_S, n_ideal = n_ideal, T_K = T_K)
	    @named D2    = ShockleyDiode(I_S = I_S, n_ideal = n_ideal, T_K = T_K)
		@named D3    = ShockleyDiode(I_S = I_S, n_ideal = n_ideal, T_K = T_K)
		@named D4    = ShockleyDiode(I_S = I_S, n_ideal = n_ideal, T_K = T_K)
		@named D5    = ShockleyDiode(I_S = I_S, n_ideal = n_ideal, T_K = T_K)
		@named D6    = ShockleyDiode(I_S = I_S, n_ideal = n_ideal, T_K = T_K)
		@named R1 = Resistor(R=1e8)
		@named R2 = Resistor(R=1e8)
		@named R3 = Resistor(R=1e8)
		@named R4 = Resistor(R=1e8)
		@named R5 = Resistor(R=1e8)
		@named R6 = Resistor(R=1e8)
	
		eqs = [
		   
	    connect(D1.p, D2.n),
	    connect(D3.p, D4.n),
	    connect(D5.p, D6.n),		
	    connect(D1.n, D3.n, D5.n),
	    connect(D2.p, D4.p, D6.p),
		connect(D1.p,R1.p),
		connect(D1.n,R1.n),
		connect(D2.p,R2.p),
		connect(D2.n,R2.n),
		connect(D3.p,R3.p),
		connect(D3.n,R3.n),
		connect(D4.p,R4.p),
		connect(D4.n,R4.n),
		connect(D5.p,R5.p),
		connect(D5.n,R5.n),
		connect(D6.p,R6.p),
		connect(D6.n,R6.n),
	
		]
	
	    System(eqs, t; systems = [D1, D2, D3, D4,D5 ,D6, R1, R2, R3, R4 , R5, R6], name)
	end

	function PMSG(; name,
	        R = 0.05,
	        L = 0.000635,   
	        Ξ¨ = 0.192,
	        J = 0.011,
	        F = 0.001889,
	        p = 4,
	        Tf = 0.0)
	
	    @named input1 = RealInput()   
	    @named pin_a  = Pin()
	    @named pin_b  = Pin()
	    @named pin_c  = Pin()
	    @named pin_n  = Pin()          
	
	    @named output1 = RealOutput()   
	    @named output2 = RealOutput()   
	
	    @parameters R=R L=L Ξ¨=Ξ¨ J=J F=F p=p Tf=Tf
	    @variables  ia(t)=0 ib(t)=0 ic(t)=0 Ο‰β‚˜(t)=0 Θ(t)=0 Tβ‚‘(t)=0
	
	    eqs = [
	        
		    D(ia) ~ (1/L)*( Ξ¨*p*Ο‰β‚˜*sin(p*Θ)        - R*ia - (pin_a.v - pin_n.v) ),
		    D(ib) ~ (1/L)*( Ξ¨*p*Ο‰β‚˜*sin(p*Θ - 2Ο€/3) - R*ib - (pin_b.v - pin_n.v) ),
		    D(ic) ~ (1/L)*( Ξ¨*p*Ο‰β‚˜*sin(p*Θ + 2Ο€/3) - R*ic - (pin_c.v - pin_n.v) ),
		
		    
		    Tβ‚‘ ~ Ξ¨*p*( ia*sin(p*Θ) + ib*sin(p*Θ - 2Ο€/3) + ic*sin(p*Θ + 2Ο€/3) ),
	
	        
	        D(Ο‰β‚˜) ~ (1/J)*( input1.u - Tβ‚‘ - Tf - F*Ο‰β‚˜ ),
	        D(Θ)  ~ Ο‰β‚˜,
	
	      
	        pin_a.i ~ -ia,
	        pin_b.i ~ -ib,
	        pin_c.i ~ -ic,
	        pin_a.i + pin_b.i + pin_c.i + pin_n.i ~ 0,
	
	        
	        output1.u ~ Tβ‚‘,
	        output2.u ~ Ο‰β‚˜,
	    ]
	
	    System(eqs, t,
	           [ia, ib, ic, Ο‰β‚˜, Θ, Tβ‚‘],
	           [R, L, Ξ¨, J, F, p, Tf];
	           systems = [input1, pin_a, pin_b, pin_c, pin_n,
	                      output1, output2],
	           name)
	end

	function WindDiodes(; name, R_L=10.0, p=4)
	
		#@named aero = Constant(k=-97.24)
		@named aero = Ramp(offset=0.0, height=-97.24, duration=2.0, start_time=0.0)
	    @named gen    = PMSG(p=p)
		@named zbridge = DiodeBridge()
	    @named rload = Resistor(R = R_L)
	    @named gnd    = Ground()   # fija V_neutro = 0
		@named leak_p = Resistor(R = 1e9)
		@named leak_n = Resistor(R = 1e9)
		@named cap = Capacitor(C=1e-6,v=0.0)
	
	    eqs = [
	        
	        connect(aero.output, gen.input1),
	
	      
	        connect(gen.pin_a, zbridge.D1.p,zbridge.D2.n),
	        connect(gen.pin_b, zbridge.D3.p,zbridge.D4.n),
	        connect(gen.pin_c, zbridge.D5.p,zbridge.D6.n),
			connect(rload.p,zbridge.D1.n,zbridge.D3.n,zbridge.D5.n),
			connect(rload.n,zbridge.D2.p,zbridge.D4.p,zbridge.D6.p),
			connect(rload.p, leak_p.p),  connect(leak_p.n, gnd.g),
			connect(rload.n, leak_n.p),  connect(leak_n.n, gnd.g),
			connect(cap.p,rload.p),
			connect(cap.n,rload.n),
		
	
	
	        connect(gen.pin_n, gnd.g),
	    ]
	
	    System(eqs, t;
	           systems = [aero, gen, rload,leak_p,leak_n,zbridge, cap, gnd],
	           name)
	end

@named winddiodes= WindDiodes(R_L=10.0)
sysWindDiodes = mtkcompile(winddiodes)
probWindDiodes= ODEProblem(sysWindDiodes , [], (0.0, 15.0),guesses=[sysWindDiodes.leak_p.v=>0,sysWindDiodes.leak_p.i=>0])
solWindDiodes = solve(probWindDiodes,FBDF(),abstol=1e-12, reltol=1e-12,maxiters=9999999999999)
plot(solWindDiodes, idxs = [winddiodes.gen.pin_a.i])

Can you provide a complete, simple example that fails?

As far as I understand, this feature needs to be added to MTK before we can use MTK as a reliable tool for circuit simulations: Homotopy Initialization Β· Issue #3988 Β· SciML/ModelingToolkit.jl Β· GitHub

I guess that it is not very difficult, but someone needs to find the time to do it. Some more explanation: https://www.perplexity.ai/search/c027f05f-7b27-4f94-a4ec-0ffc6b5a0b17

Perhaps it is possible to find a student or researcher to implement this feature?

Extra question: Is it just an initialization problem, or do problems also occur later during the simulation, after a successful initialization?

Nowadays, AI tools can do a lot, but they always need a set of good testcases. @JaviJR Perhaps you can create a repository with test cases?

To summarize where we stand: all the circuits we have tested with diodes and ideal voltage sources have ended up working. The synchronous machine with resistive load also works without issues. The problem appears specifically when combining the synchronous machine with the diode bridge. The three-phase rectifier on its own works too, with the only requirement being that the voltage sources need to ramp up slowly for initialization to succeed.

For the synchronous machine + diode bridge case, as you can see in the last plot, the system does initialize and starts running, but stops abruptly after just a few milliseconds. So it is not a complete failure, it gets going but cannot sustain the simulation.

Regarding the AI suggestion, we did try it, but honestly the most fruitful help so far has come from the contributions of everyone in this thread.

If it starts, then perhaps it is not an MTK problem, but a solver problem? Can you share the complete, failing example code? I would like to have a look at it myself.

And if the problem is that we are missing the right solver, then I would like to create an issue in the correct repository.

I think you are looking for a workaround; I am looking for a sustainable solution.

I will paste you here the complete code for the three more relevant cases, so you can check them out:

WORKING SIMULATIONS

begin
	function ShockleyDiode(; name, I_S = 1e-6, n_ideal = 1.0, T_K = 293.15)
	
	    @parameters begin
	        I_s   = I_S
	        n_id  = n_ideal
	        T     = T_K
	        k_B   = 1.380649e-23   
	        q_e   = 1.602e-19     
	    end
	    @variables begin
	        v(t) =0.0
	        i(t) 
	    end
	    systems = @named begin
	        p = Pin()
	        n = Pin()
	    end
	
	    eqs = [
	        v   ~ p.v - n.v,
	        #i   ~ I_s * (exp(v / (n_id * k_B * T / q_e)) - 1),
			i ~ I_s*(exp(clamp(v/(n_id*k_B*T/q_e),-40,40))-1),
	        p.i ~  i,
	        n.i ~ -i,
	    ]
	    System(eqs, t; name, systems)
	end

	

	
	function SourceAC3(; name,
	                          RMSVoltage = 220.0,
	                          Phase      = 0.0,
	                          Freq       = 50.0,
	                          Ο„          = 1e-6)
	    @named pin_a = Pin()
	    @named pin_b = Pin()
	    @named pin_c = Pin()
	    @named pin_n = Pin()
	
	    @parameters RMSVoltage=RMSVoltage  Phase=Phase  Freq=Freq  Ο„=Ο„
	
	    Vpeak = RMSVoltage * √2
	    ramp  = 1 - exp(-t / Ο„)
	
	    eqs = [
	        pin_a.v - pin_n.v ~ ramp * Vpeak * sin(2Ο€ * Freq * t + Phase),
	        pin_b.v - pin_n.v ~ ramp * Vpeak * sin(2Ο€ * Freq * t + Phase - 2Ο€/3),
	        pin_c.v - pin_n.v ~ ramp * Vpeak * sin(2Ο€ * Freq * t + Phase - 4Ο€/3),
	
	        pin_a.i + pin_b.i + pin_c.i + pin_n.i ~ 0,
	    ]
	
	    System(eqs, t, [], [RMSVoltage, Phase, Freq, Ο„];
	        systems = [pin_a, pin_b, pin_c, pin_n],
	        name)
	end

	
	function DiodeBridge(; name, I_S = 1e-6, n_ideal = 1.0, T_K = 293.15)
	
	    @named D1    = ShockleyDiode(I_S = I_S, n_ideal = n_ideal, T_K = T_K)
	    @named D2    = ShockleyDiode(I_S = I_S, n_ideal = n_ideal, T_K = T_K)
		@named D3    = ShockleyDiode(I_S = I_S, n_ideal = n_ideal, T_K = T_K)
		@named D4    = ShockleyDiode(I_S = I_S, n_ideal = n_ideal, T_K = T_K)
		@named D5    = ShockleyDiode(I_S = I_S, n_ideal = n_ideal, T_K = T_K)
		@named D6    = ShockleyDiode(I_S = I_S, n_ideal = n_ideal, T_K = T_K)
		@named R1 = Resistor(R=1e8)
		@named R2 = Resistor(R=1e8)
		@named R3 = Resistor(R=1e8)
		@named R4 = Resistor(R=1e8)
		@named R5 = Resistor(R=1e8)
		@named R6 = Resistor(R=1e8)
	
		eqs = [
		   
	    connect(D1.p, D2.n),
	    connect(D3.p, D4.n),
	    connect(D5.p, D6.n),		
	    connect(D1.n, D3.n, D5.n),
	    connect(D2.p, D4.p, D6.p),
		connect(D1.p,R1.p),
		connect(D1.n,R1.n),
		connect(D2.p,R2.p),
		connect(D2.n,R2.n),
		connect(D3.p,R3.p),
		connect(D3.n,R3.n),
		connect(D4.p,R4.p),
		connect(D4.n,R4.n),
		connect(D5.p,R5.p),
		connect(D5.n,R5.n),
		connect(D6.p,R6.p),
		connect(D6.n,R6.n),
	
		]
	
	    System(eqs, t; systems = [D1, D2, D3, D4,D5 ,D6, R1, R2, R3, R4 , R5, R6], name)
	end

	function CircuitBridge(; name,
                               RMSVoltage = 9.0,
                               Freq       = 50.0,
                               R_load     = 10.0,
                               I_S        = 1e-6,
                               n_ideal    = 1.0,
                               T_K        = 293.15)

    
    @named src = SourceAC3Phase(RMSVoltage = RMSVoltage, Freq = Freq, Phase = 0.0)
	@named bridge = DiodeBridge()
	@named gnd   = Ground()

    @named rload = Resistor(R = R_load)


	eqs = [
	   
    connect(src.pin_a, bridge.D1.p, bridge.D2.n),
    connect(src.pin_b, bridge.D3.p, bridge.D4.n),
    connect(src.pin_c, bridge.D5.p, bridge.D6.n),		
    connect(rload.p, bridge.D1.n, bridge.D3.n, bridge.D5.n),
    connect(rload.n, bridge.D2.p, bridge.D4.p, bridge.D6.p),
    connect(src.pin_n, gnd.g),

	]

    System(eqs, t; systems = [src, bridge ,gnd, rload], name)
	end

	@named circuitbridge =  CircuitBridge(RMSVoltage = 9.0, Freq = 50.0, R_load = 100.0)
	
	sysBridge  = mtkcompile(circuitbridge)
	probBridge = ODEProblem(sysBridge, [], (0.0, 0.1))
	
	solBridge  = solve(probBridge,Rodas5P(),abstol=1e-9, reltol=1e-12)
	plot(solBridge, idxs = [circuitbridge.rload.p.i])
end

RESULT(note that the sources do not start abruptly):

  • Synchronous machine with resistive load (no rectifiers)

The associated code is the following:

begin
	function PMSG(; name,
	        R = 0.05,
	        L = 0.000635,   
	        Ξ¨ = 0.192,
	        J = 0.011,
	        F = 0.001889,
	        p = 4,
	        Tf = 0.0)
	
	    @named input1 = RealInput()   
	    @named pin_a  = Pin()
	    @named pin_b  = Pin()
	    @named pin_c  = Pin()
	    @named pin_n  = Pin()          
	
	    @named output1 = RealOutput()   
	    @named output2 = RealOutput()   
	
	    @parameters R=R L=L Ξ¨=Ξ¨ J=J F=F p=p Tf=Tf
	    @variables  ia(t)=0 ib(t)=0 ic(t)=0 Ο‰β‚˜(t)=0 Θ(t)=0 Tβ‚‘(t)=0
	
	    eqs = [
	        
		    D(ia) ~ (1/L)*( Ξ¨*p*Ο‰β‚˜*sin(p*Θ)        - R*ia - (pin_a.v - pin_n.v) ),
		    D(ib) ~ (1/L)*( Ξ¨*p*Ο‰β‚˜*sin(p*Θ - 2Ο€/3) - R*ib - (pin_b.v - pin_n.v) ),
		    D(ic) ~ (1/L)*( Ξ¨*p*Ο‰β‚˜*sin(p*Θ + 2Ο€/3) - R*ic - (pin_c.v - pin_n.v) ),
		
		    
		    Tβ‚‘ ~ Ξ¨*p*( ia*sin(p*Θ) + ib*sin(p*Θ - 2Ο€/3) + ic*sin(p*Θ + 2Ο€/3) ),
	
	        
	        D(Ο‰β‚˜) ~ (1/J)*( input1.u - Tβ‚‘ - Tf - F*Ο‰β‚˜ ),
	        D(Θ)  ~ Ο‰β‚˜,
	
	      
	        pin_a.i ~ -ia,
	        pin_b.i ~ -ib,
	        pin_c.i ~ -ic,
	        pin_a.i + pin_b.i + pin_c.i + pin_n.i ~ 0,
	
	        
	        output1.u ~ Tβ‚‘,
	        output2.u ~ Ο‰β‚˜,
	    ]
	
	    System(eqs, t,
	           [ia, ib, ic, Ο‰β‚˜, Θ, Tβ‚‘],
	           [R, L, Ξ¨, J, F, p, Tf];
	           systems = [input1, pin_a, pin_b, pin_c, pin_n,
	                      output1, output2],
	           name)
	end
	
	function LoadABC(; name, R_L=10.0)
	
	    @named pin_a = Pin()
	    @named pin_b = Pin()
	    @named pin_c = Pin()
	    @named pin_n = Pin()  
	
	    @parameters R_L=R_L
	
	    eqs = [
	       
	        pin_a.v - pin_n.v ~ R_L * pin_a.i,
	        pin_b.v - pin_n.v ~ R_L * pin_b.i,
	        pin_c.v - pin_n.v ~ R_L * pin_c.i,
	
	        
	        pin_n.i ~ -(pin_a.i + pin_b.i + pin_c.i),
	    ]
	
	    System(eqs, t, [], [R_L];
	           systems = [pin_a, pin_b, pin_c, pin_n],
	           name)
	end
	
	function WindResistive(; name, R_L=10.0, p=4)
	
	    @named aero = Constant(k=-97.24)
		
	    @named gen    = PMSG(p=p)
	    @named Aload  = LoadABC(R_L=R_L)
	    @named gnd    = Ground()   
	
	    eqs = [
	        
	        connect(aero.output, gen.input1),
	
	       
	        connect(gen.pin_a, Aload.pin_a),
	        connect(gen.pin_b, Aload.pin_b),
	        connect(gen.pin_c, Aload.pin_c),
	
	
	        connect(gen.pin_n, gnd.g),
	    ]
	
	    System(eqs, t;
	           systems = [aero, gen, Aload, gnd],
	           name)
	end

	@named SistemWindResistive = WindResistive(R_L=10.0)

	sysWindResistive  = mtkcompile(SistemWindResistive)
	
	probWindResistive  = ODEProblem(sysWindResistive , [], (0.0, 15.0))
	solWindResistive  = solve(probWindResistive ,Rodas5P(),dt=0.00001,adaptive=false,maxiters=Int(1e9))
	plot(solWindResistive , idxs = [SistemWindResistive.Aload.pin_a.i], tspan = (0, 10))
end

RESULT (The same as in Simulink):

NOT WORKING SIMULATIONS

Synchronous generator + three phase diode rectifier:

Following this scheme, the code associated with this is the following:

	function ShockleyDiode(; name, I_S = 1e-6, n_ideal = 1.0, T_K = 293.15)
	
	    @parameters begin
	        I_s   = I_S
	        n_id  = n_ideal
	        T     = T_K
	        k_B   = 1.380649e-23   
	        q_e   = 1.602e-19     
	    end
	    @variables begin
	        v(t) =0.0
	        i(t) 
	    end
	    systems = @named begin
	        p = Pin()
	        n = Pin()
	    end
	
	    eqs = [
	        v   ~ p.v - n.v,
	        #i   ~ I_s * (exp(v / (n_id * k_B * T / q_e)) - 1),
			i ~ I_s*(exp(clamp(v/(n_id*k_B*T/q_e),-40,40))-1),
	        p.i ~  i,
	        n.i ~ -i,
	    ]
	    System(eqs, t; name, systems)
	end

	function DiodeBridge(; name, I_S = 1e-6, n_ideal = 1.0, T_K = 293.15)
	
	    @named D1    = ShockleyDiode(I_S = I_S, n_ideal = n_ideal, T_K = T_K)
	    @named D2    = ShockleyDiode(I_S = I_S, n_ideal = n_ideal, T_K = T_K)
		@named D3    = ShockleyDiode(I_S = I_S, n_ideal = n_ideal, T_K = T_K)
		@named D4    = ShockleyDiode(I_S = I_S, n_ideal = n_ideal, T_K = T_K)
		@named D5    = ShockleyDiode(I_S = I_S, n_ideal = n_ideal, T_K = T_K)
		@named D6    = ShockleyDiode(I_S = I_S, n_ideal = n_ideal, T_K = T_K)
		@named R1 = Resistor(R=1e8)
		@named R2 = Resistor(R=1e8)
		@named R3 = Resistor(R=1e8)
		@named R4 = Resistor(R=1e8)
		@named R5 = Resistor(R=1e8)
		@named R6 = Resistor(R=1e8)
	
		eqs = [
		   
	    connect(D1.p, D2.n),
	    connect(D3.p, D4.n),
	    connect(D5.p, D6.n),		
	    connect(D1.n, D3.n, D5.n),
	    connect(D2.p, D4.p, D6.p),
		connect(D1.p,R1.p),
		connect(D1.n,R1.n),
		connect(D2.p,R2.p),
		connect(D2.n,R2.n),
		connect(D3.p,R3.p),
		connect(D3.n,R3.n),
		connect(D4.p,R4.p),
		connect(D4.n,R4.n),
		connect(D5.p,R5.p),
		connect(D5.n,R5.n),
		connect(D6.p,R6.p),
		connect(D6.n,R6.n),
	
		]
	
	    System(eqs, t; systems = [D1, D2, D3, D4,D5 ,D6, R1, R2, R3, R4 , R5, R6], name)
	end

	function PMSG(; name,
	        R = 0.05,
	        L = 0.000635,   
	        Ξ¨ = 0.192,
	        J = 0.011,
	        F = 0.001889,
	        p = 4,
	        Tf = 0.0)
	
	    @named input1 = RealInput()   
	    @named pin_a  = Pin()
	    @named pin_b  = Pin()
	    @named pin_c  = Pin()
	    @named pin_n  = Pin()          
	
	    @named output1 = RealOutput()   
	    @named output2 = RealOutput()   
	
	    @parameters R=R L=L Ξ¨=Ξ¨ J=J F=F p=p Tf=Tf
	    @variables  ia(t)=0 ib(t)=0 ic(t)=0 Ο‰β‚˜(t)=0 Θ(t)=0 Tβ‚‘(t)=0
	
	    eqs = [
	        
		    D(ia) ~ (1/L)*( Ξ¨*p*Ο‰β‚˜*sin(p*Θ)        - R*ia - (pin_a.v - pin_n.v) ),
		    D(ib) ~ (1/L)*( Ξ¨*p*Ο‰β‚˜*sin(p*Θ - 2Ο€/3) - R*ib - (pin_b.v - pin_n.v) ),
		    D(ic) ~ (1/L)*( Ξ¨*p*Ο‰β‚˜*sin(p*Θ + 2Ο€/3) - R*ic - (pin_c.v - pin_n.v) ),
		
		    
		    Tβ‚‘ ~ Ξ¨*p*( ia*sin(p*Θ) + ib*sin(p*Θ - 2Ο€/3) + ic*sin(p*Θ + 2Ο€/3) ),
	
	        
	        D(Ο‰β‚˜) ~ (1/J)*( input1.u - Tβ‚‘ - Tf - F*Ο‰β‚˜ ),
	        D(Θ)  ~ Ο‰β‚˜,
	
	      
	        pin_a.i ~ -ia,
	        pin_b.i ~ -ib,
	        pin_c.i ~ -ic,
	        pin_a.i + pin_b.i + pin_c.i + pin_n.i ~ 0,
	
	        
	        output1.u ~ Tβ‚‘,
	        output2.u ~ Ο‰β‚˜,
	    ]
	
	    System(eqs, t,
	           [ia, ib, ic, Ο‰β‚˜, Θ, Tβ‚‘],
	           [R, L, Ξ¨, J, F, p, Tf];
	           systems = [input1, pin_a, pin_b, pin_c, pin_n,
	                      output1, output2],
	           name)
	end

begin
	function WindDiodes(; name, R_L=10.0, p=4)
	
		@named aero = Constant(k=-97)
		#@named aero = Ramp(offset=0.0, height=-97.24, duration=15.0, start_time=0.0)
	    @named gen    = PMSG(p=p)
		@named zbridge = DiodeBridge()
	    @named rload = Resistor(R = R_L)
	    @named gnd    = Ground()   # fija V_neutro = 0
		@named leak_p = Resistor(R = 1e9)
		@named leak_n = Resistor(R = 1e9)
		@named cap = Capacitor(C=1e-6,v=0.0)
	
	    eqs = [
	       
	        connect(aero.output, gen.input1),
	

	        connect(gen.pin_a, zbridge.D1.p,zbridge.D2.n),
	        connect(gen.pin_b, zbridge.D3.p,zbridge.D4.n),
	        connect(gen.pin_c, zbridge.D5.p,zbridge.D6.n),
			connect(rload.p,zbridge.D1.n,zbridge.D3.n,zbridge.D5.n),
			connect(rload.n,zbridge.D2.p,zbridge.D4.p,zbridge.D6.p),
			connect(rload.p, leak_p.p),  connect(leak_p.n, gnd.g),
			connect(rload.n, leak_n.p),  connect(leak_n.n, gnd.g),
			connect(cap.p,rload.p),
			connect(cap.n,rload.n),
		
	
	   
	        connect(gen.pin_n, gnd.g),
	    ]
	
	    System(eqs, t;
	           systems = [aero, gen, rload,leak_p,leak_n,zbridge, cap, gnd],
	           name)
	end

end

And this is the final system that initializes but stops abruptly.

Shall you need more information about the models, or if you encounter something that you may consider a mistake (which in that case, probably is) let me know so we cant try to make these kind of simulations work.

I created a GitHub project to make further investigations easier: GitHub - ufechner7/MTKCircuits.jl Β· GitHub

So far, I:

  • fixed JETLS warnings
  • disabled some MTK warnings
  • reformatted with Runic
  • changed the plotting to ControlPlots (I need to be able to zoom in)
  • in the last example, I changed the solver to a fixed-step solver

The simulation finishes with a result and without any error message. The combined example has one warning, though:

β”Œ Warning: Did not converge after `maxiters = 100` substitutions. Either there \
β”‚ is a cycle in the rules or `maxiters` needs to be higher.
β”” @ Symbolics ~/.julia/packages/Symbolics/oZEAe/src/variable.jl:451

Just tagging @GeorgeGkountouras in case he hasn’t seen this yet

Thank you so much for putting this together, it should make investigating the problem much more comfortable for everyone. On our end we are looking into whether there is a way to make the system less stiff and continuing to dig into it to see if we can find the root cause or a working solution.

I don’t think any of the problems you have run into so far are unique to MTK. What we have learned, is that simulating circuits is non-trivial. And the industry standard tools must be doing a lot of β€˜tricks’ to make it work out of the box for end users. Since MTK is not a tool dedicated to circuit analysis, it does not have those tricks available (yet). So disappointment isn’t the right response here. Especially if you’re in a research group, making existing tools better should be part of the job.