Repository navigation
Conversation
|
A few comments:
|
|
@pelesh I changed it so it uses the PowerFlow parser with very few changes. I do believe its valuable to have that as the input format only because it makes validation easier and there is practically unlimited data in I believed I addressed your other issues too. I am able to use the example app to change the objective/model, solve for new operating point, and start simulation in steady state at that new point! |
95913b8 to
5c4fb92
Compare
5c4fb92 to
49ad14b
Compare
pelesh
left a comment
There was a problem hiding this comment.
This is very nice work. I suggest limiting the scope of this PR to providing ACOPF module only. Imho, the integration with dynamic simulations modules should be done after ACOPF is tested and input file format specified.
979e671 to
a6e3ae1
Compare
superwhiskers
left a comment
There was a problem hiding this comment.
tests pass on my end. i think it works quite well, but there are a few implementation questions we should sort out before this moves forward.
additionally, if we are to continue with matpower, are we intending to handle capability curves for generators or the convention of a dispatchable load as a generator with negative real-power injection? if they are outside the intended scope, could we reject them and document that limitation?
i think these conventions, particularly the latter, strengthen the argument for going with our own format, as using an existing format such as matpower requires us to adhere to its conventions, which may not be desirable.
nkoukpaizan
left a comment
There was a problem hiding this comment.
Overall nice work! The component implementations are clear and I like the consolidated ComponentModelImpl.hpp.
A few comments:
- For Enzyme, can we re-use some of the existing boilerplate for
LowerSparseStorageand use buffers instead ofCooEntrieswrappingstd::vector)? Feel free to amend the wrappers. - Can we bind
GridKit::LinearAlgebra::Vectorat the component level instead ofstd::vector? This could be a follow-up PR. - Several methods, e.g., in
OptimizationEvaluator*andSteadyState*don't have a Doxygen documentation.
| static constexpr RealT POWER_TOL = 2 * std::numeric_limits<RealT>::epsilon(); | ||
| static constexpr RealT JACOBIAN_TOL = 2 * std::numeric_limits<RealT>::epsilon(); | ||
| static constexpr RealT HESSIAN_TOL = 9 * std::numeric_limits<RealT>::epsilon(); |
There was a problem hiding this comment.
I would round to 10 * and use the same tolerance.
| if(GRIDKIT_ENABLE_ENZYME) | ||
| add_subdirectory(OptimalPowerFlow) | ||
| endif() |
There was a problem hiding this comment.
I would move the guard into the OptimalPowerFlow directory.
| static constexpr RealT DERIVATIVE_TOL = 8 * std::numeric_limits<RealT>::epsilon(); | ||
| static constexpr RealT BALANCE_TOL = 1.0e-12; |
There was a problem hiding this comment.
Same here, I would round 8 to 10. And replace 1e-12 with a multiple of std::numeric_limits<RealT>::epsilon();
| bool isZero(double value) | ||
| { | ||
| return !(value < ZERO<double>) && !(value > ZERO<double>); | ||
| } |
There was a problem hiding this comment.
We've been trying to avoid these. If something needs == 0/!= 0, please highlight and we can evaluate.
| return true; | ||
| } | ||
| std::cerr << std::setprecision(std::numeric_limits<RealT>::max_digits10) << value << " differs from " | ||
| << reference << " by " << std::abs(value - reference) / (ONE<RealT> + std::abs(reference)) / std::numeric_limits<RealT>::epsilon() |
There was a problem hiding this comment.
I wouldn't re-implement Testing::isEqual.
| const RealT* x = system.x().getData(); | ||
|
|
||
| success *= isEqual(x[1], ZERO<RealT>, BALANCE_TOL); | ||
| success *= std::abs(x[3]) > BALANCE_TOL && std::abs(x[5]) > BALANCE_TOL; |
There was a problem hiding this comment.
Is checking the absolute values really needed instead of isEqual?
| is >> br.fbus >> br.tbus >> br.r >> br.x >> br.b >> br.rateA >> br.rateB | ||
| >> br.rateC >> br.ratio >> br.angle >> br.status >> br.angmin | ||
| >> br.angmax; | ||
| checkEndOfMatrixRow(is); |
There was a problem hiding this comment.
Removing checkEndOfMatrixRow partially addresses #587, but we still need to ensure the read was successful (e.g., row not too short).
pelesh
left a comment
There was a problem hiding this comment.
This looks very good and has clean component model structure. Main issues that I would raise are:
- Separating equality and inequality constraint evaluation methods.
- Selection of coordinates polar vs. Cartesian.
- Inheritance patterns should mirror
PhasorDynamicsmodule. If we want to change it, then the change should made be across the GridKit modules.
| * Filled by `mapped_store` and `lower_store`. Assembly sums duplicate | ||
| * coordinates. | ||
| */ | ||
| struct CooEntries |
There was a problem hiding this comment.
What is the purpose of this struct? This needs to be documented.
| using ScalarT = scalar_type; | ||
| using IdxT = index_type; | ||
| using RealT = typename ComponentModelT::RealT; | ||
| using ModelDataT = BranchData<RealT, IdxT>; |
There was a problem hiding this comment.
These should not be public. ScalarT, IdxT, etc. are local type aliases.
| g[0] = p1 * p1 + q1 * q1; | ||
| g[1] = p2 * p2 + q2 * q2; | ||
| g[2] = p1; | ||
| g[3] = q1; | ||
| g[4] = p2; | ||
| g[5] = q2; |
There was a problem hiding this comment.
Need to specify here what are equality and what are inequality constraints.
| * @tparam model_type - Derived component model | ||
| */ | ||
| template <typename model_type, typename scalar_type, typename index_type> | ||
| class ComponentModel : public Component<scalar_type, index_type> | ||
| { | ||
| public: |
There was a problem hiding this comment.
I suggest removing Component class and using the same inheritance structure as in PhasorDynamics module.
| const auto& terminals = component.terminals(); | ||
| for (IdxT t = 0; t < terminals.size(); ++t) | ||
| { |
There was a problem hiding this comment.
I believe that we decided to use term "ports" rather than "terminals". It would be good to use consistent nomenclature across all GridKit modules.
| Number* x_u, | ||
| Index m, |
There was a problem hiding this comment.
These are deprecated Ipopt types. Use ipnumber and ipindex instead.
| template <typename scalar_type, typename index_type> | ||
| class SteadyState : public Ipopt::TNLP | ||
| { |
There was a problem hiding this comment.
This is not SteadyState but rather Ipopt specific interface. Perhaps use SteadyState for the namespace and IpoptInterface for the class name.
| public: | ||
| using ScalarT = scalar_type; | ||
| using IdxT = index_type; | ||
| using RealT = typename ScalarTraits<ScalarT>::RealT; | ||
| using CooEntriesT = Enzyme::Sparse::CooEntries; |
There was a problem hiding this comment.
These type aliases should not be public.
| std::vector<std::unique_ptr<ComponentT>> components_; | ||
|
|
||
| /// Buses by number | ||
| std::map<IdxT, BusT*> buses_; | ||
|
|
||
| /// Map from component entries to CSR entries | ||
| std::vector<IdxT> jacobian_map_; | ||
| std::vector<IdxT> hessian_map_; |
There was a problem hiding this comment.
I would avoid using STL containers for system level objects. If it is a temporary solution, it should be marked as such.
| virtual int evaluateConstraints() = 0; | ||
| virtual int evaluateJacobian() = 0; |
There was a problem hiding this comment.
It would be good to separate equality and inequality constraints, as well as their respective Jacobians.
38dc4b5 to
5bc7404
Compare
|
Thank you all for detailed review, I believe I have addressed all except a distinct few. I will go response by response soon, but the overall design is overall more consistent with PhasorDynamics except for the inheritance structure. I am considering removing the larger ACTIVSg2000 case until after this is merged, please let me know what you think. |
superwhiskers
left a comment
There was a problem hiding this comment.
some further changes since i looked a little closer at the ipopt interface code
| */ | ||
| template <typename ScalarT, typename IdxT> | ||
| __attribute__((always_inline)) static ScalarT sparse_load(IdxT, IdxT, IdxT*, IdxT*, ScalarT*) | ||
| __attribute__((always_inline)) static ScalarT sparse_load(IdxT, IdxT, ScalarT, const IdxT*, const IdxT*, IdxT*, IdxT*, ScalarT*, IdxT&) |
There was a problem hiding this comment.
should still be marked with [[gnu::always_inline]]
| /// Bus `id`, from its number | ||
| std::string id() const | ||
| { | ||
| return std::format("bus {}", number); | ||
| } |
There was a problem hiding this comment.
this is a bit nitpicky but if we're just using this to pretty-print the bus, it would be better to either 1) do it at the site of pretty-printing or 2) overload << against an iostream / specialize std::formatter
| /// Every problem with the data | ||
| template <typename scalar_type, typename index_type> | ||
| Model::ConfigurationChecks SystemModel<scalar_type, index_type>::verify() const | ||
| { | ||
| Model::ConfigurationChecks checks; | ||
|
|
||
| std::unordered_set<const ComponentT*> components; | ||
| for (const auto& component : components_) | ||
| { | ||
| components.insert(component.get()); | ||
| } | ||
|
|
||
| for (const auto& [number, bus] : buses_) | ||
| { | ||
| checks.check(number != INVALID_INDEX<IdxT>, | ||
| std::format("{}: invalid bus number", bus->id())); | ||
| } | ||
|
|
||
| std::set<std::string> ids; | ||
| for (const auto& component : components_) | ||
| { | ||
| checks.check(ids.insert(component->id()).second, std::format("{}: duplicate id", component->id())); | ||
|
|
||
| const auto component_checks = component->verify(); | ||
| for (const auto& error : component_checks.errors()) | ||
| { | ||
| checks.fail(std::format("{}: {}", component->id(), error)); | ||
| } | ||
|
|
||
| const auto& ports = component->ports(); | ||
| for (IdxT port = 0; port < ports.size(); ++port) | ||
| { | ||
| checks.check(components.contains(ports[port].component), | ||
| std::format("{}: port {} must connect to a component in this system", component->id(), port)); | ||
| } | ||
| } | ||
|
|
||
| return checks; | ||
| } |
There was a problem hiding this comment.
the way this is laid out makes me think we could be checking some of these things at construction time and throwing an exception instead of looping over the entire system after constructing it, but it may not be right to deviate from PhasorDynamics that way in this pull request
There was a problem hiding this comment.
this would have the benefit of guaranteeing that any SystemModel is valid, making validity an invariant of the SystemModel structure as there is no way that i see to add more buses, components onto an existing SystemModel
| /** | ||
| * @file IpoptInterface.cpp | ||
| * @brief Ipopt problem over a `Model::OptimizationEvaluator`. | ||
| */ |
There was a problem hiding this comment.
given we already have an ipopt interface under GridKit/Solver/Optimization this generic name is confusing as it isn't immediately obvious how this is different (it uses the OptimizationEvaluator interface)
There was a problem hiding this comment.
Yes, we need to sort out the naming so that we distinguish between dynamic and steady state optimization interfaces. Note that Model::OptimizationEvaluator is the model interface to optimization solvers and can in principle work with different solvers. It is different from IpoptInterface, which contains code specific to Ipopt.
| } | ||
|
|
||
| // Available template instantiations | ||
| template class IpoptInterface<double, size_t>; |
There was a problem hiding this comment.
| template class IpoptInterface<double, size_t>; | |
| template class IpoptInterface<Ipopt::Number, Ipopt::Index>; |
i'm going to guess that since we're interfacing with ipopt here, it would be more proper to instantiate based upon the types it is compatible with, as is done in the existing interface code
There was a problem hiding this comment.
It would in general but that may cause type mismatch elsewhere in the code. This is one of poorly addressed issues in GridKit. To make everything consistent, we would need to build all dependencies with the same types and to type check at compile time verifying types are matching.
In this case I would set
template class IpoptInterface<RealT, IdxT>;and check that types match Ipopt::Number and Ipopt::Index.
Description
Experimental @superwhiskers @nkoukpaizan which uses MATPOWER input which makes interfacing with
PhasorDynamicsharder but mostOPFdata comes in MATPOWER, so this is probably most practical choiceOptimalDispatchChecklist
-Wall -Wpedantic -Wconversion -Wextra.Further comments
Needs some cleaning first