Project
Loading...
Searching...
No Matches
Digitizer.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
14
16#include "Framework/Logger.h"
20#include "MathUtils/Cartesian.h"
23
24#include <TRandom.h>
25#include <algorithm>
26#include <climits>
27#include <vector>
28#include <ranges>
29#include <numeric>
30
32using o2::itsmft::Hit;
34
35using namespace o2::itsmft;
36// using namespace o2::base;
37
38//_______________________________________________________________________
40{
41 mNumberOfChips = mGeometry->getNumberOfChips();
42 mChips.resize(mNumberOfChips);
43 for (int i = mNumberOfChips; i--;) {
44 mChips[i].setChipIndex(i);
45 if (mNoiseMap) {
46 mChips[i].setNoiseMap(mNoiseMap);
47 }
48 if (mDeadChanMap) {
49 mChips[i].disable(mDeadChanMap->isFullChipMasked(i));
50 mChips[i].setDeadChanMap(mDeadChanMap);
51 }
52 }
53
54 // importing the parameters from DPLDigitizerParam.h
57
58 // initializing response according to detector and back-bias value
59 if (doptMFT.Vbb == 0.0) { // for MFT
60 mAlpSimRespMFT = mAlpSimResp[0];
61 LOG(info) << "Choosing Vbb=0V for MFT";
62 } else if (doptMFT.Vbb == 3.0) {
63 mAlpSimRespMFT = mAlpSimResp[1];
64 LOG(info) << "Choosing Vbb=-3V for MFT";
65 } else {
66 LOG(fatal) << "Invalid MFT back-bias value";
67 }
68
69 if (doptITS.IBVbb == 0.0) { // for ITS Inner Barrel
70 mAlpSimRespIB = mAlpSimResp[0];
71 LOG(info) << "Choosing Vbb=0V for ITS IB";
72 } else if (doptITS.IBVbb == 3.0) {
73 mAlpSimRespIB = mAlpSimResp[1];
74 LOG(info) << "Choosing Vbb=-3V for ITS IB";
75 } else {
76 LOG(fatal) << "Invalid ITS Inner Barrel back-bias value";
77 }
78 if (doptITS.OBVbb == 0.0) { // for ITS Outer Barrel
79 mAlpSimRespOB = mAlpSimResp[0];
80 LOG(info) << "Choosing Vbb=0V for ITS OB";
81 } else if (doptITS.OBVbb == 3.0) {
82 mAlpSimRespOB = mAlpSimResp[1];
83 LOG(info) << "Choosing Vbb=-3V for ITS OB";
84 } else {
85 LOG(fatal) << "Invalid ITS Outer Barrel back-bias value";
86 }
87 mParams.print();
89
90 //
91 LOG(info) << "First IR sampled in digitization is: " << mIRFirstSampledTF;
92 LOG(info) << "First IR ns " << mIRFirstSampledTF.bc2ns();
93}
94
96{
97 if (mNumberOfChips < 10000) { // in MFT
98 return mAlpSimRespMFT;
99 }
100
101 if (chipID < 432) { // in ITS Inner Barrel
102 return mAlpSimRespIB;
103 } else { // in ITS Outer Barrel
104 return mAlpSimRespOB;
105 }
106}
107
108//_______________________________________________________________________
109void Digitizer::process(const std::vector<Hit>* hits, int evID, int srcID, int layer)
110{
111 // digitize single event, the time must have been set beforehand
112 // opt. apply a filter on the layer of the processed hits
113
114 LOG(debug) << "Digitizing " << mGeometry->getName() << ":" << layer << " hits of entry " << evID << " from source "
115 << srcID << " at time " << mEventTime << " ROFrame= " << mNewROFrame << ")"
116 << " cont.mode: " << isContinuous()
117 << " Min/Max ROFrames " << mROFrameMin << "/" << mROFrameMax;
118
119 // is there something to flush ?
120 if (mNewROFrame > mROFrameMin) {
121 fillOutputContainer(mNewROFrame - 1, layer); // flush out all frame preceding the new one
122 }
123
124 int nHits = hits->size();
125 std::vector<int> hitIdx(nHits);
126 std::iota(std::begin(hitIdx), std::end(hitIdx), 0);
127 // sort hits to improve memory access
128 std::sort(hitIdx.begin(), hitIdx.end(), [hits](auto lhs, auto rhs) {
129 return (*hits)[lhs].GetDetectorID() < (*hits)[rhs].GetDetectorID();
130 });
131 for (int i : hitIdx | std::views::filter([&](int idx) {
132 if (layer < 0) {
133 return true;
134 }
135 return mGeometry->getLayer((*hits)[idx].GetDetectorID()) == layer;
136 })) {
137 processHit((*hits)[i], mROFrameMax, evID, srcID, layer);
138 }
139
140 // in the triggered mode store digits after every MC event
141 // TODO: in the real triggered mode this will not be needed, this is actually for the
142 // single event processing only
143 if (!mParams.isContinuous()) {
144 fillOutputContainer(mROFrameMax, layer);
145 }
146}
147
148//_______________________________________________________________________
150{
151 // assign event time in ns
152 mEventTime = irt;
153 if (!mParams.isContinuous()) {
154 mROFrameMin = 0; // in triggered mode reset the frame counters
155 mROFrameMax = 0;
156 }
157 // RO frame corresponding to provided time
158 mCollisionTimeWrtROF = mEventTime.timeInBCNS; // in triggered mode the ROF starts at BC (is there a delay?)
159 if (mParams.isContinuous()) {
160 auto nbc = mEventTime.differenceInBC(mIRFirstSampledTF);
161 if (mCollisionTimeWrtROF < 0 && nbc > 0) {
162 nbc--;
163 }
164
165 // we might get interactions to digitize from before
166 // the first sampled IR
167 if (nbc < 0) {
168 mNewROFrame = 0;
169 // this event is before the first RO
170 mIsBeforeFirstRO = true;
171 } else {
172 mNewROFrame = nbc / mParams.getROFrameLengthInBC(layer);
173 mIsBeforeFirstRO = false;
174 }
175 LOG(debug) << " NewROFrame " << mNewROFrame << " nbc " << nbc;
176
177 // in continuous mode depends on starts of periodic readout frame
178 mCollisionTimeWrtROF += (nbc % mParams.getROFrameLengthInBC(layer)) * o2::constants::lhc::LHCBunchSpacingNS;
179 } else {
180 mNewROFrame = 0;
181 }
182
183 if (mNewROFrame < mROFrameMin) {
184 LOG(error) << "New ROFrame " << mNewROFrame << " (" << irt << ") precedes currently cashed " << mROFrameMin;
185 throw std::runtime_error("deduced ROFrame precedes already processed one");
186 }
187
188 if (mParams.isContinuous() && mROFrameMax < mNewROFrame) {
189 mROFrameMax = mNewROFrame - 1; // all frames up to this are finished
190 }
191}
192
193//_______________________________________________________________________
194void Digitizer::fillOutputContainer(uint32_t frameLast, int layer)
195{
196 // fill output with digits from min.cached up to requested frame, generating the noise beforehand
197 frameLast = std::min(frameLast, mROFrameMax);
198 // make sure all buffers for extra digits are created up to the maxFrame
199 getExtraDigBuffer(mROFrameMax);
200
201 LOG(info) << "Filling " << mGeometry->getName() << " digits:" << layer << " output for RO frames " << mROFrameMin << ":"
202 << frameLast;
203
205
206 // we have to write chips in RO increasing order, therefore have to loop over the frames here
207 for (; mROFrameMin <= frameLast; mROFrameMin++) {
208 rcROF.setROFrame(mROFrameMin);
209 rcROF.setFirstEntry(mDigits->size()); // start of current ROF in digits
210
211 auto& extra = *(mExtraBuff.front().get());
212 for (auto& chip : mChips) {
213 if (chip.isDisabled() || (layer >= 0 && mGeometry->getLayer(chip.getChipIndex()) != layer)) {
214 continue;
215 }
216 chip.addNoise(mROFrameMin, mROFrameMin, &mParams);
217 auto& buffer = chip.getPreDigits();
218 if (buffer.empty()) {
219 continue;
220 }
221 auto itBeg = buffer.begin();
222 auto iter = itBeg;
223 ULong64_t maxKey = chip.getOrderingKey(mROFrameMin + 1, 0, 0) - 1; // fetch digits with key below that
224 for (; iter != buffer.end(); ++iter) {
225 if (iter->first > maxKey) {
226 break; // is the digit ROFrame from the key > the max requested frame
227 }
228 auto& preDig = iter->second; // preDigit
229 if (preDig.charge >= mParams.getChargeThreshold()) {
230 int digID = mDigits->size();
231 mDigits->emplace_back(chip.getChipIndex(), preDig.row, preDig.col, preDig.charge);
232 mMCLabels->addElement(digID, preDig.labelRef.label);
233 auto& nextRef = preDig.labelRef; // extra contributors are in extra array
234 while (nextRef.next >= 0) {
235 nextRef = extra[nextRef.next];
236 mMCLabels->addElement(digID, nextRef.label);
237 }
238 }
239 }
240 buffer.erase(itBeg, iter);
241 }
242 // finalize ROF record
243 rcROF.setNEntries(mDigits->size() - rcROF.getFirstEntry()); // number of digits
244 if (isContinuous()) {
245 rcROF.getBCData().setFromLong(mIRFirstSampledTF.toLong() + mROFrameMin * mParams.getROFrameLengthInBC(layer));
246 } else {
247 rcROF.getBCData() = mEventTime; // RSTODO do we need to add trigger delay?
248 }
249 if (mROFRecords) {
250 mROFRecords->push_back(rcROF);
251 }
252 extra.clear(); // clear container for extra digits of the mROFrameMin ROFrame
253 // and move it as a new slot in the end
254 mExtraBuff.emplace_back(mExtraBuff.front().release());
255 mExtraBuff.pop_front();
256 }
257}
258
259//_______________________________________________________________________
260void Digitizer::processHit(const o2::itsmft::Hit& hit, uint32_t& maxFr, int evID, int srcID, int lay)
261{
262 // convert single hit to digits
263 auto chipID = hit.GetDetectorID();
264 auto& chip = mChips[chipID];
265 if (chip.isDisabled()) {
266 LOG(debug) << "skip disabled chip " << chipID;
267 return;
268 }
269 float timeInROF = hit.GetTime() * sec2ns;
270 if (timeInROF > 20e3) {
271 const int maxWarn = 10;
272 static int warnNo = 0;
273 if (warnNo < maxWarn) {
274 LOG(warning) << "Ignoring hit with time_in_event = " << timeInROF << " ns"
275 << ((++warnNo < maxWarn) ? "" : " (suppressing further warnings)");
276 }
277 return;
278 }
279 if (isContinuous()) {
280 timeInROF += mCollisionTimeWrtROF;
281 }
282 if (mIsBeforeFirstRO && timeInROF < 0) {
283 // disregard this hit because it comes from an event before readout starts and it does not effect this RO
284 return;
285 }
286
287 // calculate RO Frame for this hit
288 if (timeInROF < 0) {
289 timeInROF = 0.;
290 }
291 float tTot = mParams.getSignalShape().getMaxDuration();
292 // frame of the hit signal start wrt event ROFrame
293 int roFrameRel = int(timeInROF * mParams.getROFrameLengthInv(lay));
294 // frame of the hit signal end wrt event ROFrame: in the triggered mode we read just 1 frame
295 uint32_t roFrameRelMax = mParams.isContinuous() ? (timeInROF + tTot) * mParams.getROFrameLengthInv(lay) : roFrameRel;
296 int nFrames = roFrameRelMax + 1 - roFrameRel;
297 uint32_t roFrameMax = mNewROFrame + roFrameRelMax;
298 maxFr = std::max(roFrameMax, maxFr); // if signal extends beyond current maxFrame, increase the latter
299
300 // here we start stepping in the depth of the sensor to generate charge diffusion
301 float nStepsInv = mParams.getNSimStepsInv();
302 int nSteps = mParams.getNSimSteps();
303 const auto& matrix = mGeometry->getMatrixL2G(hit.GetDetectorID());
304 math_utils::Vector3D<float> xyzLocS(matrix ^ (hit.GetPosStart())); // start position in sensor frame
305 math_utils::Vector3D<float> xyzLocE(matrix ^ (hit.GetPos())); // end position in sensor frame
306
308 step -= xyzLocS;
309 step *= nStepsInv; // position increment at each step
310 // the electrons will injected in the middle of each step
311 math_utils::Vector3D<float> stepH(step * 0.5);
312 xyzLocS += stepH;
313 xyzLocE -= stepH;
314
315 int rowS = -1, colS = -1, rowE = -1, colE = -1, nSkip = 0;
316 // get entrance pixel row and col
317 while (!Segmentation::localToDetector(xyzLocS.X(), xyzLocS.Z(), rowS, colS)) { // guard-ring ?
318 if (++nSkip >= nSteps) {
319 return; // did not enter to sensitive matrix
320 }
321 xyzLocS += step;
322 }
323 // get exit pixel row and col
324 while (!Segmentation::localToDetector(xyzLocE.X(), xyzLocE.Z(), rowE, colE)) { // guard-ring ?
325 if (++nSkip >= nSteps) {
326 return; // did not enter to sensitive matrix
327 }
328 xyzLocE -= step;
329 }
330 // estimate the limiting min/max row and col where the non-0 response is possible
331 if (rowS > rowE) {
332 std::swap(rowS, rowE);
333 }
334 if (colS > colE) {
335 std::swap(colS, colE);
336 }
337 rowS -= AlpideRespSimMat::NPix / 2;
338 rowE += AlpideRespSimMat::NPix / 2;
339 rowS = std::max(rowS, 0);
340 if (rowE >= Segmentation::NRows) {
341 rowE = Segmentation::NRows - 1;
342 }
343 colS -= AlpideRespSimMat::NPix / 2;
344 colE += AlpideRespSimMat::NPix / 2;
345 colS = std::max(colS, 0);
346 if (colE >= Segmentation::NCols) {
347 colE = Segmentation::NCols - 1;
348 }
349 int rowSpan = rowE - rowS + 1, colSpan = colE - colS + 1; // size of plaquet where some response is expected
350
351 float respMatrix[rowSpan][colSpan]; // response accumulated here
352 std::fill(&respMatrix[0][0], &respMatrix[0][0] + rowSpan * colSpan, 0.f);
353
354 float nElectrons = hit.GetEnergyLoss() * mParams.getEnergyToNElectrons(); // total number of deposited electrons
355 nElectrons *= nStepsInv; // N electrons injected per step
356 if (nSkip) {
357 nSteps -= nSkip;
358 }
359 //
360 int rowPrev = -1, colPrev = -1, row, col;
361 float cRowPix = 0.f, cColPix = 0.f; // local coordinated of the current pixel center
362
364
365 // take into account that the AlpideSimResponse depth definition has different min/max boundaries
366 // although the max should coincide with the surface of the epitaxial layer, which in the chip
367 // local coordinates has Y = +SensorLayerThickness/2
368
369 xyzLocS.SetY(xyzLocS.Y() + resp->getDepthMax() - Segmentation::SensorLayerThickness / 2.);
370
371 // collect charge in every pixel which might be affected by the hit
372 for (int iStep = nSteps; iStep--;) {
373 // Get the pixel ID
374 Segmentation::localToDetector(xyzLocS.X(), xyzLocS.Z(), row, col);
375 if (row != rowPrev || col != colPrev) { // update pixel and coordinates of its center
376 if (!Segmentation::detectorToLocal(row, col, cRowPix, cColPix)) {
377 continue; // should not happen
378 }
379 rowPrev = row;
380 colPrev = col;
381 }
382 bool flipCol = false, flipRow = false;
383 // note that response needs coordinates along column row (locX) (locZ) then depth (locY)
384 auto rspmat = resp->getResponse(xyzLocS.X() - cRowPix, xyzLocS.Z() - cColPix, xyzLocS.Y(), flipRow, flipCol);
385
386 xyzLocS += step;
387 if (!rspmat) {
388 continue;
389 }
390
391 for (int irow = AlpideRespSimMat::NPix; irow--;) {
392 int rowDest = row + irow - (AlpideRespSimMat::NPix / 2) - rowS; // destination row in the respMatrix
393 if (rowDest < 0 || rowDest >= rowSpan) {
394 continue;
395 }
396 for (int icol = AlpideRespSimMat::NPix; icol--;) {
397 int colDest = col + icol - (AlpideRespSimMat::NPix / 2) - colS; // destination column in the respMatrix
398 if (colDest < 0 || colDest >= colSpan) {
399 continue;
400 }
401 respMatrix[rowDest][colDest] += rspmat->getValue(irow, icol, flipRow, flipCol);
402 }
403 }
404 }
405
406 // fire the pixels assuming Poisson(n_response_electrons)
407 o2::MCCompLabel lbl(hit.GetTrackID(), evID, srcID, false);
408 auto roFrameAbs = mNewROFrame + roFrameRel;
409 for (int irow = rowSpan; irow--;) {
410 uint16_t rowIS = irow + rowS;
411 for (int icol = colSpan; icol--;) {
412 float nEleResp = respMatrix[irow][icol];
413 if (!nEleResp) {
414 continue;
415 }
416 int nEle = gRandom->Poisson(nElectrons * nEleResp); // total charge in given pixel
417 // ignore charge which have no chance to fire the pixel
418 if (nEle < mParams.getMinChargeToAccount()) {
419 continue;
420 }
421 uint16_t colIS = icol + colS;
422 if (mNoiseMap && mNoiseMap->isNoisy(chipID, rowIS, colIS)) {
423 continue;
424 }
425 if (mDeadChanMap && mDeadChanMap->isNoisy(chipID, rowIS, colIS)) {
426 continue;
427 }
428 //
429 registerDigits(chip, roFrameAbs, timeInROF, nFrames, rowIS, colIS, nEle, lbl, lay);
430 }
431 }
432}
433
434//________________________________________________________________________________
435void Digitizer::registerDigits(ChipDigitsContainer& chip, uint32_t roFrame, float tInROF, int nROF,
436 uint16_t row, uint16_t col, int nEle, o2::MCCompLabel& lbl, int lay)
437{
438 // Register digits for given pixel, accounting for the possible signal contribution to
439 // multiple ROFrame. The signal starts at time tInROF wrt the start of provided roFrame
440 // In every ROFrame we check the collected signal during strobe
441
442 float tStrobe = mParams.getStrobeDelay(lay) - tInROF; // strobe start wrt signal start
443 for (int i = 0; i < nROF; i++) {
444 uint32_t roFr = roFrame + i;
445 int nEleROF = mParams.getSignalShape().getCollectedCharge(nEle, tStrobe, tStrobe + mParams.getStrobeLength(lay));
446 tStrobe += mParams.getROFrameLength(lay); // for the next ROF
447
448 // discard too small contributions, they have no chance to produce a digit
449 if (nEleROF < mParams.getMinChargeToAccount()) {
450 continue;
451 }
452 mEventROFrameMax = std::max(roFr, mEventROFrameMax);
453 mEventROFrameMin = std::min(roFr, mEventROFrameMin);
454 auto key = chip.getOrderingKey(roFr, row, col);
455 PreDigit* pd = chip.findDigit(key);
456 if (!pd) {
457 chip.addDigit(key, roFr, row, col, nEleROF, lbl);
458 } else { // there is already a digit at this slot, account as PreDigitExtra contribution
459 pd->charge += nEleROF;
460 if (pd->labelRef.label == lbl) { // don't store the same label twice
461 continue;
462 }
463 ExtraDig* extra = getExtraDigBuffer(roFr);
464 int& nxt = pd->labelRef.next;
465 bool skip = false;
466 while (nxt >= 0) {
467 if ((*extra)[nxt].label == lbl) { // don't store the same label twice
468 skip = true;
469 break;
470 }
471 nxt = (*extra)[nxt].next;
472 }
473 if (skip) {
474 continue;
475 }
476 // new predigit will be added in the end of the chain
477 nxt = extra->size();
478 extra->emplace_back(lbl);
479 }
480 }
481}
Definition of the ITSMFT digit.
std::ostringstream debug
int32_t i
Definition of the ITS digitizer.
Definition of a container to keep Monte Carlo truth external to simulation objects.
uint32_t col
Definition RawData.h:4
Definition of the SegmentationAlpide class.
StringRef key
int GetTrackID() const
Definition BaseHits.h:30
V GetEnergyLoss() const
Definition BaseHits.h:103
math_utils::Point3D< T > GetPos() const
Definition BaseHits.h:67
E GetTime() const
Definition BaseHits.h:71
unsigned short GetDetectorID() const
Definition BaseHits.h:73
void addElement(uint32_t dataindex, TruthElement const &element, bool noElement=false)
const char * getName() const
const Mat3D & getMatrixL2G(int sensID) const
float getCollectedCharge(float totalNEle, float tMin, float tMax) const
bool getResponse(float vRow, float vCol, float cDepth, AlpideRespSimMat &dest) const
Container for similated points connected to a given chip.
void addDigit(ULong64_t key, UInt_t roframe, UShort_t row, UShort_t col, int charge, o2::MCCompLabel lbl)
o2::itsmft::PreDigit * findDigit(ULong64_t key)
static ULong64_t getOrderingKey(UInt_t roframe, UShort_t row, UShort_t col)
Get global ordering key made of readout frame, column and row.
int getMinChargeToAccount() const
Definition DigiParams.h:84
float getStrobeDelay(int layer=-1) const
Definition DigiParams.h:64
virtual void print() const
float getROFrameLengthInv(int layer=-1) const
Definition DigiParams.h:61
const SignalShape & getSignalShape() const
Definition DigiParams.h:98
float getStrobeLength(int layer=-1) const
Definition DigiParams.h:67
float getEnergyToNElectrons() const
Definition DigiParams.h:87
int getROFrameLengthInBC(int layer=-1) const
Definition DigiParams.h:56
bool isContinuous() const
Definition DigiParams.h:54
int getChargeThreshold() const
Definition DigiParams.h:83
float getNSimStepsInv() const
Definition DigiParams.h:86
float getROFrameLength(int layer=-1) const
Definition DigiParams.h:60
int getNSimSteps() const
Definition DigiParams.h:85
Digit class for the ITS.
Definition Digit.h:30
void fillOutputContainer(uint32_t maxFrame=0xffffffff, int layer=-1)
auto getChipResponse(int chipID)
Definition Digitizer.cxx:95
bool isContinuous() const
Definition Digitizer.h:81
void setEventTime(const o2::InteractionTimeRecord &irt, int layer=-1)
void process(const std::vector< Hit > *hits, int evID, int srcID, int layer=-1)
Steer conversion of hits to digits.
virtual Int_t getLayer(Int_t index) const
Int_t getNumberOfChips() const
math_utils::Point3D< Float_t > GetPosStart() const
Definition Hit.h:60
bool isFullChipMasked(int chip) const
Definition NoiseMap.h:186
bool isNoisy(int chip, int row, int col) const
Definition NoiseMap.h:151
void setNEntries(int n)
Definition ROFRecord.h:48
const BCData & getBCData() const
Definition ROFRecord.h:58
void setFirstEntry(int idx)
Definition ROFRecord.h:47
int getFirstEntry() const
Definition ROFRecord.h:63
void setROFrame(ROFtype rof)
Definition ROFRecord.h:45
static constexpr float SensorLayerThickness
static bool localToDetector(float x, float z, int &iRow, int &iCol)
static bool detectorToLocal(L row, L col, T &xRow, T &zCol)
GLuint buffer
Definition glcorearb.h:655
GLuint GLsizei const GLchar * label
Definition glcorearb.h:2519
GLenum GLuint GLint GLint layer
Definition glcorearb.h:1310
constexpr double LHCBunchSpacingNS
value_T step
Definition TrackUtils.h:42
static double bc2ns(int bc, unsigned int orbit)
int64_t differenceInBC(const InteractionRecord &other) const
void setFromLong(int64_t l)
double timeInBCNS
time in NANOSECONDS relative to orbit/bc
int next
eventual next contribution to the same pixel
Definition PreDigit.h:36
o2::MCCompLabel label
hit label
Definition PreDigit.h:35
int charge
N electrons.
Definition PreDigit.h:46
PreDigitLabelRef labelRef
label and reference to the next one
Definition PreDigit.h:47
IR getFirstSampledTFIR() const
get TF and HB (abs) for this IR
Definition HBFUtils.h:74
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"
std::vector< int > row