maculation of the printing process.

within PNlib.Examples.Models.Printing;

model Maculation "maculation of the printing process"
  parameter Real time_between_macus = 18000 "expected time between two maculations";
  parameter Real time_channel_out = 1000 "Time from pressing the button to the first channel out";
  parameter Real time_macu = 600 "expected time of the maculation (button press)";
  parameter Real time_macu_roleChange = 300 "expected time of the maculation (role chnage)";
  Real NumberMacu = Bundle_in_macu_press.t;
  Boolean macu = bundle_channelOut.t > 0;
  Real color[3] = if macu then {255, 0, 0} else {0, 255, 0};
  // Real h=if pre(bundle_channelOut.t) > 1 then 1/time_macu else 1/time_macu_roleChange;
  parameter Real h = 1/time_macu_roleChange;
  PNlib.Components.TD channelOut1(nIn = 2, arcWeightOut = {1}, nOut = 1, delay = time_channel_out)
  PNlib.Components.TD channelOut(nIn = 2, arcWeightOut = {2}, nOut = 1, delay = 1)
  PNlib.Components.PD start_channelOut(nIn = 1, nOut = 1, maxTokens = 1)
  PNlib.Components.TDS pressing_button(nOut = 1, firingCon = start, nIn = 1, h = 1/time_between_macus)
  PNlib.Components.PD bundle_channelOut(nOut = 4, nIn = 2, maxTokens = 2)
  PNlib.Components.TDS Stop_macu(nIn = 1, nOut = 1, h = h, arcWeightIn = {pre(bundle_channelOut.t)})
  PNlib.Components.PD bundle_not_channelOut(startTokens = 1, maxTokens = 1, nOut = 3, nIn = 2)
  PNlib.Components.TA TA4(testValue = 0)
  PNlib.Components.TA TA3(testValue = 0)
  PNlib.Components.TC Macu_press(nIn = 2, nOut = 1, maximumSpeed = speed, arcWeightIn = {0, 1})
  PNlib.Components.PC Bundle_in_macu_press(nIn = 1, reStart = reStart)
  Interfaces.TransitionIn ausschleussen_[2]
  Interfaces.PlaceOut makulatur_
  Interfaces.TransitionIn buendelinMakuPresse_
  Modelica.Blocks.Interfaces.RealOutput macuBundle_
  PNlib.Components.TD T1(nIn = 2, nOut = 2, arcWeightIn = {pre(bundle_channelOut.t), 1})
  PNlib.Components.TD T2(nIn = 1, nOut = 1)
  PNlib.Components.PD P1(nIn = 2, nOut = 1)
  PNlib.Components.IA IA1(testValue = 1)
protected
  outer Boolean start;
  outer Boolean reStart;
  outer Real speed;
equation
  connect(pressing_button.outPlaces[1], start_channelOut.inTransition[1])
  connect(start_channelOut.outTransition[1], channelOut.inPlaces[1])
  connect(bundle_channelOut.outTransition[1], Stop_macu.inPlaces[1])
  connect(Stop_macu.outPlaces[1], bundle_not_channelOut.inTransition[1])
  connect(bundle_not_channelOut.outTransition[1], channelOut1.inPlaces[1])
  connect(bundle_not_channelOut.outTransition[2], channelOut.inPlaces[2])
  connect(bundle_not_channelOut.outTransition[3], TA4.inPlace)
  connect(TA4.outTransition, makulatur_)
  connect(bundle_channelOut.outTransition[2], TA3.inPlace)
  connect(TA3.outTransition, Macu_press.inPlaces[1])
  connect(Macu_press.outPlaces[1], Bundle_in_macu_press.inTransition[1])
  connect(Macu_press.inPlaces[2], buendelinMakuPresse_)
  connect(Bundle_in_macu_press.pc_t, macuBundle_)
  connect(channelOut1.outPlaces[1], bundle_channelOut.inTransition[1])
  connect(channelOut.outPlaces[1], bundle_channelOut.inTransition[2])
  connect(bundle_channelOut.outTransition[3], T1.inPlaces[1])
  connect(T1.outPlaces[1], bundle_not_channelOut.inTransition[2])
  connect(ausschleussen_[1], T1.inPlaces[2])
  connect(ausschleussen_[2], T2.inPlaces[1])
  connect(T2.outPlaces[1], P1.inTransition[1])
  connect(P1.outTransition[1], channelOut1.inPlaces[2])
  connect(T1.outPlaces[2], P1.inTransition[2])
  connect(bundle_channelOut.outTransition[4], IA1.inPlace)
  connect(IA1.outTransition, pressing_button.inPlaces[1])
end Maculation;


Top model.

within PNlib.Examples.Models.Printing;

model Model "Top model"
  extends Modelica.Icons.Example;
  inner Real speed = start_Stop.speed;
  inner Boolean reStart = start_Stop.reStart;
  inner Boolean start = start_Stop.Start.t > 0;
  Real orders__;
  Real exemplars__;
  Real maculation__;
  Real paper__;
  Real duration__;
  PNlib.Examples.Models.Printing.PrintingMachine printing_machine
  PNlib.Examples.Models.Printing.Start_Stop start_Stop(number_exemplars = 30000)
  PNlib.Examples.Models.Printing.Maculation maculation(time_between_macus = 9000, time_macu = 1200)
  PNlib.Examples.Models.Printing.Counter maculation_(hmax = 40000, text = "maculation")
  PNlib.Examples.Models.Printing.Counter exemplars_(color = {0, 255, 0}, text = "exemplars", hmax = start_Stop.number_exemplars)
  PNlib.Examples.Models.Printing.Role_change role_change(display_time = 200)
  PNlib.Examples.Models.Printing.Counter orders_(color = {0, 255, 0}, hmax = 25, text = "orders")
  PNlib.Examples.Models.Printing.Counter role_(hmax = 20000, color = {85, 170, 255}, text = "meters on role")
  PNlib.Examples.Models.Printing.Counter paper_(hmax = 60000, color = {85, 170, 255}, text = " paper")
  PNlib.Examples.Models.Printing.Counter duration_(color = {0, 255, 0}, hmax = 15000, text = "duration")
  inner PNlib.Components.Settings settings
equation
  when reStart then
    orders__ = orders_.levelSensor_;
    exemplars__ = exemplars_.levelSensor_;
    maculation__ = maculation_.levelSensor_;
    paper__ = paper_.levelSensor_;
    duration__ = duration_.levelSensor_;
  end when;
  connect(maculation.macuBundle_, maculation_.levelSensor)
  connect(printing_machine.number_exemplars_, exemplars_.levelSensor)
  connect(role_change.rolle_, printing_machine.drucken_)
  connect(role_change.rollenwechsel_, maculation.ausschleussen_)
  connect(maculation.makulatur_, printing_machine.maku_)
  connect(printing_machine.ausschleussen, maculation.buendelinMakuPresse_)
  connect(orders_.levelSensor, start_Stop.orders_)
  connect(role_change.meter_role_, role_.levelSensor)
  connect(start_Stop.rollen_wechsel_, role_change.start_)
  connect(printing_machine.papier_total_, paper_.levelSensor)
  connect(start_Stop.time_counter_, duration_.levelSensor)
  connect(printing_machine.exemplare_, start_Stop.exemplare_)
end Model;


Model of a printing production process

within PNlib.Examples.Models;

package Printing "Model of a printing production process"
  extends Modelica.Icons.ExamplesPackage;
end Printing;


printing machine.

within PNlib.Examples.Models.Printing;

model PrintingMachine "printing machine"
  PNlib.Components.TC printing(nOut = 2, maximumSpeed = speed, nIn = 1)
  PNlib.Components.PC printed_paper(nIn = 1, nOut = 1, reStart = reStart) "[m]"
  PNlib.Components.TC cutting(nIn = 1, nOut = 1, maximumSpeed = speed, arcWeightIn = {1.2})
  PNlib.Components.PC Sections(nIn = 1, nOut = 1, reStart = reStart) "m"
  PNlib.Components.TC fold(nIn = 1, nOut = 1, arcWeightOut = {2}, maximumSpeed = speed)
  PNlib.Components.PC FoldedSections(nIn = 1, nOut = 1, reStart = reStart)
  PNlib.Components.TC bundle(nIn = 1, nOut = 1, arcWeightIn = {2}, maximumSpeed = speed)
  PNlib.Components.PC Bundle(nIn = 1, nOut = 1, reStart = reStart)
  PNlib.Components.TC chainUp(nIn = 1, nOut = 2, maximumSpeed = speed)
  PNlib.Components.PC Bundle_on_chain(nIn = 1, nOut = 1, reStart = reStart)
  PNlib.Components.TC Chain_to_macu_press(nIn = 2, nOut = 1, arcWeightIn = {1, 999}, maximumSpeed = speed)
  PNlib.Components.PC bundle_at_macu_press(nIn = 1, nOut = 2, reStart = reStart)
  PNlib.Components.PC Chain_full(nOut = 1, nIn = 1, reStart = reStart)
  PNlib.Components.TA TA1(testValue = 999)
  PNlib.Components.TC chain_up2(nOut = 2, maximumSpeed = speed, arcWeightIn = {1, 0}, nIn = 2)
  PNlib.Components.PC Bundle_on_chain2(nIn = 1, nOut = 1, reStart = reStart)
  PNlib.Components.TC chain1(nIn = 2, nOut = 2, arcWeightIn = {1, 999}, maximumSpeed = speed)
  PNlib.Components.PC Chain_full2(nIn = 1, nOut = 1, reStart = reStart)
  PNlib.Components.TA TA2(testValue = 999)
  PNlib.Components.PC Counter_exemplars(nIn = 1, nOut = 1, reStart = reStart)
  PNlib.Components.PC Exemplars(nIn = 1, nOut = 1, reStart = reStart)
  PNlib.Components.TC Exemplars_Packets(nIn = 1, nOut = 1, arcWeightIn = {240}, maximumSpeed = 1)
  PNlib.Components.PC Packets(nIn = 1, nOut = 1, reStart = reStart)
  PNlib.Components.TC Packets_Pallet(nIn = 1, nOut = 1, arcWeightIn = {100})
  PNlib.Components.PC Pallets(nIn = 1, reStart = reStart)
  Interfaces.TransitionIn drucken_
  Interfaces.TransitionIn maku_
  Interfaces.PlaceOut ausschleussen
  Modelica.Blocks.Interfaces.RealOutput number_exemplars_
  Interfaces.PlaceOut exemplare_
  PNlib.Components.PC Counter_paper(nIn = 1, reStart = reStart)
  Modelica.Blocks.Interfaces.RealOutput papier_total_
protected
  outer Real speed;
  outer Boolean reStart;
equation
  connect(printing.outPlaces[1], printed_paper.inTransition[1])
  connect(printed_paper.outTransition[1], cutting.inPlaces[1])
  connect(cutting.outPlaces[1], Sections.inTransition[1])
  connect(Sections.outTransition[1], fold.inPlaces[1])
  connect(fold.outPlaces[1], FoldedSections.inTransition[1])
  connect(FoldedSections.outTransition[1], bundle.inPlaces[1])
  connect(bundle.outPlaces[1], Bundle.inTransition[1])
  connect(Bundle.outTransition[1], chainUp.inPlaces[1])
  connect(chainUp.outPlaces[1], Bundle_on_chain.inTransition[1])
  connect(Bundle_on_chain.outTransition[1], Chain_to_macu_press.inPlaces[1])
  connect(Chain_to_macu_press.outPlaces[1], bundle_at_macu_press.inTransition[1])
  connect(TA1.outTransition, Chain_to_macu_press.inPlaces[2])
  connect(TA1.inPlace, Chain_full.outTransition[1])
  connect(Chain_full.inTransition[1], chainUp.outPlaces[2])
  connect(chain_up2.outPlaces[1], Bundle_on_chain2.inTransition[1])
  connect(Bundle_on_chain2.outTransition[1], chain1.inPlaces[1])
  connect(Chain_full2.inTransition[1], chain_up2.outPlaces[2])
  connect(Chain_full2.outTransition[1], TA2.inPlace)
  connect(TA2.outTransition, chain1.inPlaces[2])
  connect(chain1.outPlaces[2], Counter_exemplars.inTransition[1])
  connect(Exemplars.outTransition[1], Exemplars_Packets.inPlaces[1])
  connect(Exemplars_Packets.outPlaces[1], Packets.inTransition[1])
  connect(Packets.outTransition[1], Packets_Pallet.inPlaces[1])
  connect(Packets_Pallet.outPlaces[1], Pallets.inTransition[1])
  connect(chain1.outPlaces[1], Exemplars.inTransition[1])
  connect(printing.inPlaces[1], drucken_)
  connect(bundle_at_macu_press.outTransition[1], chain_up2.inPlaces[1])
  connect(chain_up2.inPlaces[2], maku_)
  connect(bundle_at_macu_press.outTransition[2], ausschleussen)
  connect(Counter_exemplars.pc_t, number_exemplars_)
  connect(Counter_exemplars.outTransition[1], exemplare_)
  connect(printing.outPlaces[2], Counter_paper.inTransition[1])
  connect(Counter_paper.pc_t, papier_total_)
end PrintingMachine;


change of the paper role.

within PNlib.Examples.Models.Printing;

model Role_change "change of the paper role"
  import PNlib;
  parameter Real Role_length = 20000 "length of paper on role in meters";
  parameter Real cutting_at = 500 "length by which the rest is cut";
  parameter Real display_time = 100 "for animation";
  PNlib.Components.TD new_role(nOut = 2, delay = 1, nIn = 1, arcWeightIn = {1}, firingCon = pre(start), arcWeightOut = {Role_length, 1})
  PNlib.Components.TD cutting_rest(nIn = 1, nOut = 1, delay = 2, firingCon = pre(start))
  PNlib.Components.PD P1(maxTokens = 1, nIn = 2, nOut = 1)
  PNlib.Components.TD T1(nIn = 2, nOut = 1, firingCon = pre(start), arcWeightIn = {1, Role.t})
  PNlib.Components.PD P2(nIn = 1, nOut = 1, maxTokens = 1)
  PNlib.Components.IA IA2(testValue = cutting_at)
  PNlib.Components.TD Role_change(nIn = 1, maxTokens = 1, nOut = 2)
  PNlib.Components.PC Role(nIn = 1, nOut = 3, minMarks = 0, maxMarks = PNlib.Constants.inf, startMarks = 0, reStart = reStart) "[m]"
  PNlib.Interfaces.PlaceOut rollenwechsel_[2]
  PNlib.Interfaces.PlaceOut rolle_
  Real fireAni[3];
  Modelica.Blocks.Interfaces.RealOutput meter_role_
  PNlib.Interfaces.PlaceIn start_
protected
  outer Boolean start;
  outer Boolean reStart;
equation
  fireAni = if (new_role.fireTime + display_time >= time and new_role.ani) then {255, 255, 0} else {255, 255, 255};
  connect(new_role.outPlaces[1], Role.inTransition[1])
  connect(Role.outTransition[1], IA2.inPlace)
  connect(IA2.outTransition, cutting_rest.inPlaces[1])
  connect(cutting_rest.outPlaces[1], P1.inTransition[1])
  connect(P1.outTransition[1], T1.inPlaces[1])
  connect(T1.outPlaces[1], P2.inTransition[1])
  connect(P2.outTransition[1], new_role.inPlaces[1])
  connect(new_role.outPlaces[2], Role_change.inTransition[1])
  connect(Role_change.outTransition[1], rollenwechsel_[1])
  connect(Role.outTransition[2], T1.inPlaces[2])
  connect(Role.outTransition[3], rolle_)
  connect(Role.pc_t, meter_role_)
  connect(P1.inTransition[2], start_)
  connect(Role_change.outTransition[2], rollenwechsel_[2])
end Role_change;


starting and stop the printing machine.

within PNlib.Examples.Models.Printing;

model Start_Stop "starting and stop the printing machine"
  import PNlib;
  parameter Integer number_exemplars = 100000 "Number of exemplares that has to be produced";
  parameter Real timeStopping = 300 "Time that is needed to shut down the machine when it is stopped";
  parameter Real setupTime = 3600 "Set-up time";
  parameter Real max_Speed = 15 "Maximum speed of the machine in m/s";
  Real speed;
  Real stopTime;
  Real stopTime2;
  Real startTime;
  Boolean reStart;
  PNlib.Components.PD Start(nIn = 1, nOut = 1, maxTokens = 1, startTokens = 0)
  PNlib.Components.TD starting(nOut = 2, nIn = 1, delay = setupTime)
  PNlib.Components.PD Stop(nIn = 1, maxTokens = 1, nOut = 1, startTokens = 0)
  PNlib.Components.PD Counter_Orders(nIn = 1)
  PNlib.Components.TD stopping(nIn = 2, nOut = 1, arcWeightIn = {1, 1})
  PNlib.Components.TA TA5(testValue = number_exemplars)
  Interfaces.TransitionIn exemplare_
  Boolean start = Start.t > 0;
  Real color[3] = if start then {0, 255, 0} else {255, 0, 0};
  Modelica.Blocks.Interfaces.RealOutput orders_
  Interfaces.TransitionOut rollen_wechsel_
  Modelica.Blocks.Interfaces.RealOutput time_counter_
  PNlib.Components.TD shutDown(nIn = 1, nOut = 2, delay = timeStopping)
  PNlib.Components.PD Stop2(nIn = 1, nOut = 1, startTokens = 1, maxTokens = 1)
  Modelica.Blocks.Math.IntegerToReal integerToReal
equation
  reStart = Stop2.t > 0;
  when Stop.t > 0 then
    stopTime = time;
  end when;
  when pre(Stop2.t) > 0 then
    stopTime2 = time;
  end when;
  when start then
    startTime = time;
  end when;
  time_counter_ = time - stopTime2;
  connect(TA5.inPlace, exemplare_)
  connect(TA5.outTransition, stopping.inPlaces[1])
  connect(stopping.outPlaces[1], Stop.inTransition[1])
  connect(starting.outPlaces[1], Start.inTransition[1])
  connect(Start.outTransition[1], stopping.inPlaces[2])
  connect(starting.outPlaces[2], rollen_wechsel_)
algorithm
  if Stop.t > 0 then
    if (Stop2.t < 1 and (max_Speed - max_Speed/timeStopping*(time - stopTime)) > 0) then
      speed := max_Speed - max_Speed/timeStopping*(time - stopTime);
    else
      speed := 0;
    end if;
  else
    if Start.t > 0 then
      if (time - startTime < timeStopping and (max_Speed/timeStopping*(time - startTime)) < max_Speed) then
        speed := max_Speed/timeStopping*(time - startTime);
      else
        speed := 15;
      end if;
    else
      speed := 0;
    end if;
  end if;
equation
  connect(Stop.outTransition[1], shutDown.inPlaces[1])
  connect(shutDown.outPlaces[1], Stop2.inTransition[1])
  connect(Stop2.outTransition[1], starting.inPlaces[1])
  connect(shutDown.outPlaces[2], Counter_Orders.inTransition[1])
  connect(Counter_Orders.pd_t, integerToReal.u)
  connect(integerToReal.y, orders_)
end Start_Stop;


Animation of coffee cup.

within PNlib.Examples.Models.Senseo;

model coffee_cup "Animation of coffee cup"
  Modelica.Blocks.Interfaces.RealInput levelSensor
  Real level "Tank level in % of max height";
  parameter Real hmax = 0.7 "Max volume of tank in l";
  parameter Real color[3] = {0, 0, 255};
equation
  level = levelSensor/hmax;
end coffee_cup;


Integer Counter.

within PNlib.Examples.Models.Senseo;

model Counter "Integer Counter"
  Modelica.Blocks.Interfaces.IntegerInput levelSensor
  Real count;
  parameter Integer hmax = 20;
equation
  count = if levelSensor/hmax < 1 then levelSensor/hmax else 1;
end Counter;


Real Counter.

within PNlib.Examples.Models.Senseo;

model Counter_real "Real Counter"
  parameter String text = "counter";
  parameter Real color[3] = {255, 0, 0};
  Modelica.Blocks.Interfaces.RealInput levelSensor
  Real count;
  parameter Integer hmax = 1000000;
  Real levelSensor_ = if levelSensor > 0 then levelSensor else 0;
equation
  count = levelSensor_/hmax;
end Counter_real;


Insert pad: coffee pads are inserted to the Senseo machine (step 5)..

within PNlib.Examples.Models.Senseo;

model Insert_Pad "Insert pad: coffee pads are inserted to the Senseo machine (step 5)."
  import PNlib.Components.*;
  TDS inserting2(nIn = 1, nOut = 1, arcWeightOut = {1}, h = 1/20)
  PD puffer(nOut = 1, maxTokens = 1, nIn = 1)
  Interfaces.TransitionOut insertpad_
  Real color[3] = if fac < 1 then {80, 0, 0} else {255, 255, 255};
  Real startTime;
  Real fac = if (time - startTime)/60 < 1 and startTime > 0 then (time - startTime)/60 else 0;
  Real recsize[2, 2] = {{-100, 80}, {-100 + 200*fac, -80}};
  Interfaces.PlaceIn insert_
equation
  when puffer.t > 0 then
    startTime = time;
  end when;
  connect(puffer.outTransition[1], inserting2.inPlaces[1])
  connect(inserting2.outPlaces[1], insertpad_)
  connect(puffer.inTransition[1], insert_)
end Insert_Pad;


Model of a Senseo coffee machine

within PNlib.Examples.Models;

package Senseo "Model of a Senseo coffee machine"
  extends Modelica.Icons.ExamplesPackage;
end Senseo;


Refilling water: the water tank of the Senseo machine is refilled (step 3)..

within PNlib.Examples.Models.Senseo;

model Refill_Water "Refilling water: the water tank of the Senseo machine is refilled (step 3)."
  import PNlib.Components.*;
  TD refilling2(nIn = 1, nOut = 2, arcWeightOut = {1, 0.7 - watertank_.t}, delay = 30)
  PD puffer(nIn = 1, nOut = 1, maxTokens = 1)
  TD refilling1(delay = 1, arcWeightOut = {1}, nOut = 1, nIn = 2, arcWeightIn = {1, 0.36})
  IA IA1(testValue = 0.36)
  Interfaces.TransitionIn watertank_
  Interfaces.TransitionOut refill_[2]
  Interfaces.TransitionIn start_
  Real color[3] = if fac < 1 then {0, 0, 255} else {255, 255, 255};
  Real startTime;
  Real fac = if (time - startTime)/60 < 1 and startTime > 0 then (time - startTime)/60 else 0;
  Real recsize[2, 2] = {{-100, 80}, {-100 + 200*fac, -80}};
equation
  when puffer.t > 0 then
    startTime = time;
  end when;
  connect(IA1.inPlace, watertank_)
  connect(refilling1.outPlaces[1], puffer.inTransition[1])
  connect(puffer.outTransition[1], refilling2.inPlaces[1])
  connect(refilling2.outPlaces, refill_)
  connect(refilling1.inPlaces[1], start_)
  connect(IA1.outTransition, refilling1.inPlaces[2])
end Refill_Water;


Senseo machine: the functionality of the Senseo machine (step 4, 6, 7, 8, 9, 10)..

within PNlib.Examples.Models.Senseo;

model Senseo_Maschine "Senseo machine: the functionality of the Senseo machine (step 4, 6, 7, 8, 9, 10)."
  parameter Real Psenseo = 1.450 "power of senseo machine [kW]";
  parameter Real c = 4.182 "specific heat capacity [kJ/(kg*K)]";
  parameter Real k = 0.01177 "proportionality factor for cooling water";
  parameter Real TE = 20 "Environment temperature [°C]";
  parameter Real TC = 10 "Temperature of new water [°C]";
  PNlib.Components.TC heating(nIn = 3, nOut = 1, maximumSpeed = Psenseo/(c*water_hc.t))
  PNlib.Components.TA TA1(testValue = 0)
  PNlib.Components.TA TA2(testValue = 0.34)
  PNlib.Components.PC TW(nIn = 2, nOut = 3, maxMarks = 90, startMarks = 20) "water temperature"
  PNlib.Components.TC pumping(maximumSpeed = 0.1, nOut = 2, nIn = 1, arcWeightOut = {1, TC/water_hc.t})
  PNlib.Components.TC cooling(nIn = 2, maximumSpeed = k*(TW.t - TE), firingCon = TW.t > TE)
  PNlib.Components.TD stopornext(nIn = 2, firingCon = true, delay = 1, nOut = 1)
  PNlib.Components.PC water_hc(nIn = 1, nOut = 2, startMarks = 0.25, maxMarks = 0.25) "water in heating chamber"
  PNlib.Components.TD T1(nIn = 2, nOut = 1, arcWeightIn = {90, 1}, delay = 1)
  PNlib.Components.TA TA3(testValue = 90)
  PNlib.Components.PD decision(nIn = 1, maxTokens = 1, nOut = 2, enablingType = PNlib.Types.EnablingType.Probability, enablingProbOut = {0.7, 0.3})
  PNlib.Components.TD T2(nIn = 1, nOut = 1, delay = 1)
  PNlib.Components.TD T3(nIn = 1, nOut = 1, delay = 1)
  PNlib.Components.PD onecup(nIn = 1, nOut = 1, maxTokens = 1)
  PNlib.Components.PD twocups(nIn = 1, nOut = 1, maxTokens = 1)
  PNlib.Components.TD T4(nIn = 1, nOut = 1, arcWeightOut = {1})
  PNlib.Components.TD T5(nIn = 1, nOut = 1, arcWeightOut = {2})
  PNlib.Components.TC scalding(maximumSpeed = 0.003125, nIn = 3, nOut = 2, arcWeightIn = {1, 1, TW.t/water_hc.t})
  PNlib.Components.TA RA1(testValue = 0)
  PNlib.Components.PC coffee_cup(nIn = 1, nOut = 1)
  inner PNlib.Components.Settings settings
  PNlib.Components.PD ready(maxTokens = 1, nIn = 1, nOut = 1, startTokens = 0)
  PNlib.Components.TD T7(nIn = 2, nOut = 2, firingCon = pre(disamount.t) > 0, arcWeightIn = {pre(disamount.t)*0.125, pre(disamount.t)}, arcWeightOut = {pre(disamount.t), 1}, delay = 1)
  PNlib.Components.PD amountCups(nIn = 1)
  PNlib.Components.PD disamount(nIn = 3, nOut = 2)
  PNlib.Components.PD P1(nOut = 3, maxTokens = 1, nIn = 1)
  PNlib.Components.PD P3(nOut = 1, nIn = 1, maxTokens = 1)
  Modelica.Blocks.Interfaces.RealOutput coffee_cup_
  Modelica.Blocks.Interfaces.IntegerOutput amount_cups_
  Interfaces.TransitionIn watertank_[2]
  Interfaces.TransitionOut stopornext_
  Interfaces.PlaceIn startheating_
  Interfaces.PlaceIn padinsert_
  Modelica.Blocks.Interfaces.RealOutput tw_
  PNlib.Components.IA IA1(testValue = 1)
equation
  connect(TA1.outTransition, heating.inPlaces[1])
  connect(TA2.outTransition, heating.inPlaces[2])
  connect(heating.outPlaces[1], TW.inTransition[1])
  connect(TW.outTransition[1], cooling.inPlaces[1])
  connect(pumping.outPlaces[1], water_hc.inTransition[1])
  connect(TW.outTransition[2], TA3.inPlace)
  connect(TA3.outTransition, T1.inPlaces[1])
  connect(T1.outPlaces[1], decision.inTransition[1])
  connect(decision.outTransition[1], T2.inPlaces[1])
  connect(decision.outTransition[2], T3.inPlaces[1])
  connect(T2.outPlaces[1], onecup.inTransition[1])
  connect(T3.outPlaces[1], twocups.inTransition[1])
  connect(onecup.outTransition[1], T4.inPlaces[1])
  connect(twocups.outTransition[1], T5.inPlaces[1])
  connect(water_hc.outTransition[1], scalding.inPlaces[1])
  connect(RA1.outTransition, heating.inPlaces[3])
  connect(water_hc.outTransition[2], RA1.inPlace)
  connect(scalding.outPlaces[1], coffee_cup.inTransition[1])
  connect(coffee_cup.outTransition[1], T7.inPlaces[1])
  connect(T7.outPlaces[1], amountCups.inTransition[1])
  connect(T7.outPlaces[2], ready.inTransition[1])
  connect(T4.outPlaces[1], disamount.inTransition[1])
  connect(T5.outPlaces[1], disamount.inTransition[2])
  connect(disamount.outTransition[1], T7.inPlaces[2])
  connect(disamount.outTransition[2], scalding.inPlaces[2])
  connect(scalding.outPlaces[2], disamount.inTransition[3])
  connect(P1.outTransition[1], TA1.inPlace)
  connect(P1.outTransition[2], stopornext.inPlaces[1])
  connect(ready.outTransition[1], stopornext.inPlaces[2])
  connect(P3.outTransition[1], T1.inPlaces[2])
  connect(coffee_cup.pc_t, coffee_cup_)
  connect(amountCups.pd_t, amount_cups_)
  connect(stopornext.outPlaces[1], stopornext_)
  connect(watertank_[1], TA2.inPlace)
  connect(watertank_[2], pumping.inPlaces[1])
  connect(P1.inTransition[1], startheating_)
  connect(P3.inTransition[1], padinsert_)
  connect(pumping.outPlaces[2], TW.inTransition[2])
  connect(TW.outTransition[3], scalding.inPlaces[3])
  connect(TW.pc_t, tw_)
  connect(P1.outTransition[3], IA1.inPlace)
  connect(IA1.outTransition, cooling.inPlaces[2])
end Senseo_Maschine;


Top Model.

within PNlib.Examples.Models.Senseo;

model Senseo_Model "Top Model"
  extends Modelica.Icons.Example;
  PNlib.Examples.Models.Senseo.Senseo_Maschine senseo_Maschine
  Water_Tank water_tank
  PNlib.Examples.Models.Senseo.coffee_cup coffe_cup(color = {80, 0, 0}, hmax = 0.25)
  PNlib.Examples.Models.Senseo.Counter amount_cups
  PNlib.Examples.Models.Senseo.Start start(EV_time = 1200)
  PNlib.Examples.Models.Senseo.Refill_Water refill_Water
  PNlib.Examples.Models.Senseo.Insert_Pad insert_Pad
  PNlib.Examples.Models.Senseo.Counter_real counter_real(text = "Temperature", hmax = 90)
  inner PNlib.Components.Settings settings
equation
  connect(senseo_Maschine.amount_cups_, amount_cups.levelSensor)
  connect(refill_Water.refill_[2], water_tank.inTransition1)
  connect(water_tank.refill_, refill_Water.watertank_)
  connect(water_tank.outTransition1, senseo_Maschine.watertank_)
  connect(refill_Water.refill_[1], start.refillstart_)
  connect(start.refill_, refill_Water.start_)
  connect(senseo_Maschine.stopornext_, start.stopornext_)
  connect(start.start_[1], senseo_Maschine.startheating_)
  connect(start.start_[2], insert_Pad.insert_)
  connect(insert_Pad.insertpad_, senseo_Maschine.padinsert_)
  connect(senseo_Maschine.coffee_cup_, coffe_cup.levelSensor)
  connect(senseo_Maschine.tw_, counter_real.levelSensor)
end Senseo_Model;


User action: starting and stopping the machine (step 1, 2, 11), initiating the insertion of coffee pads (step 5) and the refill of water (step 3)..

within PNlib.Examples.Models.Senseo;

model Start "User action: starting and stopping the machine (step 1, 2, 11), initiating the insertion of coffee pads (step 5) and the refill of water (step 3)."
  import PNlib.Components.*;
  parameter Real EV_time = 600;
  TDS starting(h = 1/EV_time, nIn = 1, nOut = 1)
  Real colorHead[3] = if stop.t > 0 then {255, 0, 0} else {0, 255, 0};
  PD start(nOut = 2, nIn = 3, maxTokens = 1)
  PD stop(nIn = 1, startTokens = 1, nOut = 1, maxTokens = 1)
  Interfaces.PlaceOut refill_
  TD T8(nIn = 1, nOut = 1, delay = 1)
  TD T6(nOut = 1, nIn = 1, delay = 1)
  PD nextcoffee(nIn = 1, maxTokens = 1, startTokens = 0, nOut = 1)
  TD T10(nIn = 1, nOut = 1)
  PD P1(nIn = 1, nOut = 2, enablingType = PNlib.Types.EnablingType.Probability, enablingProbOut = {0.2, 0.8}, maxTokens = 1)
  Interfaces.PlaceIn stopornext_
  Interfaces.PlaceIn refillstart_
  TD T1(nIn = 1, nOut = 2)
  Interfaces.TransitionOut start_[2]
equation
  connect(stop.outTransition[1], starting.inPlaces[1])
  connect(start.outTransition[1], refill_)
  connect(P1.outTransition[1], T6.inPlaces[1])
  connect(P1.outTransition[2], T8.inPlaces[1])
  connect(T6.outPlaces[1], nextcoffee.inTransition[1])
  connect(T8.outPlaces[1], stop.inTransition[1])
  connect(P1.inTransition[1], stopornext_)
  connect(nextcoffee.outTransition[1], T10.inPlaces[1])
  connect(T10.outPlaces[1], start.inTransition[1])
  connect(starting.outPlaces[1], start.inTransition[2])
  connect(start.inTransition[3], refillstart_)
  connect(start.outTransition[2], T1.inPlaces[1])
  connect(T1.outPlaces[1], start_[1])
  connect(T1.outPlaces[2], start_[2])
end Start;


Water tank: the water tank of the Senseo machine..

within PNlib.Examples.Models.Senseo;

model Water_Tank "Water tank: the water tank of the Senseo machine."
  import PNlib.Components.*;
  Real level "Tank level in % of max height";
  parameter Real color[3] = {0, 0, 255};
  PC water_tank(nOut = 3, startMarks = 0.5, maxMarks = 0.75, nIn = 1) "cold water in tank"
  Interfaces.PlaceOut outTransition1[2]
  Interfaces.PlaceIn inTransition1
  Interfaces.PlaceOut refill_
equation
  level = water_tank.t/water_tank.maxMarks;
  connect(water_tank.outTransition[1], outTransition1[1])
  connect(water_tank.outTransition[2], outTransition1[2]);
  connect(water_tank.inTransition[1], inTransition1)
  connect(water_tank.outTransition[3], refill_)
end Water_Tank;


within PNlib;

package Functions "contains functions with specific algorithmic procedures which are used in the Petri net component models"
end Functions;


enabling functions

within PNlib.Functions;

package Enabling "enabling functions"
end Enabling;


help functions

within PNlib.Functions;

package OddsAndEnds "help functions"
end OddsAndEnds;


random functions

within PNlib.Functions;

package Random "random functions"
end Random;


within PNlib;

package Interfaces "contains the connectors for the Petri net component models"
  extends Modelica.Icons.InterfacesPackage;
end Interfaces;


part of place model to connect places to input transitions.

within PNlib.Interfaces;

connector PlaceIn "part of place model to connect places to input transitions"
  input Boolean active "Are the input transitions active?"
  input Boolean fire "Do the input transitions fire?"
  input Real arcWeight "Arc weights of input transitions"
  input Integer arcWeightint "Integer arc weights of input transitions"
  input Boolean enabledByInPlaces "Are the input transitions enabled by all theier input places?"
  input Boolean disTransition "Types of input transitions (discrete/stochastic or continuous)"
  input Real instSpeed "Instantaneous speeds of continuous input transitions"
  input Real prelimSpeed "Preliminary speeds of continuous input transitions"
  input Real maxSpeed "Maximum speeds of continuous input transitions"
  output Real t "Marking of the place"
  output Integer tint "Integer marking of the place"
  output Real maxTokens "Maximum capacity of the place"
  output Integer maxTokensint "Integer maximum capacity of the place"
  output Boolean enable "Which of the input transitions are enabled by the place?"
  output Real decreasingFactor "Factor for decreasing the speed of continuous input transitions"
  output Boolean disPlace "Type of the place (discrete or continuous)"
  output Boolean emptied "Is the continuous place emptied by output transitions?"
  output Real speedSum "Output speed of a continuous place"
end PlaceIn;


part of place model to connect places to output transitions.

within PNlib.Interfaces;

connector PlaceOut "part of place model to connect places to output transitions"
  import PNlib.Types.ArcType;
  input Boolean active "Are the output transitions active?"
  input Boolean fire "Do the output transitions fire?"
  input Real arcWeight "Arc weights of output transitions"
  input Integer arcWeightint "Integer arc weights of output transitions"
  input Boolean disTransition "Are the output transitions discrete?"
  input Real instSpeed "Instantaneous speeds of continuous output transitions"
  input Real prelimSpeed "Preliminary speeds of continuous output transitions"
  input Real maxSpeed "Maximum speeds of continuous output transitions"
  output Real t "Marking of the place"
  output Integer tint "Integer marking of the place"
  output Real minTokens "Minimum capacity of the place"
  output Integer minTokensint "Integer minimum capacity of the place"
  output Boolean enable "Which of the output transitions are enabled by the place?"
  output Real decreasingFactor "Factor for decreasing the speed of continuous input transitions"
  output Boolean disPlace "Type of the place (discrete or continuous)"
  output ArcType arcType "Type of output arcs (normal, test, inhibition, or read)"
  output Boolean fed "Is the continuous place fed by input transitions?"
  output Real speedSum "Input speed of a continuous place"
  output Boolean tokenInOut "Does the place have a discrete token change?"
  output Real testValue "Test value of a test or inhibitor arc"
  output Integer testValueint "Integer test value of a test or inhibitor arc"
  output Boolean normalArc "Double arc: test and normal arc or inhibitor and normal arc"
end PlaceOut;


part of transition model to connect transitions to input places.

within PNlib.Interfaces;

connector TransitionIn "part of transition model to connect transitions to input places"
  import PNlib.Types.ArcType;
  input Real t "Markings of input places"
  input Integer tint "Integer Markings of input places"
  input Real minTokens "Minimum capacites of input places"
  input Integer minTokensint "Integer minimum capacites of input places"
  input Boolean enable "Is the transition enabled by input places?"
  input Real decreasingFactor "Factor of continuous input places for decreasing the speed"
  input Boolean disPlace "Types of input places (discrete or continuous)"
  input ArcType arcType "Types of input arcs (normal, test, inhibition, or read)"
  input Boolean fed "Are the continuous input places fed?"
  input Real speedSum "Input speeds of continuous input places"
  input Boolean tokenInOut "Do the input places have a discrete token change?"
  input Real testValue "Test value of a test or inhibitor arc"
  input Integer testValueint "Integer test value of a test or inhibitor arc"
  input Boolean normalArc "Double arc: test and normal arc or inhibitor and normal arc"
  output Boolean active "Is the transition active?"
  output Boolean fire "Does the transition fire?"
  output Real arcWeight "Input arc weights of the transition"
  output Integer arcWeightint "Integer input arc weights of the transition"
  output Boolean disTransition "Type of the transition(discrete/stochastic or continuous)"
  output Real instSpeed "Instantaneous speed of a continuous transition"
  output Real prelimSpeed "Preliminary speed of a continuous transition"
  output Real maxSpeed "Maximum speed of a continuous transition"
end TransitionIn;


part of transition model to connect transitions to output places.

within PNlib.Interfaces;

connector TransitionOut "part of transition model to connect transitions to output places"
  input Real t "Markings of output places"
  input Integer tint "Integer markings of output places"
  input Real maxTokens "Maximum capacities of output places"
  input Integer maxTokensint "Integer maximum capacities of output places"
  input Boolean enable "Is the transition enabled by output places?"
  input Real decreasingFactor "Factor of continuous output places for decreasing the speed"
  input Boolean disPlace "Types of output places (discrete or continuous)"
  input Boolean emptied "Are the continuous output places emptied?"
  input Real speedSum "Output speeds of continuous output places"
  output Boolean active "Is the transition active?"
  output Boolean fire "Does the transition fire?"
  output Real arcWeight "Output arc weights of the transition"
  output Integer arcWeightint "Integer output arc weights of the transition"
  output Boolean enabledByInPlaces "Is the transition enabled by all input places?"
  output Boolean disTransition "Type of the transition (discrete/stochastic or continuous)"
  output Real instSpeed "Instantaneous speed of a continuous transition"
  output Real prelimSpeed "Preliminary speed of a continuous transition"
  output Real maxSpeed "Maximum speed of a continuous transition"
end TransitionOut;


within PNlib;

package Types "contains Types which are used in the Petri net component models"
  type EnablingType = enumeration(Priority, Probability, Benefit);
  type BenefitType = enumeration(Greedy, BenefitQuotient, BranchAndBound);
  type ArcType = enumeration(NormalArc, RealTestArc, TestArc, RealInhibitorArc, InhibitorArc);
  type DistributionType = enumeration(Exponential, Triangular, Uniform, TruncatedNormal, Discrete);
end Types;


Dead-Band.

within PowerGrids.Controls;
model DeadBand "Dead-Band"
  extends Modelica.Blocks.Interfaces.SISO;
  parameter Real uMax(start=1) "Upper limits of dead zones";
  parameter Real uMin=-uMax "Lower limits of dead zones";
  parameter Boolean smoothed = false "Smooth operator is applied"

equation
  assert(uMax >= uMin, "Limits must be consistent");
  if smoothed then
    // The simplified model has no saturation, to make the controller equations linear
    y = homotopy(
      actual = smooth(0, if u < uMin then u - uMin elseif u > uMax then u - uMax else 0),
      simplified = u);
  else
    // The simplified model has no saturation, to make the controller equations linear
    y = homotopy(
      actual = if u < uMin then u - uMin elseif u > uMax then u - uMax else 0,
      simplified = u);
  end if;
end DeadBand;


Derivative filter with lag.

within PowerGrids.Controls;
model DerivativeLag "Derivative filter with lag"
  extends Modelica.Blocks.Interfaces.SISO(y(start = yStart));
  parameter SI.Time T1 "Lag time constant";
  parameter SI.Time Td = T1 "Derivative time constant";
  parameter Boolean noDynamics = not(T1 > 0) "Trivial configuration with no dynamics G(s) = 1";
  parameter Modelica.Blocks.Types.Init initType=Modelica.Blocks.Types.Init.NoInit "Type of initialization (1: no init, 2: steady state, 3/4: initial output)"
  parameter Real yStart=0 "Initial or guess value of output (= state)"
  Modelica.Blocks.Math.Feedback feedback
  Modelica.Blocks.Math.Gain gain(k = if noDynamics then 1 else Td / T1)
  Modelica.Blocks.Continuous.Integrator integrator(
    initType = initType,
    k = 1 / T1,
    y_start = yStart*Td/T1) if not noDynamics
  Modelica.Blocks.Sources.RealExpression zero if noDynamics
initial equation
  assert((T1 > 0 and Td > 0) or ((not T1 > 0) and (not Td > 0)), "Either T1 = T2 = 0 or T1 > 0 and T2 > 0");
equation
  connect(zero.y, feedback.u2)
  connect(integrator.y, feedback.u2)
  connect(feedback.y, integrator.u)
  connect(feedback.y, y)
  connect(gain.y, feedback.u1)
  connect(u, gain.u)
end DerivativeLag;


Discontinuous dead-band.

within PowerGrids.Controls;
model DiscontinuousDeadBand "Discontinuous dead-band"
  extends Modelica.Blocks.Interfaces.SISO;
  parameter Real uMax(start=1) "Upper limits of dead zones";
  parameter Real uMin=-uMax "Lower limits of dead zones";
  parameter Boolean smoothed = false "Smooth operator is applied"

equation
  assert(uMax >= uMin, "Limits must be consistent");
  if smoothed then
    // The simplified model has no saturation, to make the controller equations linear
    y = homotopy(
      actual = smooth(0, if u < uMin then u elseif u > uMax then u else 0),
      simplified = u);
  else
    // The simplified model has no saturation, to make the controller equations linear
    y = homotopy(
      actual = if u < uMin then u elseif u > uMax then u else 0,
      simplified = u);
  end if;
end DiscontinuousDeadBand;


Trivial configuration with no dynamics G(s) = k.

within PowerGrids.Controls;
model FirstOrder
  extends Modelica.Blocks.Interfaces.SISO(y(start = y_start));
  parameter Boolean noDynamics = not(T > 0) "Trivial configuration with no dynamics G(s) = k";
  parameter Real k=1 "Gain";
  parameter SI.Time T "Time Constant";
  parameter Modelica.Blocks.Types.Init initType=Modelica.Blocks.Types.Init.NoInit "Type of initialization (1: no init, 2: steady state, 3/4: initial output)"
  parameter Real y_start=0 "Initial or guess value of output (= state)"

  Modelica.Blocks.Continuous.FirstOrder firstOrder1(T = T, initType = initType, k = k, y_start = y_start)  if not noDynamics
  Modelica.Blocks.Math.Gain gain(k = k)  if noDynamics
equation
  connect(gain.y, y)
  connect(firstOrder1.y, y)
  connect(u, firstOrder1.u)
  connect(u, gain.u)

end FirstOrder;


First order filter with non-windup limiter.

within PowerGrids.Controls;
model FirstOrderWithNonWindupLimiter "First order filter with non-windup limiter"
  extends Modelica.Blocks.Interfaces.SISO(y(start = yStart));
  parameter Real k = 1 "Gain";
  parameter SI.Time T "Time Constant";
  parameter Real yMax "Upper limits of output signal";
  parameter Real yMin = -yMax "Lower limits of output signal";
  parameter Modelica.Blocks.Types.Init initType = Modelica.Blocks.Types.Init.InitialState "Type of initialization (1: no init, 2: steady state, 3,4: initial output)"
  parameter Real yStart = 0 "Initial or guess value of output (= state)"
  parameter Boolean strict = false "= true, if strict limits with noEvent(..)"
  Modelica.Blocks.Nonlinear.Limiter lim(limitsAtInit = true, strict = strict, uMax = yMax, uMin = yMin)
  Modelica.Blocks.Math.Feedback feedback
  Modelica.Blocks.Math.Gain G(k = 1 / T)
  Modelica.Blocks.Continuous.Integrator I(initType = initType, k = 1, y_start = yStart)
  Modelica.Blocks.Math.Gain Gk(k = k)
initial equation
  assert(T > 0, "T must be positive");
equation
  connect(Gk.u, u)
  connect(Gk.y, feedback.u1)
  connect(lim.y, feedback.u2)
  connect(feedback.y, G.u)
  connect(lim.y, y)
  connect(I.y, lim.u)
  // The simplified model has no saturation, to make the controller equations linear
  I.u = homotopy(
    actual = if (G.y >= 0 and lim.u > lim.uMax) or (G.y <= 0 and lim.u < lim.uMin) then 0 else G.y,
    simplified = G.y);
end FirstOrderWithNonWindupLimiter;


= true if time varying output is required.

within PowerGrids.Controls;

model FreeOffset
  extends Modelica.Blocks.Interfaces.SO;
  import PowerGrids.Types.Choices.InitializationOption;

  parameter Boolean use_u = false "= true if time varying output is required";
  final parameter Boolean fixedOffsetDefault =
    if systemPowerGrids.initOpt == InitializationOption.globalSteadyStateFixedPowerFlow or
       systemPowerGrids.initOpt == InitializationOption.localSteadyStateFixedPowerFlow
    then false
    else true "Default choice of fixedOffsetDefault from system object";
  parameter Boolean fixedOffset = fixedOffsetDefault "= true if offset is fixed to zero, = false if offset is left free";
  final parameter Real offset(fixed = false) "Free offset of output y";

  outer PowerGrids.Electrical.System systemPowerGrids "Reference to system object";

  Modelica.Blocks.Interfaces.RealInput u if use_u
protected
  Modelica.Blocks.Interfaces.RealInput u_internal "Protected connector to be used in equations in place of conditional u";

initial equation
  if fixedOffset then
    offset = 0;
  end if;

equation
  y = u_internal + offset;
  connect(u, u_internal) "Automatically removed if u is disabled";
  if not use_u then
    u_internal = 0 "Provide a default zero value if u is disabled";
  end if;
end FreeOffset;


Integrator with non-windup limiter.

within PowerGrids.Controls;
model IntegratorWithNonWindupLimiter "Integrator with non-windup limiter"
  extends Modelica.Blocks.Interfaces.SISO;
  parameter Real k(unit = "1/s") = 1 "Integral constant";
  parameter Real yMax = 1 "Upper limits of output signal";
  parameter Real yMin = -yMax "Lower limits of output signal";
  parameter Modelica.Blocks.Types.Init initType = Modelica.Blocks.Types.Init.InitialState "Type of initialization (1: no init, 2: steady state, 3,4: initial output)"
  parameter Real yStart = 0 "Initial or guess value of output (= state)"
  parameter Boolean strict = false "= true, if strict limits with noEvent(..)"
  Modelica.Blocks.Nonlinear.Limiter lim(limitsAtInit = true, strict = strict, uMax = yMax, uMin = yMin)
  Modelica.Blocks.Continuous.Integrator I(initType = initType, k = k, y_start = yStart)
initial equation
equation
  connect(I.y, lim.u)
  connect(lim.y, y)
  // The simplified model has no saturation, to make the controller equations linear
  I.u = homotopy(
    actual = if (u*k > 0 and lim.u > lim.uMax) or (u*k < 0 and lim.u < lim.uMin) then 0 else u,
    simplified = u);
end IntegratorWithNonWindupLimiter;


Simple lead-lag filter.

within PowerGrids.Controls;
model LeadLag "Simple lead-lag filter"
  extends Modelica.Blocks.Interfaces.SISO(y(start = yStart));
  parameter Real k = 1 "Gain";
  parameter SI.Time T1 "Lead time constant";
  parameter SI.Time T2 "Lag time constant";
  parameter Boolean noDynamics = not(T2 > 0) "Trivial configuration with no dynamics G(s) = k";
  parameter Modelica.Blocks.Types.Init initType=Modelica.Blocks.Types.Init.NoInit "Type of initialization (1: no init, 2: steady state, 3/4: initial output)"
  parameter Real yStart=0 "Initial or guess value of output (= state)"
  Modelica.Blocks.Math.Feedback feedback
  Modelica.Blocks.Math.Gain gain(k = if noDynamics then k else k * T1 / T2)
  Modelica.Blocks.Continuous.FirstOrder firstOrder(
    T = T1,
    initType = initType,k = (T1 - T2) / (k * T1), y_start = yStart * (T1 - T2) / (k * T1)) if not noDynamics
  Modelica.Blocks.Sources.RealExpression zero if noDynamics
initial equation
  assert((T1 > 0 and T2 > 0) or ((not T1 > 0) and (not T2 > 0)), "Either T1 = T2 = 0 or T1 > 0 and T2 > 0");
equation
  connect(zero.y, feedback.u2)
  connect(firstOrder.y, feedback.u2)
  connect(u, feedback.u1)
  connect(feedback.y, gain.u)
  connect(gain.y, firstOrder.u)
  connect(gain.y, y)
end LeadLag;


Lead-lag filter with non-windup limiter.

within PowerGrids.Controls;

model LeadLagWithNonWindupLimiter "Lead-lag filter with non-windup limiter"
  extends Modelica.Blocks.Interfaces.SISO(y(start = yStart));
  parameter Real k = 1 "Gain";
  parameter SI.Time T1 = 1 "Lead time constant";
  parameter SI.Time T2 = 10 "Lag time constant";
  parameter Real yMax = 1 "Upper limits of output signal";
  parameter Real yMin = -yMax "Lower limits of output signal";
  parameter Modelica.Blocks.Types.Init initType = Modelica.Blocks.Types.Init.InitialState "Type of initialization (1: no init, 2: steady state, 3,4: initial output)"
  parameter Real yStart = 0 "Initial or guess value of output (= state)"
  parameter Boolean strict = false "= true, if strict limits with noEvent(..)"
  Modelica.Blocks.Math.Gain gain_dir(k = k * T1 / T2)
  Modelica.Blocks.Nonlinear.Limiter limiter_y(limitsAtInit = true, strict = strict, uMax = yMax, uMin = yMin)
  Modelica.Blocks.Math.Feedback feedback_y
  Modelica.Blocks.Continuous.Integrator integ_fb(initType = initType, k = 1 / T1, y_start = yStart)
  Modelica.Blocks.Math.Gain gain_fb(k = (1 - T2 / T1) / k)
  Modelica.Blocks.Math.Feedback feedback_u
initial equation
  assert(T1 > 0, "T1 must be positive");
  assert(T2 > 0, "T2 must be positive");
equation
  connect(gain_fb.y, feedback_u.u2)
  connect(integ_fb.y, gain_fb.u)
  connect(feedback_u.y, gain_dir.u)
  connect(u, feedback_u.u1)
  connect(integ_fb.y, feedback_y.u2)
  connect(feedback_y.y, integ_fb.u)
  connect(limiter_y.y, feedback_y.u1)
  connect(limiter_y.y, y)
  connect(gain_dir.y, limiter_y.u)
end LeadLagWithNonWindupLimiter;


Lead-lag filter with M poles.

within PowerGrids.Controls;
model LeadMOrderLag "Lead-lag filter with M poles"
  extends Modelica.Blocks.Interfaces.SISO(y(start = yStart));
  parameter Integer M = 1 "Lag Order";
  parameter Real k(unit="1") = 1 "Gain";
  parameter SI.Time T1 "Lead time constant";
  parameter SI.Time T2 "Lag time constant";
  parameter Modelica.Blocks.Types.Init initType=Modelica.Blocks.Types.Init.NoInit "Type of initialization (1: no init, 2: steady state, 3/4: initial output)"
  parameter Real yStart=0 "Initial or guess value of output (= state)"
  LeadLag leadLag(T1 = T1, T2 = T2, initType = initType, k = k, yStart = yStart)
  PowerGrids.Controls.FirstOrder firstOrderCascade[max(0,M-1)](each T = T2, each initType = initType, each k = 1, each y_start = yStart) if M > 1
initial equation
  assert((T1 > 0 and T2 > 0) or ((not T1 > 0) and (not T2 > 0)), "Either T1 = T2 = 0 or T1 > 0 and T2 > 0");
  assert(M > 0, "minimum lag order is 1");
equation
  connect(u, leadLag.u)

  if M > 1 then
    connect(leadLag.y, firstOrderCascade[1].u);
    connect(firstOrderCascade[M-1].y, y);
  else
    connect(leadLag.y, y);
  end if;

  if M > 2 then
    for i in 1:M-2 loop
      connect(firstOrderCascade[i].y, firstOrderCascade[i+1].u);
    end for;
  end if;
end LeadMOrderLag;


Upper limit reached.

within PowerGrids.Controls;
block LimiterWithLag
  extends Modelica.Blocks.Interfaces.SISO;

  encapsulated type State = enumeration(
    upperSat "Upper limit reached",
    lowerSat "Lower limit reached",
    notSat   "u is in range"
  );

  parameter Real uMax  "Upper limit";
  parameter Real uMin  "Lower limit";
  parameter Types.Time LagMax  "Time lag before taking action when u going above uMax";
  parameter Types.Time LagMin  "Time lag before taking action when u going below uMin";
  parameter State stateStart = State.notSat "saturation initial state";
  final parameter Boolean useLag = (LagMax > 0) or (LagMin > 0) "Lags are used only if at least one is not null";

  State state(start = stateStart, fixed = true) "saturation state";
  discrete SI.Time saturationLimitReached(start = 0, fixed = true) "Time in which the saturation limit is reached";
  Boolean satON "Saturation is active";

algorithm
  when u > uMax then
    state := State.upperSat;
    saturationLimitReached := time;
  end when;

  when u < uMin then
    state := State.lowerSat;
    saturationLimitReached := time;
  end when;

  when u < uMax and u > uMin then
    state := State.notSat;
  end when;

  when useLag and state == State.notSat then
    satON := false;
  elsewhen useLag and state == State.upperSat and (time - saturationLimitReached) > LagMax then
    satON := true;
  elsewhen useLag and state == State.lowerSat and (time - saturationLimitReached) > LagMin then
    satON := true;
  end when;

  if not useLag then
    satON := true;
  end if;

equation
  // The simplified model has no saturation, to make the controller equations linear
  y = homotopy(
    actual = if state == State.upperSat and satON then uMax
             elseif state == State.lowerSat and satON then uMin
             else u,
    simplified =  u);
end LimiterWithLag;


Control blocks

within PowerGrids;
package Controls "Control blocks"
  extends Modelica.Icons.Package;
end Controls;


Proportional-Integral filter with non-windup limiter.

within PowerGrids.Controls;
model PIWithNonWindupLimiter "Proportional-Integral filter with non-windup limiter"
  extends Modelica.Blocks.Interfaces.SISO;
  parameter Real Kp "Proportional gain";
  parameter Real Ki(unit = "1/s") = 0 "Integral constant";
  parameter Boolean noDynamics = not(Ki > 0) and not(Ki < 0) "Trivial configuration with no dynamics G(s) = k";
  parameter Real yMax = 1 "Upper limits of output signal";
  parameter Real yMin = -yMax "Lower limits of output signal";
  parameter Modelica.Blocks.Types.Init initType = Modelica.Blocks.Types.Init.InitialState "Type of initialization (1: no init, 2: steady state, 3,4: initial output)"
  parameter Real yStart = 0 "Initial or guess value of output (= state)"
  parameter Boolean strict = false "= true, if strict limits with noEvent(..)"
  Modelica.Blocks.Nonlinear.Limiter lim(limitsAtInit = true, strict = strict, uMax = yMax, uMin = yMin)
  Modelica.Blocks.Math.Feedback feedback
  Modelica.Blocks.Math.Gain G(k = Kp)
  Modelica.Blocks.Continuous.FirstOrder firstOrder(T = Kp / Ki, initType = initType, k = -1, y_start = -yStart) if not noDynamics
  Modelica.Blocks.Sources.RealExpression zero if noDynamics
initial equation
  assert(Kp*Ki >= 0, "Kp and Ki must not have opposite sign");
equation
  connect(zero.y, feedback.u2)
  connect(feedback.y, lim.u)
  connect(G.y, feedback.u1)
  connect(firstOrder.y, feedback.u2)
  connect(u, G.u)
  connect(lim.y, firstOrder.u)
  connect(lim.y, y)
end PIWithNonWindupLimiter;


Ramp tracking filter.

within PowerGrids.Controls;
model RampTrackingFilter "Ramp tracking filter"
  extends Modelica.Blocks.Interfaces.SISO(y(start = yStart));
  parameter Integer N(min = 0) = 1 "N Order";
  parameter Integer M(min = 0) = 1 "M Order";
  parameter Real k(unit="1")=1 "Gain";
  parameter SI.Time T1 "Lead time constant";
  parameter SI.Time T2 "Lag time constant";
  parameter Modelica.Blocks.Types.Init initType=Modelica.Blocks.Types.Init.NoInit "Type of initialization (1: no init, 2: steady state, 3/4: initial output)"
  parameter Real yStart=0 "Initial or guess value of output (= state)"
  PowerGrids.Controls.LeadMOrderLag leadMOrderLagCascade[N](each M = M, each T1 = T1, each T2 = T2, each initType = initType, each k = 1, each yStart = yStart)
  Modelica.Blocks.Math.Gain gain(k = k)
initial equation
  assert((N > 0 and M > 0) or N == 0, "If N > 0 then also M must be positive");
  assert((T1 > 0 and T2 > 0) or ((not T1 > 0) and (not T2 > 0)), "Either T1 = T2 = 0 or T1 > 0 and T2 > 0");
equation
  connect(u, gain.u)
  if N == 0 then
    connect(gain.y, y);
  else
    connect(gain.y, leadMOrderLagCascade[1].u)
    connect(y, leadMOrderLagCascade[N].y)
    if N > 1 then
      for i in 1:N-1 loop
        connect(leadMOrderLagCascade[i].y, leadMOrderLagCascade[i+1].u);
      end for;
    end if;
  end if;
end RampTrackingFilter;


<span style=\.

within PowerGrids.Controls.Test;
model TestDeadBand
  extends Modelica.Icons.Example;
  PowerGrids.Controls.DeadBand deadBand(smoothed = false, uMax = 1)
  Modelica.Blocks.Sources.RealExpression inputSignal(y = time - 2)
  Modelica.Blocks.Sources.RealExpression deadBandAnalyticSolution(
    y = -1 + time - (if time < 1 then 0 else time-1)+ (if time < 3 then 0 else time - 3))
equation
  connect(inputSignal.y, deadBand.u)
end TestDeadBand;


within PowerGrids.Controls.Test;
model TestDerivativeLag
  extends Modelica.Icons.Example;
  PowerGrids.Controls.DerivativeLag derivativeLag(Td = 4, T1 = 2, initType = Modelica.Blocks.Types.Init.SteadyState)
  PowerGrids.Controls.DerivativeLag derivativeLagNoDynamics(T1 = 0, initType = Modelica.Blocks.Types.Init.SteadyState)
  Modelica.Blocks.Sources.Step step(offset = 1, startTime = 1)
  Modelica.Blocks.Sources.RealExpression derivativeLagAnalyticSolution(
   y = if time < 1 then 0 else 2*exp(-(time-1)/2))
  Modelica.Blocks.Sources.RealExpression derivativeLagNoDynamicsAnalyticSolution(y = 1 + (if time < 1 then 0 else 1))
equation
  connect(step.y, derivativeLagNoDynamics.u)
  connect(step.y, derivativeLag.u)
end TestDerivativeLag;


<span style=\.

within PowerGrids.Controls.Test;
model TestDiscontinuousDeadBand
  extends Modelica.Icons.Example;
  PowerGrids.Controls.DiscontinuousDeadBand discontinuousDeadBand(uMax = 1)
  Modelica.Blocks.Sources.RealExpression inputSignal(y = time - 2)
  Modelica.Blocks.Sources.RealExpression discontinuousDeadBandAnalyticSolution(y = if time < 1 then time - 2 else if time < 3 then 0 else time - 2)
equation
  connect(inputSignal.y, discontinuousDeadBand.u)
end TestDiscontinuousDeadBand;


within PowerGrids.Controls.Test;

model TestFirstOrderWithNonWindupLimiter
  extends Modelica.Icons.Example;
  PowerGrids.Controls.FirstOrderWithNonWindupLimiter firstOrderPositiveGain(T = 2, initType = Modelica.Blocks.Types.Init.SteadyState, k = 2, yMax = 5, yMin = -5, yStart = 2)
  Modelica.Blocks.Sources.TimeTable inputSignal(table = [0, 1; 1, 1; 1, 4; 10, 4; 10, -4; 100, -4])
  Modelica.Blocks.Sources.RealExpression firstOrderAnalyticSolution(
    y = 2
        + (if time < 1 then 0 elseif time < 2.38629 then 6*(1-exp(-(time-1)/2)) else 3)
        + (if time < 10 then 0 elseif time < 12.9327 then -13*(1-exp(-(time-10)/2)) else -10))
  PowerGrids.Controls.FirstOrderWithNonWindupLimiter firstOrderNegativeGain(T = 2, initType = Modelica.Blocks.Types.Init.SteadyState, k = -2, yMax = 5, yMin = -5, yStart = 2)
  Modelica.Blocks.Math.Gain negativeGain(k = -1)
equation
  connect(inputSignal.y, negativeGain.u)
  connect(inputSignal.y, firstOrderPositiveGain.u)
  connect(negativeGain.y, firstOrderNegativeGain.u)
end TestFirstOrderWithNonWindupLimiter;


Tests for the <a href=\.

within PowerGrids.Controls.Test;

model TestFreeOffset
  extends Modelica.Icons.Example;
  PowerGrids.Controls.FreeOffset freeOffset1
  FreeOffset freeOffset2(use_u = true)
  Modelica.Blocks.Sources.Sine sine(freqHz = 1)
initial equation
  freeOffset1.y = 1;
  freeOffset2.y = 2;
equation
  connect(sine.y, freeOffset2.u)
end TestFreeOffset;


within PowerGrids.Controls.Test;

model TestIntegratorWithNonWindupLimiter
  extends Modelica.Icons.Example;
  PowerGrids.Controls.IntegratorWithNonWindupLimiter integrator1(
     initType = Modelica.Blocks.Types.Init.InitialOutput, k = 2, yMax = 4)
  Modelica.Blocks.Sources.TimeTable inputSignal(table = [0, 0; 1, 0; 1, 1; 5, 1; 5, -1; 100, -1])
  Modelica.Blocks.Sources.RealExpression integratorAnalyticSolution(y = (if time < 1 then 0 elseif time < 3 then 2 * (time - 1) else 4) + (if time < 5 then 0 elseif time < 9 then -2*(time - 5) else -8))
  PowerGrids.Controls.IntegratorWithNonWindupLimiter integrator2(initType = Modelica.Blocks.Types.Init.InitialOutput, k = -2, yMax = 4)
  Modelica.Blocks.Math.Gain neg(k = -1)
equation
  connect(inputSignal.y, integrator1.u)
  connect(inputSignal.y, neg.u)
  connect(neg.y, integrator2.u)
end TestIntegratorWithNonWindupLimiter;


within PowerGrids.Controls.Test;

model TestLeadLag
  extends Modelica.Icons.Example;
  PowerGrids.Controls.LeadLag leadLag(T1 = 4, T2 = 2, initType = Modelica.Blocks.Types.Init.SteadyState, k = 10, yStart = 10)
  PowerGrids.Controls.LeadLag leadLagNoDynamics(T1 = 0, T2 = 0,initType = Modelica.Blocks.Types.Init.SteadyState, k = 10)
  Modelica.Blocks.Sources.Step step(offset = 1, startTime = 1)
  Modelica.Blocks.Sources.RealExpression leadLagAnalyticSolution(y = 10 * (1 + (if time < 1 then 0 else 1 + exp(-(time - 1) / 2))))
  Modelica.Blocks.Sources.RealExpression leadLagNoDynamicsAnalyticSolution(y = 10 * (1 + (if time < 1 then 0 else 1)))
equation
  connect(step.y, leadLag.u)
  connect(step.y, leadLagNoDynamics.u)
end TestLeadLag;


within PowerGrids.Controls.Test;

model TestLeadLagWithNonWindupLimiter
  extends Modelica.Icons.Example;
  PowerGrids.Controls.LeadLagWithNonWindupLimiter leadLagPositiveGain(T1 = 1, T2 = 2, initType = Modelica.Blocks.Types.Init.SteadyState, k = 2, yMax = 5, yMin = -5, yStart = 2)
  Modelica.Blocks.Sources.TimeTable inputSignal(table = [0, 1; 1, 1; 1, 4; 10, 4; 10, -4; 100, -4])
  Modelica.Blocks.Sources.RealExpression leadLagAnalyticSolution(
    y = 2
        + (if time < 1 then 0 else 3)
        + (if time < 10 then 0 elseif time < 11.54 then (-13 / 2) - 13 / 2 * (1 - exp(-(time - 10) / 2)) else -10))
  PowerGrids.Controls.LeadLagWithNonWindupLimiter leadLagNegativeGain(T1 = 1, T2 = 2, initType = Modelica.Blocks.Types.Init.SteadyState, k = -2, yMax = 5, yMin = -5, yStart = 2)
  Modelica.Blocks.Math.Gain negativeGain(k = -1)
equation
  connect(inputSignal.y, negativeGain.u)
  connect(inputSignal.y, leadLagPositiveGain.u)
  connect(negativeGain.y, leadLagNegativeGain.u)
end TestLeadLagWithNonWindupLimiter;


within PowerGrids.Controls.Test;
model TestLeadMOrderLag
  extends Modelica.Icons.Example;
  PowerGrids.Controls.LeadMOrderLag leadLag( M = 1,T1 = 4, T2 = 2, initType = Modelica.Blocks.Types.Init.SteadyState, k = 10, yStart = 2)
  Modelica.Blocks.Sources.Step step(height = 1, offset = 1, startTime = 1)
  Modelica.Blocks.Sources.RealExpression leadLagAnalyticSolution(y = 10 * (1 + (if time < 1 then 0 else 1 + exp(-(time - 1) / 2))))
  PowerGrids.Controls.LeadMOrderLag mOrderLag(M = 100, T1 = 1e-5, T2 = 1 / 100, initType = Modelica.Blocks.Types.Init.SteadyState, k = 1, yStart = 1)
  Modelica.Blocks.Sources.RealExpression mOrderLagAnalyticSolution(y = 1 + (if time < 2 then 0 else 1))
equation
  connect(step.y, mOrderLag.u)
  connect(step.y, leadLag.u)
end TestLeadMOrderLag;


The model demonstrate the corerct behavior of the <a href=\.

within PowerGrids.Controls.Test;
model TestLimiterWithLag
  extends Modelica.Icons.Example;
  PowerGrids.Controls.LimiterWithLag limiterWithLag(LagMax = 2, LagMin = 3, uMax = 0.7, uMin = 0.3)
  Modelica.Blocks.Sources.Trapezoid LfSignal(falling = 0.5, nperiod = 2, period = 10, rising = 0.5, width = 4)
  Modelica.Blocks.Sources.Trapezoid HfSignal(falling = 0.5, nperiod = 4, period = 4, rising = 0.5, startTime = 20, width = 1)
  Modelica.Blocks.Math.Sum sum(nin = 2)
equation
  connect(LfSignal.y, sum.u[1])
  connect(HfSignal.y, sum.u[2])
  connect(sum.y, limiterWithLag.u)
end TestLimiterWithLag;


within PowerGrids.Controls.Test;
model TestPIWithNonWindupLimiter
  extends Modelica.Icons.Example;
  PowerGrids.Controls.PIWithNonWindupLimiter PIController(Ki = 4, Kp = 2, initType = Modelica.Blocks.Types.Init.InitialOutput, yMax = 10)
  Modelica.Blocks.Sources.TimeTable inputSignal(table = [0, 0; 1, 0; 1, 1; 5, 1; 5, -1; 100, -1])
  Modelica.Blocks.Sources.RealExpression PIControllerAnalyticSolution(y = (if time < 1 then 0 elseif time < 3 then 2 + 4 * (time - 1) else 10) + (if time < 5 then 0 elseif time < 9.5 then -2 -4*(time - 5) else -20))
  PowerGrids.Controls.PIWithNonWindupLimiter PIControllerNegativeKp(Ki = -4, Kp = -2, initType = Modelica.Blocks.Types.Init.InitialOutput, yMax = 10)
  PowerGrids.Controls.PIWithNonWindupLimiter PController(Kp = 2, initType = Modelica.Blocks.Types.Init.InitialOutput, yMax = 10)
  Modelica.Blocks.Math.Gain negative(k = -1)
  Modelica.Blocks.Sources.RealExpression PControllerAnalyticSolution(y = (if time < 1 then 0 elseif time < 5 then 2 else -2))
equation
  connect(inputSignal.y, PController.u)
  connect(inputSignal.y, PIController.u)
  connect(inputSignal.y, negative.u)
  connect(negative.y, PIControllerNegativeKp.u)
end TestPIWithNonWindupLimiter;


within PowerGrids.Controls.Test;
model TestRampTrackingFilter
  extends Modelica.Icons.Example;
  Modelica.Blocks.Sources.Step step(height = 1, offset = 1, startTime = 1)
  PowerGrids.Controls.RampTrackingFilter rampTrackingFilter1(M = 2, N = 50, T1 = 1e-5, T2 = 1 / 100, initType = Modelica.Blocks.Types.Init.SteadyState, k = 10, yStart = 10)
  PowerGrids.Controls.RampTrackingFilter rampTrackingFilter2(M = 1, N = 1, T1 = 2, T2 = 2, initType = Modelica.Blocks.Types.Init.SteadyState, k = 10, yStart = 10)
  Modelica.Blocks.Sources.RealExpression rampTrackingFilter1AnalyticSolution(y = 10 * (if time < 2 then 1 else 2))
  Modelica.Blocks.Sources.RealExpression rampTrackingFilter2AnalyticSolution(y = 10 * (if time < 1 then 1 else 2))
  PowerGrids.Controls.RampTrackingFilter rampTrackingFilter3(M = 0, N = 0, T1 = 4, T2 = 2, initType = Modelica.Blocks.Types.Init.SteadyState, k = 10, yStart = 10)
  Modelica.Blocks.Sources.RealExpression rampTrackingFilter3AnalyticSolution(y = 10 * (if time < 1 then 1 else 2))
equation
  connect(step.y, rampTrackingFilter3.u)
  connect(step.y, rampTrackingFilter2.u)
  connect(step.y, rampTrackingFilter1.u)
end TestRampTrackingFilter;


Electrical components

within PowerGrids;

package Electrical "Electrical components"
  extends Modelica.Icons.Package;
end Electrical;


System object.

within PowerGrids.Electrical;
model System "System object"
  import PowerGrids.Types.Choices.ReferenceFrequency;
  import PowerGrids.Types.Choices.InitializationOption;
  parameter SI.Frequency fNom = 50 "Nominal system frequency";
  parameter ReferenceFrequency referenceFrequency =
    ReferenceFrequency.nominalFrequency "Choice of reference frequency for generators";
  parameter InitializationOption initOpt =
    InitializationOption.globalSteadyStateFixedSetPoints "Initialization option";
  final parameter SI.AngularVelocity omegaNom = fNom*2*Modelica.Constants.pi "Nominal system angular frequency";

  Modelica.Blocks.Interfaces.RealInput omegaRefIn(unit = "rad/s")
    if referenceFrequency == ReferenceFrequency.fixedReferenceGenerator "Reference frequency input"
  Types.AngularVelocity omegaRef = omegaRefInternal "Reference frequency";
protected
  Modelica.Blocks.Interfaces.RealInput omegaRefInternal(unit = "rad/s")
    "Protected connector for conditional connector handling";

initial equation
  assert(not referenceFrequency == ReferenceFrequency.adaptiveReferenceGenerators,
   "Adaptive reference generators option not yet implemented");

equation
  connect(omegaRefIn, omegaRefInternal) "Effective only if omegaRefIn is instantiated";
  if referenceFrequency == ReferenceFrequency.nominalFrequency then
    omegaRefInternal = omegaNom;
  end if;
end System;


Capacitor bank with fixed capacitance.

within PowerGrids.Electrical.Banks;
model CapacitorBankFixed "Capacitor bank with fixed capacitance"
  extends Icons.CapacitorBank;
  extends BaseClasses.ShuntConductance(Y = Complex(0,B));
  parameter Types.Susceptance B = 0 "Capacitor bank susceptance";
end CapacitorBankFixed;


Models of capacitor banks

within PowerGrids.Electrical;

package Banks "Models of capacitor banks"
  extends Modelica.Icons.Package;
end Banks;


Base class for AC components with one port.

within PowerGrids.Electrical.BaseClasses;

partial model OnePortAC "Base class for AC components with one port"
  import PowerGrids.Types.Choices.LocalInitializationOption;
  parameter Types.Voltage UNom(start = 400e3) "Nominal/rated line-to-line voltage"
  parameter Types.ApparentPower SNom(start = 100e6) "Nominal/rated apparent power"
  parameter Boolean portVariablesPhases = false "Compute voltage and current phases for monitoring purposes only"
  constant Boolean portVariablesPu = true "Add per-unit variables to model";
  constant Boolean generatorConvention = false "Add currents with generator convention (i > 0 when exiting the device) to model";
  parameter LocalInitializationOption localInit = LocalInitializationOption.none
    "Initialize the component locally in steady state from port start values"
  parameter Types.Voltage UStart = UNom "Start value of phase-to-phase voltage phasor, absolute value"
  parameter Types.Angle UPhaseStart = 0 "Start value of phase-to-phase voltage phasor, phase angle"
  parameter Types.ActivePower PStart = SNom "Start value of active power flowing into the port"
  parameter Types.ReactivePower QStart = 0 "Start value of reactive power flowing into the port"

  PowerGrids.Interfaces.TerminalAC terminal
    (v(re(start = port.vStart.re), im(start = port.vStart.im)),
     i(re(start = port.iStart.re), im(start = port.iStart.im)))
  PortAC port(final UNom = UNom, final SNom = SNom,
              final portVariablesPu = portVariablesPu,
              final portVariablesPhases = portVariablesPhases,
              final generatorConvention = generatorConvention,
              final UStart = UStart,
              final UPhaseStart = UPhaseStart,
              final PStart = PStart,
              final QStart = QStart)
              "AC port of node";
  outer Electrical.System systemPowerGrids "Reference to system object";
equation
  if initial() and localInit == LocalInitializationOption.PV then
    // During local initialization, P,V is enforced at the connector towards
    // the grid, while Q,phase(V) is enforced at the port towards the component
    // Further two initial equations will be needed (e.g. in the controller)
    // to enforce P,V at the port or achieve actuator saturation if that is not possible
    CM.real(terminal.v*CM.conj(terminal.i)) = port.PStart;
    CM.'abs'(terminal.v) = port.VStart;
    port.Q = port.QStart;
    port.v.re*port.vStart.im = port.v.im*port.vStart.re;
  elseif initial() and localInit == LocalInitializationOption.PQ then
    // During local initialization, P,Q is enforced at the connector towards
    // the grid, while phase(V), phase(I) is enforced at the port towards the component
    // Further two initial equations will be needed (e.g. in the controller)
    // to enforce P,Q at the port or achieve actuator saturation if that is not possible
    terminal.v*CM.conj(terminal.i) = Complex(port.PStart, port.QStart);
    port.v.re*port.vStart.im = port.v.im*port.vStart.re;
    port.i.re*port.iStart.im = port.i.im*port.iStart.re;
  else
    // In all other cases, the port voltage and current are equal to
    // the connector voltage and current
    port.v = terminal.v;
    port.i = terminal.i;
  end if;
end OnePortAC;


Base class for one-port AC components with p.u. Park's transformation.

within PowerGrids.Electrical.BaseClasses;

partial model OnePortACdqPU "Base class for one-port AC components with p.u. Park's transformation"
  extends OnePortAC(
    final portVariablesPu = true);

  parameter SI.Angle thetaStart = UPhaseStart "Start value of rotation between machine rotor frame and port phasor frame";
  final parameter Types.PerUnit udPuStart(fixed = false) "Start value of udPu";
  final parameter Types.PerUnit uqPuStart(fixed = false) "Start value of uqPu";
  final parameter Types.PerUnit idPuStart(fixed = false) "Start value of idPu";
  final parameter Types.PerUnit iqPuStart(fixed = false) "Start value of iqPu";

  // d-q axis p.u. variables
  Types.PerUnit udPu(start = udPuStart) "Voltage of direct axis in p.u.";
  Types.PerUnit uqPu(start = uqPuStart) "Voltage of quadrature axis in p.u.";
  Types.PerUnit idPu(start = idPuStart) "Current of direct axis in p.u.";
  Types.PerUnit iqPu(start = iqPuStart) "Current of quadrature axis in p.u.";

  // other variables
  Types.Angle theta(start = thetaStart) "Rotation between machine rotor frame and port phasor frame";

initial equation
  // Park's transformations for start values
  port.vStart.re/port.VBase =  sin(thetaStart)*udPuStart + cos(thetaStart)*uqPuStart;
  port.vStart.im/port.VBase = -cos(thetaStart)*udPuStart + sin(thetaStart)*uqPuStart;
  port.iStart.re/port.IBase =  sin(thetaStart)*idPuStart + cos(thetaStart)*iqPuStart;
  port.iStart.im/port.IBase = -cos(thetaStart)*idPuStart + sin(thetaStart)*iqPuStart;

equation
  // Park's transformations
  port.vPu.re =  sin(theta)*udPu + cos(theta)*uqPu;
  port.vPu.im = -cos(theta)*udPu + sin(theta)*uqPu;
  port.iPu.re =  sin(theta)*idPu + cos(theta)*iqPu;
  port.iPu.im = -cos(theta)*idPu + sin(theta)*iqPu;end OnePortACdqPU;


Base classes package

within PowerGrids.Electrical;

package BaseClasses "Base classes package"
  extends Modelica.Icons.BasesPackage;
end BaseClasses;


AC port computing auxiliary quantities.

within PowerGrids.Electrical.BaseClasses;

model PortAC "AC port computing auxiliary quantities"
  parameter Types.Power   SNom(start = 100e6) "Nominal or rated power";
  parameter Types.Voltage UNom(start = 400e3) "Nominal or rated phase-to-phase voltage";
  parameter Types.Power   SBase = SNom "Base power";
  parameter Types.Voltage UBase = UNom "Base phase-to-phase voltage";

  parameter Types.Power PStart "Start value of active power flowing into port";
  parameter Types.Power QStart "Start value of reactive power flowing into port";
  parameter Types.Voltage UStart = UNom "Start value of phase-to-phase voltage modulus";
  parameter Types.Angle UPhaseStart = 0 "Start value of voltage phase";

  parameter Boolean portVariablesPhases = false "Compute voltage and current phases for monitoring purposes only"
  constant Boolean portVariablesPu = true "Add per-unit variables to model";
  constant Boolean generatorConvention = false "Add currents with generator convention (i > 0 when exiting the device) to model";

  // Computed parameters
  final parameter Types.Voltage VBase = UBase/sqrt(3) "Base phase-to-ground voltage";
  final parameter Types.Current IBase = SBase/(3*VBase) "Base current";

  final parameter Types.Voltage VStart = UStart/sqrt(3) "Start value of phase-to ground voltage modulus";
  final parameter Types.Current IStart = sqrt(PStart^2 + QStart^2)/(3*VStart) "Start value of current modulus";

  final parameter Types.ComplexVoltage vStart = CM.fromPolar(UStart/sqrt(3), UPhaseStart)
    "Start value of phase-to-ground voltage phasor";
  final parameter Types.ComplexCurrent iStart = CM.conj(Complex(PStart,QStart)/(Complex(3)*vStart))
    "Start value of current phasor flowing into the port";

  connector InputComplexVoltage = input Types.ComplexVoltage "Marks potential input for balancedness check without requiring binding equation";
  connector InputComplexCurrent = input Types.ComplexCurrent "Marks potential input for balancedness check without requiring binding equation";

  InputComplexVoltage v(re(nominal = VBase, start = vStart.re),
                        im(nominal = VBase, start = vStart.im)) "Port voltage (line-to-neutral)";
  InputComplexCurrent i(re(nominal = IBase, start = iStart.re),
                        im(nominal = IBase, start = iStart.im)) "Port current";

  Types.ComplexVoltage u(re(nominal = UBase, start = vStart.re*sqrt(3)),
                         im(nominal = UBase, start = vStart.im*sqrt(3))) = sqrt(3)*v
     "Complex phase-to-phase voltage";
  Types.ComplexPower   S(re(nominal = SBase), im(nominal = SBase)) = 3*v*CM.conj(i)
     "Complex power flowing into the port";
  Types.ActivePower   P(nominal = SBase, start = PStart) = S.re "Active power flowing into the port";
  Types.ReactivePower Q(nominal = SBase, start = QStart) = S.im "Reactive power flowing into the port";

  Types.Voltage U(nominal = UBase, start = UStart) = CM.'abs'(u) "Port voltage absolute value (phase-to-phase)";
  Types.Current I(nominal = IBase, start = IStart) = CM.'abs'(i) "Port current (positive entering)";

  Types.PerUnit        PPu(start = PStart/SBase) = if portVariablesPu then S.re/SBase else 0 "Active power flowing into the port in p.u. (base SBase)"
  Types.PerUnit        QPu(start = QStart/SBase) = if portVariablesPu then S.im/SBase else 0 "Reactive power flowing into the port in p.u. (base SBase)"
  Types.ComplexPerUnit vPu(re(start = vStart.re/VBase),im(start = vStart.im/VBase)) = if portVariablesPu then u*(1/UBase) else Complex(0) "Complex voltage across the port in p.u. (base VBase)"
  SI.PerUnit           VPu(start = VStart/VBase) = if portVariablesPu then U/UBase else 0 "Absolute value of voltage across the port in p.u. (base VBase)"
  Types.ComplexPerUnit iPu(re(start = iStart.re/IBase), im(start = iStart.im/IBase)) = if portVariablesPu then i*(1/IBase) else Complex(0) "Complex current flowing into the port in p.u. (base IBase)"
  SI.PerUnit           IPu(start = IStart/IBase) = if portVariablesPu then I/IBase else 0 "Absolute value of complex current flowing into the port in p.u. (base IBase)"

  Types.Angle UPhase(start = UPhaseStart) = if portVariablesPhases then atan2(v.im, v.re) else 0 "Phase of voltage across the port"
  Types.Angle IPhase(start = CM.arg(iStart)) = if portVariablesPhases then atan2(i.im, i.re) else 0 "Phase of current into the port"

  Types.ComplexCurrent iGen(re(nominal = IBase, start = -iStart.re),
                            im(nominal = IBase, start = -iStart.im)) = -i "Port current, generator convention";
  Types.ActivePower PGen(nominal = SBase, start = -PStart) = -S.re "Active power flowing out of the port";
  Types.ReactivePower QGen(nominal = SBase, start = -QStart) = -S.im "Reactive power flowing out of the port";
  Types.PerUnit PGenPu(start = -PStart/SBase) = -PPu "Active power flowing out of the port in p.u. (base SBase)";
  Types.PerUnit QGenPu(start = -QStart/SBase) = -QPu "Reactive power flowing out of the port in p.u. (base SBase)";
  Types.ComplexPerUnit iGenPu(re(start = -iStart.re/IBase),im(start = -iStart.im/IBase)) = -iPu "Complex current flowing out of the port in p.u. (base IBase)";
  SI.PerUnit IGenPu(start = IStart/IBase) = if portVariablesPu and generatorConvention then I/IBase else 0 "Absolute value of current flowing out of the port in p.u. (base IBase)"
end PortAC;


Connects the port to ground via a conductor.

within PowerGrids.Electrical.BaseClasses;

model ShuntConductance "Connects the port to ground via a conductor"
  extends OnePortAC;
  input Types.ComplexAdmittance Y "Shunt admittance";
  Types.ComplexVoltage v(re(nominal = port.VBase), im(nominal = port.VBase)) = port.v;
  Types.ComplexCurrent i(re(nominal = port.IBase), im(nominal = port.IBase)) = port.i;
equation
  i = Y*v;
end ShuntConductance;


Base class for two-port AC components.

within PowerGrids.Electrical.BaseClasses;

partial model TwoPortAC "Base class for two-port AC components"
  parameter Types.Voltage UNomA(start = 400e3) "Nominal/rated voltage, port A"
  parameter Types.Voltage UNomB = UNomA "Nominal/rated voltage, port B"
  parameter Types.Power SNom(start = 100e6) "Nominal/rated power"

  parameter Boolean portVariablesPhases = false "Compute voltage and current phases for monitoring purposes only"
  constant Boolean portVariablesPu = true "Add per-unit variables to model";
  parameter Boolean computePowerBalance = true "Compute net balance of complex power entering the component";

  parameter Types.Voltage UStartA = UNomA "Start value of phase-to-phase voltage phasor at port A, absolute value"
  parameter Types.Angle UPhaseStartA = 0 "Start value of phase-to-phase voltage phasor at port A, phase angle"
  parameter Types.ActivePower PStartA = SNom "Start value of active power flowing into port A"
  parameter Types.ReactivePower QStartA = 0 "Start value of reactive power flowing into port A"

  parameter Types.Voltage UStartB = UNomB "Start value of phase-to-phase voltage phasor at port B, absolute value"
  parameter Types.Angle UPhaseStartB = 0 "Start value of phase-to-phase voltage phasor at port B, phase angle"
  parameter Types.ActivePower PStartB = -SNom "Start value of active power flowing into port B"
  parameter Types.ReactivePower QStartB = 0 "Start value of reactive power flowing into port B"

  PowerGrids.Interfaces.TerminalAC terminalA
  PowerGrids.Interfaces.TerminalAC terminalB
  PortAC portA(final v = terminalA.v, final i = terminalA.i,
               final UNom = UNomA, final SNom = SNom,
               final portVariablesPu = portVariablesPu,
               final portVariablesPhases = portVariablesPhases,
               final generatorConvention = false,
               final UStart = UStartA,
               final UPhaseStart = UPhaseStartA,
               final PStart = PStartA,
               final QStart = QStartA)
               "AC port - terminalA";
  PortAC portB(final v = terminalB.v, final i = terminalB.i,
               final UNom = UNomB, final SNom = SNom,
               final portVariablesPu = portVariablesPu,
               final portVariablesPhases = portVariablesPhases,
               final generatorConvention = false,
               final UStart = UStartB,
               final UPhaseStart = UPhaseStartB,
               final PStart = PStartB,
               final QStart = QStartB)
               "AC port - terminalB";

  Types.ComplexPower Sbal = portA.S + portB.S if computePowerBalance "Complex power balance";
  outer Electrical.System systemPowerGrids "Reference to system object";
end TwoPortAC;


Transmission line with constant impedance.

within PowerGrids.Electrical.Branches;
model LineConstantImpedance "Transmission line with constant impedance"
  extends BaseClasses.PiNetwork(
    UNomA = UNom,
    UNomB = UNomA);
  extends Icons.Line;

  parameter Types.Voltage UNom(start = 400e3) "Nominal/rated voltage";
  parameter Types.Resistance R "Series resistance";
  parameter Types.Reactance X "Series reactance";
  parameter Types.Conductance G = 0  "Shunt conductance";
  parameter Types.Susceptance B = 0 "Shunt susceptance";
equation
  k = Complex(1);
  Y = 1/Complex(R, X);
  YA = Complex(G/2, B/2);
  YB = Complex(G/2, B/2);
end LineConstantImpedance;


Transmission line with constant impedance and fault in intermediate position.

within PowerGrids.Electrical.Branches;
model LineConstantImpedanceFault "Transmission line with constant impedance and fault in intermediate position"
  extends Electrical.BaseClasses.TwoPortAC(
    UNomA = UNom,
    UNomB = UNomA);
  extends Icons.Line;

  parameter Types.Voltage UNom(start = 400e3) "Nominal/rated voltage";
  parameter Types.Resistance R "Series resistance";
  parameter Types.Reactance X "Series reactance";
  parameter Types.Conductance G = 0  "Shunt conductance";
  parameter Types.Susceptance B = 0 "Shunt susceptance";
  parameter SI.PerUnit faultLocationPu "Location of the fault (0: port A, 1: port B)"
  parameter SI.Time startTime "Start time of the fault"
  parameter SI.Time stopTime "End time of the fault"
  parameter Types.Resistance RFault "Fault resistance"
  parameter Types.Reactance XFault "Fault reactance"
  PowerGrids.Electrical.Branches.LineConstantImpedance lineA(B = faultLocationPu * B, G = faultLocationPu * G, PStartA = PStartA, PStartB = (1 - faultLocationPu) * PStartA + faultLocationPu * PStartB, QStartA = QStartA, QStartB = (1 - faultLocationPu) * QStartA + faultLocationPu * QStartB, R = faultLocationPu,SNom = SNom, UNom = UNom, UPhaseStartA = UPhaseStartA, UPhaseStartB = (1 - faultLocationPu) * UPhaseStartA + faultLocationPu * UPhaseStartB, UStartA = UStartA, UStartB = (1 - faultLocationPu) * UStartA + faultLocationPu * UStartB, X = faultLocationPu * X)
  PowerGrids.Electrical.Branches.LineConstantImpedance lineB(B = (1 - faultLocationPu) * B, G = (1 - faultLocationPu) * G, PStartA = (1 - faultLocationPu) * PStartA + faultLocationPu * PStartB, PStartB = PStartB, QStartA = (1 - faultLocationPu) * QStartA + faultLocationPu * QStartB, QStartB = QStartA, R = (1 - faultLocationPu) * R,SNom = SNom, UNom = UNom, UPhaseStartA = UPhaseStartA, UPhaseStartB = UPhaseStartB, UStartA = UStartA, UStartB = UStartB, X = (1 - faultLocationPu) * X, portVariablesPu = false)
  PowerGrids.Electrical.Buses.BusFault busFault(R = RFault, SNom = SNom, UNom = UNom, X = XFault, portVariablesPu = false, startTime = startTime, stopTime = stopTime)
equation
  connect(busFault.terminal, lineA.terminalB)
  connect(lineB.terminalB, terminalB)
  connect(lineA.terminalB, lineB.terminalA)
  connect(lineA.terminalA, terminalA)
end LineConstantImpedanceFault;


Transmission line with constant impedance and breakers.

within PowerGrids.Electrical.Branches;

model LineConstantImpedanceWithBreakers "Transmission line with constant impedance and breakers"
  extends BaseClasses.PiNetwork(UNomA = UNom, UNomB = UNomA);
  extends Icons.Line;
  encapsulated type BreakersState = enumeration(
    AcBc "Both breakers at port A and at port B closed",
    AcBo "Breaker at port A closed, breaker at port B open",
    AoBc "Breaker at port A open, breaker at port B closed",
    AoBo "Both breakers at port A and at port B open"
  );
  parameter Boolean useBreakerA = false "Use breaker at port A"
  parameter Boolean useBreakerB = false "Use breaker at port B"
  parameter Types.Voltage UNom(start = 400e3) "Nominal/rated voltage";
  parameter Types.Resistance R "Series resistance";
  parameter Types.Resistance X "Series reactance";
  parameter Types.Conductance G = 0 "Shunt conductance";
  parameter Types.Conductance B = 0 "Shunt susceptance";
  parameter Boolean breakerAStatusStart = true "Breaker A start status - true means breaker closed"
  parameter Boolean breakerBStatusStart = true "Breaker B start status - true means breaker closed"
  final parameter Types.ComplexAdmittance Yseries = 1 / Complex(R, X) "Series admittance"
  final parameter Types.ComplexAdmittance Yshunt = Complex(G / 2, B / 2) "Shunt admittance at port A/B";
  final parameter Types.ComplexAdmittance YbreakerOpen = Yseries * Yshunt / (Yseries + Yshunt) "Total admittance when breaker A/B is open"

  Types.ComplexAdmittance Y_actual "Actual series admittance";
  Types.ComplexAdmittance YA_actual "Actual shunt admittance at port A";
  Types.ComplexAdmittance YB_actual "Actual shunt admittance at port B";
  BreakersState breakerStatus(start = BreakersState.AcBc) "Breakers current status";

  Modelica.Blocks.Interfaces.BooleanInput breakerStatusA if useBreakerA "Breaker status at port A - true means breaker closed"
  Modelica.Blocks.Interfaces.BooleanInput breakerStatusB if useBreakerB "Breaker status at port B - true means breaker closed"

protected
  Modelica.Blocks.Interfaces.BooleanInput breakerStatusInternalA(start = breakerAStatusStart, fixed = true) "Vreaker status at port A";
  Modelica.Blocks.Interfaces.BooleanInput breakerStatusInternalB(start = breakerBStatusStart, fixed = true) "Breaker status at port B";

initial algorithm
  if breakerAStatusStart then
    if breakerBStatusStart then
      breakerStatus := BreakersState.AcBc;
    else
      breakerStatus := BreakersState.AcBo;
    end if;
  else
    if breakerBStatusStart then
      breakerStatus := BreakersState.AoBc;
    else
      breakerStatus := BreakersState.AoBo;
    end if;
  end if;

algorithm
  when breakerStatus == BreakersState.AcBc
       and not pre(breakerStatusInternalB) then
    breakerStatus := BreakersState.AcBo "AcBc to AcBo";

  elsewhen breakerStatus == BreakersState.AcBc
           and not pre(breakerStatusInternalA) then
    breakerStatus := BreakersState.AoBc "AcBc to AoBc";

  elsewhen breakerStatus == BreakersState.AcBo
           and not pre(breakerStatusInternalA) then
    breakerStatus := BreakersState.AoBo "AcBo to AoBo";

  elsewhen breakerStatus == BreakersState.AoBc
           and not pre(breakerStatusInternalB) then
    breakerStatus := BreakersState.AoBo "AoBc to AoBo";

  elsewhen breakerStatus == BreakersState.AoBo
           and pre(breakerStatusInternalA) then
    breakerStatus := BreakersState.AcBo "AoBo to AcBo";

  elsewhen breakerStatus == BreakersState.AoBo
           and pre(breakerStatusInternalB) then
    breakerStatus := BreakersState.AoBc "AoBo to AoBc";

  elsewhen breakerStatus == BreakersState.AcBo
           and pre(breakerStatusInternalB) then
    breakerStatus := BreakersState.AcBc "AcBo to AcBc";

  elsewhen breakerStatus == BreakersState.AoBc
           and pre(breakerStatusInternalA) then
    breakerStatus := BreakersState.AcBc "AoBc to AcBc";
  end when;

equation
  if breakerStatus == BreakersState.AcBc then
    Y_actual = Yseries;
    YA_actual = Yshunt;
    YB_actual = Yshunt;

  elseif breakerStatus == BreakersState.AcBo then
    Y_actual = Complex(0);
    YB_actual = Complex(0);
    YA_actual = YbreakerOpen;

  elseif breakerStatus == BreakersState.AoBc then
    Y_actual = Complex(0);
    YA_actual = Complex(0);
    YB_actual = YbreakerOpen;

  else
    // AoBo
    Y_actual = Complex(0);
    YB_actual = Complex(0);
    YA_actual = Complex(0);
  end if;

  k = Complex(1);
  Y = Y_actual;
  YA = YA_actual;
  YB = YB_actual;

  connect(breakerStatusA, breakerStatusInternalA);
  connect(breakerStatusB, breakerStatusInternalB);
  if not useBreakerA then
    breakerStatusInternalA = true "Breaker closed";
  end if;
  if not useBreakerB then
    breakerStatusInternalB = true "Breaker closed";
  end if;

end LineConstantImpedanceWithBreakers;


Branch component package

within PowerGrids.Electrical;

package Branches "Branch component package"
  extends Modelica.Icons.Package;
end Branches;


Transformer with fixed voltage ratio.

within PowerGrids.Electrical.Branches;

model TransformerFixedRatio "Transformer with fixed voltage ratio"
  extends BaseClasses.PiNetwork;
  extends Icons.Transformer;

  parameter SI.PerUnit rFixed = 1 "Fixed transformer ratio VB/VA";
  parameter SI.Angle thetaFixed = 0 "Fixed phase lead of VB w.r.t. VA";
  parameter Types.Resistance R = 0 "Series resistance on B side";
  parameter Types.Reactance X = 0 "Series reactance on B side";
  parameter Types.Conductance G = 0 "Shunt conductance on B side";
  parameter Types.Susceptance B = 0 "Shunt susceptance on B side";
equation
  k = CM.fromPolar(rFixed, thetaFixed);
  Y = Complex(1)/Complex(R, X);
  YA = Complex(0);
  YB = Complex(G, B);
end TransformerFixedRatio;


breaker closed.

within PowerGrids.Electrical.Branches;
model TransformerFixedRatioWithBreaker
  extends BaseClasses.PiNetwork;
  extends Icons.Transformer;

  encapsulated type BreakersState = enumeration(
    Bc "breaker closed",
    Bo "breaker open");
  parameter Boolean useBreaker = false "Use breaker (port b)"
  parameter SI.PerUnit rFixed = 1 "Fixed transformer ratio VB/VA";
  parameter SI.Angle thetaFixed = 0 "Fixed phase lead of VB w.r.t. VA";
  parameter Types.Resistance R "Series resistance on B side";
  parameter Types.Reactance X "Series reactance on B side";
  parameter Types.Conductance G = 0 "Shunt conductance on B side";
  parameter Types.Susceptance B = 0
                                   "Shunt susceptance on B side";
  parameter Boolean breakerStatusStart = true "Breaker start status - true means breaker closed"
  final parameter Types.ComplexAdmittance Yseries = Complex(1)/Complex(R, X) "Series admittance"
  final parameter Types.ComplexAdmittance Yshunt = Complex(G, B) "Shunt admittance at port B";
  final parameter Types.ComplexAdmittance YbreakerOpen = Yseries*Yshunt/(Yseries + Yshunt) "Total admittance when breaker is open (port B)"

  Types.ComplexAdmittance Y_actual "Actual series admittance";
  Types.ComplexAdmittance YA_actual "Actual shunt admittance at port A";
  Types.ComplexAdmittance YB_actual "Actual shunt admittance at port B";
  BreakersState breakerStatus(start = BreakersState.Bc) "breacker current status";

  Modelica.Blocks.Interfaces.BooleanInput breakerStatusIn if useBreaker "Breaker Status (port B) - true means breaker closed"

initial algorithm
  if breakerStatusStart then
    breakerStatus := BreakersState.Bc;
  else
    breakerStatus := BreakersState.Bo;
  end if;

protected
  Modelica.Blocks.Interfaces.BooleanInput breakerStatusInInternal(start = breakerStatusStart, fixed = true) "breaker Status (port B)";

algorithm
  when breakerStatus == BreakersState.Bc
       and not pre(breakerStatusInInternal) then
    breakerStatus := BreakersState.Bo "Bc to Bo";

  elsewhen breakerStatus == BreakersState.Bo
           and pre(breakerStatusInInternal) then
    breakerStatus := BreakersState.Bc "Bo to Bc";

  end when;

equation
  if breakerStatus == BreakersState.Bc then
    Y_actual = Yseries;
    YA_actual = Complex(0);
    YB_actual = Yshunt;

  else
    // Bo
    Y_actual = Complex(0);
    YA_actual = YbreakerOpen;
    YB_actual = Complex(0);
  end if;

  k = CM.fromPolar(rFixed, thetaFixed);
  Y = Y_actual;
  YA = YA_actual;
  YB = YB_actual;

  connect(breakerStatusIn, breakerStatusInInternal);
  if not useBreaker then
    breakerStatusInInternal = true "Breaker closed";
  end if;
end TransformerFixedRatioWithBreaker;


Array with angle of transformer complex ratio for each tap.

within PowerGrids.Electrical.Branches;
model TransformerWithPhaseShifterInterval
  extends BaseClasses.TapChangerPhaseShifterCommon;
  extends BaseClasses.TapChangerPhaseShifterLogicInterval;

  parameter Types.Angle kPhase[Ntap] "Array with angle of transformer complex ratio for each tap";
  parameter PowerGrids.Electrical.Branches.BaseClasses.TapChangerPhaseShifterCommon.MonitoredQuantitySelection quantitySel "monitored quantity selection";
  parameter Types.Current IMax = NotUsed "Maximum Current threshold for phase shifter logic"
  parameter Types.Current IMin = NotUsed "Minimum Current threshold for phase shifter logic"
  parameter Types.ActivePower PMax = NotUsed "Maximum Active Power threshold for phase shifter logic"
  parameter Types.ActivePower PMin = NotUsed "Minimum Active Power threshold for phase shifter logic"

initial equation
  if quantitySel == MonitoredQuantitySelection.currentMagnitude then
    assert(IMax > IMin, "Wrong Current interval");
    assert(IMax >= 0, "Current threshold IMax must be positive");
    assert(IMin >= 0, "Current threshold IMin must be positive");
  elseif quantitySel == MonitoredQuantitySelection.activePower then
    assert(PMax > PMin, "wrong Active Power interval");
    assert(PMax >= 0, "Active Power threshold PMax must be positive");
    assert(PMin >= 0, "Active Power threshold PMin must be positive");
  end if;

equation
// Phase shifter applied on output port
  locked = false;
  running = true;

  if quantitySel == MonitoredQuantitySelection.currentMagnitude then
    valueUnderMin = portB.I < IMin;
    valueAboveMax = portB.I > IMax;
  elseif quantitySel == MonitoredQuantitySelection.activePower then
    valueUnderMin = portB.P < PMin;
    valueAboveMax = portB.P > PMax;
  end if;

  k = CM.fromPolar(1, kPhase[pre(tap)])
      "pre() is required because k influences the triggering conditions
       of the state machine so it should be computed before the event takes place";
end TransformerWithPhaseShifterInterval;


Angle of transformer complex ratio for each tap.

within PowerGrids.Electrical.Branches;
model TransformerWithPhaseShifterMax
  extends BaseClasses.TapChangerPhaseShifterCommon;
  extends BaseClasses.TapChangerPhaseShifterLogicMax;

  parameter Types.Angle kPhase[Ntap] "Angle of transformer complex ratio for each tap";
  parameter PowerGrids.Electrical.Branches.BaseClasses.TapChangerPhaseShifterCommon.MonitoredQuantitySelection quantitySel "monitored quantity selection";
  parameter Types.Current IMax = NotUsed "Max Current threshold for phase shifter logic"
  parameter Types.Current IStop = NotUsed "Stop Current threshold for phase shifter logic"
  parameter Types.ActivePower PMax = NotUsed "Max Active Power threshold for phase shifter logic"
  parameter Types.ActivePower PStop = NotUsed "Stop Active Power threshold for phase shifter logic"

initial equation
  if quantitySel == MonitoredQuantitySelection.currentMagnitude then
    assert(IMax > IStop, "Wrong current interval");
    assert(IMax >= 0, "Current threshold IMax must be positive");
    assert(IStop >= 0, "Current threshold IStop must be positive");
  elseif quantitySel == MonitoredQuantitySelection.activePower then
    assert(PMax > PStop, "Wrong Active Power interval");
    assert(PMax >= 0, "Active Power threshold PMax must be positive");
    assert(PStop >= 0, "Active Power threshold PMax must be positive");
  end if;

equation
// phase shifter applied on output port
  locked = false;
  running = true;

  if quantitySel == MonitoredQuantitySelection.currentMagnitude then
    valueUnderStop = portB.I < IStop;
    valueAboveMax  = portB.I > IMax;
  elseif quantitySel == MonitoredQuantitySelection.activePower then
    valueUnderStop = portB.P < PStop;
    valueAboveMax  = portB.P > PMax;
  end if;

  k = CM.fromPolar(1, kPhase[pre(tap)])
      "pre() is required because k influences the triggering conditions
       of the state machine so it should be computed before the event takes place";
end TransformerWithPhaseShifterMax;


Current target value.

within PowerGrids.Electrical.Branches;
model TransformerWithPhaseShifterTarget
  extends TransformerWithPhaseShifterInterval(
    final IMax = targetValueI + deadBandI,
    final IMin = targetValueI - deadBandI,
    final PMax = targetValueP + deadBandP,
    final PMin = targetValueP - deadBandP);

  parameter Types.Current targetValueI = NotUsed "Current target value"
  parameter Types.Current deadBandI = NotUsed "Acceptable dead-band next to the Current target value"
  parameter Types.ActivePower targetValueP = NotUsed "Current target value"
  parameter Types.ActivePower deadBandP = NotUsed "Maximum Active Power threshold for phase shifter logic"

initial equation
  if quantitySel == MonitoredQuantitySelection.currentMagnitude then
    assert(targetValueI > deadBandI, "Dead band cannot exceed the target value");
    assert(deadBandI > 0, "Dead band must be positive");
    assert(targetValueI >= 0, "Target value must be positive");
  elseif quantitySel == MonitoredQuantitySelection.activePower then
    assert(targetValueP > deadBandP, "Dead band cannot exceed the target value");
    assert(deadBandP > 0, "Dead band must be positive");
    assert(targetValueP >= 0, "Target value must be positive");
  end if;
end TransformerWithPhaseShifterTarget;


Array of transformer ratios for each tap.

within PowerGrids.Electrical.Branches;
model TransformerWithTapChangerInterval
  extends BaseClasses.TapChangerPhaseShifterCommon;
  extends BaseClasses.TapChangerPhaseShifterLogicInterval;

  parameter Types.PerUnit K[Ntap] "Array of transformer ratios for each tap";
  parameter Types.Voltage UMax "Maximum phase-to-phase voltage threshold for tap changer logic";
  parameter Types.Voltage UMin "Minimum phase-to-phase voltage threshold for tap changer logic";

initial equation
  assert(UMax > UMin, "Wrong phase-to-phase voltage interval");
  assert(UMax >= 0, "Maximum phase-to-phase voltage threshold must be positive");
  assert(UMin >= 0, "Minimum phase-to-phase voltage threshold must be positive");

equation
  // Tap changer applied on output phase-to-phase voltage module
  locked = false;
  running = true;
  valueUnderMin = portB.U < UMin;
  valueAboveMax = portB.U > UMax;
  k = Complex(K[pre(tap)])
    "pre() is required because k influences the triggering conditions
     of the state machine so it should be computed before the event takes place";

end TransformerWithTapChangerInterval;


Magnitude of transformer complex ratio for each tap.

within PowerGrids.Electrical.Branches;
model TransformerWithTapChangerMax
  extends PowerGrids.Electrical.Branches.BaseClasses.TapChangerPhaseShifterCommon;
  extends PowerGrids.Electrical.Branches.BaseClasses.TapChangerPhaseShifterLogicMax;

  parameter Types.PerUnit K[Ntap] "Magnitude of transformer complex ratio for each tap";
  parameter Types.Voltage UMax "MAX phase-to-phase voltage threshold for tap changer logic";
  parameter Types.Voltage UStop "Stop phase-to-phase voltage threshold for tap changer logic";

initial equation
  assert(UMax > UStop, "Phase-to-phase voltage MAX threshold must be higher than stop one");
  assert(UMax >= 0, "Maximum phase-to-phase voltage threshold must be positive");
  assert(UStop >= 0, "Stopping phase-to-phase voltage threshold must be positive");

equation
// Tap changer is applied on output phase-to-phase voltage module
  locked = false;
  running = true;
  valueUnderStop = portB.U < UStop;
  valueAboveMax  = portB.U > UMax;
  k = Complex(K[pre(tap)])
      "pre() is required because k influences the triggering conditions
       of the state machine so it should be computed before the event takes place";
end TransformerWithTapChangerMax;


Phase-to-phase voltage target value.

within PowerGrids.Electrical.Branches;
model TransformerWithTapChangerTarget
  extends TransformerWithTapChangerInterval(
    final UMax = targetValue + deadBand,
    final UMin = targetValue - deadBand);

  parameter Types.Voltage targetValue(min = 0) "Phase-to-phase voltage target value";
  parameter Types.Voltage deadBand(min = 0) "Acceptable dead-band next to the phase-to-phase voltage target value";

initial equation
  assert(targetValue > deadBand, "Dead band cannot exceed the target value");
  assert(deadBand > 0, "Dead band must be positive");
  assert(targetValue >= 0, "Target value must be positive");
end TransformerWithTapChangerTarget;


Generic Pi-Network base model.

within PowerGrids.Electrical.Branches.BaseClasses;
partial model PiNetwork "Generic Pi-Network base model"
  extends Electrical.BaseClasses.TwoPortAC;
  Types.ComplexAdmittance Y "Series admittance";
  Types.ComplexAdmittance YA "Shunt admittance at port a";
  Types.ComplexAdmittance YB "Shunt admittance at port b";
  Types.ComplexPerUnit k "Complex ratio of ideal transformer at port A (vB/vA)";
  Types.ComplexVoltage vA(re(nominal = portA.VBase), im(nominal = portA.VBase)) = portA.v;
  Types.ComplexVoltage vB(re(nominal = portB.VBase), im(nominal = portB.VBase)) = portB.v;
  Types.ComplexCurrent iA(re(nominal = portA.IBase), im(nominal = portA.IBase)) = portA.i;
  Types.ComplexCurrent iB(re(nominal = portB.IBase), im(nominal = portB.IBase)) = portB.i;
  // See diagram in the documentation layer for the variable definitions
  Types.ComplexVoltage vAt(re(nominal = portB.VBase), im(nominal = portB.VBase));
  Types.ComplexVoltage vz(re(nominal = portB.VBase), im(nominal = portB.VBase));
  Types.ComplexCurrent iAt(re(nominal = portB.IBase), im(nominal = portB.IBase));
  Types.ComplexCurrent iAs(re(nominal = portB.IBase), im(nominal = portB.IBase));
  Types.ComplexCurrent iBs(re(nominal = portB.IBase), im(nominal = portB.IBase));
  Types.ComplexCurrent iz(re(nominal = portB.IBase), im(nominal = portB.IBase));
equation
// Kirchhoff's laws
  iAt = iz + iAs;
  iBs = iz + iB;
  vAt = vz + vB;
// Constitutive equations
  vAt = k * vA;
  iA = CM.conj(k) * iAt;
  iAs = YA * vAt;
  iBs = YB * vB;
  iz = Y * vz;
end PiNetwork;