Commit 0fdbde8b authored by Nikola Dinev's avatar Nikola Dinev Committed by Tobias Lasser

Introduce the TikhonovProblem class, with support for conversion to single-term WLSProblem.

- add BlockLinearOperator to operators
- add block descriptors (IdenticalBlockDescriptor, PartitionDescriptor, RandomBlocksDescriptor) and made BlockDescriptor abstract
parent 2737ad1b
Pipeline #203116 passed with stages
in 4 minutes and 40 seconds
#include "BlockDescriptor.h"
#include <algorithm>
#include <stdexcept>
namespace elsa
{
BlockDescriptor::BlockDescriptor(index_t numberOfBlocks, const DataDescriptor& dataDescriptor)
: DataDescriptor(dataDescriptor), _blockOffsets(numberOfBlocks)
{
// sanity check
if (numberOfBlocks <= 0)
throw std::invalid_argument("BlockDescriptor: number of blocks has to be positive");
for (index_t i = 0; i < numberOfBlocks; ++i) {
_blockDescriptors.emplace_back(dataDescriptor.clone());
_blockOffsets(i) = dataDescriptor.getNumberOfCoefficients() * i;
}
// update the base class DataDescriptor with additional dimension/size
_numberOfDimensions++;
_numberOfCoefficientsPerDimension.conservativeResize(_numberOfDimensions);
_numberOfCoefficientsPerDimension(_numberOfDimensions - 1) = numberOfBlocks;
_spacingPerDimension.conservativeResize(_numberOfDimensions);
_spacingPerDimension(_numberOfDimensions - 1) = 1.0;
_productOfCoefficientsPerDimension.conservativeResize(_numberOfDimensions);
_productOfCoefficientsPerDimension(_numberOfDimensions - 1) =
_numberOfCoefficientsPerDimension.head(_numberOfDimensions - 1).prod();
}
index_t BlockDescriptor::getNumberOfBlocks() const
{
return static_cast<index_t>(_blockDescriptors.size());
}
const DataDescriptor& BlockDescriptor::getDescriptorOfBlock(index_t i) const
{
// std::vector is using unsigned indices.. so oblige it
auto j = static_cast<decltype(_blockDescriptors)::size_type>(i);
return *_blockDescriptors.at(j);
}
index_t BlockDescriptor::getOffsetOfBlock(elsa::index_t i) const
{
if (i < 0 || i >= _blockOffsets.size())
throw std::invalid_argument("BlockDescriptor: index i is out of bounds");
return _blockOffsets.coeff(i);
}
BlockDescriptor::BlockDescriptor(const BlockDescriptor& blockDescriptor)
: DataDescriptor(blockDescriptor),
_blockOffsets{blockDescriptor._blockOffsets}
{
for (const auto& descriptor : blockDescriptor._blockDescriptors)
_blockDescriptors.emplace_back(descriptor->clone());
}
BlockDescriptor* BlockDescriptor::cloneImpl() const { return new BlockDescriptor(*this); }
bool BlockDescriptor::isEqual(const DataDescriptor& other) const
{
if (!DataDescriptor::isEqual(other))
return false;
auto otherBlock = dynamic_cast<const BlockDescriptor*>(&other);
if (otherBlock == nullptr)
return false;
if (_blockDescriptors.size() != otherBlock->_blockDescriptors.size())
return false;
if (!std::equal(_blockDescriptors.begin(), _blockDescriptors.end(),
otherBlock->_blockDescriptors.begin(),
[](const auto& i1, const auto& i2) { return *i1 == *i2; }))
return false;
return _blockOffsets == otherBlock->_blockOffsets;
}
} // namespace elsa
......@@ -4,22 +4,18 @@
#include "Cloneable.h"
#include "DataDescriptor.h"
#include <memory>
#include <vector>
namespace elsa
{
/**
* \brief Class representing metadata for blocked, linearized n-dimensional signal stored in
* memory
* \brief Abstract class defining the interface of all block descriptors.
*
* \author Matthias Wieczorek - initial code
* \author David Frank - rewrite
* \author Nikola Dinev - various enhancements
* \author Tobias Lasser - rewrite, modularization, modernization
* \author Nikola Dinev - rework into abstract class
*
* This class provides metadata about a signal that is stored in memory (typically a
* A block descriptor provides metadata about a signal that is stored in memory (typically a
* DataContainer). This signal can be n-dimensional, and will be stored in memory in a
* linearized fashion in blocks. The blocks can be used to support various operations (like
* blocked operators or ordered subsets), however, the blocks have to lie in memory one after
......@@ -28,47 +24,23 @@ namespace elsa
class BlockDescriptor : public DataDescriptor
{
public:
/// delete default constructor (having no metadata is invalid)
BlockDescriptor() = delete;
/// default destructor
~BlockDescriptor() override = default;
/**
* \brief Create a new descriptor, replicating the dataDescriptor numberOfBlocks times
* along a new dimension
*
* \param[in] numberOfBlocks is the desired number of blocks
* \param[in] dataDescriptor is the descriptor that will be replicated numberOfBlocks times
* along a new dimension
*
* \throw std::invalid_argument if numberOfBlocks is non-positive
*/
explicit BlockDescriptor(index_t numberOfBlocks, const DataDescriptor& dataDescriptor);
/// return the number of blocks
index_t getNumberOfBlocks() const;
virtual index_t getNumberOfBlocks() const = 0;
/// return the DataDescriptor of the i-th block
const DataDescriptor& getDescriptorOfBlock(index_t i) const;
virtual const DataDescriptor& getDescriptorOfBlock(index_t i) const = 0;
/// return the offset to access the data of the i-th block
index_t getOffsetOfBlock(index_t i) const;
virtual index_t getOffsetOfBlock(index_t i) const = 0;
protected:
/// vector of DataDescriptors describing the individual blocks
std::vector<std::unique_ptr<DataDescriptor>> _blockDescriptors;
/// vector of the individual block data offsets
IndexVector_t _blockOffsets;
/// protected copy constructor
BlockDescriptor(const BlockDescriptor& blockDescriptor);
/// implement the polymorphic clone operation
BlockDescriptor* cloneImpl() const override;
/// used by derived classes to initialize the DataDescriptor base
BlockDescriptor(DataDescriptor&& base) : DataDescriptor{std::move(base)} {}
/// implement the polymorphic comparison operation
bool isEqual(const DataDescriptor& other) const override;
/// used by derived classes to initialize the DataDescriptor base
BlockDescriptor(const DataDescriptor& base) : DataDescriptor{base} {}
};
} // namespace elsa
......@@ -12,6 +12,9 @@ set(MODULE_HEADERS
Cloneable.h
DataDescriptor.h
BlockDescriptor.h
IdenticalBlocksDescriptor.h
PartitionDescriptor.h
RandomBlocksDescriptor.h
DataContainer.h
DataContainerIterator.h
DataHandler.h
......@@ -24,7 +27,9 @@ set(MODULE_HEADERS
# list all the code files of the module
set(MODULE_SOURCES
DataDescriptor.cpp
BlockDescriptor.cpp
RandomBlocksDescriptor.cpp
IdenticalBlocksDescriptor.cpp
PartitionDescriptor.cpp
DataContainer.cpp
DataHandlerCPU.cpp
DataHandlerMapCPU.cpp
......
......@@ -282,7 +282,7 @@ namespace elsa
}
template <typename data_t>
DataContainer<data_t> DataContainer<data_t>::getBlock(index_t i) const
const DataContainer<data_t> DataContainer<data_t>::getBlock(index_t i) const
{
const auto blockDesc = dynamic_cast<const BlockDescriptor*>(_dataDescriptor.get());
if (!blockDesc)
......@@ -312,7 +312,8 @@ namespace elsa
}
template <typename data_t>
DataContainer<data_t> DataContainer<data_t>::viewAs(const DataDescriptor& dataDescriptor) const
const DataContainer<data_t>
DataContainer<data_t>::viewAs(const DataDescriptor& dataDescriptor) const
{
if (dataDescriptor.getNumberOfCoefficients() != getSize())
throw std::invalid_argument("DataContainer: view must have same size as container");
......
......@@ -258,13 +258,13 @@ namespace elsa
DataContainer<data_t> getBlock(index_t i);
/// returns a const reference to the i-th block, wrapped in a DataContainer
DataContainer<data_t> getBlock(index_t i) const;
const DataContainer<data_t> getBlock(index_t i) const;
/// return a view of this DataContainer with a different descriptor
DataContainer<data_t> viewAs(const DataDescriptor& dataDescriptor);
/// return a const view of this DataContainer with a different descriptor
DataContainer<data_t> viewAs(const DataDescriptor& dataDescriptor) const;
const DataContainer<data_t> viewAs(const DataDescriptor& dataDescriptor) const;
/// iterator for DataContainer (random access and continuous)
using iterator = DataContainerIterator<DataContainer<data_t>>;
......
#include "DataDescriptor.h"
#include <stdexcept>
#include <algorithm>
namespace elsa
{
......@@ -96,10 +97,60 @@ namespace elsa
return coordinate;
}
std::unique_ptr<DataDescriptor>
DataDescriptor::bestCommon(const std::vector<const DataDescriptor*>& descList)
{
if (descList.empty())
throw std::invalid_argument("DataDescriptor::bestCommon: descriptor list empty");
const auto& firstDesc = *descList[0];
auto coeffs = firstDesc.getNumberOfCoefficientsPerDimension();
auto size = firstDesc.getNumberOfCoefficients();
auto spacing = firstDesc.getSpacingPerDimension();
bool allSame =
std::all_of(descList.begin(), descList.end(),
[&firstDesc](const DataDescriptor* d) { return *d == firstDesc; });
if (allSame)
return firstDesc.clone();
bool allSameCoeffs =
std::all_of(descList.begin(), descList.end(), [&coeffs](const DataDescriptor* d) {
return d->getNumberOfCoefficientsPerDimension().size() == coeffs.size()
&& d->getNumberOfCoefficientsPerDimension() == coeffs;
});
if (allSameCoeffs) {
bool allSameSpacing =
std::all_of(descList.begin(), descList.end(), [&spacing](const DataDescriptor* d) {
return d->getSpacingPerDimension() == spacing;
});
if (allSameSpacing) {
return std::make_unique<DataDescriptor>(coeffs, spacing);
} else {
return std::make_unique<DataDescriptor>(coeffs);
}
}
bool allSameSize =
std::all_of(descList.begin(), descList.end(), [size](const DataDescriptor* d) {
return d->getNumberOfCoefficients() == size;
});
if (!allSameSize)
throw std::invalid_argument(
"DataDescriptor::bestCommon: descriptor sizes do not match");
return std::make_unique<DataDescriptor>(IndexVector_t::Constant(1, size));
}
DataDescriptor* DataDescriptor::cloneImpl() const { return new DataDescriptor(*this); }
bool DataDescriptor::isEqual(const DataDescriptor& other) const
{
if (typeid(other) != typeid(*this))
return false;
return (_numberOfDimensions == other._numberOfDimensions)
&& (_numberOfCoefficientsPerDimension == other._numberOfCoefficientsPerDimension)
&& (_spacingPerDimension == other._spacingPerDimension)
......
......@@ -3,6 +3,8 @@
#include "elsaDefines.h"
#include "Cloneable.h"
#include <vector>
namespace elsa
{
......@@ -40,8 +42,8 @@ namespace elsa
* \brief Constructor for DataDescriptor, accepts dimension, size and spacing
*
* \param[in] numberOfCoefficientsPerDimension vector containing the number of coefficients
* per dimension, (dimension is set implicitly from the size of the vector) \param[in]
* spacingPerDimension vector containing the spacing per dimension
* per dimension, (dimension is set implicitly from the size of the vector)
* \param[in] spacingPerDimension vector containing the spacing per dimension
*
* \throw std::invalid_argument if any number of coefficients is non-positive,
* or sizes of numberOfCoefficientsPerDimension and spacingPerDimension do not match
......@@ -88,6 +90,35 @@ namespace elsa
*/
IndexVector_t getCoordinateFromIndex(index_t index) const;
/**
* \brief Finds the descriptor with the same number of coefficients as the descriptors in
* the list that retains as much information as possible
*
* \param[in] descriptorList a vector of plain pointers to DataDescriptor
*
* \return std::unique_ptr<DataDescriptor> the best common descriptor
*
* \throw std::invalid_argument if the vector is empty or the descriptors in the vector
* don't all have the same size
*
* If all descriptors are equal, a clone of the first descriptor in the list is returned.
* If all descriptors have a common base descriptor, that data descriptor is returned.
* If the base descriptors only differ in spacing, the base descriptor with a uniform
* spacing of 1 is returned.
* Otherwise, the linearized descriptor with a spacing of 1 is returned.
*/
static std::unique_ptr<DataDescriptor>
bestCommon(const std::vector<const DataDescriptor*>& descriptorList);
/// convenience overload for invoking bestCommon() with a number of const DataDescriptor&
template <
typename... DescriptorType,
typename = std::enable_if_t<(std::is_base_of_v<DataDescriptor, DescriptorType> && ...)>>
static std::unique_ptr<DataDescriptor> bestCommon(const DescriptorType&... descriptors)
{
return bestCommon(std::vector{static_cast<const DataDescriptor*>(&descriptors)...});
}
protected:
/// Number of dimensions
index_t _numberOfDimensions;
......@@ -108,6 +139,9 @@ namespace elsa
/// default copy constructor, hidden from non-derived classes to prevent potential slicing
DataDescriptor(const DataDescriptor&) = default;
/// default move constructor, hidden from non-derived classes to prevent potential slicing
DataDescriptor(DataDescriptor&&) = default;
/// implement the polymorphic clone operation
DataDescriptor* cloneImpl() const override;
......
#include "IdenticalBlocksDescriptor.h"
namespace elsa
{
IdenticalBlocksDescriptor::IdenticalBlocksDescriptor(index_t numberOfBlocks,
const DataDescriptor& dataDescriptor)
: BlockDescriptor{initBase(numberOfBlocks, dataDescriptor)},
_blockDescriptor{dataDescriptor.clone()},
_numberOfBlocks{numberOfBlocks}
{
}
index_t IdenticalBlocksDescriptor::getNumberOfBlocks() const { return _numberOfBlocks; }
const DataDescriptor& IdenticalBlocksDescriptor::getDescriptorOfBlock(index_t i) const
{
if (i < 0 || i >= _numberOfBlocks)
throw std::invalid_argument("BlockDescriptor: index i is out of bounds");
return *_blockDescriptor;
}
index_t IdenticalBlocksDescriptor::getOffsetOfBlock(index_t i) const
{
if (i < 0 || i >= _numberOfBlocks)
throw std::invalid_argument("BlockDescriptor: index i is out of bounds");
return i * _blockDescriptor->getNumberOfCoefficients();
}
IdenticalBlocksDescriptor* IdenticalBlocksDescriptor::cloneImpl() const
{
return new IdenticalBlocksDescriptor(_numberOfBlocks, *_blockDescriptor);
}
bool IdenticalBlocksDescriptor::isEqual(const DataDescriptor& other) const
{
if (!BlockDescriptor::isEqual(other))
return false;
// static cast as type checked in base comparison
auto otherBlock = static_cast<const IdenticalBlocksDescriptor*>(&other);
if (*_blockDescriptor != *otherBlock->_blockDescriptor)
return false;
return true;
}
DataDescriptor IdenticalBlocksDescriptor::initBase(index_t numberOfBlocks,
const DataDescriptor& dataDescriptor)
{
if (numberOfBlocks < 1)
throw std::invalid_argument(
"IdenticalBlockDescriptor: number of blocks has to be positive");
auto numberOfCoeffs = dataDescriptor.getNumberOfCoefficientsPerDimension();
index_t numDim = numberOfCoeffs.size() + 1;
auto spacingOfCoeffs = dataDescriptor.getSpacingPerDimension();
numberOfCoeffs.conservativeResize(numDim);
spacingOfCoeffs.conservativeResize(numDim);
numberOfCoeffs[numDim - 1] = numberOfBlocks;
spacingOfCoeffs[numDim - 1] = 1;
return DataDescriptor(numberOfCoeffs, spacingOfCoeffs);
}
} // namespace elsa
\ No newline at end of file
#include "BlockDescriptor.h"
namespace elsa
{
/**
* \brief Class representing a series of identical descriptors concatenated along a new
* dimension (the last dimension of the full descriptor).
*
* \author Nikola Dinev
*
* The blocks are, essentially, slices (though not necessarily two-dimensional) of the full
* descriptor along its last dimension. The last dimension of the full descriptor serves solely
* for the indexing of the different blocks, and will always have a spacing of one and a number
* of coefficients corresponding to the number of blocks.
*
* This descriptor should be the prefferred choice when dealing with vector fields.
*/
class IdenticalBlocksDescriptor : public BlockDescriptor
{
public:
/**
* \brief Create a new descriptor, replicating the dataDescriptor numberOfBlocks times
* along a new dimension
*
* \param[in] numberOfBlocks is the desired number of blocks
* \param[in] dataDescriptor is the descriptor that will be replicated numberOfBlocks
times
* along a new dimension
*
* \throw std::invalid_argument if numberOfBlocks is non-positive
*/
IdenticalBlocksDescriptor(index_t numberOfBlocks, const DataDescriptor& dataDescriptor);
/// make copy constructor deletion explicit
IdenticalBlocksDescriptor(const IdenticalBlocksDescriptor&) = delete;
/// default destructor
~IdenticalBlocksDescriptor() override = default;
/// return the number of blocks
index_t getNumberOfBlocks() const override;
/// return the DataDescriptor of the i-th block
const DataDescriptor& getDescriptorOfBlock(index_t i) const override;
/// return the offset to access the data of the i-th block
index_t getOffsetOfBlock(index_t i) const override;
protected:
/// descriptor of a single block
std::unique_ptr<DataDescriptor> _blockDescriptor;
/// the total number of identical blocks
index_t _numberOfBlocks;
/// implement the polymorphic clone operation
IdenticalBlocksDescriptor* cloneImpl() const override;
/// implement the polymorphic comparison operation
bool isEqual(const DataDescriptor& other) const override;
private:
/// generates the
DataDescriptor initBase(index_t numberOfBlocks, const DataDescriptor& dataDescriptor);
};
} // namespace elsa
\ No newline at end of file
......@@ -227,16 +227,21 @@ namespace elsa
template <typename data_t>
bool LinearOperator<data_t>::isEqual(const LinearOperator<data_t>& other) const
{
if (typeid(other) != typeid(*this))
return false;
if (*_domainDescriptor != *other._domainDescriptor
|| *_rangeDescriptor != *other._rangeDescriptor)
return false;
if (_isLeaf ^ other._isLeaf || _isComposite ^ other._isComposite)
return false;
if (_isLeaf)
return other._isLeaf && (_isAdjoint == other._isAdjoint) && (*_lhs == *other._lhs);
return (_isAdjoint == other._isAdjoint) && (*_lhs == *other._lhs);
if (_isComposite)
return other._isComposite && _mode == other._mode && (*_lhs == *other._lhs)
&& (*_rhs == *other._rhs);
return _mode == other._mode && (*_lhs == *other._lhs) && (*_rhs == *other._rhs);
return true;
}
......@@ -256,8 +261,14 @@ namespace elsa
template <typename data_t>
LinearOperator<data_t>::LinearOperator(const LinearOperator<data_t>& lhs,
const LinearOperator<data_t>& rhs, CompositeMode mode)
: _domainDescriptor{rhs.getDomainDescriptor().clone()},
_rangeDescriptor{lhs.getRangeDescriptor().clone()},
: _domainDescriptor{mode == CompositeMode::MULT
? rhs.getDomainDescriptor().clone()
: DataDescriptor::bestCommon(*lhs._domainDescriptor,
*rhs._domainDescriptor)},
_rangeDescriptor{
mode == CompositeMode::MULT
? lhs.getRangeDescriptor().clone()
: DataDescriptor::bestCommon(*lhs._rangeDescriptor, *rhs._rangeDescriptor)},
_lhs{lhs.clone()},
_rhs{rhs.clone()},
_isComposite{true},
......@@ -266,16 +277,13 @@ namespace elsa
// sanity check the descriptors
switch (_mode) {
case CompositeMode::ADD:
// for addition, both domains and ranges should match
if (_lhs->getDomainDescriptor() != _rhs->getDomainDescriptor()
|| _lhs->getRangeDescriptor() != _rhs->getRangeDescriptor())
throw std::invalid_argument(
"LinearOperator: composite add domain/range mismatch");
/// feasibility checked by bestCommon()
break;
case CompositeMode::MULT:
// for multiplication, domain of _lhs should match range of _rhs
if (_lhs->getDomainDescriptor() != _rhs->getRangeDescriptor())
if (_lhs->getDomainDescriptor().getNumberOfCoefficients()
!= _rhs->getRangeDescriptor().getNumberOfCoefficients())
throw std::invalid_argument(
"LinearOperator: composite mult domain/range mismatch");
break;
......
#include "PartitionDescriptor.h"
#include <map>
namespace elsa
{
PartitionDescriptor::PartitionDescriptor(const DataDescriptor& dataDescriptor,
index_t numberOfBlocks)
: BlockDescriptor{dataDescriptor},
_indexMap(numberOfBlocks),
_blockDescriptors(0),
_blockOffsets(numberOfBlocks)
{
if (numberOfBlocks < 2)
throw std::invalid_argument(
"PartitionDescriptor: number of blocks must be greater than one");
index_t lastDimSize = _numberOfCoefficientsPerDimension[_numberOfDimensions - 1];
if (numberOfBlocks > lastDimSize)
throw std::invalid_argument(
"PartitionDescriptor: number of blocks too large for given descriptor");
index_t rest = lastDimSize % numberOfBlocks;
auto blockDesc = generateDescriptorOfPartition(lastDimSize / numberOfBlocks);
_blockDescriptors.push_back(std::move(blockDesc));
_indexMap.head(numberOfBlocks - rest).setZero();
for (index_t i = 0; i < numberOfBlocks && i <= numberOfBlocks - rest; i++)
_blockOffsets[i] = i * _blockDescriptors[0]->getNumberOfCoefficients();
if (rest > 0) {
blockDesc = generateDescriptorOfPartition(lastDimSize / numberOfBlocks + 1);
_blockDescriptors.push_back(std::move(blockDesc));
_indexMap.tail(rest).array().setConstant(1);
auto numCoeffs = _blockDescriptors[1]->getNumberOfCoefficients();
for (index_t i = numberOfBlocks - rest + 1; i < numberOfBlocks; i++)
_blockOffsets[i] = _blockOffsets[i - 1] + numCoeffs;
}
}
PartitionDescriptor::PartitionDescriptor(const DataDescriptor& dataDescriptor,
IndexVector_t slicesInBlock)
: BlockDescriptor{dataDescriptor},
_indexMap(slicesInBlock.size()),
_blockDescriptors(0),
_blockOffsets(slicesInBlock.size())
{
if (slicesInBlock.size() < 2)
throw std::invalid_argument(
"PartitionDescriptor: number of blocks must be greater than one");
if ((slicesInBlock.array() <= 0).any())
throw std::invalid_argument(
"PartitionDescriptor: non-positive number of coefficients not allowed");
if (slicesInBlock.sum() != _numberOfCoefficientsPerDimension[_numberOfDimensions - 1])
throw std::invalid_argument("PartitionDescriptor: cumulative size of partitioned "
"descriptor does not match size of original descriptor");
std::map<index_t, std::size_t> sizeToIndex;
_blockOffsets[0] = 0;
for (index_t i = 0; i < getNumberOfBlocks(); i++) {
auto it = sizeToIndex.find(slicesInBlock[i]);
index_t numCoeffs;
if (it != sizeToIndex.end()) {
_indexMap[i] = it->second;
numCoeffs = _blockDescriptors[it->second]->getNumberOfCoefficients();
} else {
sizeToIndex.insert({slicesInBlock[i], _blockDescriptors.size()});
_indexMap[i] = _blockDescriptors.size();
_blockDescriptors.push_back(generateDescriptorOfPartition(slicesInBlock[i]));
numCoeffs = _blockDescriptors.back()->getNumberOfCoefficients();
}
if (i != getNumberOfBlocks() - 1)
_blockOffsets[i + 1] = _blockOffsets[i] + numCoeffs;
}
}
PartitionDescriptor::PartitionDescriptor(const PartitionDescriptor& other)
: BlockDescriptor(other), _indexMap(other._indexMap), _blockOffsets{other._blockOffsets}
{
for (const auto& blockDesc : other._blockDescriptors)
_blockDescriptors.push_back(blockDesc->clone());
}
index_t PartitionDescriptor::getNumberOfBlocks() const { return _indexMap.size(); }
const DataDescriptor& PartitionDescriptor::getDescriptorOfBlock(index_t i) const
{
if (i < 0 || i >= getNumberOfBlocks())
throw std::invalid_argument("BlockDescriptor: index i is out of bounds");
return *_blockDescriptors[_indexMap[i]];
}
index_t PartitionDescriptor::getOffsetOfBlock(index_t i) const
{
if (i < 0 || i >= getNumberOfBlocks())
throw std::invalid_argument("BlockDescriptor: index i is out of bounds");
return _blockOffsets[i];
}
PartitionDescriptor* PartitionDescriptor::cloneImpl() const
{
return new PartitionDescriptor(*this);
}
bool PartitionDescriptor::isEqual(const DataDescriptor& other) const
{
if (!BlockDescriptor::isEqual(other))
return false;
// static cast as type checked in base comparison
auto otherBlock = static_cast<const PartitionDescriptor*>(&other);
return _blockOffsets == otherBlock->_blockOffsets;
}
std::unique_ptr<DataDescriptor>
PartitionDescriptor::generateDescriptorOfPartition(index_t numberOfSlices) const
{