Project
Loading...
Searching...
No Matches
test_RDataFrameSupport.cxx
Go to the documentation of this file.
1// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
12#define BOOST_TEST_MODULE Test Framework TableBuilder
13#define BOOST_TEST_MAIN
14#define BOOST_TEST_DYN_LINK
15
16#include <boost/test/unit_test.hpp>
17
18#include "Framework/Logger.h"
24#include <arrow/table.h>
26#include <ROOT/RDataFrame.hxx>
27#include <ROOT/RArrowDS.hxx>
28#include <arrow/ipc/writer.h>
29#include <arrow/io/memory.h>
30#include <arrow/ipc/writer.h>
31#include <arrow/ipc/reader.h>
32#include "../src/ArrowDebugHelpers.h"
33
34using namespace o2::framework;
35
36namespace test
37{
38DECLARE_SOA_COLUMN_FULL(X, x, uint64_t, "x");
39DECLARE_SOA_COLUMN_FULL(Y, y, uint64_t, "y");
40DECLARE_SOA_COLUMN_FULL(Pos, pos, int[4], "pos");
41} // namespace test
42
45
46BOOST_AUTO_TEST_CASE(TestTableBuilder)
47{
48 using namespace o2::framework;
49 TableBuilder builder;
50 auto rowWriter = builder.persist<uint64_t, uint64_t>({"x", "y"});
51 rowWriter(0, 0, 0);
52 rowWriter(0, 10, 1);
53 rowWriter(0, 20, 2);
54 rowWriter(0, 30, 3);
55 rowWriter(0, 40, 4);
56 rowWriter(0, 50, 5);
57 rowWriter(0, 60, 6);
58 rowWriter(0, 70, 7);
59 auto table = builder.finalize();
60 BOOST_REQUIRE_EQUAL(table->num_columns(), 2);
61 BOOST_REQUIRE_EQUAL(table->num_rows(), 8);
62 BOOST_REQUIRE_EQUAL(table->schema()->field(0)->name(), "x");
63 BOOST_REQUIRE_EQUAL(table->schema()->field(1)->name(), "y");
64 BOOST_REQUIRE_EQUAL(table->schema()->field(0)->type()->id(), arrow::uint64()->id());
65 BOOST_REQUIRE_EQUAL(table->schema()->field(1)->type()->id(), arrow::uint64()->id());
66
67 auto readBack = TestTable{table};
68
69 size_t i = 0;
70 for (auto& row : readBack) {
71 BOOST_CHECK_EQUAL(row.x(), i * 10);
73 ++i;
74 }
75}
76
77BOOST_AUTO_TEST_CASE(TestTableBuilderArray)
78{
79 using namespace o2::framework;
80 TableBuilder builder;
81 const int numElem = 4;
82 auto rowWriter = builder.persist<int[numElem]>({"pos"});
83 int a[numElem] = {1, 10, 300, 350};
84 int b[numElem] = {0, 20, 30, 40};
85 rowWriter(0, a);
86 rowWriter(0, b);
87 using v3 = std::array<int, numElem>;
88 rowWriter(0, v3{0, 11, 123, 256}.data());
89 auto table = builder.finalize();
90
91 BOOST_REQUIRE_EQUAL(table->num_columns(), 1);
92 BOOST_REQUIRE_EQUAL(table->num_rows(), 3);
93 BOOST_REQUIRE_EQUAL(table->schema()->field(0)->name(), "pos");
94 BOOST_REQUIRE_EQUAL(table->schema()->field(0)->type()->id(), arrow::fixed_size_list(arrow::int32(), numElem)->id());
95
96 auto chunkToUse = table->column(0)->chunk(0);
97 chunkToUse = std::static_pointer_cast<arrow::FixedSizeListArray>(chunkToUse)->values();
98 auto data = chunkToUse->data();
99
100 BOOST_REQUIRE_EQUAL(data->GetValues<int>(1)[0], 1);
101 BOOST_REQUIRE_EQUAL(data->GetValues<int>(1)[1], 10);
102 BOOST_REQUIRE_EQUAL(data->GetValues<int>(1)[2], 300);
103 BOOST_REQUIRE_EQUAL(data->GetValues<int>(1)[3], 350);
104 BOOST_REQUIRE_EQUAL(data->GetValues<int>(1)[4], 0);
105 BOOST_REQUIRE_EQUAL(data->GetValues<int>(1)[5], 20);
106 BOOST_REQUIRE_EQUAL(data->GetValues<int>(1)[6], 30);
107 BOOST_REQUIRE_EQUAL(data->GetValues<int>(1)[7], 40);
108
109 auto readBack = ArrayTable{table};
110 auto row = readBack.begin();
111
112 BOOST_CHECK_EQUAL(row.pos()[0], 1);
113 BOOST_CHECK_EQUAL(row.pos()[1], 10);
114 BOOST_CHECK_EQUAL(row.pos()[2], 300);
115 BOOST_CHECK_EQUAL(row.pos()[3], 350);
116
117 row++;
118 BOOST_CHECK_EQUAL(row.pos()[0], 0);
119 BOOST_CHECK_EQUAL(row.pos()[1], 20);
120 BOOST_CHECK_EQUAL(row.pos()[2], 30);
121 BOOST_CHECK_EQUAL(row.pos()[3], 40);
122
123 row++;
124 BOOST_CHECK_EQUAL(row.pos()[0], 0);
125 BOOST_CHECK_EQUAL(row.pos()[1], 11);
126 BOOST_CHECK_EQUAL(row.pos()[2], 123);
127 BOOST_CHECK_EQUAL(row.pos()[3], 256);
128}
129
130BOOST_AUTO_TEST_CASE(TestTableBuilderStruct)
131{
132 using namespace o2::framework;
133 TableBuilder builder;
134 struct Foo {
135 uint64_t x;
136 uint64_t y;
137 };
138 auto rowWriter = builder.persist<Foo>({"x", "y"});
139 rowWriter(0, Foo{0, 0});
140 rowWriter(0, Foo{10, 1});
141 rowWriter(0, Foo{20, 2});
142 rowWriter(0, Foo{30, 3});
143 rowWriter(0, Foo{40, 4});
144 rowWriter(0, Foo{50, 5});
145 rowWriter(0, Foo{60, 6});
146 rowWriter(0, Foo{70, 7});
147 auto table = builder.finalize();
148 BOOST_REQUIRE_EQUAL(table->num_columns(), 2);
149 BOOST_REQUIRE_EQUAL(table->num_rows(), 8);
150 BOOST_REQUIRE_EQUAL(table->schema()->field(0)->name(), "x");
151 BOOST_REQUIRE_EQUAL(table->schema()->field(1)->name(), "y");
152 BOOST_REQUIRE_EQUAL(table->schema()->field(0)->type()->id(), arrow::uint64()->id());
153 BOOST_REQUIRE_EQUAL(table->schema()->field(1)->type()->id(), arrow::uint64()->id());
154
155 auto readBack = TestTable{table};
156
157 size_t i = 0;
158 for (auto& row : readBack) {
159 BOOST_CHECK_EQUAL(row.x(), i * 10);
160 BOOST_CHECK_EQUAL(row.y(), i);
161 ++i;
162 }
163}
164
165BOOST_AUTO_TEST_CASE(TestTableBuilderBulk)
166{
167 using namespace o2::framework;
168 TableBuilder builder;
169 auto bulkWriter = builder.bulkPersist<int, int>({"x", "y"}, 10);
170 int x[] = {0, 1, 2, 3, 4, 5, 6, 7};
171 int y[] = {0, 1, 2, 3, 4, 5, 6, 7};
172
173 bulkWriter(0, 8, x, y);
174
175 auto table = builder.finalize();
176 BOOST_REQUIRE_EQUAL(table->num_columns(), 2);
177 BOOST_REQUIRE_EQUAL(table->num_rows(), 8);
178 BOOST_REQUIRE_EQUAL(table->schema()->field(0)->name(), "x");
179 BOOST_REQUIRE_EQUAL(table->schema()->field(1)->name(), "y");
180 BOOST_REQUIRE_EQUAL(table->schema()->field(0)->type()->id(), arrow::int32()->id());
181 BOOST_REQUIRE_EQUAL(table->schema()->field(1)->type()->id(), arrow::int32()->id());
182
183 for (size_t i = 0; i < 8; ++i) {
184 auto p = std::dynamic_pointer_cast<arrow::NumericArray<arrow::Int32Type>>(table->column(0)->chunk(0));
185 BOOST_CHECK_EQUAL(p->Value(i), i);
186 }
187}
188
189BOOST_AUTO_TEST_CASE(TestTableBuilderMore)
190{
191 using namespace o2::framework;
192 TableBuilder builder;
193 auto rowWriter = builder.persist<int, float, std::string, bool>({"x", "y", "s", "b"});
195 rowWriter(0, 0, 0., "foo", true);
196 rowWriter(0, 1, 1., "bar", false);
197 rowWriter(0, 2, 2., "fbr", false);
198 rowWriter(0, 3, 3., "bar", false);
199 rowWriter(0, 4, 4., "abr", true);
200 rowWriter(0, 5, 5., "aaa", false);
201 rowWriter(0, 6, 6., "bbb", true);
202 rowWriter(0, 7, 7., "ccc", false);
203 auto table = builder.finalize();
204 BOOST_REQUIRE_EQUAL(table->num_columns(), 4);
205 BOOST_REQUIRE_EQUAL(table->num_rows(), 8);
206 BOOST_REQUIRE_EQUAL(table->schema()->field(0)->name(), "x");
207 BOOST_REQUIRE_EQUAL(table->schema()->field(1)->name(), "y");
208 BOOST_REQUIRE_EQUAL(table->schema()->field(2)->name(), "s");
209 BOOST_REQUIRE_EQUAL(table->schema()->field(3)->name(), "b");
210 BOOST_REQUIRE_EQUAL(table->schema()->field(0)->type()->id(), arrow::int32()->id());
211 BOOST_REQUIRE_EQUAL(table->schema()->field(1)->type()->id(), arrow::float32()->id());
212 BOOST_REQUIRE_EQUAL(table->schema()->field(2)->type()->id(), arrow::utf8()->id());
213 BOOST_REQUIRE_EQUAL(table->schema()->field(3)->type()->id(), arrow::boolean()->id());
214}
215
216// Use RDataFrame to build the table
217// BOOST_AUTO_TEST_CASE(TestRDataFrame)
218//{
219// using namespace o2::framework;
220// TableBuilder builder;
221// ROOT::RDataFrame rdf(100);
222// auto t = rdf.Define("x", "1")
223// .Define("y", "2")
224// .Define("z", "x+y");
225// t.ForeachSlot(builder.persist<int, int>({"x", "z"}), {"x", "z"});
226//
227// auto table = builder.finalize();
228// BOOST_REQUIRE_EQUAL(table->num_rows(), 100);
229// BOOST_REQUIRE_EQUAL(table->num_columns(), 2);
230// BOOST_REQUIRE_EQUAL(table->column(0)->type()->id(), arrow::int32()->id());
231// BOOST_REQUIRE_EQUAL(table->column(1)->type()->id(), arrow::int32()->id());
232//
233// /// Writing to a stream
234// std::shared_ptr<arrow::io::BufferOutputStream> stream;
235// auto streamOk = arrow::io::BufferOutputStream::Create(100000, arrow::default_memory_pool(), &stream);
236// BOOST_REQUIRE_EQUAL(streamOk.ok(), true);
237// std::shared_ptr<arrow::ipc::RecordBatchWriter> writer;
238// auto outBatch = arrow::ipc::RecordBatchStreamWriter::Open(stream.get(), table->schema(), &writer);
239// auto outStatus = writer->WriteTable(*table);
240// BOOST_REQUIRE_EQUAL(writer->Close().ok(), true);
241//
242// std::shared_ptr<arrow::Buffer> inBuffer;
243// BOOST_REQUIRE_EQUAL(stream->Finish(&inBuffer).ok(), true);
244//
245// BOOST_REQUIRE_EQUAL(outStatus.ok(), true);
246//
247// /// Reading back from the stream
248// TableConsumer consumer(inBuffer->data(), inBuffer->size());
249// std::shared_ptr<arrow::Table> inTable = consumer.asArrowTable();
250//
251// BOOST_REQUIRE_EQUAL(inTable->num_columns(), 2);
252// BOOST_REQUIRE_EQUAL(inTable->num_rows(), 100);
253//
254// auto source = std::make_unique<ROOT::RDF::RArrowDS>(inTable, std::vector<std::string>{});
255// ROOT::RDataFrame finalDF{std::move(source)};
256// BOOST_REQUIRE_EQUAL(*finalDF.Count(), 100);
257//}
258
259BOOST_AUTO_TEST_CASE(TestCombinedDS)
260{
261 using namespace o2::framework;
262 TableBuilder builder1;
263 auto rowWriter1 = builder1.persist<int, int, int>({"x", "y", "event"});
264 for (size_t i = 0; i < 8; ++i) {
265 rowWriter1(0, i, i, i / 4);
266 }
267 auto table1 = builder1.finalize();
268
269 TableBuilder builder2;
270 auto rowWriter2 = builder2.persist<int, int>({"x", "y"});
271 for (size_t i = 0; i < 8; ++i) {
272 rowWriter2(0, i, i);
273 }
274 auto table2 = builder2.finalize();
275 BOOST_REQUIRE_EQUAL(table2->num_columns(), 2);
276 BOOST_REQUIRE_EQUAL(table2->num_rows(), 8);
277 for (size_t i = 0; i < 8; ++i) {
278 auto p2 = std::dynamic_pointer_cast<arrow::NumericArray<arrow::Int32Type>>(table2->column(0)->chunk(0));
279 BOOST_CHECK_EQUAL(p2->Value(i), i);
280 }
281
282 auto source1 = std::make_unique<ROOT::RDF::RArrowDS>(table1, std::vector<std::string>{});
283 auto source2 = std::make_unique<ROOT::RDF::RArrowDS>(table2, std::vector<std::string>{});
284 auto cross = ROOT::RDF::MakeCrossProductDataFrame(std::move(source1), std::move(source2));
285 auto source3 = std::make_unique<ROOT::RDF::RArrowDS>(table1, std::vector<std::string>{});
286 auto source4 = std::make_unique<ROOT::RDF::RArrowDS>(table2, std::vector<std::string>{});
287 auto indexed = ROOT::RDF::MakeColumnIndexedDataFrame(std::move(source3), std::move(source4), "x");
288 auto source5 = std::make_unique<ROOT::RDF::RArrowDS>(table1, std::vector<std::string>{});
289 auto source6 = std::make_unique<ROOT::RDF::RArrowDS>(table2, std::vector<std::string>{});
290 auto unionDS = ROOT::RDF::MakeFriendDataFrame(std::move(source5), std::move(source6));
291 auto source7 = std::make_unique<ROOT::RDF::RArrowDS>(table1, std::vector<std::string>{});
292 auto source8 = std::make_unique<ROOT::RDF::RArrowDS>(table1, std::vector<std::string>{}); // Notice the table needs to be the same
293 auto blockDS = ROOT::RDF::MakeBlockAntiDataFrame(std::move(source7), std::move(source8), "event");
294
295 ROOT::RDataFrame finalDF{std::move(cross)};
296 ROOT::RDataFrame indexedDF{std::move(indexed)};
297 ROOT::RDataFrame unionDF{std::move(unionDS)};
298 ROOT::RDataFrame blockDF{std::move(blockDS)};
299
300 BOOST_CHECK_EQUAL(*finalDF.Count(), 64); // Full cross product of 8x8 rows, 64 entries
301 BOOST_CHECK_EQUAL(*indexedDF.Count(), 8); // Indexing the left table using a column of the right table
302 // the number of rows remains as the right table ones: 8
303 BOOST_CHECK_EQUAL(*unionDF.Count(), 8); // Pairing one by one the rows of the two tables, still 8
304 BOOST_CHECK_EQUAL(*blockDF.Count(), 24); // The entries of the table are categorized by event:
305 // 4 in event 0 and 4 in event 1. So the total number
306 // of row is given by the cross product of the two parts, minus
307 // the diagonal (4*4) - 4 + (4*4) - 4
308 auto sum = [](int lx, int rx) { return lx + rx; };
309 auto left = [](int lx, int) { return lx; };
310 auto right = [](int, int rx) { return rx; };
311
312 BOOST_CHECK_EQUAL(*finalDF.Define("s1", sum, {"left_x", "left_y"}).Sum("s1"), 448);
313 BOOST_CHECK_EQUAL(*finalDF.Define("s4", sum, {"right_x", "left_x"}).Sum("s4"), 448);
314 BOOST_CHECK_EQUAL(*finalDF.Define("s2", left, {"left_x", "left_y"}).Sum("s2"), 224);
315 BOOST_CHECK_EQUAL(*finalDF.Define("s3", right, {"right_x", "left_x"}).Sum("s3"), 224);
316 BOOST_CHECK_EQUAL(*indexedDF.Define("s4", sum, {"right_x", "left_x"}).Sum("s4"), 56);
317 BOOST_CHECK_EQUAL(*unionDF.Define("s5", sum, {"right_x", "left_x"}).Sum("s5"), 56);
318 BOOST_CHECK_EQUAL(*blockDF.Define("s5", sum, {"right_x", "left_x"}).Sum("s5"), 168);
319}
320
321BOOST_AUTO_TEST_CASE(TestSoAIntegration)
322{
323 TableBuilder builder;
324 auto rowWriter = builder.cursor<TestTable>();
325 rowWriter(0, 0, 0);
326 rowWriter(0, 10, 1);
327 rowWriter(0, 20, 2);
328 rowWriter(0, 30, 3);
329 rowWriter(0, 40, 4);
330 rowWriter(0, 50, 5);
331 auto table = builder.finalize();
332 auto readBack = TestTable{table};
333
334 size_t i = 0;
335 for (auto& row : readBack) {
336 BOOST_CHECK_EQUAL(row.x(), i * 10);
337 BOOST_CHECK_EQUAL(row.y(), i);
338 ++i;
339 }
340}
341
342BOOST_AUTO_TEST_CASE(TestDataAllocatorReturnType)
343{
344 const Output output{"TST", "DUMMY", 0, Lifetime::Timeframe};
345 // we require reference to object owned by allocator contexallocatort
346}
347
348BOOST_AUTO_TEST_CASE(TestPodInjestion)
349{
350 struct A {
351 uint64_t x;
352 uint64_t y;
353 };
354 TableBuilder builder;
355 auto rowWriter = builder.cursor<TestTable, A>();
356 rowWriter(0, A{0, 0});
357 rowWriter(0, A{10, 1});
358 rowWriter(0, A{20, 2});
359 rowWriter(0, A{30, 3});
360 rowWriter(0, A{40, 4});
361 rowWriter(0, A{50, 5});
362 auto table = builder.finalize();
363 auto readBack = TestTable{table};
364
365 size_t i = 0;
366 for (auto& row : readBack) {
367 BOOST_CHECK_EQUAL(row.x(), i * 10);
368 BOOST_CHECK_EQUAL(row.y(), i);
369 ++i;
370 }
371}
372
373BOOST_AUTO_TEST_CASE(TestColumnCount)
374{
375 struct Foo {
376 int x;
377 int y;
378 };
379 struct Bar {
380 int x;
381 int y;
382 std::string s;
383 };
384 struct FooBar {
385 int x;
386 int y;
387 float f;
388 };
389 BOOST_REQUIRE_EQUAL(TableBuilder::countColumns<Foo>(), 2);
390 BOOST_REQUIRE_EQUAL(TableBuilder::countColumns<Bar>(), 3);
391 BOOST_REQUIRE_EQUAL(TableBuilder::countColumns<FooBar>(), 3);
392 int count = TableBuilder::countColumns<float, int>();
393 BOOST_REQUIRE_EQUAL(count, 2);
394 int count2 = TableBuilder::countColumns<float, int, char[3]>();
395 BOOST_REQUIRE_EQUAL(count2, 3);
396}
397
398BOOST_AUTO_TEST_CASE(TestMakeFields)
399{
400 auto fields = TableBuilderHelpers::makeFields<int, float>({"i", "f"});
401 BOOST_REQUIRE_EQUAL(fields.size(), 2);
402 BOOST_REQUIRE_EQUAL(fields[0]->name(), "i");
403 BOOST_REQUIRE_EQUAL(fields[1]->name(), "f");
404 BOOST_REQUIRE_EQUAL(fields[0]->type()->name(), "int32");
405 BOOST_REQUIRE_EQUAL(fields[1]->type()->name(), "float");
406}
407
409{
410 using namespace o2::framework;
412 TableBuilder builder;
413 ROOT::RDataFrame rdf(100);
414 auto t = rdf.Define("x", "1")
415 .Define("y", "2")
416 .Define("z", "x+y");
417 t.ForeachSlot(builder.persist<int, int>({"x", "z"}), {"x", "z"});
418
419 auto table = builder.finalize();
420 auto transport = fair::mq::TransportFactory::CreateTransportFactory("zeromq");
421 auto creator = [&transport](size_t size) -> std::unique_ptr<fair::mq::Message> {
422 return transport->CreateMessage(size);
423 };
424 auto buffer = std::make_shared<FairMQResizableBuffer>(creator);
426 auto stream = std::make_shared<arrow::io::BufferOutputStream>(buffer);
427 auto outBatch = arrow::ipc::MakeStreamWriter(stream.get(), table->schema());
428 auto outStatus = outBatch.ValueOrDie()->WriteTable(*table);
429 if (outStatus.ok() == false) {
430 throw std::runtime_error("Unable to Write table");
431 }
432
433 std::unique_ptr<fair::mq::Message> payload = buffer->Finalise();
434}
#define DECLARE_SOA_COLUMN_FULL(_Name_, _Getter_, _Type_, _Label_)
Definition ASoA.h:2285
int32_t i
constexpr int p2()
void output(const std::map< std::string, ChannelStat > &channels)
Definition rawdump.cxx:197
uint16_t pos
Definition RawData.h:3
Definition A.h:16
auto bulkPersist(std::array< char const *, NCOLUMNS > const &columnNames, size_t nRows)
auto reserve(o2::framework::pack< ARGS... > &&, int s)
auto persist(std::array< char const *, sizeof...(ARGS)+1 > const &columnNames)
std::shared_ptr< arrow::Table > finalize()
unfiltered_iterator begin()
Definition ASoA.h:1966
float sum(float s, o2::dcs::DataPointValue v)
Definition dcs-ccdb.cxx:39
GLint GLenum GLint x
Definition glcorearb.h:403
GLint GLsizei count
Definition glcorearb.h:399
GLuint buffer
Definition glcorearb.h:655
GLsizeiptr size
Definition glcorearb.h:659
GLuint const GLchar * name
Definition glcorearb.h:781
GLdouble GLdouble right
Definition glcorearb.h:4077
GLdouble f
Definition glcorearb.h:310
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLint y
Definition glcorearb.h:270
GLint GLint GLsizei GLint GLenum GLenum type
Definition glcorearb.h:275
GLboolean * data
Definition glcorearb.h:298
GLfloat GLfloat GLfloat GLfloat v3
Definition glcorearb.h:814
GLboolean GLboolean GLboolean GLboolean a
Definition glcorearb.h:1233
GLuint GLuint stream
Definition glcorearb.h:1806
RDataFrame MakeColumnIndexedDataFrame(std::unique_ptr< RDataSource > left, std::unique_ptr< RDataSource >, std::string indexColName, std::string leftPrefix="left_", std::string rightPrefix="right_")
RDataFrame MakeBlockAntiDataFrame(std::unique_ptr< RDataSource > left, std::unique_ptr< RDataSource > right, std::string indexColumnName, std::string leftPrefix="left_", std::string rightPrefix="right_")
RDataFrame MakeFriendDataFrame(std::unique_ptr< RDataSource > left, std::unique_ptr< RDataSource > right, std::string leftPrefix="left_", std::string rightPrefix="right_")
RDataFrame MakeCrossProductDataFrame(std::unique_ptr< RDataSource > left, std::unique_ptr< RDataSource >, std::string leftPrefix="left_", std::string rightPrefix="right_")
Defining PrimaryVertex explicitly as messageable.
Definition TFIDInfo.h:20
FIXME: do not use data model tables.
BOOST_AUTO_TEST_CASE(TestTableBuilder)
o2::soa::Table< test::X, test::Y > TestTable
BOOST_CHECK_EQUAL(triggersD.size(), triggers.size())
std::vector< int > row