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;


Common base class for tap-changer/phase shifter transformers.

within PowerGrids.Electrical.Branches.BaseClasses;
partial model TapChangerPhaseShifterCommon "Common base class for tap-changer/phase shifter transformers"
  extends BaseClasses.PiNetwork;
  extends Icons.Transformer;

  final constant Real NotUsed = Modelica.Constants.inf "Generic default value for not-used parameters";
  encapsulated type MonitoredQuantitySelection = enumeration(currentMagnitude "phase shifter monitors the port_b current magnitude",
                                                             activePower "phase shifter monitors the port_b active power");

  parameter Types.Resistance R = 0 "Series resistance";
  parameter Types.Reactance X = 0 "Series reactance";
  parameter Types.Conductance G = 0 "Shunt conductance";
  parameter Types.Susceptance B = 0 "Shunt susceptance";

equation
  Y = 1/Complex(R, X);
  YA = Complex(0);
  YB = Complex(G, B);
end TapChangerPhaseShifterCommon;


Default value for counters.

within PowerGrids.Electrical.Branches.BaseClasses;
partial model TapChangerPhaseShifterLogicCommon
  final constant Types.Time timeInfinity = Modelica.Constants.inf "Default value for counters";

  encapsulated type State = enumeration (moveDownN "1: tap-changer/phase-shifter has decreased the next tap",
                                         moveDown1 "2: tap-changer/phase-shifter has decreased the first tap",
                                         waitingToMoveDown "3: tap-changer/phase-shifter is waiting to decrease the first tap",
                                         standard "4:tap-changer/phase-shifter is in standard state with UThresholdDown <= UMonitored <= UThresholdUp",
                                         waitingToMoveUp "5: tap-changer/phase-shifter is waiting to increase the first tap",
                                         moveUp1 "6: tap-changer/phase-shifter has increased the first tap",
                                         moveUpN "7: tap-changer/phase-shifter has increased the next tap",
                                         locked "8: tap-changer/phase-shifter locked");
  State state(start = stateStart, fixed = true);

  encapsulated type ActionType = enumeration (direct "Whether increasing the tap will increase the monitored value",
                                              reverse "Whether increasing the tap will decrease the monitored value");

  parameter Types.Time t1st(min = 0) "Time lag before changing the first tap";
  parameter Types.Time tNext(min = 0) "Time lag before changing subsequent taps";
  parameter Integer Ntap "Number of tap positions";
  parameter Integer tapMax = Ntap "Max tap position";
  parameter Integer tapMin = 1    "Min tap position";
  parameter ActionType actionSel = ActionType.direct "Tap direct or reverse action selector";
  parameter Boolean lockedStart = false "Whether the tap-changer/phase-shifter is initially locked"
  parameter Boolean runningStart = true "Whether the tap-changer/phase-shifter is initially running"
  parameter Integer tapStart "Initial tap"
  parameter State stateStart = State.standard "Initial state"
  final parameter Boolean directAction = (actionSel == ActionType.direct) "= true, when direct action is selected";
  final parameter Boolean reverseAction = (actionSel == ActionType.reverse) "= true, when reverse action is selected";

  Boolean locked (start = lockedStart) "whether the tap-changer/phase-shifter is locked";
  Boolean running (start = runningStart) "whether the tap-changer/phase-shifter is running";
  Integer tap(start = tapStart, fixed = true) "Current tap";

protected
  Boolean lookingToIncreaseTap "true if the tap-changer/phase-shifter wants to increase tap";
  Boolean lookingToDecreaseTap "true if the tap-changer/phase-shifter wants to decrease tap";
  Types.Time tTapMoved(start = timeInfinity, fixed = true) "Time when the tap has been moved";

initial equation
  assert (tapMax <= Ntap,  "Max tap value supposed to be equal or below Ntap");
  assert (tapMin >= 1,     "Min tap value supposed to be equal or higher than 1");
  assert (tapMax > tapMin, "Max tap value supposed to be higher than Min tap value");

equation
  assert (tap <= tapMax, "Tap value supposed to be below maximum tap");
  assert (tap >= tapMin, "Tap value supposed to be above minimum tap");
end TapChangerPhaseShifterLogicCommon;


time lag before transition to standard state, in order to avoid chattering.

within PowerGrids.Electrical.Branches.BaseClasses;
partial model TapChangerPhaseShifterLogicInterval
  extends TapChangerPhaseShifterLogicCommon;
  parameter Types.Time tToStandardState = 0.1 "time lag before transition to standard state, in order to avoid chattering";

protected
    Boolean valueUnderMin(start = false) "true if the monitored signal is under the minimum limit";
    Boolean valueAboveMax(start = false) "true if the monitored signal is above the maximum limit";
    Boolean valueInRange = not(pre(valueUnderMin)) and not(pre(valueAboveMax)) "true if the monitored signal is into the [valueMin ; valueMax] range";
    Types.Time tValueUnderMinWhileRunning(start = timeInfinity) "time when the monitored signal went under the minimum limit and the tap-changer/phase-shifter is running";
    Types.Time tValueAboveMaxWhileRunning(start = timeInfinity) "time when the monitored signal went above the maximum limit and the tap-changer/phase-shifter is running";
    Types.Time tValueInRangeWhileRunning(start = timeInfinity) "time when the monitored signal comes back into the [valueMin ; valueMax] range and the tap-changer/phase-shifter is running";

equation
  lookingToDecreaseTap = (pre(running) and pre(valueAboveMax) and directAction) or (pre(running) and pre(valueUnderMin) and reverseAction);
  lookingToIncreaseTap = (pre(running) and pre(valueUnderMin) and directAction) or (pre(running) and pre(valueAboveMax) and reverseAction);

  when pre(running) and not(pre(locked)) and pre(valueUnderMin) then
    tValueUnderMinWhileRunning = time;
    tValueAboveMaxWhileRunning = pre(tValueAboveMaxWhileRunning);
    tValueInRangeWhileRunning = pre(tValueInRangeWhileRunning);
  elsewhen pre(running) and not(pre(locked)) and not(pre(valueUnderMin)) then
    tValueUnderMinWhileRunning = pre(tValueUnderMinWhileRunning);
    tValueAboveMaxWhileRunning = pre(tValueAboveMaxWhileRunning);
    tValueInRangeWhileRunning = time;
  elsewhen pre(running) and not(pre(locked)) and pre(valueAboveMax) then
    tValueUnderMinWhileRunning = pre(tValueUnderMinWhileRunning);
    tValueAboveMaxWhileRunning = time;
    tValueInRangeWhileRunning = pre(tValueInRangeWhileRunning);
  elsewhen pre(running) and not(pre(locked)) and not(pre(valueAboveMax)) then
    tValueUnderMinWhileRunning = pre(tValueUnderMinWhileRunning);
    tValueAboveMaxWhileRunning = pre(tValueAboveMaxWhileRunning);
    tValueInRangeWhileRunning = time;
  elsewhen pre(locked) then
    tValueUnderMinWhileRunning = timeInfinity;
    tValueAboveMaxWhileRunning = timeInfinity;
    tValueInRangeWhileRunning = timeInfinity;
  end when;

  //Transition to "locked"
  when not(pre(running)) or pre(locked) then
    state = State.locked;
    tap = pre(tap);
    tTapMoved = timeInfinity;

  //Transition from "locked" to "standard"
  elsewhen pre(running) and pre(state) == State.locked and not(pre(locked))
                   and valueInRange
                   and time - tValueInRangeWhileRunning >= tToStandardState then
    state = State.standard;
    tap = pre(tap);
    tTapMoved = timeInfinity;

  //Transition from "locked" to "waitingToMoveDown"
  elsewhen pre(running) and pre(state) == State.locked
                   and not(pre(locked))
                   and lookingToDecreaseTap then
    state = State.waitingToMoveDown;
    tap = pre(tap);
    tTapMoved = timeInfinity;

  //Transition from "locked" to "waitingToMoveUp"
  elsewhen pre(running) and pre(state) == State.locked
                   and not(pre(locked))
                   and lookingToIncreaseTap then
    state = State.waitingToMoveUp;
    tap = pre(tap);
    tTapMoved = timeInfinity;

  //Transition from "standard" to "waitingToMoveUp"
  elsewhen pre(running) and pre(state) == State.standard
                   and lookingToIncreaseTap then
    state = State.waitingToMoveUp;
    tap = pre(tap);
    tTapMoved = timeInfinity;

  //Transition from "standard" to "waitingToMoveDown"
  elsewhen pre(running) and pre(state) == State.standard
                   and lookingToDecreaseTap then
    state = State.waitingToMoveDown;
    tap = pre(tap);
    tTapMoved = timeInfinity;

  //Transition from "waitingToMoveUp" to "standard"
  elsewhen pre(running) and pre(state) == State.waitingToMoveUp
                   and valueInRange
                   and time - tValueInRangeWhileRunning >= tToStandardState then
    state = State.standard;
    tap = pre(tap);
    tTapMoved = timeInfinity;

  //Transition from "waitingToMoveUp" to "waitingToMoveDown"
  elsewhen pre(running) and pre(state) == State.waitingToMoveUp
                   and lookingToDecreaseTap then
    state = State.waitingToMoveDown;
    tap = pre(tap);
    tTapMoved = timeInfinity;

  //Transition from "waitingToMoveUp" to "moveUp1"
  elsewhen pre(running) and pre(state) == State.waitingToMoveUp
                   and time - (if directAction then tValueUnderMinWhileRunning else tValueAboveMaxWhileRunning) >= t1st
                   and pre(tap) < tapMax then
    state = State.moveUp1;
    tap = pre(tap) + 1;
    tTapMoved = time;

  //Transition from "waitingToMoveDown" to "standard"
  elsewhen pre(running) and pre(state) == State.waitingToMoveDown
                   and valueInRange
                   and time - tValueInRangeWhileRunning >= tToStandardState then
    state = State.standard;
    tap = pre(tap);
    tTapMoved = timeInfinity;

  //Transition from "waitingToMoveDown" to "waitingToMoveUp"
  elsewhen pre(running) and pre(state) == State.waitingToMoveDown
                   and lookingToIncreaseTap then
    state = State.waitingToMoveUp;
    tap = pre(tap);
    tTapMoved = timeInfinity;

  //Transition from "waitingToMoveDown" to "moveDown1"
  elsewhen pre(running) and pre(state) == State.waitingToMoveDown
                   and time - (if directAction then tValueAboveMaxWhileRunning else tValueUnderMinWhileRunning) >= t1st
                   and pre(tap) > tapMin then
    state = State.moveDown1;
    tap = pre(tap) - 1;
    tTapMoved = time; //Transition from "moveUp1" to "standard"
  elsewhen pre(running) and pre(state) == State.moveUp1
                   and valueInRange
                   and time - tValueInRangeWhileRunning >= tToStandardState then
    state = State.standard;
    tap = pre(tap);
    tTapMoved = timeInfinity;

  //Transition from "moveUp1" to "waitingToMoveDown"
  elsewhen pre(running) and pre(state) == State.moveUp1
                   and lookingToDecreaseTap then
    state = State.waitingToMoveDown;
    tap = pre(tap);
    tTapMoved = timeInfinity;

  //Transition from "moveUp1" to "moveUpN"
  elsewhen pre(running) and pre(state) == State.moveUp1
                   and time - pre(tTapMoved) >= tNext
                   and pre(tap) < tapMax then
    state = State.moveUpN;
    tap = pre(tap) + 1;
    tTapMoved = time;

  //Transition from "moveUpN" to "standard"
  elsewhen pre(running) and pre(state) == State.moveUpN
                   and valueInRange
                   and time - tValueInRangeWhileRunning >= tToStandardState then
    state = State.standard;
    tap = pre(tap);
    tTapMoved = timeInfinity;

  //Transition from "moveUpN" to "waitingToMoveDown"
  elsewhen pre(running) and pre(state) == State.moveUpN
                   and lookingToDecreaseTap then
    state = State.waitingToMoveDown;
    tap = pre(tap);
    tTapMoved = timeInfinity;

  //Transition from "moveUpN" to "moveUpN"
  elsewhen pre(running) and pre(state) == State.moveUpN
                   and time - pre(tTapMoved) >= tNext
                   and pre(tap) < tapMax then
    state = State.moveUpN;
    tap = pre(tap) + 1;
    tTapMoved = time;

  //Transition from "moveDown1" to "standard"
  elsewhen pre(running) and pre(state) == State.moveDown1
                   and valueInRange
                   and time - tValueInRangeWhileRunning >= tToStandardState then
    state = State.standard;
    tap = pre(tap);
    tTapMoved = timeInfinity;

  //Transition from "moveDown1" to "waitingToMoveUp"
  elsewhen pre(running) and pre(state) == State.moveDown1
                   and lookingToIncreaseTap then
    state = State.waitingToMoveUp;
    tap = pre(tap);
    tTapMoved = timeInfinity;

  //Transition from "moveDown1" to "moveDownN"
  elsewhen pre(running) and pre(state) == State.moveDown1
                   and time - pre(tTapMoved) >= tNext
                   and pre(tap) > tapMin then
    state = State.moveDownN;
    tap = pre(tap) - 1;
    tTapMoved = time;

  //Transition from "moveDownN" to "standard"
  elsewhen pre(running) and pre(state) == State.moveDownN
                   and valueInRange
                   and time - tValueInRangeWhileRunning >= tToStandardState then
    state = State.standard;
    tap = pre(tap);
    tTapMoved = timeInfinity;

  //Transition from "moveDownN" to "waitingToMoveUp"
  elsewhen pre(running) and pre(state) == State.moveDownN
                   and lookingToIncreaseTap then
    state = State.waitingToMoveUp;
    tap = pre(tap);
    tTapMoved = timeInfinity;

  //Transition from "moveDownN" to "moveDownN"
  elsewhen pre(running) and pre(state) == State.moveDownN
                   and time - pre(tTapMoved) >= tNext
                   and pre(tap) > tapMin then
    state = State.moveDownN;
    tap = pre(tap) - 1;
    tTapMoved = time;

  end when;
end TapChangerPhaseShifterLogicInterval;


whether the monitored signal is under the stop limit.

within PowerGrids.Electrical.Branches.BaseClasses;
partial model TapChangerPhaseShifterLogicMax
  extends TapChangerPhaseShifterLogicCommon;

protected
  Boolean valueUnderStop(start = false) "whether the monitored signal is under the stop limit";
  Boolean valueAboveMax(start = false) "wheter the monitored signal is above the maximum limit";
  Types.Time tValueAboveMaxWhileRunning(start = timeInfinity, fixed = true) "time when the monitored signal went above the maximum limit and the tap-changer/phase-shifter is running";

equation
  lookingToDecreaseTap = pre(running) and pre(valueAboveMax) and directAction;
  lookingToIncreaseTap = pre(running) and pre(valueAboveMax) and reverseAction;

  when pre(running) and not(pre(locked)) and pre(valueAboveMax) then
    tValueAboveMaxWhileRunning = time;
  elsewhen pre(running) and not(pre(locked)) and pre(valueUnderStop) then
    tValueAboveMaxWhileRunning = timeInfinity;
  elsewhen pre(locked) then
    tValueAboveMaxWhileRunning = timeInfinity;
  end when;

  //Transition to "locked"
  when (not pre(running)) or pre(locked) then
    state = State.locked;
    tap = pre(tap);
    tTapMoved = timeInfinity;

  //Transition from "locked" to "standard"
  elsewhen pre(running) and pre(state) == State.locked
                   and not(pre(locked))
                   and not(pre(valueAboveMax)) then
    state = State.standard;
    tap = pre(tap);
    tTapMoved = timeInfinity;

  //Transition from "locked" to "waitingToMoveDown"
  elsewhen pre(running) and pre(state) == State.locked
                   and not(pre(locked))
                   and lookingToDecreaseTap then
    state = State.waitingToMoveDown;
    tap = pre(tap);
    tTapMoved = timeInfinity;

  //Transition from "locked" to "waitingToMoveUp"
  elsewhen pre(running) and pre(state) == State.locked
                   and not(pre(locked))
                   and lookingToIncreaseTap then
    state = State.waitingToMoveUp;
    tap = pre(tap);
    tTapMoved = timeInfinity;

  //Transition from "standard" to "waitingToMoveUp"
  elsewhen pre(running) and pre(state) == State.standard
                   and lookingToIncreaseTap then
    state = State.waitingToMoveUp;
    tap = pre(tap);
    tTapMoved = timeInfinity;

  //Transition from "standard" to "waitingToMoveDown"
  elsewhen pre(running) and pre(state) == State.standard
                   and lookingToDecreaseTap then
    state = State.waitingToMoveDown;
    tap = pre(tap);
    tTapMoved = timeInfinity;

  //Transition from "waitingToMoveUp" to "standard"
  elsewhen pre(running) and pre(state) == State.waitingToMoveUp
                   and pre(valueUnderStop) then
    state = State.standard;
    tap = pre(tap);
    tTapMoved = timeInfinity;

  //Transition from "waitingToMoveUp" to "moveUp1"
  elsewhen pre(running) and pre(state) == State.waitingToMoveUp
                   and time - tValueAboveMaxWhileRunning >= t1st
                   and pre(tap) < tapMax then
    state = State.moveUp1;
    tap = pre(tap) + 1;
    tTapMoved = time;

  //Transition from "moveUp1" to "standard"
  elsewhen pre(running) and pre(state) == State.moveUp1
                   and pre(valueUnderStop) then
    state = State.standard;
    tap = pre(tap);
    tTapMoved = timeInfinity;

  //Transition from "moveUp1" to "moveUpN"
  elsewhen pre(running) and pre(state) == State.moveUp1
                   and time - pre(tTapMoved) >= tNext
                   and pre(tap) < tapMax then
    state = State.moveUpN;
    tap = pre(tap) + 1;
    tTapMoved = time;

  //Transition from "moveUpN" to "standard"
  elsewhen pre(running) and pre(state) == State.moveUpN
                   and pre(valueUnderStop) then
    state = State.standard;
    tap = pre(tap);
    tTapMoved = timeInfinity;

  //Transition from "moveUpN" to "moveUpN"
  elsewhen pre(running) and pre(state) == State.moveUpN
                   and time - pre(tTapMoved) >= tNext
                   and pre(tap) < tapMax then
    state = State.moveUpN;
    tap = pre(tap) + 1;
    tTapMoved = time;

  //Transition from "waitingToMoveDown" to "standard"
  elsewhen pre(running) and pre(state) == State.waitingToMoveDown
                   and pre(valueUnderStop) then
    state = State.standard;
    tap = pre(tap);
    tTapMoved = timeInfinity;

  //Transition from "waitingToMoveDown" to "moveDown1"
  elsewhen pre(running) and pre(state) == State.waitingToMoveDown
                   and time - tValueAboveMaxWhileRunning >= t1st
                   and pre(tap) > tapMin then
    state = State.moveDown1;
    tap = pre(tap) - 1;
    tTapMoved = time;

  //Transition from "moveDown1" to "standard"
  elsewhen pre(running) and pre(state) == State.moveDown1
                   and pre(valueUnderStop) then
    state = State.standard;
    tap = pre(tap);
    tTapMoved = timeInfinity;

  //Transition from "moveDown1" to "moveDownN"
  elsewhen pre(running) and pre(state) == State.moveDown1
                   and time - pre(tTapMoved) >= tNext
                   and pre(tap) > tapMin then
    state = State.moveDownN;
    tap = pre(tap) - 1;
    tTapMoved = time;

  //Transition from "moveDownN" to "standard"
  elsewhen pre(running) and pre(state) == State.moveDownN
                   and pre(valueUnderStop) then
    state = State.standard;
    tap = pre(tap);
    tTapMoved = timeInfinity;

  //Transition from "moveDownN" to "moveDownN"
  elsewhen pre(running) and pre(state) == State.moveDownN
                   and time - pre(tTapMoved) >= tNext
                   and pre(tap) > tapMin then
    state = State.moveDownN;
    tap = pre(tap) - 1;
    tTapMoved = time;

  end when;
end TapChangerPhaseShifterLogicMax;


within PowerGrids.Electrical.Buses;

package BaseClasses
  extends Modelica.Icons.BasesPackage;

  model BusBase
    extends PowerGrids.Electrical.BaseClasses.OnePortAC;
    input Types.ComplexVoltage e(re(nominal = port.VBase), im(nominal = port.VBase)) "Voltage of ideal generator, phase-to-ground";
    input Types.ComplexImpedance Z "Internal impedance";
    Types.ComplexVoltage v(re(nominal = port.VBase), im(nominal = port.VBase)) = port.v "Port voltage, phase-to-ground";
    Types.ComplexCurrent i(re(nominal = port.IBase), im(nominal = port.IBase)) = port.i "Port current";
  equation
    v = e + Z*i;
  end BusBase;
end BaseClasses;


Bus model.

within PowerGrids.Electrical.Buses;

model Bus "Bus model"
  extends PowerGrids.Electrical.BaseClasses.OnePortAC(
    final PStart = 0,
    final QStart = 0);
  extends Icons.Bus;

equation
  port.i = Complex(0);
end Bus;


Series resistance to ground during fault.

within PowerGrids.Electrical.Buses;
model BusFault
  extends Icons.Bus;
  extends Electrical.BaseClasses.OnePortAC;
  parameter Types.Resistance R = 0 "Series resistance to ground during fault"
  parameter Types.Reactance X = 0 "Series reactance to ground during fault"
  parameter SI.Time startTime "Start time of the fault"
  parameter SI.Time stopTime "End time of the fault"

  Types.ComplexVoltage v(re(nominal = port.VBase), im(nominal = port.VBase)) = port.v "Port voltage, phase-to-ground";
  Types.ComplexCurrent i(re(nominal = port.IBase), im(nominal = port.IBase)) = port.i "Port current";

  discrete Types.ComplexAdmittance Y(re(start = 0, fixed = true), im(start = 0, fixed = true)) "Shunt admittance";
  Boolean fault(start = false, fixed = true) "The fault is active";

// State machine to compute the fault variable
algorithm
   when time >= startTime then
     fault := true;
   end when;

   when time >= stopTime then
     fault := false;
   end when;

// State machine to compute the shunt admittance
algorithm
   when pre(fault) then
     Y := 1/Complex(R, X);
   end when;

   when not pre(fault) then
     Y := Complex(0);
   end when;
equation
   i = Y*v;
end BusFault;


Equivalent grid model characterized by short circuit capacity.

within PowerGrids.Electrical.Buses;

model EquivalentGrid "Equivalent grid model characterized by short circuit capacity"
  extends PowerGrids.Electrical.Buses.BaseClasses.BusBase(
    e = eSource,
    Z = ZGrid,
    UStart = URef, UPhaseStart = 0);
  extends Icons.Grid;
  import Modelica.ComplexMath;

  parameter Types.Voltage URef = UNom "Reference phase-to-phase voltage at connection terminal";
  parameter Types.Angle UPhaseRef = 0 "Voltage phase angle at connection terminal";
  parameter Types.ApparentPower SSC "Short-circuit capacity";
  parameter Types.PerUnit c = 1.1 "c-factor for calculation of short-circuit capacity";
  parameter Types.PerUnit R_X = 1/10 "Ratio of resistance to reactance of the grid";

  final parameter Types.ComplexPower Ssc = ComplexMath.fromPolar(SSC/c, atan(1/R_X))  "Short circuit active power";
  final parameter Types.ComplexImpedance ZGrid = 1/ComplexMath.conj(Ssc/UNom ^ 2) "Grid impedance";
  final parameter Types.ComplexVoltage eStart = ComplexMath.fromPolar(URef,UPhaseStart) "Tentative value of voltage source";
  final parameter Types.ComplexVoltage eSource(
    re(fixed = false, start = eStart.re),
    im(fixed = false, start = eStart.im)) "Constant value of voltage source";
initial equation
  // Initial voltage at port fixed at reference values
  port. u= ComplexMath.fromPolar(URef, UPhaseRef);
end EquivalentGrid;


within PowerGrids.Electrical.Buses;

model Ground
  extends Icons.Ground;
  PowerGrids.Interfaces.TerminalAC terminal
equation
  terminal.v = Complex(0);
end Ground;


Infinite bus with internal impedance.

within PowerGrids.Electrical.Buses;
model InfiniteBus "Infinite bus with internal impedance"
  extends BaseClasses.BusBase(
      e = CM.fromPolar(ERef, theta),
      Z = Complex(R, X));
  extends Icons.Bus;
  parameter Types.Resistance R = 0 "Series resistance";
  parameter Types.Reactance X = 0 "Series reactance";
  parameter Types.Angle theta = 0 "Voltage phase angle of ideal voltage generator";
  parameter Types.Voltage URef = UNom "Phase-to-phase voltage of ideal voltage generator";
  final parameter Types.Voltage ERef = URef/sqrt(3) "Phase-to-ground voltage of ideal voltage generator";
end InfiniteBus;


Fixed source voltage modulus, phase-to-phase.

within PowerGrids.Electrical.Buses;
model InfiniteBusVariableVoltage
  extends BaseClasses.BusBase(
    e = CM.fromPolar(UAux/sqrt(3), thetaAux),
    Z = Complex(R, X));
  extends Icons.Bus;
  parameter Types.Voltage UFixed = UNom "Fixed source voltage modulus, phase-to-phase"
  parameter Types.Angle thetaFixed = 0 "Fixed angle of source voltage"
  parameter Boolean useUIn = false "Use external input for source voltage magnitude"
  parameter Boolean useThetaIn = false "Use external input for source voltage angle"
  parameter Types.Resistance R = 0 "Series resistance";
  parameter Types.Reactance X = 0 "Series reactance";

  Modelica.Blocks.Interfaces.RealInput UIn(unit = "V", displayUnit = "kV") if useUIn "Source voltage modulus input, phase-to-phase, V"
  Modelica.Blocks.Interfaces.RealInput thetaIn(unit = "rad", displayUnit = "deg") if useThetaIn "Source voltage phase angle input, rad"

protected
  // Auxiliary hidden connectors to manage the conditional connectors
  Modelica.Blocks.Interfaces.RealInput UAux "Source voltage modulus, phase-to-phase";
  Modelica.Blocks.Interfaces.RealInput thetaAux "Source voltage phase angle";

equation
// Conditional connectors
   connect(UIn, UAux);
   if not useUIn then
     UAux = UFixed;
   end if;

   connect(thetaIn, thetaAux);
   if not useThetaIn then
     thetaAux = thetaFixed;
   end if;

   assert(UAux >= 0, "Magnitude must be positive");
end InfiniteBusVariableVoltage;


Bus models

within PowerGrids.Electrical;
package Buses "Bus models"
  extends Modelica.Icons.Package;
end Buses;


Reference bus for an isolated grid.

within PowerGrids.Electrical.Buses;

model ReferenceBus "Reference bus for an isolated grid"
  extends PowerGrids.Electrical.BaseClasses.OnePortAC;
  extends Icons.Bus;
  import PowerGrids.Types.Choices.InitializationOption;
  parameter Boolean setPhaseOnly = false "= true if only the initial voltage phase is to be set";
  parameter InitializationOption initOpt = systemPowerGrids.initOpt "Initialization option";
  final parameter Types.ComplexPerUnit nStart = CM.fromPolar(1, UPhaseStart) "Unit phasor with angle UPhaseStart";
  final parameter Types.ActivePower PSlack(fixed = false) "Constant slack active power leaving system through bus";
  final parameter Types.ReactivePower QSlack(fixed = false) "Constant slack reactive power leaving system through bus";
initial equation
  if not setPhaseOnly then
    port.u = CM.fromPolar(UStart, UPhaseStart) "Set initial bus voltage, phase-to-phase";
  else
    port.u.re*nStart.im = port.u.im*nStart.re "port.u has the same phase as nStart";
    QSlack = 0 "No reactive power leaving system through bus";
  end if;
  assert(abs(PSlack)/SNom < 0.01, "The active power flowing into or out of the reference bus is above 0.01 pu.\n"+
    "You probably need a better balancing of the active power generators in the system.\n" +
    "Please check the documentation of ReferenceBus for further reference", AssertionLevel.warning);
  assert(abs(QSlack)/SNom < 0.01, "The reactive power flowing into or out of the reference bus is above 0.01 pu.\n"+
    "You probably need a better setup of excitation voltages, or you can set setPhaseOnly = true.\n" +
    "Please check the documentation of ReferenceBus for further reference", AssertionLevel.warning);
equation
  port.P = PSlack;
  port.Q = QSlack;
end ReferenceBus;


Power systems controllers

within PowerGrids.Electrical;
package Controls "Power systems controllers"
  extends Modelica.Icons.Package;
end Controls;


Base framework for all controllers such as AVR, PSS, etc..

within PowerGrids.Electrical.Controls.BaseClasses;

block BaseControllerFramework "Base framework for all controllers such as AVR, PSS, etc."
  final parameter Boolean fixInitialControlledVariableDefault =
    if (systemPowerGrids.initOpt == Types.Choices.InitializationOption.globalSteadyStateFixedPowerFlow or
        systemPowerGrids.initOpt == Types.Choices.InitializationOption.localSteadyStateFixedPowerFlow)
    then true else false
    "= true adds extra initial equation to fix controlled variable"
  parameter Boolean fixInitialControlledVariable = fixInitialControlledVariableDefault
    "= true adds extra initial equation to fix controlled variable"

  outer System systemPowerGrids "Reference to system object for system-level defaults";
end BaseControllerFramework;


Base classes for controllers

within PowerGrids.Electrical.Controls;

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


Static excitation system - IEEE type AC4A.

within PowerGrids.Electrical.Controls.ExcitationSystems;
block IEEE_AC4A "Static excitation system - IEEE type AC4A"
  extends Controls.BaseClasses.BaseControllerFramework;

  parameter SI.PerUnit ViMax = 100 "Input voltage max limit in p.u.";
  parameter SI.PerUnit ViMin = -100 "Input voltage min limit in p.u.";
  parameter SI.Time Tc = 3 "Lead time constant";
  parameter SI.Time Tb = 10 "Lag delay time constant";
  parameter SI.PerUnit Ka(min = 0) = 200 "Overall equivalent gain";
  parameter SI.Time Ta = 0.05 "Overall time constant";
  parameter SI.PerUnit VrMax = 4 "Output voltage max limit in p.u.";
  parameter SI.PerUnit VrMin = 0 "Output voltage min limit in p.u.";
  parameter SI.PerUnit VcPuStart = 1 "Required start value of VcPu when fixInitialControlledVariable = true"
  parameter SI.PerUnit oversaturationPu = 0.1 "abs(u-usat)/(Vmax-Vmin) in case of saturated initial condition"
  final parameter Real delta = (inputLimiter.uMax-inputLimiter.uMin)*oversaturationPu "Actuator saturation margin";

  Modelica.Blocks.Interfaces.RealInput VsPu "PSS output p.u"
  Modelica.Blocks.Interfaces.RealInput VcPu "Machine terminal voltage p.u."
  Modelica.Blocks.Interfaces.RealInput VrefPu "Voltage reference p.u."
  Modelica.Blocks.Interfaces.RealInput VuelPu "Underexcitation limit p.u."
  Modelica.Blocks.Interfaces.RealOutput efdPu "Exciter output voltage p.u."
  Modelica.Blocks.Math.Add3 addIng(k1 = +1, k2 = -1, k3 = +1)
  Modelica.Blocks.Nonlinear.Limiter inputLimiter(limitsAtInit = true, uMax = ViMax, uMin = ViMin)
  PowerGrids.Controls.LeadLag leadLag(T1 = Tc, T2 = Tb, initType = Modelica.Blocks.Types.Init.SteadyState, k = 1, yStart = 1 / Ka)
  PowerGrids.Controls.FirstOrderWithNonWindupLimiter firstOrderLim(T = Ta, initType = Modelica.Blocks.Types.Init.SteadyState, k = Ka, yMax = VrMax, yMin = VrMin, yStart = 1)
  Modelica.Blocks.Nonlinear.VariableLimiter variableLimiter
  Modelica.Blocks.Sources.Constant largeReal(k = 1e9)
initial equation
  /* The following equation could be written as

     if fixInitialControlledVariable then
       if inputLimiter.u > inputLimiter.uMax or firstOrderLim.u > firstOrderLim.yMax/Ka then
         inputLimiter.u = inputLimiter.uMax + delta;
       elseif inputLimiter.u < inputLimiter.uMin or firstOrderLim.u < firstOrderLim.yMin/Ka then
         inputLimiter.u = inputLimiter.uMin - delta;
       else
         VcPu = VcPuStart;
       end if;
     end if;

     However, we need to use homotopy with a simplified linear model, to
     avoid the need of explicitly initializing all the internal controller
     variables. This is only possible by writing those equations in implicit form
  */
  if fixInitialControlledVariable then
    0 = homotopy(
      actual = if inputLimiter.u > inputLimiter.uMax then inputLimiter.u - (inputLimiter.uMax + delta)
               else if inputLimiter.u < inputLimiter.uMin then inputLimiter.u - (inputLimiter.uMin - delta)
               else VcPu - VcPuStart,
      simplified = VcPu - VcPuStart);
  end if;
equation
  connect(firstOrderLim.y, efdPu)
  connect(inputLimiter.y, leadLag.u)
  connect(addIng.y, inputLimiter.u)
  connect(VrefPu, addIng.u3)
  connect(VsPu, addIng.u1)
  connect(VcPu, addIng.u2)
  connect(variableLimiter.y, firstOrderLim.u)
  connect(leadLag.y, variableLimiter.u)
  connect(largeReal.y, variableLimiter.limit1)
  connect(VuelPu, variableLimiter.limit2)
end IEEE_AC4A;