Add a vector class to GridKit#418
Conversation
PhilipFackler
left a comment
There was a problem hiding this comment.
For an all-in-one solution like this, I would prefer some revisions as explained in my line comments. None of those is a deal-breaker if this is meant only for "advanced" use-cases, but I still think it would be helpful.
| class Vector | ||
| { | ||
| public: | ||
| Vector(IdxT n); |
There was a problem hiding this comment.
This would need a default constructor for use in the phasor dynamics initialization.
There was a problem hiding this comment.
With default constructor, we would need allocate and/or reallocate methods, as well.
There was a problem hiding this comment.
If every bus/component has a constant (hard-coded) size_ then the default constructor is not necessary.
If some components might not know their size until run time, it would make more sense to have a default-constructed vector from the component constructor and then just replace the instance in the allocate method when you know the size (y_ = Vector(n);), rather than "reallocate"ing.
But again, if this will never happen, we don't have to worry about it.
There was a problem hiding this comment.
If we add option to fault each bus/component then there will be not one but two different hard-coded sizes. One for clear and another for faulted object. I can't think of anything else at the moment. @lukelowry or @abirchfield may have additional use cases where bus/component might need to change its model size.
There was a problem hiding this comment.
Everything I can think of would be deterministically sized at initialization. I am struggling to think of a case where we would resize the vector after initialization. I think the only exception I can see that would be tangible to resize after initialization is the network itself or related operations by the SystemModel
There was a problem hiding this comment.
This is a common issue with all scientific software that I can think of. You need to compute your workspace before you can allocate it. If you know the size of the vector is 124 a priori, then you can instantiate and allocate it like this:
Vector v(124);
v.allocate(HOST);Otherwise you need to do something like
Vector v;
// compute size n
v.resize(n);
v.allocate(HOST);The resize method is there just to give you some flexibility.
| case HOST: | ||
| if (h_data_ == nullptr) | ||
| { | ||
| h_data_ = new ScalarT[n_capacity_ * k_]; |
There was a problem hiding this comment.
It doesn't make sense for allocating to be part of the semantics of this function. It certainly wouldn't be obvious to me as a user that this might happen. I'm used to having no implicit memory operations, only explicit (the user has to say so). This should at least be documented if not reconsidered.
There was a problem hiding this comment.
Yes, you are right, this should be removed.
| * you are doing. | ||
| */ | ||
| template <typename ScalarT, typename IdxT> | ||
| int Vector<ScalarT, IdxT>::setData(ScalarT* data, memory::MemorySpace memspace) |
There was a problem hiding this comment.
This would make more sense as part of a constructor. An instance should be constructed with ownership and data (if non-owning) for host and device respectively. Ownership should not be allowed to change. And I think the data you're wrapping in the non-owning case shouldn't be allowed to change either. This function wouldn't exist in that case.
There was a problem hiding this comment.
I agree, changing ownership after construction is looking for trouble.
This method was experimental and should not be a part of the vector class.
There was a problem hiding this comment.
This method was used to get a vector from a multivector. Better solution is to construct non-data-owning vector object instead, or create a separate VectorView object as @lukelowry suggested.
| delete[] h_data_; | ||
| h_data_ = new ScalarT[n_capacity_ * k_]; | ||
| owns_cpu_data_ = true; |
There was a problem hiding this comment.
This ignores the case where we're already non-owning. See my comment above. If ownership was settled at construction there could be tighter invariants for the rest of these functions.
There was a problem hiding this comment.
I think it would be good to have allocate and reallocate methods, both refusing to operate on data if not owned by the vector object.
allocateshould return error message and do nothing if data pointer is not null.reallocateshould check if the new size is larger than capacity and if so delete previous and allocate new data.
All of your suggestions are spot on and should be incorporated before this PR is merged. |
lukelowry
left a comment
There was a problem hiding this comment.
I think we need a zombie approach if we do merge this implementation. We should take the best parts of this and graft them onto a more scalable implementation that can isolate the Component in memory (is "closure" the right word here?).
| * flags correctly after changing the values. | ||
| */ | ||
| template <typename ScalarT, typename IdxT> | ||
| ScalarT* Vector<ScalarT, IdxT>::getData(memory::MemorySpace memspace) |
There was a problem hiding this comment.
getData and its overloads may trigger a memory copy, which I would advise we avoid at all costs once the system is initialized. I see how we could functionally achieve zero-copy with this Vector class, but I do not want to give Components the chance to copy data in this way.
To prevent our future selves from having access to functions like this in simulation-time functions like evaluateResidual, I strongly recommend that we enforce zero-copy de facto after initialization to force us to design scalably. (This is the motivation of VectorView, which I'll draft up so you can get a better picture of what I am imagining)
There was a problem hiding this comment.
Zero copy will be enforced.
|
|
||
| switch (memspace) | ||
| { | ||
| case HOST: |
There was a problem hiding this comment.
I think this was mentioned in the meeting earlier today, but the Host and Device vocabulary and GPU vocab are not desirable IMHO
There was a problem hiding this comment.
Domain folks do not need to see that. We could implement all functions with HOST memory space as the default, e.g.
int allocate(MemSpace m = HOST);That way, if you don't specify memory space everything will run on CPU (host). Only if you want to run something on GPU, you would need to add memory space argument, but in that case you would already know what the host and device are.
Description
Add vector class to GridKit to replace
std::vectorand provide portable data management.Proposed changes
The
LinearAlgebra::Vectorclass was ported from Re::Solve. It has basic initialization and data management functionality. I can be use as a vector, multivector or vector view class.Checklist
-Wall -Wpedantic -Wconversion -Wextra.Further comments
The multivector capability in this class should replace
DenseMatrixobject in GridKit.