Skip to content

Commit 272aa73

Browse files
committed
ENH: Support (expr)[T, C] sub-expression tuple+component extraction
Adds TupleComponentExtract as a new RPN item type that extracts a single scalar value from a computed sub-expression result at a specific tuple and component index. Produces a Number that broadcasts like a literal. Signed-off-by: Joey Kleingers <joey.kleingers@bluequartz.net>
1 parent 037e3f3 commit 272aa73

3 files changed

Lines changed: 145 additions & 20 deletions

File tree

src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ArrayCalculator.cpp

Lines changed: 98 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,14 @@ struct ParsedItem
3333
LParen,
3434
RParen,
3535
Comma,
36-
ComponentExtract
36+
ComponentExtract,
37+
TupleComponentExtract
3738
} kind;
3839

3940
CalcValue value{CalcValue::Kind::Number, 0};
4041
const OperatorDef* op = nullptr;
4142
usize componentIndex = std::numeric_limits<usize>::max();
43+
usize tupleIndex = std::numeric_limits<usize>::max();
4244
bool isNegativePrefix = false;
4345
};
4446

@@ -707,24 +709,48 @@ Result<> ArrayCalculatorParser::parse()
707709
}
708710
else if(prevItem.kind == ParsedItem::Kind::RParen)
709711
{
710-
// Case B: )[C] -- component extraction on sub-expression result
711-
if(bracketNumbers.size() != 1)
712+
// Case B: )[C] or )[T, C] -- extraction on sub-expression result
713+
if(bracketNumbers.size() == 1)
712714
{
713-
return MakeErrorResult(static_cast<int>(CalculatorErrorCode::InvalidComponent), "Component extraction on sub-expression must have exactly one index: [C].");
715+
// )[C]: component extraction
716+
usize compIdx = 0;
717+
try
718+
{
719+
compIdx = static_cast<usize>(std::stoull(bracketNumbers[0]));
720+
} catch(const std::exception&)
721+
{
722+
return MakeErrorResult(static_cast<int>(CalculatorErrorCode::InvalidComponent), fmt::format("Invalid component index '{}'.", bracketNumbers[0]));
723+
}
724+
725+
ParsedItem ce;
726+
ce.kind = ParsedItem::Kind::ComponentExtract;
727+
ce.componentIndex = compIdx;
728+
items.push_back(ce);
714729
}
715-
usize compIdx = 0;
716-
try
730+
else if(bracketNumbers.size() == 2)
717731
{
718-
compIdx = static_cast<usize>(std::stoull(bracketNumbers[0]));
719-
} catch(const std::exception&)
732+
// )[T, C]: tuple+component extraction (produces scalar)
733+
usize tupleIdx = 0;
734+
usize compIdx = 0;
735+
try
736+
{
737+
tupleIdx = static_cast<usize>(std::stoull(bracketNumbers[0]));
738+
compIdx = static_cast<usize>(std::stoull(bracketNumbers[1]));
739+
} catch(const std::exception&)
740+
{
741+
return MakeErrorResult(static_cast<int>(CalculatorErrorCode::InvalidComponent), fmt::format("Invalid tuple/component index in '[{}, {}]'.", bracketNumbers[0], bracketNumbers[1]));
742+
}
743+
744+
ParsedItem tce;
745+
tce.kind = ParsedItem::Kind::TupleComponentExtract;
746+
tce.tupleIndex = tupleIdx;
747+
tce.componentIndex = compIdx;
748+
items.push_back(tce);
749+
}
750+
else
720751
{
721-
return MakeErrorResult(static_cast<int>(CalculatorErrorCode::InvalidComponent), fmt::format("Invalid component index '{}'.", bracketNumbers[0]));
752+
return MakeErrorResult(static_cast<int>(CalculatorErrorCode::InvalidComponent), "Sub-expression index must be [C] or [T, C].");
722753
}
723-
724-
ParsedItem ce;
725-
ce.kind = ParsedItem::Kind::ComponentExtract;
726-
ce.componentIndex = compIdx;
727-
items.push_back(ce);
728754
}
729755
else
730756
{
@@ -1334,21 +1360,31 @@ Result<> ArrayCalculatorParser::parse()
13341360
return MakeErrorResult(static_cast<int>(CalculatorErrorCode::NoNumericArguments), "The expression does not have any arguments that simplify down to a number.");
13351361
}
13361362

1337-
// Check if there is a ComponentExtract item in the parsed list.
1338-
// If so, the final output will be single-component regardless of the
1339-
// input array's component shape.
1363+
// Check if there is a ComponentExtract or TupleComponentExtract item in the parsed list.
1364+
// ComponentExtract produces a single-component array.
1365+
// TupleComponentExtract produces a scalar (single tuple, single component).
13401366
bool hasComponentExtract = false;
1367+
bool hasTupleComponentExtract = false;
13411368
for(const auto& item : items)
13421369
{
13431370
if(item.kind == ParsedItem::Kind::ComponentExtract)
13441371
{
13451372
hasComponentExtract = true;
1346-
break;
1373+
}
1374+
if(item.kind == ParsedItem::Kind::TupleComponentExtract)
1375+
{
1376+
hasTupleComponentExtract = true;
13471377
}
13481378
}
13491379

13501380
// Store the parsed shape info for use by parseAndValidate()
1351-
if(hasArray)
1381+
if(hasTupleComponentExtract)
1382+
{
1383+
// TupleComponentExtract produces a scalar
1384+
m_ParsedTupleShape = {1};
1385+
m_ParsedComponentShape = {1};
1386+
}
1387+
else if(hasArray)
13521388
{
13531389
if(tupleShapesMatch)
13541390
{
@@ -1445,6 +1481,15 @@ Result<> ArrayCalculatorParser::parse()
14451481
break;
14461482
}
14471483

1484+
case ParsedItem::Kind::TupleComponentExtract: {
1485+
RpnItem tce;
1486+
tce.type = RpnItem::Type::TupleComponentExtract;
1487+
tce.tupleIndex = item.tupleIndex;
1488+
tce.componentIndex = item.componentIndex;
1489+
m_RpnItems.push_back(tce);
1490+
break;
1491+
}
1492+
14481493
} // end switch
14491494
}
14501495

@@ -1648,6 +1693,40 @@ Result<> ArrayCalculatorParser::evaluateInto(DataStructure& dataStructure, const
16481693
break;
16491694
}
16501695

1696+
case RpnItem::Type::TupleComponentExtract: {
1697+
if(evalStack.empty())
1698+
{
1699+
return MakeErrorResult(static_cast<int>(CalculatorErrorCode::NotEnoughArguments), "Not enough arguments for tuple+component extraction.");
1700+
}
1701+
CalcValue operand = evalStack.top();
1702+
evalStack.pop();
1703+
1704+
auto* operandArr = m_TempDataStructure.getDataAs<Float64Array>(operand.arrayId);
1705+
if(operandArr == nullptr)
1706+
{
1707+
return MakeErrorResult(static_cast<int>(CalculatorErrorCode::InvalidEquation), "Internal error: could not find operand array for tuple+component extraction.");
1708+
}
1709+
1710+
usize numComps = operandArr->getNumberOfComponents();
1711+
usize numTuples = operandArr->getNumberOfTuples();
1712+
usize tupleIdx = rpnItem.tupleIndex;
1713+
usize compIdx = rpnItem.componentIndex;
1714+
1715+
if(tupleIdx >= numTuples)
1716+
{
1717+
return MakeErrorResult(static_cast<int>(CalculatorErrorCode::TupleOutOfRange), fmt::format("Tuple index {} is out of range for array with {} tuples.", tupleIdx, numTuples));
1718+
}
1719+
if(compIdx >= numComps)
1720+
{
1721+
return MakeErrorResult(static_cast<int>(CalculatorErrorCode::ComponentOutOfRange), fmt::format("Component index {} is out of range for array with {} components.", compIdx, numComps));
1722+
}
1723+
1724+
double value = operandArr->at(tupleIdx * numComps + compIdx);
1725+
DataObject::IdType scalarId = createScalarInTemp(value);
1726+
evalStack.push(CalcValue{CalcValue::Kind::Number, scalarId});
1727+
break;
1728+
}
1729+
16511730
} // end switch
16521731
}
16531732

src/Plugins/SimplnxCore/src/SimplnxCore/Filters/Algorithms/ArrayCalculator.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,12 +155,14 @@ struct SIMPLNXCORE_EXPORT RpnItem
155155
{
156156
Value,
157157
Operator,
158-
ComponentExtract
158+
ComponentExtract,
159+
TupleComponentExtract
159160
} type;
160161

161162
CalcValue value;
162163
const OperatorDef* op = nullptr;
163164
usize componentIndex = std::numeric_limits<usize>::max();
165+
usize tupleIndex = std::numeric_limits<usize>::max();
164166
};
165167

166168
// ---------------------------------------------------------------------------

src/Plugins/SimplnxCore/test/ArrayCalculatorTest.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1323,3 +1323,47 @@ TEST_CASE("SimplnxCore::ArrayCalculatorFilter: Multi-word Array Names")
13231323

13241324
UnitTest::CheckArraysInheritTupleDims(ds);
13251325
}
1326+
1327+
TEST_CASE("SimplnxCore::ArrayCalculatorFilter: Sub-expression Tuple Component Extraction")
1328+
{
1329+
UnitTest::LoadPlugins();
1330+
DataStructure ds = ::createDataStructure();
1331+
ArrayCalculatorFilter filter;
1332+
1333+
// MultiComponent Array1 has 10 tuples, 3 components, values 0,1,2,3,...,29
1334+
// (ArrayA + ArrayB) at tuple 2, component 1 = 2*(2*3+1) = 14
1335+
1336+
SECTION("(expr)[T, C] produces scalar")
1337+
{
1338+
Arguments args;
1339+
args.insertOrAssign(ArrayCalculatorFilter::k_CalculatorParameter_Key, std::make_any<CalculatorParameter::ValueType>(CalculatorParameter::ValueType{
1340+
k_AttributeMatrixPath, "(\"MultiComponent Array1\" + \"MultiComponent Array2\")[2, 1]", CalculatorParameter::Radians}));
1341+
args.insertOrAssign(ArrayCalculatorFilter::k_ScalarType_Key, std::make_any<NumericTypeParameter::ValueType>(NumericType::float64));
1342+
args.insertOrAssign(ArrayCalculatorFilter::k_CalculatedArray_Key, std::make_any<DataPath>(k_AttributeArrayPath));
1343+
auto result = filter.execute(ds, args);
1344+
SIMPLNX_RESULT_REQUIRE_VALID(result.result);
1345+
1346+
REQUIRE_NOTHROW(ds.getDataRefAs<Float64Array>(k_AttributeArrayPath));
1347+
const auto& outputArray = ds.getDataRefAs<Float64Array>(k_AttributeArrayPath);
1348+
// Scalar result broadcast to AM shape (10 tuples)
1349+
double expected = 2.0 * (2 * 3 + 1); // tuple 2, comp 1, doubled = 14
1350+
for(usize i = 0; i < outputArray.getNumberOfTuples(); i++)
1351+
{
1352+
REQUIRE(UnitTest::CloseEnough<double>(outputArray.at(i), expected, 0.01));
1353+
}
1354+
}
1355+
1356+
SECTION("(expr)[T, C] out of bounds tuple")
1357+
{
1358+
Arguments args;
1359+
args.insertOrAssign(ArrayCalculatorFilter::k_CalculatorParameter_Key, std::make_any<CalculatorParameter::ValueType>(CalculatorParameter::ValueType{
1360+
k_AttributeMatrixPath, "(\"MultiComponent Array1\" + \"MultiComponent Array2\")[100, 0]", CalculatorParameter::Radians}));
1361+
args.insertOrAssign(ArrayCalculatorFilter::k_ScalarType_Key, std::make_any<NumericTypeParameter::ValueType>(NumericType::float64));
1362+
args.insertOrAssign(ArrayCalculatorFilter::k_CalculatedArray_Key, std::make_any<DataPath>(k_AttributeArrayPath));
1363+
auto result = filter.execute(ds, args);
1364+
SIMPLNX_RESULT_REQUIRE_INVALID(result.result);
1365+
REQUIRE(result.result.errors()[0].code == static_cast<int32>(CalculatorErrorCode::TupleOutOfRange));
1366+
}
1367+
1368+
UnitTest::CheckArraysInheritTupleDims(ds);
1369+
}

0 commit comments

Comments
 (0)