Model adjustments caught during validation - #525
Conversation
80cc441 to
1bff371
Compare
1bff371 to
b925f6b
Compare
abirchfield
left a comment
There was a problem hiding this comment.
These are good cleanup steps that help clarify documentation, increase test coverage, and update a few implementation details.
b925f6b to
4eb2d17
Compare
pelesh
left a comment
There was a problem hiding this comment.
Great work and very helpful expansion of unit tests.
There are some solutions in this PR that I don't quite understand (see comments below) and I would like us to clarify that before we merge.
| Ispdlim_ = std::visit([](auto value) | ||
| { return value != 0 ? ONE<RealT> : ZERO<RealT>; }, | ||
| data.parameters.at(Parameter::Ispdlim)); |
There was a problem hiding this comment.
It is difficult to see what this function does. I suggest rewriting or at least commenting better.
There was a problem hiding this comment.
The rest of the code below is quite cryptic. It needs to be commented better.
| When one saturation value is zero, the normal curve fit uses the corresponding | ||
| voltage as the quadratic knee: | ||
|
|
There was a problem hiding this comment.
I believe you wanted to say "When only one ...". Also, it is not quite clear to me what the rest of this sentence means.
| if (Pvmax_ < Pvmin_) | ||
| { | ||
| std::swap(Pvmin_, Pvmax_); | ||
| } |
There was a problem hiding this comment.
I don't know what is the purpose of this but I don't see anything good coming from swapping Pvmax and Pvmin.
There was a problem hiding this comment.
Commercial software often has a practice of trying to determine user intent when bad data is encountered. In this case, the most likely explanation for Pvmax < Pvmin is that the user accidentally switched them. Some users prefer this autocorrection to throwing lots of errors, especially when they don't have detailed familiarity with the models. I'm OK with this or without it.
| if (H_ == ZERO<RealT> && D_ == ZERO<RealT>) | ||
| { | ||
| H_inv_ = ZERO<RealT>; | ||
| } | ||
| else | ||
| { | ||
| H_ = std::max(H_, static_cast<RealT>(0.1)); | ||
| H_inv_ = ONE<RealT> / (TWO<RealT> * H_); | ||
| } |
There was a problem hiding this comment.
This is in my opinion unnecessary and is a bad modeling practice. Setting H_inv_ to zero changes the model.
There was a problem hiding this comment.
Sometimes there is a convention of letting a 0 value represent infinity, for a parameter for which 0 does not make sense. A GENCLS with infinite inertia is just an infinite bus, and this is sometimes used to represent infinite buses.
| H_ = std::max(H_, static_cast<RealT>(0.1)); | ||
| if (Xdp_ > Xd_) | ||
| { | ||
| Xdp_ = static_cast<RealT>(0.8) * Xd_; | ||
| } | ||
| if (Xqp_ > Xq_) | ||
| { | ||
| Xqp_ = Xq_; | ||
| } | ||
| const RealT transient_min = std::min(Xdp_, Xqp_); | ||
| if (Xdpp_ > transient_min) | ||
| { | ||
| Xdpp_ = static_cast<RealT>(0.8) * transient_min; | ||
| } | ||
| Xdpp_ = std::max(Xdpp_, static_cast<RealT>(0.05)); | ||
| if (Xl_ > Xdpp_) | ||
| { | ||
| Xl_ = static_cast<RealT>(0.8) * Xdpp_; | ||
| } | ||
| Xqpp_ = Xdpp_; |
There was a problem hiding this comment.
Changing user input silently is a very bad idea. There has to be justification for that, and this block of code needs comments explaining what is done here and why.
There was a problem hiding this comment.
I think these are the PowerWorld autocorrection rules, trying to fix obviously bad data for a user who may not have the expertise to determine what values should be put for these parameters. I guess there is a general question of whether we should implement autocorrection or not.
There was a problem hiding this comment.
These are the autocorrection rules from PowerWorld. I will add a logger warning when this autocorrection occurs.
I think this is necessary if we are going to be validating against PowerWorld. If we want to get to solver-tolerance precision against a reference solution, I need to be certain it's not autocorrected parameters causing differences. I also agree with @abirchfield, for his listed reasons
| 0 &= -V_{d} -\psi''_{q}(1+\omega)\\ | ||
| 0 &= -V_{q} +\psi''_{d}(1+\omega)\\ | ||
| 0 &= -T_{elec} +(\psi''_{d} - I_dX_d'')I_q-(\psi''_{q} - I_qX_d'')I_d \\ | ||
| 0 &= -k_{sat} + S_B(\psi''-S_A)^2\sigma(\psi''-S_A) \\ | ||
| 0 &= -k_{sat} + S_B q(\psi''-S_A) \\ | ||
| 0 &= -I_d + I_r \sin(\delta) - I_i \cos(\delta) \\ | ||
| 0 &= -I_q + I_r \cos(\delta) + I_i \sin(\delta) \\ | ||
| 0 &= -I_r + G (V_d \sin(\delta) + V_q \cos(\delta) - V_r) - B (V_d \cos(\delta) + V_q \sin(\delta) - V_i) \\ | ||
| 0 &= -I_i + B (V_d \sin(\delta) + V_q \cos(\delta) - V_r) + G (V_d \cos(\delta) + V_q \sin(\delta) - V_i) |
There was a problem hiding this comment.
These equations do not render correctly.
| H_ = std::max(H_, static_cast<RealT>(0.1)); | ||
| if (Xdp_ > Xd_) | ||
| { | ||
| Xdp_ = static_cast<RealT>(0.8) * Xd_; | ||
| } | ||
| if (Xdpp_ > Xdp_) | ||
| { | ||
| Xdpp_ = static_cast<RealT>(0.8) * Xdp_; | ||
| } | ||
| Xdpp_ = std::max(Xdpp_, static_cast<RealT>(0.05)); | ||
| if (Xl_ > Xdpp_) | ||
| { | ||
| Xl_ = static_cast<RealT>(0.8) * Xdpp_; | ||
| } |
There was a problem hiding this comment.
Again, changing user's input silently is a Bad Idea™.
| static constexpr ScalarT tol_ = 100 * std::numeric_limits<ScalarT>::epsilon(); | ||
|
|
There was a problem hiding this comment.
Why are we using 100 x epsilon tolerance? It would be good to have this value set as global testing requirement rather than set it on a test by test basis.
| Ke_ = ke0; | ||
| y[EFDP] = efdp0; |
There was a problem hiding this comment.
Here is another instance of auto-correction. It is not commented and it is unclear why user supplied parameter is changed.
| RealT ke0 = Ke_; | ||
| if (ke0 == ZERO<RealT>) |
There was a problem hiding this comment.
Another autocorrection here.
| Pvmin_ = std::min(Pvmin_, static_cast<RealT>(pv0)); | ||
| Pvmax_ = std::max(Pvmax_, static_cast<RealT>(pv0)); |
There was a problem hiding this comment.
I think this is really dangerous auto-correct. Instead of changing initial value, it changes properties of the governor.
There was a problem hiding this comment.
This one is actually the intended behavior of the governor. If we solved for the steady-state power flow solution in the equivalent PowerFlow or OPF model, we would set the constraint PowerFlow data and PhasorDynamics data are different, then it would be possible for the power flow solution to be an infeasible steady-state solution in the transient model.
I'll add a logger warning for this. A lot of the test cases have a power flow solution that invokes this.
Description
These are the last few issues I ran into during validation. Mostly initialization and parameter guard fixes.
Lots of tiny edge cases in the full unmodified cases that we did not account for.
Proposed changes
Checklist
-Wall -Wpedantic -Wconversion -Wextra.Further comments
Looking very good. I think you all will be very happy with the validation results. Sneak peek: